Compare commits

...

40 Commits

Author SHA1 Message Date
lorenz.stechauner ab61edc402 AppDbUpdater: Using async/await 2024-01-05 10:20:53 +01:00
lorenz.stechauner ba55692cbe Models: Update Payment models 2024-01-05 09:53:05 +01:00
lorenz.stechauner eb46955b3b Billing: Add BillingData and JSON schema validation 2024-01-05 00:41:20 +01:00
lorenz.stechauner 37bf8d0855 Documents: Add Property Name 2024-01-04 15:05:40 +01:00
lorenz.stechauner be87f31211 ChartWindow: Small fixes 2024-01-04 13:12:48 +01:00
lorenz.stechauner 4738cde9e4 Billing: Update EBICS exporter 2023-12-29 13:14:32 +01:00
lorenz.stechauner 6cf5e0d45e Documents: Change wording in documents 2023-12-23 23:05:25 +01:00
lorenz.stechauner 8e71e82efc CreditNote: Display under deliveries of area commitments 2023-12-23 21:27:26 +01:00
lorenz.stechauner 4f07d9b129 AreaComUnderDeliveryData: Fix typo 2023-12-23 14:49:43 +01:00
lorenz.stechauner 8555748202 CreditNote: Add FormatRow() 2023-12-23 14:07:10 +01:00
lorenz.stechauner a0914f4d54 BillingVariant: Fix retrieval of last used payment variant 2023-12-23 14:07:01 +01:00
lorenz.stechauner 2301251420 CreditNote: Small fixes 2023-12-23 14:06:57 +01:00
lorenz.stechauner 83f9b58d4d CreditNoteData: Fix SQL query for multiple payment variants in one year 2023-12-23 14:06:57 +01:00
lorenz.stechauner f8aef20b0d CreditNote: Add VAT display 2023-12-23 14:06:57 +01:00
lorenz.stechauner 0dcffc8677 PaymentVariantsWindow: Add commit and revert buttons 2023-12-23 14:06:57 +01:00
lorenz.stechauner bc578b212e BillingVariant: Add Commit() and Revert() functions 2023-12-23 14:06:52 +01:00
lorenz.stechauner 8368caf58a CreditNote: Add member modifier display 2023-12-22 20:18:48 +01:00
lorenz.stechauner c836b45920 Documents: Add .bold for tables 2023-12-22 19:48:13 +01:00
lorenz.stechauner b9a2893d80 [#17] CreditNote: Overhaul CreditNote 2023-12-22 15:34:03 +01:00
lorenz.stechauner 82f93746ab MemberDataSheet: Fix typo 2023-12-22 10:11:35 +01:00
lorenz.stechauner b79fcfb1ed MemberDataSheet: Make more compact 2023-12-22 00:56:57 +01:00
lorenz.stechauner ba241c98a9 MemberDataSheet: Make Bankverbindung section smaller 2023-12-22 00:15:42 +01:00
lorenz.stechauner f7cdd7a4c1 MemberDataSheet: Minor improvements 2023-12-21 23:24:14 +01:00
lorenz.stechauner 1bdb7183ed [#5][#17][#23] MemberDataSheet: Overhaul entire MemberDataSheet 2023-12-21 22:24:04 +01:00
lorenz.stechauner 4d5ad13e0c Utils: Add PhoneNrTypes 2023-12-21 22:21:38 +01:00
lorenz.stechauner 69e6b6a713 Member: Use current calendar year to retrieve ActiveAreaCommitments 2023-12-21 22:20:24 +01:00
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
49 changed files with 1628 additions and 1231 deletions
+4 -2
View File
@@ -16,6 +16,7 @@ using Elwig.Helpers.Printing;
using Elwig.Windows; using Elwig.Windows;
using Elwig.Dialogs; using Elwig.Dialogs;
using System.Threading.Tasks; using System.Threading.Tasks;
using Elwig.Helpers.Billing;
namespace Elwig { namespace Elwig {
public partial class App : Application { public partial class App : Application {
@@ -65,7 +66,7 @@ namespace Elwig {
CurrentApp = this; CurrentApp = this;
} }
protected override void OnStartup(StartupEventArgs evt) { protected override async void OnStartup(StartupEventArgs evt) {
var locale = new CultureInfo("de-AT"); var locale = new CultureInfo("de-AT");
locale.NumberFormat.CurrencyGroupSeparator = "\u202f"; locale.NumberFormat.CurrencyGroupSeparator = "\u202f";
locale.NumberFormat.NumberGroupSeparator = "\u202f"; locale.NumberFormat.NumberGroupSeparator = "\u202f";
@@ -82,7 +83,7 @@ namespace Elwig {
Version = typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split("+")[0] ?? "0.0.0"; Version = typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split("+")[0] ?? "0.0.0";
try { try {
AppDbUpdater.CheckDb(); await AppDbUpdater.CheckDb();
} catch (Exception e) { } catch (Exception e) {
MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown(); Shutdown();
@@ -104,6 +105,7 @@ namespace Elwig {
Utils.RunBackground("HTML Initialization", () => Html.Init(PrintingReadyChanged)); Utils.RunBackground("HTML Initialization", () => Html.Init(PrintingReadyChanged));
Utils.RunBackground("PDF Initialization", () => Pdf.Init(PrintingReadyChanged)); Utils.RunBackground("PDF Initialization", () => Pdf.Init(PrintingReadyChanged));
Utils.RunBackground("JSON Schema Initialization", BillingData.Init);
var list = new List<IScale>(); var list = new List<IScale>();
foreach (var s in Config.Scales) { foreach (var s in Config.Scales) {
+146
View File
@@ -1,5 +1,7 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Documents { namespace Elwig.Documents {
public abstract class BusinessDocument : Document { public abstract class BusinessDocument : Document {
@@ -32,5 +34,149 @@ 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}"; 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.ToString().Replace(',', '.')}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 bold\"><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, int? area = null,
bool isGa = false, bool showPayment = false, bool showArea = false
) {
payment ??= delivery;
var baseline = showPayment ? payment : delivery;
if (showArea) {
return $"<td>{(area == null ? "" : $"{area:N0}")}</td>" +
$"<td>{obligation:N0}</td>" +
$"<td>{right:N0}</td>";
}
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, bool showArea = false) {
return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.Payment, bucket.Area, isGa, showPayment, showArea);
}
public string PrintBucketTable(
Season season, Dictionary<string, MemberBucket> buckets,
bool includeDelivery = true, bool includePayment = false,
bool isTiny = false, IEnumerable<string>? filter = null
) {
includePayment = includePayment && includeDelivery;
string tbl = GetColGroup(!includeDelivery ? [105, 20, 20, 20] : includePayment ? [45, 17, 17, 17, 19, 16, 17, 17] : [45, 20, 20, 20, 20, 20, 20]);
tbl += $"""
<thead>
<tr>
<th{(!includeDelivery ? " rowspan=\"2\"" : "")}>
<b>{(includeDelivery ? "Lese " + season.Year : "Zusammengefasste Flächenbindungen")}</b>
per {Date:dd.MM.yyyy} {(includeDelivery ? "[kg]" : "")}
</th>
{(!includeDelivery ? "<th>Fläche</th>" : "")}
<th>Lieferpflicht</th>
<th>Lieferrecht</th>
{(includeDelivery ? "<th>Unterliefert</th>" : "")}
{(includeDelivery ? "<th>Noch lieferbar</th>" : "")}
{(includeDelivery ? "<th>Überliefert</th>" : "")}
{(includePayment ? "<th>Zugeteilt</th>" : "")}
{(includeDelivery ? "<th>Geliefert</th>" : "")}
</tr>
{(!includeDelivery ? "<tr><th class=\"unit\">[m²]</th><th class=\"unit\">[kg]</th><th class=\"unit\">[kg]</th></tr>" : "")}
</thead>
""";
var mBuckets = buckets
.Where(b => ((!includeDelivery && b.Value.Area > 0) ||
(includeDelivery && (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, showArea: !includeDelivery
)}</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, showArea: !includeDelivery)}</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, showArea: !includeDelivery)}</tr>";
}
}
tbl += "\n</tbody>\n";
return $"<table class=\"buckets {(isTiny ? "tiny" : "small")} number cohere\">\n" + tbl + "\n</table>";
}
} }
} }
+6 -3
View File
@@ -1,5 +1,5 @@
.address-wrapper, aside, main { .address-wrapper, aside {
overflow: hidden; overflow: hidden;
} }
@@ -64,7 +64,7 @@ aside table thead:not(:first-child) tr {
border-top: 0.5pt solid #808080; border-top: 0.5pt solid #808080;
} }
aside table thead th { aside table thead tr {
background-color: #E0E0E0; background-color: #E0E0E0;
font-size: 10pt; font-size: 10pt;
} }
@@ -95,7 +95,10 @@ main > *:first-child {
margin-top: 0; margin-top: 0;
} }
main h1, .main-wrapper p { main h1,
main h2,
main h3,
.main-wrapper p {
font-size: 12pt; font-size: 12pt;
margin: 1em 0; margin: 1em 0;
text-align: justify; text-align: justify;
+35 -6
View File
@@ -1,31 +1,60 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Models.Dtos; using Elwig.Models.Dtos;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Documents { namespace Elwig.Documents {
public class CreditNote : BusinessDocument { public class CreditNote : BusinessDocument {
public new static string Name => "Traubengutschrift";
public PaymentMember? Payment;
public Credit? Credit; public Credit? Credit;
public CreditNoteData Data; public CreditNoteData Data;
public string? Text; public string? Text;
public string CurrencySymbol; public string CurrencySymbol;
public int Precision; public int Precision;
public string MemberModifier;
public IEnumerable<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries;
public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data) : public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data, Dictionary<string, UnderDelivery>? underDeliveries = null) :
base($"Traubengutschrift {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr}" : p.Member.Name)} {p.Variant.Name}", p.Member) { base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.Name)} {p.Variant.Name}", p.Member) {
UseBillingAddress = true; UseBillingAddress = true;
ShowDateAndLocation = true; ShowDateAndLocation = true;
Data = data; Data = data;
Payment = p;
Credit = p.Credit; Credit = p.Credit;
var season = p.Variant.Season;
var mod = App.Client.IsMatzen ? ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefault() : null;
if (mod != null) {
MemberModifier = $"{mod.Name} ({mod.ValueStr})";
} else {
MemberModifier = "Sonstige Zu-/Abschläge";
}
Aside = Aside.Replace("</table>", "") + Aside = Aside.Replace("</table>", "") +
$"<thead><tr><th colspan='2'>Gutschrift</th></tr></thead><tbody>" + $"<thead><tr><th colspan='2'>Gutschrift</th></tr></thead><tbody>" +
$"<tr><th>TG-Nr.</th><td>{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr}" : "-")}</td></tr>" + $"<tr><th>TG-Nr.</th><td>{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : "-")}</td></tr>" +
$"<tr><th>Überw. am</th><td>{p.Variant.TransferDate:dd.MM.yyyy}</td></tr>" + $"<tr><th>Überw. am</th><td>{p.Variant.TransferDate:dd.MM.yyyy}</td></tr>" +
$"<tr><th>Datum/Zeit</th><td>{p.Credit?.ModifiedTimestamp:dd.MM.yyyy} / {p.Credit?.ModifiedTimestamp:HH:mm}</td></tr>" + $"<tr><th>Datum/Zeit</th><td>{p.Credit?.ModifiedTimestamp:dd.MM.yyyy} / {p.Credit?.ModifiedTimestamp:HH:mm}</td></tr>" +
$"</tbody></table>"; $"</tbody></table>";
Text = App.Client.TextDeliveryNote; Text = App.Client.TextDeliveryNote;
DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr}" : p.MgNr); DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr);
CurrencySymbol = p.Variant.Season.Currency.Symbol ?? p.Variant.Season.Currency.Code; CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code;
Precision = p.Variant.Season.Precision; Precision = season.Precision;
var variants = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t);
MemberUnderDeliveries = underDeliveries?
.OrderBy(u => u.Key)
.Select(u => (
variants[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""),
u.Value.Diff,
u.Value.Diff * (comTypes[u.Key].PenaltyPerKg ?? 0)
- (comTypes[u.Key].PenaltyAmount ?? 0)
- ((u.Value.Weight == 0 ? comTypes[u.Key].PenaltyNone : null) ?? 0)))
.Where(u => u.Item3 != 0)
.ToList();
} }
}} }}
+105 -47
View File
@@ -1,3 +1,4 @@
@using Elwig.Helpers
@using RazorLight @using RazorLight
@inherits TemplatePage<Elwig.Documents.CreditNote> @inherits TemplatePage<Elwig.Documents.CreditNote>
@model Elwig.Documents.CreditNote @model Elwig.Documents.CreditNote
@@ -8,84 +9,141 @@
<table class="credit"> <table class="credit">
<colgroup> <colgroup>
<col style="width: 25mm;"/> <col style="width: 25mm;"/>
<col style="width: 5mm;"/> <col style="width: 5mm;"/>
<col style="width: 20mm;"/>
<col style="width: 20mm;"/> <col style="width: 20mm;"/>
<col style="width: 18mm;"/>
<col style="width: 10mm;"/> <col style="width: 10mm;"/>
<col style="width: 10mm;"/> <col style="width: 10mm;"/>
<col style="width: 15mm;"/> <col style="width: 15mm;"/>
<col style="width: 12mm;"/> <col style="width: 12mm;"/>
<col style="width: 15mm;"/> <col style="width: 15mm;"/>
<col style="width: 10mm;"/> <col style="width: 17mm;"/>
<col style="width: 10mm;"/> <col style="width: 16mm;"/>
<col style="width: 15mm;"/>
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
<th rowspan="3" style="text-align: left;">Lieferschein-Nr.</th> <th rowspan="2" style="text-align: left;">Lieferschein-Nr.</th>
<th rowspan="3">Pos.</th> <th rowspan="2" class="narrow">Pos.</th>
<th rowspan="3" style="text-align: left;">Sorte</th> <th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="3" style="text-align: left;">Attribut</th> <th rowspan="2" style="text-align: left;">Attribut</th>
<th rowspan="2" colspan="2">Gradation</th> <th colspan="2">Gradation</th>
<th rowspan="2" colspan="2">Flächenbindung</th> <th colspan="2">Flächenbindung</th>
<th rowspan="2">Preis</th> <th>Preis</th>
<th colspan="2">Zu-/Abschläge</th> <th class="narrow">Zu-/Abschläge</th>
<th rowspan="2">Betrag</th> <th>Betrag</th>
</tr> </tr>
<tr> <tr>
<th>Rel.</th> <th class="unit">[°Oe]</th>
<th>Abs.</th> <th class="unit narrow">[°KMW]</th>
</tr> <th class="unit" colspan="2">[kg]</th>
<tr> <th class="unit">[@Model.CurrencySymbol/kg]</th>
<th>[°Oe]</th> <th class="unit">[@Model.CurrencySymbol]</th>
<th>[°KMW]</th> <th class="unit">[@Model.CurrencySymbol]</th>
<th colspan="2">[kg]</th>
<th>[@Model.CurrencySymbol/kg]</th>
<th>[%]</th>
<th>[@Model.CurrencySymbol/kg]</th>
<th>[@Model.CurrencySymbol]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var p in Model.Data.Rows) { @foreach (var p in Model.Data.Rows) {
var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1); var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1);
var first = true;
//var pmt = p.Payment;
var abs = 0; // pmt?.ModAbs == null || pmt?.ModAbs == 0 ? "-" : pmt?.ModAbs.ToString("0." + string.Concat(Enumerable.Repeat('0', Model.Precision)));
var rel = 0; // pmt?.ModRel == null || pmt?.ModRel == 0 ? "-" : $"{pmt?.ModRel * 100:0.00##}";
@for (int i = 0; i < rows; i++) { @for (int i = 0; i < rows; i++) {
<tr class="@(first ? "first" : "") @(rows > i + 1 ? "trailing" : "")"> <tr class="@(i == 0 ? "first" : "") @(rows == i + 1 ? "last" : "")">
@if (first) { @if (i == 0) {
<td rowspan="@rows" class="lsnr">@p.LsNr</td> <td rowspan="@rows">@p.LsNr</td>
<td rowspan="@rows" class="dpnr">@p.DPNr</td> <td rowspan="@rows">@p.DPNr</td>
<td class="variant small">@p.Variant</td> <td class="small">@p.Variant</td>
<td class="attribute small">@p.Attribute</td> <td class="small">@p.Attribute</td>
<td rowspan="@rows" class="oe">@($"{p.Gradation.Oe:N0}")</td> <td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
<td rowspan="@rows" class="kmw">@($"{p.Gradation.Kmw:N1}")</td> <td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
} }
@if (i > 0 && i <= p.Modifiers.Length) { @if (i > 0 && i <= p.Modifiers.Length) {
<td colspan="2" class="mod">@(p.Modifiers[i - 1])</td> <td colspan="2" class="small mod">@p.Modifiers[i - 1]</td>
} else if (i > 0) { } else if (i > 0) {
<td colspan="2"></td> <td colspan="2"></td>
} }
@if (i < p.Buckets.Length) { @if (i < p.Buckets.Length) {
var bucket = p.Buckets[i]; var bucket = p.Buckets[i];
<td class="geb small">@bucket.Name:</td> <td class="small">@bucket.Name:</td>
<td class="weight">@($"{bucket.Value:N0}")</td> <td class="number">@($"{bucket.Value:N0}")</td>
<td class="price">@($"{bucket.Price:N4}")</td> <td class="number">@($"{bucket.Price:N4}")</td>
} else { } else {
<td colspan="3"></td> <td colspan="3"></td>
} }
@if (first) { @if (i == p.Buckets.Length - 1) {
<td rowspan="@rows" class="rel">@rel</td> var totalMod = p.TotalModifiers ?? 0;
<td rowspan="@rows" class="abs">@abs</td> <td class="number@(totalMod == 0 ? " center" : "")">@(totalMod == 0 ? "-" : Utils.GetSign(totalMod) + $"{Math.Abs(totalMod):N2}")</td>
<!-- FIXME rel/abs mods --> <td class="number">@($"{p.Amount:N2}")</td>
<td rowspan="@rows" class="amount">@($"{p.Buckets.Sum(b => b.Amount):N2}")</td> } else {
first = false; <td colspan="2"></td>
} }
</tr> </tr>
} }
} }
</tbody> </tbody>
@{
string FormatRow(string name, decimal? value, bool add = false, bool halfLine = true, bool bold = false, bool subCat = false) {
return $"<tr class=\"{(halfLine || add ? "" : "sum")} {(bold ? "large bold" : "")}\">"
+ $"<td colspan=\"4\"></td>"
+ $"<td class=\"{(halfLine && !add ? "sum" : "")} {(subCat ? "small" : "")}\" colspan=\"4\" style=\"overflow: visible;\">{name}:</td>"
+ $"<td class=\"number {(subCat ? "small" : "large")} {(halfLine && !add ? "sum" : "")}\">{(value < 0 ? "" : (add ? "+" : ""))}</td>"
+ $"<td colspan=\"2\" class=\"number {(subCat ? "small" : "large")} {(halfLine && !add ? "sum" : "")}\">"
+ $"<span class=\"fleft\">{Model.CurrencySymbol}</span>{Math.Abs(value ?? 0):N2}</td>"
+ $"</tr>\n";
}
}
<tbody style="break-inside: avoid; break-before: avoid;">
@{ var sum = Model.Data.Rows.Sum(p => p.Amount); }
@if (Model.Payment == null) {
@Raw(FormatRow("Gesamt", sum, halfLine: false, bold: true))
} else {
var totalLine = false;
if (Model.Payment.NetAmount != Model.Payment.Amount) {
@Raw(FormatRow("Zwischensumme", Model.Payment.NetAmount, halfLine: totalLine))
totalLine = true;
@Raw(FormatRow(Model.MemberModifier, Model.Payment.Amount - Model.Payment.NetAmount, add: true, halfLine: totalLine))
}
if (Model.Credit == null) {
@Raw(FormatRow("Gesamtbetrag", Model.Payment.Amount, bold: true, halfLine: totalLine))
totalLine = true;
// TODO Mock VAT
} else {
var hasPrev = Model.Credit.PrevNetAmount != null;
@Raw(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Model.Credit.NetAmount, bold: true, halfLine: totalLine))
totalLine = true;
if (hasPrev) {
@Raw(FormatRow("Bisher berücksichtigt", -Model.Credit.PrevNetAmount, add: true))
@Raw(FormatRow("Nettobetrag", Model.Credit.NetAmount - (Model.Credit.PrevNetAmount ?? 0)))
}
@Raw(FormatRow($"Mehrwertsteuer ({Model.Credit.Vat * 100} %)", Model.Credit.VatAmount, add: true))
@Raw(FormatRow("Bruttobetrag", Model.Credit.GrossAmount, bold: true))
}
}
</tbody>
<tbody style="break-inside: avoid;">
@{ decimal penalty = 0; }
@if (Model.MemberUnderDeliveries != null && Model.MemberUnderDeliveries.Count() > 0) {
<tr class="small">
<td colspan="4"></td>
<td colspan="5" style="padding-top: 5mm;">Anfallende Pönalen durch Unterlieferungen:</td>
<td colspan="2"></td>
</tr>
foreach (var u in Model.MemberUnderDeliveries) {
@Raw(FormatRow($"{u.Name} ({u.Kg:N0} kg)", u.Amount, add: true, subCat: true))
penalty += u.Amount;
}
penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero);
}
@if (Model.Credit == null) {
@Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true))
} else {
if (Model.Credit.Modifiers - penalty != 0) {
@Raw(FormatRow("Weitere Abzüge", Model.Credit.Modifiers - penalty, add: true))
}
if (Model.Credit.PrevModifiers != null && Model.Credit.PrevModifiers != 0) {
@Raw(FormatRow("Bereits berücksichtigte Abzüge", -Model.Credit.PrevModifiers, add: true))
}
@Raw(FormatRow("Auszahlungsbetrag", Model.Credit.Amount, bold: true))
}
</tbody>
</table> </table>
</main> </main>
+15 -55
View File
@@ -1,69 +1,29 @@
table.credit { table.credit .mod {
font-size: 10pt; padding-left: 5mm;
} }
table.credit th, table.credit tbody tr:not(.first) {
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;
}
table.credit tbody tr:not(.first):not(.last) {
break-before: avoid; break-before: avoid;
}
table.credit tbody tr:not(.last) {
break-after: avoid; break-after: avoid;
} }
table.credit tbody tr.first td { table.credit tr:not(.first) td {
padding-top: 1mm; padding-top: 0;
} }
table.credit tbody tr.last td { table.credit tr.last td {
padding-bottom: 1mm; padding-bottom: 0;
} }
table.credit tbody tr.new { table.credit .sum {
border-top: 0.5pt solid black; font-size: 12pt;
} }
table.credit .small { table.credit tr.sum td,
font-size: 8pt; table.credit td.sum {
padding-top: 1mm !important;
} }
+4 -2
View File
@@ -7,13 +7,15 @@ using System.Collections.Generic;
namespace Elwig.Documents { namespace Elwig.Documents {
public class DeliveryConfirmation : BusinessDocument { public class DeliveryConfirmation : BusinessDocument {
public new static string Name => "Anlieferungsbestätigung";
public Season Season; public Season Season;
public DeliveryConfirmationData Data; public DeliveryConfirmationData Data;
public string? Text = App.Client.TextDeliveryConfirmation; 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) : public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationData data) :
base($"Anlieferungsbestätigung {year}", m) { base($"{Name} {year}", m) {
Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season"); Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season");
ShowDateAndLocation = true; ShowDateAndLocation = true;
UseBillingAddress = true; UseBillingAddress = true;
+21 -98
View File
@@ -1,3 +1,4 @@
@using Elwig.Documents
@using RazorLight @using RazorLight
@inherits TemplatePage<Elwig.Documents.DeliveryConfirmation> @inherits TemplatePage<Elwig.Documents.DeliveryConfirmation>
@model Elwig.Documents.DeliveryConfirmation @model Elwig.Documents.DeliveryConfirmation
@@ -8,7 +9,7 @@
<table class="delivery-confirmation"> <table class="delivery-confirmation">
<colgroup> <colgroup>
<col style="width: 25mm;"/> <col style="width: 25mm;"/>
<col style="width: 5mm;"/> <col style="width: 5mm;"/>
<col style="width: 20mm;"/> <col style="width: 20mm;"/>
<col style="width: 21mm;"/> <col style="width: 21mm;"/>
<col style="width: 19mm;"/> <col style="width: 19mm;"/>
@@ -22,7 +23,7 @@
<thead> <thead>
<tr> <tr>
<th rowspan="2" style="text-align: left;">Lieferschein-Nr.</th> <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;">Sorte</th>
<th rowspan="2" style="text-align: left;">Attribut</th> <th rowspan="2" style="text-align: left;">Attribut</th>
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th> <th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
@@ -32,11 +33,11 @@
<th>Davon<br/>abzuwerten</th> <th>Davon<br/>abzuwerten</th>
</tr> </tr>
<tr> <tr>
<th>[°Oe]</th> <th class="unit">[°Oe]</th>
<th>[°KMW]</th> <th class="unit narrow">[°KMW]</th>
<th colspan="2">[kg]</th> <th class="unit" colspan="2">[kg]</th>
<th>[kg]</th> <th class="unit">[kg]</th>
<th>[kg]</th> <th class="unit">[kg]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -47,128 +48,50 @@
var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1); var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1);
var first = true; var first = true;
@for (int i = 0; i < rows; i++) { @for (int i = 0; i < rows; i++) {
<tr class="@(first ? "first" : "") @(p.Variant != lastVariant && lastVariant != "" ? "new": "") @(rows > i + 1 ? "trailing" : "")"> <tr class="@(first ? "first" : "") @(p.Variant != lastVariant && lastVariant != "" ? "new": "") @(rows > i + 1 ? "last" : "")">
@if (first) { @if (first) {
<td rowspan="@rows">@p.LsNr</td> <td rowspan="@rows">@p.LsNr</td>
<td rowspan="@rows">@p.DPNr</td> <td rowspan="@rows">@p.DPNr</td>
<td class="small">@p.Variant</td> <td class="small">@p.Variant</td>
<td class="small">@p.Attribute</td> <td class="small">@p.Attribute</td>
<td class="small">@p.QualityLevel</td> <td class="small">@p.QualityLevel</td>
<td rowspan="@rows" class="grad">@($"{p.Gradation.Oe:N0}")</td> <td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
<td rowspan="@rows" class="grad">@($"{p.Gradation.Kmw:N1}")</td> <td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
} }
@if (i > 0 && i <= p.Modifiers.Length) { @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) { } else if (i > 0) {
<td colspan="3"></td> <td colspan="3"></td>
} }
@if (i < p.Buckets.Length) { @if (i < p.Buckets.Length) {
var bucket = p.Buckets[i]; var bucket = p.Buckets[i];
<td class="geb">@bucket.Name:</td> <td class="small">@bucket.Name:</td>
<td class="weight">@($"{bucket.Value:N0}")</td> <td class="number">@($"{bucket.Value:N0}")</td>
} else { } else {
<td colspan="2"></td> <td colspan="2"></td>
} }
@if (i == p.Buckets.Length - 1) { @if (i == p.Buckets.Length - 1) {
<td class="weight">@($"{p.Weight:N0}")</td> <td class="number">@($"{p.Weight:N0}")</td>
} else { } else {
<td></td> <td></td>
} }
@if (first) { @if (first) {
<td rowspan="@rows" class="weight"></td> <td rowspan="@rows" class="number"></td>
first = false; first = false;
} }
</tr> </tr>
lastVariant = p.Variant;
} }
lastVariant = p.Variant;
} }
<tr class="sum"> <tr class="sum bold">
<td colspan="8">Gesamt:</td> <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> <td></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<table class="delivery-confirmation-stats"> @Raw(BusinessDocument.PrintSortenaufteilung(Model.MemberBuckets))
<colgroup> @Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includePayment: true))
<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>
<div class="text" style="margin-top: 2em;"> <div class="text" style="margin-top: 2em;">
@if (Model.Text != null) { @if (Model.Text != null) {
<p class="comment" style="white-space: pre-wrap; break-inside: avoid;">@Model.Text</p> <p class="comment" style="white-space: pre-wrap; break-inside: avoid;">@Model.Text</p>
+5 -86
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 { table.delivery-confirmation .mod {
font-size: 8pt;
padding-left: 5mm; 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) { table.delivery-confirmation tr:not(.first) {
break-before: avoid; break-before: avoid;
} }
@@ -55,14 +11,11 @@ table.delivery-confirmation tr:not(.first) td {
padding-top: 0; padding-top: 0;
} }
table.delivery-confirmation tr.trailing td { table.delivery-confirmation tr.last td {
padding-bottom: 0; padding-bottom: 0;
} }
table.delivery-confirmation tr.sum { table.delivery-confirmation tr.sum {
border-top: 0.5pt solid black;
break-before: avoid;
font-weight: bold;
font-size: 12pt; font-size: 12pt;
} }
@@ -70,44 +23,10 @@ table.delivery-confirmation tr.sum td {
padding-top: 1mm; padding-top: 1mm;
} }
table.delivery-confirmation-stats { table.sortenaufteilung {
font-size: 10pt; break-after: avoid;
break-inside: avoid;
} }
table.delivery-confirmation-stats th, table.buckets {
table.delivery-confirmation-stats td { break-before: avoid;
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;
} }
+18 -18
View File
@@ -10,9 +10,9 @@
<colgroup> <colgroup>
<col style="width: 25mm;"/> <col style="width: 25mm;"/>
<col style="width: 5mm;"/> <col style="width: 5mm;"/>
<col style="width: 17mm;"/> <col style="width: 15mm;"/>
<col style="width: 10mm;"/>
<col style="width: 8mm;"/> <col style="width: 8mm;"/>
<col style="width: 12mm;"/>
<col style="width: 38mm;"/> <col style="width: 38mm;"/>
<col style="width: 28mm;"/> <col style="width: 28mm;"/>
<col style="width: 10mm;"/> <col style="width: 10mm;"/>
@@ -22,7 +22,7 @@
<thead> <thead>
<tr> <tr>
<th rowspan="2" style="text-align: left;">Lieferschein-Nr.</th> <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">Datum</th>
<th rowspan="2">Zeit</th> <th rowspan="2">Zeit</th>
<th colspan="2" rowspan="2" style="text-align: left;">Mitglied</th> <th colspan="2" rowspan="2" style="text-align: left;">Mitglied</th>
@@ -31,9 +31,9 @@
<th>Gewicht</th> <th>Gewicht</th>
</tr> </tr>
<tr> <tr>
<th>[°Oe]</th> <th class="unit">[°Oe]</th>
<th>[°KMW]</th> <th class="unit narrow">[°KMW]</th>
<th>[kg]</th> <th class="unit">[kg]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -41,26 +41,26 @@
<tr> <tr>
<td>@p.Delivery.LsNr</td> <td>@p.Delivery.LsNr</td>
<td>@p.DPNr</td> <td>@p.DPNr</td>
<td>@($"{p.Delivery.Date:dd.MM.yyyy}")</td> <td class="small">@($"{p.Delivery.Date:dd.MM.yyyy}")</td>
<td>@($"{p.Delivery.Time:HH:mm}")</td> <td class="small">@($"{p.Delivery.Time:HH:mm}")</td>
<td class="mgnr">@p.Delivery.Member.MgNr</td> <td class="number">@p.Delivery.Member.MgNr</td>
<td>@p.Delivery.Member.AdministrativeName</td> <td class="small">@p.Delivery.Member.AdministrativeName</td>
<td>@p.Variant.Name</td> <td class="small">@p.Variant.Name</td>
<td class="grad">@($"{p.Oe:N0}")</td> <td class="center">@($"{p.Oe:N0}")</td>
<td class="grad">@($"{p.Kmw:N1}")</td> <td class="center">@($"{p.Kmw:N1}")</td>
<td class="weight">@($"{p.Weight:N0}")</td> <td class="number">@($"{p.Weight:N0}")</td>
</tr> </tr>
} }
<tr class="sum"> <tr class="sum bold">
@{ @{
var kmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(Model.Deliveries); var kmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(Model.Deliveries);
var oe = Elwig.Helpers.Utils.KmwToOe(kmw); var oe = Elwig.Helpers.Utils.KmwToOe(kmw);
} }
<td colspan="2">Gesamt:</td> <td colspan="2">Gesamt:</td>
<td colspan="5">(Teil-)Lieferungen: @($"{Model.Deliveries.DistinctBy(p => p.Delivery).Count():N0}") (@($"{Model.Deliveries.Count():N0}"))</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="center">@($"{oe:N0}")</td>
<td class="grad">@($"{kmw:N1}")</td> <td class="center">@($"{kmw:N1}")</td>
<td class="weight">@($"{Model.Deliveries.Sum(p => p.Weight):N0}")</td> <td class="number">@($"{Model.Deliveries.Sum(p => p.Weight):N0}")</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
-35
View File
@@ -10,38 +10,3 @@ h2 {
font-size: 14pt; font-size: 14pt;
margin-top: 2mm; 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 Delivery Delivery;
public string? Text; public string? Text;
public Dictionary<string, (string, int, int, int, int)> MemberBuckets; public Dictionary<string, MemberBucket> MemberBuckets;
// 0 - none // 0 - none
// 1 - GA only // 1 - GA only
+19 -70
View File
@@ -5,7 +5,7 @@
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryNote.css" /> <link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryNote.css" />
<main> <main>
<h1>@Model.Title</h1> <h1>@Model.Title</h1>
<table class="delivery"> <table class="delivery large">
<colgroup> <colgroup>
<col style="width: 10.00mm;"/> <col style="width: 10.00mm;"/>
<col style="width: 21.25mm;"/> <col style="width: 21.25mm;"/>
@@ -19,7 +19,7 @@
</colgroup> </colgroup>
<thead> <thead>
<tr> <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">Sorte</th>
<th class="main" rowspan="2" colspan="2">Attribut</th> <th class="main" rowspan="2" colspan="2">Attribut</th>
<th class="main" rowspan="2">Qualitätsstufe</th> <th class="main" rowspan="2">Qualitätsstufe</th>
@@ -27,21 +27,21 @@
<th>Gewicht</th> <th>Gewicht</th>
</tr> </tr>
<tr> <tr>
<th style="font-size: 8pt;">[°Oe]</th> <th class="unit">[°Oe]</th>
<th style="font-size: 8pt;">[°KMW]</th> <th class="unit narrow">[°KMW]</th>
<th style="font-size: 8pt;">[kg]</th> <th class="unit">[kg]</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) { @foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) {
<tr class="main"> <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.Variant.Name</td>
<td colspan="2">@part.Attribute?.Name</td> <td colspan="2">@part.Attribute?.Name</td>
<td>@part.Quality.Name</td> <td>@part.Quality.Name</td>
<td class="narrow" style="text-align: center;">@($"{part.Oe:N0}")</td> <td class="center">@($"{part.Oe:N0}")</td>
<td class="narrow" style="text-align: center;">@($"{part.Kmw:N1}")</td> <td class="center">@($"{part.Kmw:N1}")</td>
<td class="narrow" style="text-align: right;">@($"{part.Weight:N0}")</td> <td class="number">@($"{part.Weight:N0}")</td>
</tr> </tr>
<tr><td></td><td colspan="5" style="white-space: pre;"><i>Herkunft:</i> @part.OriginString</td></tr> <tr><td></td><td colspan="5" style="white-space: pre;"><i>Herkunft:</i> @part.OriginString</td></tr>
@if (part.Modifiers.Count() > 0) { @if (part.Modifiers.Count() > 0) {
@@ -63,11 +63,11 @@
} }
} }
@if (Model.Delivery.Parts.Count() > 1) { @if (Model.Delivery.Parts.Count() > 1) {
<tr class="main sum"> <tr class="main sum bold">
<td colspan="6">Gesamt:</td> <td colspan="6">Gesamt:</td>
<td style="text-align: center;">@($"{Model.Delivery.Oe:N0}")</td> <td class="center">@($"{Model.Delivery.Oe:N0}")</td>
<td style="text-align: center;">@($"{Model.Delivery.Kmw:N1}")</td> <td class="center">@($"{Model.Delivery.Kmw:N1}")</td>
<td style="text-align: right;">@($"{Model.Delivery.Weight:N0}")</td> <td class="number">@($"{Model.Delivery.Weight:N0}")</td>
</tr> </tr>
} }
</tbody> </tbody>
@@ -76,63 +76,12 @@
<p class="comment">Amerkung zur Lieferung: @Model.Delivery.Comment</p> <p class="comment">Amerkung zur Lieferung: @Model.Delivery.Comment</p>
} }
@if (Model.DisplayStats > 0) { @if (Model.DisplayStats > 0) {
<table class="delivery-note-stats @(Model.DisplayStats > 2 ? "expanded" : "")"> @Raw(Model.PrintBucketTable(
<colgroup> Model.Delivery.Season, Model.MemberBuckets, isTiny: true,
<col style="width: 45mm;"/> filter: Model.DisplayStats > 2 ? null :
<col style="width: 20mm;"/> Model.DisplayStats == 1 ? new List<string>() :
<col style="width: 20mm;"/> Model.Delivery.Parts.Select(p => p.SortId).Distinct().ToList()
<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>
} }
</main> </main>
@for (int i = 0; i < 2; i++) { @for (int i = 0; i < 2; i++) {
-51
View File
@@ -11,12 +11,6 @@ table.delivery tr:not(.main) {
break-before: avoid; break-before: avoid;
} }
table.delivery th {
font-weight: normal;
font-style: italic;
font-size: 10pt;
}
table.delivery th.main { table.delivery th.main {
text-align: left; text-align: left;
} }
@@ -43,55 +37,10 @@ table.delivery tr.tight:has(+ tr:not(.tight)) td {
padding-bottom: 0.5mm !important; padding-bottom: 0.5mm !important;
} }
table.delivery tr.sum {
border-top: 0.5pt solid black;
break-before: avoid;
}
table.delivery tr.sum td { table.delivery tr.sum td {
padding-top: 1mm; padding-top: 1mm;
} }
table.delivery-note-stats { table.delivery-note-stats {
font-size: 8pt;
break-inside: avoid;
break-after: 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 { .page::after {
content: "Seite " counter(page) " von " counter(pages) !important; content: "Seite " counter(page) " von " counter(pages) !important;
} }
a {
text-decoration: inherit;
color: inherit;
}
} }
+154
View File
@@ -0,0 +1,154 @@
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.border {
border: 0.5pt solid black;
}
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 .small {
font-size: 8pt;
}
main table .large {
font-size: 12pt;
}
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,
main table tr.subheading td {
font-weight: bold;
}
main table tr.subheading th {
text-align: left;
font-size: 10pt;
}
main table.small tr.subheading th,
main table.small tr.subheading td,
main table.tiny tr.subheading th,
main table.tiny tr.subheading td {
font-size: 8pt;
}
main table tr.sectionheading {
background-color: #E0E0E0;
}
main table tr.sectionheading th {
padding-top: 0.5mm;
padding-bottom: 0.5mm;
font-weight: bold;
text-align: center;
font-size: 10pt;
border-top: 0.5pt solid black;
}
main table.number thead tr:first-child th:first-child {
text-align: left;
}
main table tr.bold td {
font-weight: bold;
}
main table tr.sum,
main table td.sum,
main table tr.new,
main table tr.border {
border-top: 0.5pt solid black;
}
main table tr.sum,
main table td.sum {
break-before: avoid;
}
main table th.unit {
font-size: 8pt;
}
main table.number th.unit {
padding-right: 2mm;
}
main table th.narrow {
padding-left: 0;
padding-right: 0;
}
main table .lborder {border-left: 0.5pt solid black;}
main table .rborder {border-right: 0.5pt solid black;}
main table .fleft {
float: left;
}
+5 -1
View File
@@ -9,6 +9,8 @@ using Elwig.Helpers.Printing;
namespace Elwig.Documents { namespace Elwig.Documents {
public abstract partial class Document : IDisposable { public abstract partial class Document : IDisposable {
public static string Name => "Dokument";
private TempFile? _pdfFile = null; private TempFile? _pdfFile = null;
public bool ShowFoldMarks = App.Config.Debug; public bool ShowFoldMarks = App.Config.Debug;
@@ -33,7 +35,9 @@ namespace Elwig.Documents {
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ") Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
.Item(c.NameFull).NextLine() .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.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) .Item("BIC", c.Bic).Item("IBAN", c.Iban)
.ToString(); .ToString();
Date = DateTime.Today; Date = DateTime.Today;
+1
View File
@@ -9,6 +9,7 @@
<meta charset="UTF-8"/> <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.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.Page.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) { @if (Model.DoubleSided) {
<style> <style>
@@page :left { @@page :left {
+5 -5
View File
@@ -6,16 +6,16 @@ using System.Collections.Generic;
namespace Elwig.Documents { namespace Elwig.Documents {
public class MemberDataSheet : BusinessDocument { public class MemberDataSheet : BusinessDocument {
public new static string Name => "Stammdatenblatt";
public Season Season; public Season Season;
public int Year = Utils.CurrentYear; public int Year = Utils.CurrentYear;
public Dictionary<string, (string, int, int, int, int)> MemberBuckets; public Dictionary<string, MemberBucket> MemberBuckets;
public Dictionary<string, int> BucketAreas;
public MemberDataSheet(Member m, AppDbContext ctx) : base($"Stammdatenblatt {m.AdministrativeName}", m) { public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) {
DocumentId = $"Stammdatenblatt {m.MgNr}"; DocumentId = $"{Name} {m.MgNr}";
Season = ctx.Seasons.Find(Year) ?? throw new ArgumentException("invalid season"); Season = ctx.Seasons.Find(Year) ?? throw new ArgumentException("invalid season");
MemberBuckets = ctx.GetMemberBuckets(Year, m.MgNr).GetAwaiter().GetResult(); MemberBuckets = ctx.GetMemberBuckets(Year, m.MgNr).GetAwaiter().GetResult();
BucketAreas = ctx.GetMemberBucketAreas(Year, m.MgNr).GetAwaiter().GetResult();
} }
} }
} }
+136 -247
View File
@@ -1,4 +1,5 @@
@using RazorLight @using RazorLight
@using Elwig.Helpers
@inherits TemplatePage<Elwig.Documents.MemberDataSheet> @inherits TemplatePage<Elwig.Documents.MemberDataSheet>
@model Elwig.Documents.MemberDataSheet @model Elwig.Documents.MemberDataSheet
@{ @{
@@ -7,166 +8,146 @@
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\MemberDataSheet.css" /> <link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\MemberDataSheet.css" />
<main> <main>
<h1>@Model.Title</h1> <h1>@Model.Title</h1>
<table class="member"> <table class="member border">
<thead> <colgroup>
<tr><th colspan="4">Persönliche Daten</th></tr> <col style="width: 30.0mm;"/>
</thead> <col style="width: 51.5mm;"/>
<col style="width: 20.0mm;"/>
<col style="width: 12.0mm;"/>
<col style="width: 18.0mm;"/>
<col style="width: 31.5mm;"/>
</colgroup>
<tbody> <tbody>
<tr class="sectionheading"><th colspan="6">Persönliche Daten</th></tr>
<tr> <tr>
<th>Mitglieds-Nr.</th> <th class="small">Titel (vorangestellt)</th>
<th class="small">Vorname</th>
<th colspan="3" class="small">Nachname</th>
<th class="small">Titel (nachgestellt)</th>
</tr>
<tr>
<td class="large">@Model.Member.Prefix</td>
<td class="large">@Model.Member.GivenName @Model.Member.MiddleName</td>
<td class="large" colspan="3">@Model.Member.FamilyName</td>
<td class="large">@Model.Member.Suffix</td>
</tr>
<tr>
<th>Mitglieds-Nr.:</th>
<td>@Model.Member.MgNr</td> <td>@Model.Member.MgNr</td>
<th>Vorgänger MgNr.</th> <th colspan="2">Geburtsjahr/-tag:</th>
<td>@Model.Member.PredecessorMgNr</td> <td colspan="2">@(string.Join('.', Model.Member.Birthday?.Split('-')?.Reverse() ?? Array.Empty<string>()))</td>
</tr> </tr>
<tr> <tr>
<th>Titel (vorangestellt)</th> <th>Adresse:</th>
<td>@Model.Member.Prefix</td> <td colspan="5">@Model.Member.Address</td>
<th>Titel (nachgestellt)</th>
<td>@Model.Member.Suffix</td>
</tr> </tr>
<tr> <tr>
<th>Vorname</th> <th>PLZ/Ort:</th>
<td>@Model.Member.GivenName</td> <td colspan="5">
<th>Nachname</th> @Model.Member.PostalDest.AtPlz?.Plz
<td>@Model.Member.FamilyName</td> @Model.Member.PostalDest.AtPlz?.Dest
(@Model.Member.PostalDest.AtPlz?.Ort.Name)
</td>
</tr>
<tr class="sectionheading"><th colspan="6">Rechnungsadresse (optional)</th></tr>
<tr>
<th>Name:</th>
<td colspan="5">@Model.Member.BillingAddress?.Name</td>
</tr> </tr>
<tr> <tr>
<th>Weitere Vornamen</th> <th>Adresse:</th>
<td colspan="3">@Model.Member.MiddleName</td> <td colspan="5">@Model.Member.BillingAddress?.Address</td>
</tr> </tr>
<tr> <tr>
<th>Geburtsjahr/-tag</th> <th>PLZ/Ort:</th>
<td colspan="3">@(string.Join('.', Model.Member.Birthday?.Split('-')?.Reverse()??Array.Empty<string>()))</td> <td colspan="5">
</tr> @if (Model.Member.BillingAddress != null) {
</tbody> @Model.Member.BillingAddress.PostalDest.AtPlz?.Plz
@Model.Member.BillingAddress.PostalDest.AtPlz?.Dest
<thead> @("(")@Model.Member.BillingAddress.PostalDest.AtPlz?.Ort.Name@(")")
<tr><th colspan="4">Anschrift</th></tr>
</thead>
<tbody>
<tr>
<th>Adresse</th>
<td colspan="3">@Model.Member.Address</td>
</tr>
<tr>
<th>PLZ/Ort</th>
<td colspan="3">@Model.Member.PostalDest.AtPlz?.Plz @Model.Member.PostalDest.AtPlz?.Dest (@Model.Member.PostalDest.AtPlz?.Ort.Name)</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Rechnungsadresse</th></tr>
</thead>
<tbody>
<tr>
<th>Name</th>
<td colspan="3">@Model.Member.BillingAddress?.Name</td>
</tr>
<tr>
<th>Adresse</th>
<td colspan="3">@Model.Member.BillingAddress?.Address</td>
</tr>
<tr>
<th>PLZ/Ort</th>
<td colspan="3">@Model.Member.BillingAddress?.PostalDest.AtPlz?.Plz @Model.Member.BillingAddress?.PostalDest.AtPlz?.Dest @((Model.Member.BillingAddress != null && Model.Member.BillingAddress.PostalDest.AtPlz != null) ? "(" + Model.Member.BillingAddress?.PostalDest.AtPlz?.Ort.Name + ")" : "")</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Kontaktdaten</th></tr>
</thead>
<tbody>
@foreach (var e in Model.Member.EmailAddresses) {
var emailNumber = Model.Member.EmailAddresses.Count > 1 ? 1 : 0;
<tr>
<th>E-Mail-Adresse @(emailNumber != 0 ? $"({emailNumber})" : "")</th>
<td colspan="3">@e.Address</td>
</tr>
emailNumber++;
}
@foreach (var k in Model.Member.TelephoneNumbers) {
<tr>
@if (k.Type.Equals("landline")) {
<th>Tel.-Nr. (Festnetz)</th>
} else if (k.Type.Equals("mobile")) {
<th>Tel.-Nr. (mobil)</th>
} else if (k.Type.Equals("fax")) {
<th>Fax-Nr.</th>
} }
<td colspan="3">@k.Number</td> </td>
</tr>
<tr class="sectionheading">
<th colspan="3">Kontaktdaten</th>
<th colspan="3" class="lborder">Bankverbindung</th>
</tr>
@{
List<string?[]> subTbl1 = new();
subTbl1.AddRange(Model.Member.EmailAddresses.Select(a => new[] { "E-Mail-Adresse", a.Address }));
subTbl1.AddRange(Model.Member.TelephoneNumbers.Select(n => new[] { Utils.PhoneNrTypeToString(n.Type), n.Number, n.Comment }));
subTbl1.Add(new[] { "Tel.-Nr./E-Mail-Adr.", null });
List<string?[]> subTbl2 = new();
subTbl2.Add(new[] { "IBAN", Model.Member.Iban != null ? Elwig.Helpers.Utils.FormatIban(Model.Member.Iban) : null });
subTbl2.Add(new[] { "BIC", Model.Member.Bic });
}
@for (int i = 0; i < Math.Max(subTbl1.Count, subTbl2.Count); i++) {
<tr>
<th>@(i < subTbl1.Count ? subTbl1[i][0] + ":" : "")</th>
@if (i < subTbl1.Count && subTbl1[i].Length >= 3 && subTbl1[i][2] != null) {
<td>@subTbl1[i][1]</td>
<td>(@subTbl1[i][2])</td>
} else {
<td colspan="2">@(i < subTbl1.Count ? subTbl1[i][1] : "")</td>
}
<th class="lborder">@(i < subTbl2.Count ? subTbl2[i][0] + ":" : "")</th>
<td colspan="2">@(i < subTbl2.Count ? subTbl2[i][1] : "")</td>
</tr> </tr>
} }
</tbody> <tr class="sectionheading"><th colspan="6">Betrieb</th></tr>
<thead>
<tr><th colspan="4">Bankverbindung</th></tr>
</thead>
<tbody>
<tr> <tr>
<th>IBAN</th> <th>Betriebs-Nr.:</th>
<td colspan="3">@(Model.Member.Iban != null ? Elwig.Helpers.Utils.FormatIban(Model.Member.Iban) : "")</td>
</tr>
<tr>
<th>BIC</th>
<td colspan="3">@Model.Member.Bic</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Betrieb</th></tr>
</thead>
<tbody>
<tr>
<th>UID</th>
<td>@Model.Member.UstIdNr</td>
<th>Betriebs-Nr.</th>
<td>@Model.Member.LfbisNr</td> <td>@Model.Member.LfbisNr</td>
<th colspan="2">UID:</th>
<td colspan="2">@Model.Member.UstIdNr</td>
</tr> </tr>
<tr> <tr>
<th>Buchführend</th> <th>Stammgemeinde:</th>
<td>@(Model.Member.IsBuchführend ? "Ja" : "Nein")</td>
<th>Bio</th>
<td>@(Model.Member.IsOrganic ? "Ja" : "Nein")</td>
</tr>
</tbody>
<thead>
<tr><th colspan="4">Genossenschaft</th></tr>
</thead>
<tbody>
<tr>
<th>Eintritt</th>
<td>@Model.Member.EntryDate</td>
<th>Austritt</th>
<td>@Model.Member.ExitDate</td>
</tr>
<tr>
<th>Aktiv</th>
<td>@(Model.Member.IsActive ? "Ja" : "Nein")</td>
<th>Geschäftsanteile</th>
<td>@Model.Member.BusinessShares</td>
</tr>
<tr>
<th>Volllierferant</th>
<td>@(Model.Member.IsVollLieferant ? "Ja" : "Nein")</td>
<th>Funktionär</th>
<td>@(Model.Member.IsFunktionär ? "Ja" : "Nein")</td>
</tr>
<tr>
<th>Stamm-Zweigstelle</th>
<td>@Model.Member.Branch?.Name</td>
<th>Stammgemeinde</th>
<td>@Model.Member.DefaultKg?.Name</td> <td>@Model.Member.DefaultKg?.Name</td>
<th colspan="2">Buchführend:</th>
<td colspan="2">@(Model.Member.IsBuchführend ? "Ja" : "Nein")</td>
</tr> </tr>
<tr> <tr>
<th>Kontakt via Post</th> <th colspan="2" class="small">(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)</th>
<td>@(Model.Member.ContactViaPost ? "Ja" : "Nein")</td> <th colspan="2">Bio:</th>
<th>Kontakt via E-Mail</th> <td colspan="2">@(Model.Member.IsOrganic ? "Ja" : "Nein")</td>
<td>@(Model.Member.ContactViaEmail ? "Ja" : "Nein")</td> </tr>
<tr class="sectionheading"><th colspan="6">Genossenschaft</th></tr>
<tr>
<th>Status:</th>
<td>
@(Model.Member.IsActive ? "Aktiv" : "Nicht aktiv")
<span class="small">
(@(Model.Member.ExitDate != null ?
$"{Model.Member.EntryDate:dd.MM.yyyy}{Model.Member.ExitDate:dd.MM.yyyy}" :
$"seit {Model.Member.EntryDate:dd.MM.yyyy}"))
</span>
</td>
<th colspan="2">Geschäftsanteile:</th>
<td colspan="2">@Model.Member.BusinessShares</td>
</tr>
<tr>
<th>Stamm-Zweigstelle:</th>
<td>@Model.Member.Branch?.Name</td>
<th colspan="2">Volllierferant:</th>
<td colspan="2">@(Model.Member.IsVollLieferant ? "Ja" : "Nein")</td>
</tr>
<tr>
<th>Zusendungen via...</th>
<td>
<i>Post:</i> @(Model.Member.ContactViaPost ? "Ja" : "Nein")
<i>E-Mail:</i> @(Model.Member.ContactViaEmail ? "Ja" : "Nein")
</td>
<th colspan="2">Funktionär:</th>
<td colspan="2">@(Model.Member.IsFunktionär ? "Ja" : "Nein")</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includeDelivery: false))
@{ @{
var areaComs = Model.Member.ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new { var areaComs = Model.Member.ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new {
AreaComType = group.Key, AreaComType = group.Key,
@@ -177,15 +158,15 @@
} }
@if (areaComs.Count != 0) { @if (areaComs.Count != 0) {
<h1>Flächenbindungen</h1> <h2>Flächenbindungen per @($"{Model.Date:dd.MM.yyyy}")</h2>
<table class="area-commitements"> <table class="area-commitements">
<colgroup> <colgroup>
<col style="width: 40mm;" /> <col style="width: 40mm;"/>
<col style="width: 30mm;" /> <col style="width: 30mm;"/>
<col style="width: 35mm;" /> <col style="width: 35mm;"/>
<col style="width: 15mm;" /> <col style="width: 15mm;"/>
<col style="width: 25mm;" /> <col style="width: 25mm;"/>
<col style="width: 20mm;" /> <col style="width: 20mm;"/>
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
@@ -201,124 +182,32 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var contractType in areaComs) { @foreach (var contractType in areaComs) {
<tr class="contract-type @(contractType.AreaComType.DisplayName != lastContract && lastContract != "" ? "new" : "")"> <tr class="subheading @(contractType.AreaComType.DisplayName != lastContract && lastContract != "" ? "new" : "")">
<th colspan="3" style="text-align: left"> <th colspan="3">
@($"{contractType.AreaComType.WineVar.Name} {(contractType.AreaComType.WineAttr != null ? "(" + contractType.AreaComType.WineAttr + ")" : "")}") @($"{contractType.AreaComType.WineVar.Name} {(contractType.AreaComType.WineAttr != null ? "(" + contractType.AreaComType.WineAttr + ")" : "")}")
</th> </th>
<th style="text-align: right">@($"{contractType.Size:N0}")</th> <td class="number">@($"{contractType.Size:N0}")</td>
<th colspan="2"></th> <td colspan="2"></td>
</tr> </tr>
@foreach (var areaCom in contractType.AreaComs) { @foreach (var areaCom in contractType.AreaComs) {
<tr class="area-commitment"> <tr class="area-commitment">
<td>@areaCom.Kg.AtKg.Name <span style="font-size: 8pt;">(@areaCom.Kg.AtKg.KgNr)</span></td> <td>@areaCom.Kg.AtKg.Name <span style="font-size: 8pt;">(@($"{areaCom.Kg.AtKg.KgNr:00000}"))</span></td>
<td>@areaCom.Rd?.Name</td> <td>@areaCom.Rd?.Name</td>
<td>@areaCom.GstNr.Replace(",", ", ")</td> <td class="text">@areaCom.GstNr.Replace(",", ", ").Replace("-", "")</td>
<td style="text-align: right;">@($"{areaCom.Area:N0}")</td> <td class="number">@($"{areaCom.Area:N0}")</td>
<td style="text-align: center;">@areaCom.WineCult.Name</td> <td class="center">@areaCom.WineCult.Name</td>
<td style="text-align: center;">Ab @areaCom.YearFrom</td> <td class="center">@(areaCom.YearTo == null ? $"ab {areaCom.YearFrom}" : $"{areaCom.YearFrom}{areaCom.YearTo}")</td>
</tr> </tr>
lastContract = contractType.AreaComType.DisplayName; lastContract = contractType.AreaComType.DisplayName;
} }
} }
<tr class="sum bold">
<tr class="new"> <td colspan="3">Gesamt:</td>
<th colspan="3" style="text-align: left">Gesamt</th> <td class="number">@($"{Model.Member.ActiveAreaCommitments.Select(a => a.Area).Sum():N0}")</td>
<th style="text-align: right">@($"{Model.Member.ActiveAreaCommitments.Select(a => a.Area).Sum():N0}")</th> <td colspan="2"></td>
<th colspan="2"></th>
</tr>
</tbody>
</table>
<table class="delivery-confirmation-stats">
<colgroup>
<col style="width: 102mm;" />
<col style="width: 21mm;" />
<col style="width: 21mm;" />
<col style="width: 21mm;" />
</colgroup>
<thead>
<tr>
<th rowspan="2"></th>
<th style="text-align: center;">Fläche</th>
<th style="text-align: center;">Lieferpflicht</th>
<th style="text-align: center;">Lieferrecht</th>
</tr>
<tr>
<th style="text-align: center;">[m²]</th>
<th style="text-align: center;">[kg]</th>
<th style="text-align: center;">[kg]</th>
</tr>
</thead>
<tbody>
@{
string FormatRow(int mode, int area, int obligation, int right) {
return $"<td>{(mode == 0 || mode == 1 ? "" : area == 0 ? "-" : $"{area:N0}")}</td>" +
$"<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);
}
<tr>
<th>Laut gezeichneten GA</th>
@Raw(FormatRow(
0,
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) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, Model.BucketAreas[id], obligation, right))
</tr>
}
@if (vtr.Any()) {
<tr class="subheading"><th colspan="8">Verträge:</th></tr>
}
@foreach (var (id, (name, right, obligation, _, _)) in vtr) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, Model.BucketAreas[id], obligation, right))
</tr>
}
</tbody>
</table>
} else {
<table class="delivery-confirmation-stats" style="margin-top: 5mm;">
<colgroup>
<col style="width: 123mm;" />
<col style="width: 21mm;" />
<col style="width: 21mm;" />
</colgroup>
<thead>
<tr>
<th rowspan="2"></th>
<th style="text-align: center;">Lieferpflicht</th>
<th style="text-align: center;">Lieferrecht</th>
</tr>
<tr>
<th style="text-align: center;">[kg]</th>
<th style="text-align: center;">[kg]</th>
</tr>
</thead>
<tbody>
<tr>
<th>Laut gezeichneten GA</th>
<td>@($"{Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare:N0}")</td>
<td>@($"{Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare:N0}")</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
} }
</main> </main>
+11 -69
View File
@@ -1,82 +1,24 @@
table.member {
border: 0.5pt solid black; h2 {
margin-bottom: 0.5em !important;
} }
table.member, table.area-commitements { table.member {
font-size: 10pt; margin-bottom: 5mm;
} }
table.member tbody th { table.area-commitements {
font-weight: normal; margin-top: 0;
font-style: italic;
text-align: left;
}
table.member thead,
table.member tbody {
border: 0.5pt solid black;
} }
table.area-commitements td { table.area-commitements td {
overflow: hidden;
}
table.area-commitements thead tr th {
font-weight: normal;
font-style: italic;
}
table.area-commitements tbody tr.contract-type {
margin-bottom: 10px;
}
table.area-commitements tbody td {
vertical-align: top; vertical-align: top;
} }
table.area-commitements tr.new th { table.area-commitements td.text {
border-top: 0.5pt solid black; white-space: normal;
} }
table.delivery-confirmation-stats { table.area-commitements tr.sum {
font-size: 10pt; font-size: 12pt;
break-inside: 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: 10pt;
}
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;
}
+11 -9
View File
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
@@ -14,6 +14,7 @@
<ItemGroup> <ItemGroup>
<Resource Include="Resources\Images\Elwig.png" /> <Resource Include="Resources\Images\Elwig.png" />
<Content Include="Resources\Images\Elwig.ico" /> <Content Include="Resources\Images\Elwig.ico" />
<EmbeddedResource Include="Schemas\payment_variant.json" />
</ItemGroup> </ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
@@ -22,16 +23,17 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" /> <PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" />
<PackageReference Include="ini-parser" Version="2.5.2" /> <PackageReference Include="LinqKit" Version="1.2.5" />
<PackageReference Include="LinqKit" Version="1.2.4" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.25" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.24" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.13" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2088.41" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2151.40" />
<PackageReference Include="NJsonSchema" Version="11.0.0" />
<PackageReference Include="RazorLight" Version="2.3.1" /> <PackageReference Include="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="4.1.68" /> <PackageReference Include="ScottPlot.WPF" Version="4.1.68" />
<PackageReference Include="System.IO.Ports" Version="7.0.0" /> <PackageReference Include="System.IO.Ports" Version="8.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+69 -41
View File
@@ -12,6 +12,11 @@ using System.Collections.Generic;
using Elwig.Models.Dtos; using Elwig.Models.Dtos;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public record struct AreaComBucket(int Area, int Obligation, int Right);
public record struct UnderDelivery(int Weight, int Diff);
public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int Payment);
public class AppDbContext : DbContext { public class AppDbContext : DbContext {
public DbSet<Country> Countries { get; private set; } public DbSet<Country> Countries { get; private set; }
@@ -60,10 +65,11 @@ namespace Elwig.Helpers {
public static string ConnectionString => $"Data Source=\"{App.Config.DatabaseFile}\"; Foreign Keys=True; Mode=ReadWrite; Cache=Default"; 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 = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = new(); private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = new(); private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBucketsStrict = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberBucketAreas = new(); private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, UnderDelivery>>> _memberUnderDelivery = [];
public AppDbContext() { public AppDbContext() {
if (App.Config.DatabaseLog != null) { if (App.Config.DatabaseLog != null) {
@@ -203,22 +209,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; var ownCnx = cnx == null;
cnx ??= await ConnectAsync(); 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()) { 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(); using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0); var mgnr = reader.GetInt32(0);
var vtrgid = reader.GetString(1); var vtrgid = reader.GetString(1);
if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = [];
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(); if (ownCnx) await cnx.DisposeAsync();
_memberRightsAndObligations[year] = buckets; _memberAreaCommitmentBuckets[year] = buckets;
} }
private async Task FetchMemberDeliveryBuckets(int year, SqliteConnection? cnx = null) { private async Task FetchMemberDeliveryBuckets(int year, SqliteConnection? cnx = null) {
@@ -231,7 +237,7 @@ namespace Elwig.Helpers {
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0); var mgnr = reader.GetInt32(0);
var bucket = reader.GetString(1); var bucket = reader.GetString(1);
if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = [];
buckets[mgnr][bucket] = reader.GetInt32(2); buckets[mgnr][bucket] = reader.GetInt32(2);
} }
} }
@@ -239,6 +245,24 @@ namespace Elwig.Helpers {
_memberDeliveryBuckets[year] = buckets; _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] = [];
buckets[mgnr][bucket] = reader.GetInt32(2);
}
}
if (ownCnx) await cnx.DisposeAsync();
_memberDeliveryBucketsStrict[year] = buckets;
}
private async Task FetchMemberPaymentBuckets(int year, SqliteConnection? cnx = null) { private async Task FetchMemberPaymentBuckets(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null; var ownCnx = cnx == null;
cnx ??= await ConnectAsync(); cnx ??= await ConnectAsync();
@@ -249,7 +273,7 @@ namespace Elwig.Helpers {
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0); var mgnr = reader.GetInt32(0);
var bucket = reader.GetString(1); var bucket = reader.GetString(1);
if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = [];
buckets[mgnr][bucket] = reader.GetInt32(2); buckets[mgnr][bucket] = reader.GetInt32(2);
} }
} }
@@ -257,71 +281,75 @@ namespace Elwig.Helpers {
_memberPaymentBuckets[year] = buckets; _memberPaymentBuckets[year] = buckets;
} }
private async Task FetchMemberBucketAreas(int year, SqliteConnection? cnx = null) { private async Task FetchMemberUnderDelivery(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null; var ownCnx = cnx == null;
cnx ??= await ConnectAsync(); cnx ??= await ConnectAsync();
var buckets = new Dictionary<int, Dictionary<string, int>>(); var buckets = new Dictionary<int, Dictionary<string, UnderDelivery>>();
using (var cmd = cnx.CreateCommand()) { using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"SELECT mgnr, bucket, area FROM v_area_commitment_bucket_strict WHERE year = {year}"; cmd.CommandText = $"SELECT mgnr, bucket, weight, diff FROM v_under_delivery WHERE year = {year}";
using var reader = await cmd.ExecuteReaderAsync(); using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0); var mgnr = reader.GetInt32(0);
var bucket = reader.GetString(1); var bucket = reader.GetString(1);
var v = reader.GetInt32(2); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = [];
if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); buckets[mgnr][bucket] = new(reader.GetInt32(2), reader.GetInt32(3));
buckets[mgnr][bucket] = v;
if (bucket.Length > 2) {
buckets[mgnr][bucket[..2]] = buckets[mgnr].GetValueOrDefault(bucket[..2], 0) + v;
}
} }
} }
if (ownCnx) await cnx.DisposeAsync(); if (ownCnx) await cnx.DisposeAsync();
_memberBucketAreas[year] = buckets; _memberUnderDelivery[year] = buckets;
} }
public async Task<Dictionary<string, (int, int)>> GetMemberRightsAndObligations(int year, int mgnr, SqliteConnection? cnx = null) { public async Task<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberRightsAndObligations.ContainsKey(year)) if (!_memberAreaCommitmentBuckets.ContainsKey(year))
await FetchMemberRightsAndObligations(year, cnx); await FetchMemberAreaCommitmentBuckets(year, cnx);
return _memberRightsAndObligations[year].GetValueOrDefault(mgnr, new()); return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, []);
} }
public async Task<Dictionary<string, int>> GetMemberDeliveryBuckets(int year, int mgnr, SqliteConnection? cnx = null) { public async Task<Dictionary<string, int>> GetMemberDeliveryBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberDeliveryBuckets.ContainsKey(year)) if (!_memberDeliveryBuckets.ContainsKey(year))
await FetchMemberDeliveryBuckets(year, cnx); await FetchMemberDeliveryBuckets(year, cnx);
return _memberDeliveryBuckets[year].GetValueOrDefault(mgnr, new()); return _memberDeliveryBuckets[year].GetValueOrDefault(mgnr, []);
}
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, []);
} }
public async Task<Dictionary<string, int>> GetMemberPaymentBuckets(int year, int mgnr, SqliteConnection? cnx = null) { public async Task<Dictionary<string, int>> GetMemberPaymentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberPaymentBuckets.ContainsKey(year)) if (!_memberPaymentBuckets.ContainsKey(year))
await FetchMemberPaymentBuckets(year, cnx); await FetchMemberPaymentBuckets(year, cnx);
return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, new()); return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, []);
} }
public async Task<Dictionary<string, int>> GetMemberBucketAreas(int year, int mgnr, SqliteConnection? cnx = null) { public async Task<Dictionary<string, UnderDelivery>> GetMemberUnderDelivery(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberBucketAreas.ContainsKey(year)) if (!_memberUnderDelivery.ContainsKey(year))
await FetchMemberBucketAreas(year, cnx); await FetchMemberUnderDelivery(year, cnx);
return _memberBucketAreas[year].GetValueOrDefault(mgnr, new()); return _memberUnderDelivery[year].GetValueOrDefault(mgnr, []);
} }
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; var ownCnx = cnx == null;
cnx ??= await ConnectAsync(); 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 deliveryBuckets = await GetMemberDeliveryBuckets(year, mgnr, cnx);
var deliveryBucketsStrict = await GetMemberDeliveryBucketsStrict(year, mgnr, cnx);
var paymentBuckets = await GetMemberPaymentBuckets(year, mgnr, cnx); var paymentBuckets = await GetMemberPaymentBuckets(year, mgnr, cnx);
if (ownCnx) await cnx.DisposeAsync(); 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)) { foreach (var id in rightsAndObligations.Keys.Union(deliveryBuckets.Keys).Union(paymentBuckets.Keys)) {
var variety = await WineVarieties.FindAsync(id[..2]); var variety = await WineVarieties.FindAsync(id[..2]);
var attrIds = id[2..]; var attribute = await WineAttributes.FindAsync(id[2..]);
var attrs = await WineAttributes.Where(a => attrIds.Contains(a.AttrId)).ToListAsync(); var name = (variety?.Name ?? "") + (id[2..] == "_" ? " (kein Qual.Wein)" : attribute != null ? $" ({attribute})" : "");
var name = (variety?.Name ?? "") + (attrIds == "_" ? " (kein Qual.Wein)" : attrs.Count > 0 ? $" ({string.Join(" / ", attrs.Select(a => a.Name))})" : ""); buckets[id] = new(
buckets[id] = (
name, name,
rightsAndObligations.GetValueOrDefault(id).Item1, rightsAndObligations.GetValueOrDefault(id).Area,
rightsAndObligations.GetValueOrDefault(id).Item2, rightsAndObligations.GetValueOrDefault(id).Obligation,
rightsAndObligations.GetValueOrDefault(id).Right,
deliveryBuckets.GetValueOrDefault(id), deliveryBuckets.GetValueOrDefault(id),
deliveryBucketsStrict.GetValueOrDefault(id),
paymentBuckets.GetValueOrDefault(id) paymentBuckets.GetValueOrDefault(id)
); );
} }
+256 -155
View File
@@ -1,61 +1,61 @@
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using System; using System;
using System.Windows; using System.Threading.Tasks;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public static class AppDbUpdater { public static class AppDbUpdater {
public static readonly int RequiredSchemaVersion = 9; public static readonly int RequiredSchemaVersion = 12;
private static int _versionOffset = 0; private static int _versionOffset = 0;
private static readonly Action<SqliteConnection>[] _updaters = new[] { private static readonly Func<SqliteConnection, Task>[] _updaters = [
UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3, UpdateDbSchema_3_To_4, UpdateDbSchema_4_To_5, 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_5_To_6, UpdateDBSchema_6_To_7, UpdateDbSchema_7_To_8, UpdateDbSchema_8_To_9,
}; UpdateDbSchema_9_To_10, UpdateDbSchema_10_To_11, UpdateDbSchema_11_To_12,
];
private static void ExecuteNonQuery(SqliteConnection cnx, string sql) { private static async Task ExNonQuery(SqliteConnection cnx, string sql) {
using var cmd = cnx.CreateCommand(); using var cmd = cnx.CreateCommand();
cmd.CommandText = sql; cmd.CommandText = sql;
cmd.ExecuteNonQuery(); await cmd.ExecuteNonQueryAsync();
} }
private static object? ExecuteScalar(SqliteConnection cnx, string sql) { private static async Task<object?> ExScalar(SqliteConnection cnx, string sql) {
using var cmd = cnx.CreateCommand(); using var cmd = cnx.CreateCommand();
cmd.CommandText = sql; cmd.CommandText = sql;
return cmd.ExecuteScalar(); return await cmd.ExecuteScalarAsync();
} }
public static string CheckDb() { public static async Task<string> CheckDb() {
using var cnx = AppDbContext.Connect(); using var cnx = AppDbContext.Connect();
var applId = (long?)ExecuteScalar(cnx, "PRAGMA application_id") ?? 0; var applId = (long?)await ExScalar(cnx, "PRAGMA application_id") ?? 0;
if (applId != 0x454C5747) throw new Exception("Invalid application_id of database"); if (applId != 0x454C5747) throw new Exception("Invalid application_id of database");
var schemaVers = (long?)ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0; var schemaVers = (long?)await ExScalar(cnx, "PRAGMA schema_version") ?? 0;
_versionOffset = (int)(schemaVers % 100); _versionOffset = (int)(schemaVers % 100);
if (_versionOffset != 0) { if (_versionOffset != 0) {
// schema was modified manually/externally // schema was modified manually/externally
// TODO issue warning // TODO issue warning
} }
UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion); await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
var userVers = (long?)ExecuteScalar(cnx, "PRAGMA user_version") ?? 0; var userVers = (long?)await ExScalar(cnx, "PRAGMA user_version") ?? 0;
var major = userVers >> 24; var major = userVers >> 24;
var minor = (userVers >> 16) & 0xFF; var minor = (userVers >> 16) & 0xFF;
var patch = userVers & 0xFFFF; var patch = userVers & 0xFFFF;
if (App.VersionMajor > major || if (App.VersionMajor > major ||
(App.VersionMajor == major && App.VersionMinor > minor) || (App.VersionMajor == major && App.VersionMinor > minor) ||
(App.VersionMajor == major && App.VersionMinor == minor && App.VersionPatch > patch)) (App.VersionMajor == major && App.VersionMinor == minor && App.VersionPatch > patch)) {
{
long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch; long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch;
ExecuteNonQuery(cnx, $"PRAGMA user_version = {vers}"); await ExNonQuery(cnx, $"PRAGMA user_version = {vers}");
} }
return $"{major}.{minor}.{patch}"; return $"{major}.{minor}.{patch}";
} }
private static void UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) { private static async Task UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) {
if (fromVersion == toVersion) { if (fromVersion == toVersion) {
return; return;
} else if (fromVersion > toVersion) { } else if (fromVersion > toVersion) {
@@ -66,27 +66,27 @@ namespace Elwig.Helpers {
throw new Exception("schema_version of database is invalid"); throw new Exception("schema_version of database is invalid");
} }
ExecuteNonQuery(cnx, "PRAGMA locking_mode = EXCLUSIVE"); await ExNonQuery(cnx, "PRAGMA locking_mode = EXCLUSIVE");
ExecuteNonQuery(cnx, "PRAGMA foreign_keys = OFF"); await ExNonQuery(cnx, "PRAGMA foreign_keys = OFF");
ExecuteNonQuery(cnx, "BEGIN EXCLUSIVE"); await ExNonQuery(cnx, "BEGIN EXCLUSIVE");
for (int i = fromVersion; i < toVersion; i++) { for (int i = fromVersion; i < toVersion; i++) {
_updaters[i - 1](cnx); await _updaters[i - 1](cnx);
} }
ExecuteNonQuery(cnx, "PRAGMA foreign_key_check"); await ExNonQuery(cnx, "PRAGMA foreign_key_check");
ExecuteNonQuery(cnx, "COMMIT"); await ExNonQuery(cnx, "COMMIT");
ExecuteNonQuery(cnx, "PRAGMA foreign_keys = ON"); await ExNonQuery(cnx, "PRAGMA foreign_keys = ON");
ExecuteNonQuery(cnx, "VACUUM"); await ExNonQuery(cnx, "VACUUM");
ExecuteNonQuery(cnx, $"PRAGMA schema_version = {toVersion * 100 + _versionOffset}"); await ExNonQuery(cnx, $"PRAGMA schema_version = {toVersion * 100 + _versionOffset}");
} }
private static void UpdateDbSchema_1_To_2(SqliteConnection cnx) { private static async Task UpdateDbSchema_1_To_2(SqliteConnection cnx) {
ExecuteNonQuery(cnx, "DROP VIEW v_area_commitment"); await ExNonQuery(cnx, "DROP VIEW v_area_commitment");
ExecuteNonQuery(cnx, "ALTER TABLE delivery_part DROP COLUMN weighing_reason"); await ExNonQuery(cnx, "ALTER TABLE delivery_part DROP COLUMN weighing_reason");
ExecuteNonQuery(cnx, "ALTER TABLE delivery_part ADD COLUMN weighing_reason TEXT CHECK(NOT (manual_weighing = FALSE AND weighing_reason IS NOT NULL))"); await ExNonQuery(cnx, "ALTER TABLE delivery_part ADD COLUMN weighing_reason TEXT CHECK(NOT (manual_weighing = FALSE AND weighing_reason IS NOT NULL))");
} }
private static void UpdateDbSchema_2_To_3(SqliteConnection cnx) { private static async Task UpdateDbSchema_2_To_3(SqliteConnection cnx) {
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TABLE delivery_part_bin ( CREATE TABLE delivery_part_bin (
year INTEGER NOT NULL, year INTEGER NOT NULL,
did INTEGER NOT NULL, did INTEGER NOT NULL,
@@ -102,14 +102,14 @@ namespace Elwig.Helpers {
ON DELETE CASCADE ON DELETE CASCADE
) STRICT; ) STRICT;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value) INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value)
SELECT year, did, dpnr, 0, '_', bucket_2 + bucket_3 SELECT year, did, dpnr, 0, '_', bucket_2 + bucket_3
FROM payment_delivery_part FROM payment_delivery_part
WHERE COALESCE(bucket_1, bucket_2, bucket_3, bucket_4, bucket_5, bucket_6, bucket_7, bucket_8, bucket_9) IS NOT NULL WHERE COALESCE(bucket_1, bucket_2, bucket_3, bucket_4, bucket_5, bucket_6, bucket_7, bucket_8, bucket_9) IS NOT NULL
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value) INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value)
SELECT d.year, d.did, d.dpnr, 1, COALESCE(attributes, ''), bucket_1 SELECT d.year, d.did, d.dpnr, 1, COALESCE(attributes, ''), bucket_1
FROM payment_delivery_part p FROM payment_delivery_part p
@@ -117,29 +117,29 @@ namespace Elwig.Helpers {
WHERE COALESCE(bucket_1, bucket_2, bucket_3, bucket_4, bucket_5, bucket_6, bucket_7, bucket_8, bucket_9) IS NOT NULL WHERE COALESCE(bucket_1, bucket_2, bucket_3, bucket_4, bucket_5, bucket_6, bucket_7, bucket_8, bucket_9) IS NOT NULL
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
"""); """);
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_1"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_1");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_2"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_2");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_3"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_3");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_4"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_4");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_5"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_5");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_6"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_6");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_7"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_7");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_8"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_8");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_9"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN bucket_9");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_1_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_1_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_2_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_2_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_3_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_3_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_4_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_4_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_5_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_5_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_6_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_6_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_7_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_7_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_8_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_8_name");
ExecuteNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_9_name"); await ExNonQuery(cnx, "ALTER TABLE payment_variant DROP COLUMN bucket_9_name");
ExecuteNonQuery(cnx, "ALTER TABLE delivery_part ADD COLUMN gebunden INTEGER CHECK (gebunden IN (TRUE, FALSE)) DEFAULT NULL"); await ExNonQuery(cnx, "ALTER TABLE delivery_part ADD COLUMN gebunden INTEGER CHECK (gebunden IN (TRUE, FALSE)) DEFAULT NULL");
ExecuteNonQuery(cnx, "DROP VIEW v_delivery"); await ExNonQuery(cnx, "DROP VIEW v_delivery");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_delivery AS CREATE VIEW v_delivery AS
SELECT s.*, GROUP_CONCAT(o.modid) AS modifiers SELECT s.*, GROUP_CONCAT(o.modid) AS modifiers
FROM (SELECT p.year, p.did, p.dpnr, FROM (SELECT p.year, p.did, p.dpnr,
@@ -164,8 +164,8 @@ namespace Elwig.Helpers {
ORDER BY s.year, s.lsnr, s.dpnr, o.modid; ORDER BY s.year, s.lsnr, s.dpnr, o.modid;
"""); """);
ExecuteNonQuery(cnx, "DROP VIEW v_bucket"); await ExNonQuery(cnx, "DROP VIEW v_bucket");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_delivery_bin AS CREATE VIEW v_delivery_bin AS
SELECT year, mgnr, SELECT year, mgnr,
sortid || IIF(min_quw, REPLACE(COALESCE(attributes, ''), ',', ''), '_') AS bin, sortid || IIF(min_quw, REPLACE(COALESCE(attributes, ''), ',', ''), '_') AS bin,
@@ -175,7 +175,7 @@ namespace Elwig.Helpers {
ORDER BY year, mgnr, LENGTH(bin) DESC, bin; ORDER BY year, mgnr, LENGTH(bin) DESC, bin;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_payment_bin AS CREATE VIEW v_payment_bin AS
SELECT d.year, d.mgnr, SELECT d.year, d.mgnr,
sortid || discr AS bin, sortid || discr AS bin,
@@ -187,12 +187,12 @@ namespace Elwig.Helpers {
ORDER BY d.year, d.mgnr, bin; ORDER BY d.year, d.mgnr, bin;
"""); """);
ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN fill_lower_bins INTEGER NOT NULL CHECK (fill_lower_bins IN (0, 1, 2)) DEFAULT 0"); await ExNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN fill_lower_bins INTEGER NOT NULL CHECK (fill_lower_bins IN (0, 1, 2)) DEFAULT 0");
} }
private static void UpdateDbSchema_3_To_4(SqliteConnection cnx) { private static async Task UpdateDbSchema_3_To_4(SqliteConnection cnx) {
ExecuteNonQuery(cnx, "DROP VIEW v_payment_bin"); await ExNonQuery(cnx, "DROP VIEW v_payment_bin");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_payment_bin AS CREATE VIEW v_payment_bin AS
SELECT d.year, d.mgnr, SELECT d.year, d.mgnr,
sortid || discr AS bin, sortid || discr AS bin,
@@ -204,7 +204,7 @@ namespace Elwig.Helpers {
ORDER BY d.year, d.mgnr, LENGTH(bin) DESC, bin; ORDER BY d.year, d.mgnr, LENGTH(bin) DESC, bin;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_area_commitment_bin AS CREATE VIEW v_area_commitment_bin AS
SELECT s.year, c.mgnr, SELECT s.year, c.mgnr,
c.vtrgid AS bin, c.vtrgid AS bin,
@@ -219,8 +219,8 @@ namespace Elwig.Helpers {
"""); """);
} }
private static void UpdateDbSchema_4_To_5(SqliteConnection cnx) { private static async Task UpdateDbSchema_4_To_5(SqliteConnection cnx) {
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TABLE _area_commitment_type ( CREATE TABLE _area_commitment_type (
vtrgid TEXT NOT NULL CHECK (vtrgid = sortid || COALESCE(attrid, '') || disc), vtrgid TEXT NOT NULL CHECK (vtrgid = sortid || COALESCE(attrid, '') || disc),
sortid TEXT NOT NULL, sortid TEXT NOT NULL,
@@ -241,22 +241,22 @@ namespace Elwig.Helpers {
ON DELETE RESTRICT ON DELETE RESTRICT
) STRICT; ) STRICT;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO _area_commitment_type (vtrgid, sortid, attrid, disc, min_kg_per_ha, max_kg_per_ha, penalty_amount) INSERT INTO _area_commitment_type (vtrgid, sortid, attrid, disc, min_kg_per_ha, max_kg_per_ha, penalty_amount)
SELECT vtrgid, sortid, attrid_1, disc, min_kg_per_ha, max_kg_per_ha, penalty_amount FROM area_commitment_type SELECT vtrgid, sortid, attrid_1, disc, min_kg_per_ha, max_kg_per_ha, penalty_amount FROM area_commitment_type
"""); """);
ExecuteNonQuery(cnx, "PRAGMA writable_schema = ON"); await ExNonQuery(cnx, "PRAGMA writable_schema = ON");
ExecuteNonQuery(cnx, "DROP TABLE area_commitment_type"); await ExNonQuery(cnx, "DROP TABLE area_commitment_type");
ExecuteNonQuery(cnx, "ALTER TABLE _area_commitment_type RENAME TO area_commitment_type"); await ExNonQuery(cnx, "ALTER TABLE _area_commitment_type RENAME TO area_commitment_type");
ExecuteNonQuery(cnx, "PRAGMA writable_schema = OFF"); await ExNonQuery(cnx, "PRAGMA writable_schema = OFF");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
ALTER TABLE delivery_part ADD COLUMN attrid TEXT DEFAULT NULL ALTER TABLE delivery_part ADD COLUMN attrid TEXT DEFAULT NULL
REFERENCES wine_attribute (attrid) REFERENCES wine_attribute (attrid)
ON UPDATE CASCADE ON UPDATE CASCADE
ON DELETE RESTRICT ON DELETE RESTRICT
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
UPDATE delivery_part UPDATE delivery_part
SET attrid = (SELECT attrid SET attrid = (SELECT attrid
FROM delivery_part_attribute a FROM delivery_part_attribute a
@@ -264,13 +264,13 @@ namespace Elwig.Helpers {
ORDER BY attrid DESC ORDER BY attrid DESC
LIMIT 1) LIMIT 1)
"""); """);
ExecuteNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_i_mtime_delivery_part"); await ExNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_i_mtime_delivery_part");
ExecuteNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_u_mtime_delivery_part"); await ExNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_u_mtime_delivery_part");
ExecuteNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_d_mtime_delivery_part"); await ExNonQuery(cnx, "DROP TRIGGER t_delivery_part_attribute_d_mtime_delivery_part");
ExecuteNonQuery(cnx, "DROP TABLE delivery_part_attribute"); await ExNonQuery(cnx, "DROP TABLE delivery_part_attribute");
ExecuteNonQuery(cnx, "DROP VIEW v_delivery"); await ExNonQuery(cnx, "DROP VIEW v_delivery");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_delivery AS CREATE VIEW v_delivery AS
SELECT p.year, p.did, p.dpnr, SELECT p.year, p.did, p.dpnr,
d.date, d.time, d.zwstid, d.lnr, d.lsnr, d.date, d.time, d.zwstid, d.lnr, d.lsnr,
@@ -293,8 +293,8 @@ namespace Elwig.Helpers {
ORDER BY p.year, p.did, p.dpnr, o.modid; ORDER BY p.year, p.did, p.dpnr, o.modid;
"""); """);
ExecuteNonQuery(cnx, "DROP VIEW v_delivery_bin"); await ExNonQuery(cnx, "DROP VIEW v_delivery_bin");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_delivery_bin AS CREATE VIEW v_delivery_bin AS
SELECT year, mgnr, SELECT year, mgnr,
sortid || IIF(min_quw, COALESCE(attrid, ''), '_') AS bin, sortid || IIF(min_quw, COALESCE(attrid, ''), '_') AS bin,
@@ -304,8 +304,8 @@ namespace Elwig.Helpers {
ORDER BY year, mgnr, LENGTH(bin) DESC, bin; ORDER BY year, mgnr, LENGTH(bin) DESC, bin;
"""); """);
ExecuteNonQuery(cnx, "DROP VIEW v_stat_attr"); await ExNonQuery(cnx, "DROP VIEW v_stat_attr");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_stat_attr AS CREATE VIEW v_stat_attr AS
SELECT year, attrid, SELECT year, attrid,
SUM(weight) as sum, SUM(weight) as sum,
@@ -318,8 +318,8 @@ namespace Elwig.Helpers {
ORDER BY year, attrid; ORDER BY year, attrid;
"""); """);
ExecuteNonQuery(cnx, "DROP VIEW v_stat_sort_attr"); await ExNonQuery(cnx, "DROP VIEW v_stat_sort_attr");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_stat_sort_attr AS CREATE VIEW v_stat_sort_attr AS
SELECT year, sortid, attrid, SELECT year, sortid, attrid,
SUM(weight) as sum, SUM(weight) as sum,
@@ -333,15 +333,15 @@ namespace Elwig.Helpers {
"""); """);
} }
private static void UpdateDbSchema_5_To_6(SqliteConnection cnx) { private static async Task UpdateDbSchema_5_To_6(SqliteConnection cnx) {
ExecuteNonQuery(cnx, "DROP VIEW IF EXISTS v_area_commitment"); await ExNonQuery(cnx, "DROP VIEW IF EXISTS v_area_commitment");
ExecuteNonQuery(cnx, "PRAGMA writable_schema = ON"); await ExNonQuery(cnx, "PRAGMA writable_schema = ON");
ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute DROP COLUMN fill_lower_bins"); await ExNonQuery(cnx, "ALTER TABLE wine_attribute DROP COLUMN fill_lower_bins");
ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN strict INTEGER NOT NULL CHECK (strict IN (TRUE, FALSE)) DEFAULT FALSE"); await ExNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN strict INTEGER NOT NULL CHECK (strict IN (TRUE, FALSE)) DEFAULT FALSE");
ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN fill_lower INTEGER NOT NULL CHECK (fill_lower IN (0, 1, 2)) DEFAULT 0"); await ExNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN fill_lower INTEGER NOT NULL CHECK (fill_lower IN (0, 1, 2)) DEFAULT 0");
ExecuteNonQuery(cnx, "DROP VIEW v_delivery"); await ExNonQuery(cnx, "DROP VIEW v_delivery");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_delivery AS CREATE VIEW v_delivery AS
SELECT p.year, p.did, p.dpnr, SELECT p.year, p.did, p.dpnr,
d.date, d.time, d.zwstid, d.lnr, d.lsnr, d.date, d.time, d.zwstid, d.lnr, d.lsnr,
@@ -363,31 +363,31 @@ namespace Elwig.Helpers {
GROUP BY p.year, p.did, p.dpnr GROUP BY p.year, p.did, p.dpnr
ORDER BY p.year, p.did, p.dpnr, o.modid; ORDER BY p.year, p.did, p.dpnr, o.modid;
"""); """);
ExecuteNonQuery(cnx, "PRAGMA writable_schema = OFF"); await ExNonQuery(cnx, "PRAGMA writable_schema = OFF");
ExecuteNonQuery(cnx, "DROP VIEW v_area_commitment_bin"); await ExNonQuery(cnx, "DROP VIEW v_area_commitment_bin");
ExecuteNonQuery(cnx, "DROP VIEW v_delivery_bin"); await ExNonQuery(cnx, "DROP VIEW v_delivery_bin");
ExecuteNonQuery(cnx, "DROP VIEW v_payment_bin"); await ExNonQuery(cnx, "DROP VIEW v_payment_bin");
ExecuteNonQuery(cnx, "ALTER TABLE area_commitment_type DROP COLUMN max_kg_per_ha"); await ExNonQuery(cnx, "ALTER TABLE area_commitment_type DROP COLUMN max_kg_per_ha");
ExecuteNonQuery(cnx, "ALTER TABLE area_commitment_type ADD COLUMN penalty_per_kg INTEGER DEFAULT NULL"); await ExNonQuery(cnx, "ALTER TABLE area_commitment_type ADD COLUMN penalty_per_kg INTEGER DEFAULT NULL");
ExecuteNonQuery(cnx, "ALTER TABLE area_commitment_type ADD COLUMN penalty_none INTEGER DEFAULT NULL"); await ExNonQuery(cnx, "ALTER TABLE area_commitment_type ADD COLUMN penalty_none INTEGER DEFAULT NULL");
ExecuteNonQuery(cnx, "ALTER TABLE wine_cultivation ADD COLUMN description TEXT DEFAULT NULL"); await ExNonQuery(cnx, "ALTER TABLE wine_cultivation ADD COLUMN description TEXT DEFAULT NULL");
ExecuteNonQuery(cnx, "ALTER TABLE member ADD COLUMN organic INTEGER NOT NULL CHECK (organic IN (TRUE, FALSE)) DEFAULT FALSE"); await ExNonQuery(cnx, "ALTER TABLE member ADD COLUMN organic INTEGER NOT NULL CHECK (organic IN (TRUE, FALSE)) DEFAULT FALSE");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN max_kg_per_ha INTEGER NOT NULL DEFAULT 10000"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN max_kg_per_ha INTEGER NOT NULL DEFAULT 10000");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN vat_normal REAL NOT NULL DEFAULT 0.10"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN vat_normal REAL NOT NULL DEFAULT 0.10");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN vat_flatrate REAL NOT NULL DEFAULT 0.13"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN vat_flatrate REAL NOT NULL DEFAULT 0.13");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN min_kg_per_bs INTEGER NOT NULL DEFAULT 750"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN min_kg_per_bs INTEGER NOT NULL DEFAULT 750");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN max_kg_per_bs INTEGER NOT NULL DEFAULT 3000"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN max_kg_per_bs INTEGER NOT NULL DEFAULT 3000");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_per_kg INTEGER DEFAULT NULL"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_per_kg INTEGER DEFAULT NULL");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_amount INTEGER DEFAULT NULL"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_amount INTEGER DEFAULT NULL");
ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_none INTEGER DEFAULT NULL"); await ExNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_none INTEGER DEFAULT NULL");
ExecuteNonQuery(cnx, "DELETE FROM client_parameter WHERE param IN ('DELIVERY_RIGHT', 'DELIVERY_OBLIGATION', 'VAT_NORMAL', 'VAT_REDUCED', 'VAT_FLATRATE')"); await ExNonQuery(cnx, "DELETE FROM client_parameter WHERE param IN ('DELIVERY_RIGHT', 'DELIVERY_OBLIGATION', 'VAT_NORMAL', 'VAT_REDUCED', 'VAT_FLATRATE')");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TABLE delivery_part_bucket ( CREATE TABLE delivery_part_bucket (
year INTEGER NOT NULL, year INTEGER NOT NULL,
did INTEGER NOT NULL, did INTEGER NOT NULL,
@@ -403,14 +403,14 @@ namespace Elwig.Helpers {
ON DELETE CASCADE ON DELETE CASCADE
) STRICT; ) STRICT;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
SELECT year, did, dpnr, binnr, discr, value SELECT year, did, dpnr, binnr, discr, value
FROM delivery_part_bin FROM delivery_part_bin
"""); """);
ExecuteNonQuery(cnx, "DROP TABLE delivery_part_bin"); await ExNonQuery(cnx, "DROP TABLE delivery_part_bin");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_area_commitment_bucket_strict AS CREATE VIEW v_area_commitment_bucket_strict AS
SELECT s.year, c.mgnr, SELECT s.year, c.mgnr,
t.sortid || COALESCE(a.attrid, '') AS bucket, t.sortid || COALESCE(a.attrid, '') AS bucket,
@@ -426,7 +426,7 @@ namespace Elwig.Helpers {
GROUP BY s.year, c.mgnr, bucket GROUP BY s.year, c.mgnr, bucket
ORDER BY s.year, c.mgnr, bucket; ORDER BY s.year, c.mgnr, bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_area_commitment_bucket AS CREATE VIEW v_area_commitment_bucket AS
SELECT year, mgnr, bucket, min_kg, max_kg SELECT year, mgnr, bucket, min_kg, max_kg
FROM v_area_commitment_bucket_strict FROM v_area_commitment_bucket_strict
@@ -441,7 +441,7 @@ namespace Elwig.Helpers {
GROUP BY b.year, b.mgnr, b.sortid GROUP BY b.year, b.mgnr, b.sortid
ORDER BY year, mgnr, bucket; ORDER BY year, mgnr, bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_delivery_bucket_strict AS CREATE VIEW v_delivery_bucket_strict AS
SELECT year, mgnr, SELECT year, mgnr,
sortid || IIF(min_quw, COALESCE(attrid, ''), '_') AS bucket, sortid || IIF(min_quw, COALESCE(attrid, ''), '_') AS bucket,
@@ -452,7 +452,7 @@ namespace Elwig.Helpers {
GROUP BY year, mgnr, bucket GROUP BY year, mgnr, bucket
ORDER BY year, mgnr, bucket; ORDER BY year, mgnr, bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_delivery_bucket AS CREATE VIEW v_delivery_bucket AS
SELECT year, mgnr, bucket, weight SELECT year, mgnr, bucket, weight
FROM v_delivery_bucket_strict FROM v_delivery_bucket_strict
@@ -466,7 +466,7 @@ namespace Elwig.Helpers {
GROUP BY b.year, b.mgnr, b.sortid GROUP BY b.year, b.mgnr, b.sortid
ORDER BY year, mgnr, bucket; ORDER BY year, mgnr, bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_payment_bucket_strict AS CREATE VIEW v_payment_bucket_strict AS
SELECT d.year, d.mgnr, SELECT d.year, d.mgnr,
d.sortid || b.discr AS bucket, d.sortid || b.discr AS bucket,
@@ -479,7 +479,7 @@ namespace Elwig.Helpers {
HAVING SUM(b.value) > 0 HAVING SUM(b.value) > 0
ORDER BY d.year, d.mgnr, bucket; ORDER BY d.year, d.mgnr, bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_payment_bucket AS CREATE VIEW v_payment_bucket AS
SELECT year, mgnr, bucket, weight SELECT year, mgnr, bucket, weight
FROM v_payment_bucket_strict FROM v_payment_bucket_strict
@@ -495,9 +495,9 @@ namespace Elwig.Helpers {
"""); """);
} }
private static void UpdateDBSchema_6_To_7(SqliteConnection cnx) { private static async Task UpdateDBSchema_6_To_7(SqliteConnection cnx) {
ExecuteNonQuery(cnx, "DROP VIEW v_area_commitment_bucket_strict"); await ExNonQuery(cnx, "DROP VIEW v_area_commitment_bucket_strict");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_area_commitment_bucket_strict AS CREATE VIEW v_area_commitment_bucket_strict AS
SELECT s.year, c.mgnr, SELECT s.year, c.mgnr,
t.sortid || COALESCE(a.attrid, '') AS bucket, t.sortid || COALESCE(a.attrid, '') AS bucket,
@@ -515,14 +515,14 @@ namespace Elwig.Helpers {
ORDER BY s.year, c.mgnr, bucket; ORDER BY s.year, c.mgnr, bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_under_delivery_bucket_strict AS CREATE VIEW v_under_delivery_bucket_strict AS
SELECT c.year, c.mgnr, c.bucket, c.min_kg, COALESCE(p.weight, 0) AS weight SELECT c.year, c.mgnr, c.bucket, c.min_kg, COALESCE(p.weight, 0) AS weight
FROM v_area_commitment_bucket_strict c FROM v_area_commitment_bucket_strict c
LEFT JOIN v_payment_bucket_strict p ON (p.year, p.mgnr, p.bucket) = (c.year, c.mgnr, c.bucket) LEFT JOIN v_payment_bucket_strict p ON (p.year, p.mgnr, p.bucket) = (c.year, c.mgnr, c.bucket)
ORDER BY c.year, c.mgnr, c.bucket; ORDER BY c.year, c.mgnr, c.bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_under_delivery_bucket AS CREATE VIEW v_under_delivery_bucket AS
SELECT u.year, u.mgnr, u.bucket, u.min_kg, SELECT u.year, u.mgnr, u.bucket, u.min_kg,
u.weight + SUM(MAX(COALESCE(p.weight - s.min_kg, 0), 0)) AS weight u.weight + SUM(MAX(COALESCE(p.weight - s.min_kg, 0), 0)) AS weight
@@ -534,7 +534,7 @@ namespace Elwig.Helpers {
GROUP BY u.year, u.mgnr, u.bucket GROUP BY u.year, u.mgnr, u.bucket
ORDER BY u.year, u.mgnr, u.bucket; ORDER BY u.year, u.mgnr, u.bucket;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE VIEW v_under_delivery AS CREATE VIEW v_under_delivery AS
SELECT year, mgnr, bucket, min_kg, weight, weight - min_kg AS diff SELECT year, mgnr, bucket, min_kg, weight, weight - min_kg AS diff
FROM v_under_delivery_bucket FROM v_under_delivery_bucket
@@ -542,76 +542,76 @@ namespace Elwig.Helpers {
"""); """);
} }
private static void UpdateDbSchema_7_To_8(SqliteConnection cnx) { private static async Task UpdateDbSchema_7_To_8(SqliteConnection cnx) {
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT a.gkz, 'WLNO' SELECT a.gkz, 'WLNO'
FROM AT_gem a FROM AT_gem a
LEFT JOIN wb_gem w ON w.gkz = a.gkz LEFT JOIN wb_gem w ON w.gkz = a.gkz
WHERE a.gkz / 10000 = 3 AND w.hkid IS NULL; WHERE a.gkz / 10000 = 3 AND w.hkid IS NULL;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'SLVL' SELECT gkz, 'SLVL'
FROM AT_gem FROM AT_gem
WHERE gkz / 100 IN (617, 622, 623); WHERE gkz / 100 IN (617, 622, 623);
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'SLSS' SELECT gkz, 'SLSS'
FROM AT_gem FROM AT_gem
WHERE gkz / 100 = 610; WHERE gkz / 100 = 610;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
UPDATE wb_gem UPDATE wb_gem
SET hkid = 'SLVL' SET hkid = 'SLVL'
WHERE gkz IN (61007, 61052, 61001, 61055, 61027, 61057, 61008, 61057); WHERE gkz IN (61007, 61052, 61001, 61055, 61027, 61057, 61008, 61057);
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'SLWS' SELECT gkz, 'SLWS'
FROM AT_gem FROM AT_gem
WHERE gkz / 100 IN (603, 616) OR gkz IN (60101, 60663, 60651, 60659, 60664, 60647, 60641, 60639, 60665, 60669, 60618, 60629, 60608, 60670, 60624, 60660, 60656, 60655); WHERE gkz / 100 IN (603, 616) OR gkz IN (60101, 60663, 60651, 60659, 60664, 60647, 60641, 60639, 60665, 60669, 60618, 60629, 60608, 60670, 60624, 60660, 60656, 60655);
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT g.gkz, 'SLVL' SELECT g.gkz, 'SLVL'
FROM AT_gem g FROM AT_gem g
LEFT JOIN wb_gem w ON w.gkz = g.gkz LEFT JOIN wb_gem w ON w.gkz = g.gkz
WHERE g.gkz / 100 = 606 AND w.hkid IS NULL; WHERE g.gkz / 100 = 606 AND w.hkid IS NULL;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT g.gkz, 'SLST' SELECT g.gkz, 'SLST'
FROM AT_gem g FROM AT_gem g
LEFT JOIN wb_gem w ON w.gkz = g.gkz LEFT JOIN wb_gem w ON w.gkz = g.gkz
WHERE g.gkz / 10000 = 6 AND w.hkid IS NULL; WHERE g.gkz / 10000 = 6 AND w.hkid IS NULL;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'BLOO' SELECT gkz, 'BLOO'
FROM AT_gem FROM AT_gem
WHERE gkz / 10000 = 4; WHERE gkz / 10000 = 4;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'BLKA' SELECT gkz, 'BLKA'
FROM AT_gem FROM AT_gem
WHERE gkz / 10000 = 2; WHERE gkz / 10000 = 2;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'BLSB' SELECT gkz, 'BLSB'
FROM AT_gem FROM AT_gem
WHERE gkz / 10000 = 5; WHERE gkz / 10000 = 5;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'BLTI' SELECT gkz, 'BLTI'
FROM AT_gem FROM AT_gem
WHERE gkz / 10000 = 7; WHERE gkz / 10000 = 7;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
INSERT INTO wb_gem INSERT INTO wb_gem
SELECT gkz, 'BLVO' SELECT gkz, 'BLVO'
FROM AT_gem FROM AT_gem
@@ -619,8 +619,8 @@ namespace Elwig.Helpers {
"""); """);
} }
private static void UpdateDbSchema_8_To_9(SqliteConnection cnx) { private static async Task UpdateDbSchema_8_To_9(SqliteConnection cnx) {
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TABLE payment_delivery_part_bucket ( CREATE TABLE payment_delivery_part_bucket (
year INTEGER NOT NULL, year INTEGER NOT NULL,
did INTEGER NOT NULL, did INTEGER NOT NULL,
@@ -641,13 +641,13 @@ namespace Elwig.Helpers {
) STRICT; ) STRICT;
"""); """);
ExecuteNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_i"); await ExNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_i");
ExecuteNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_u"); await ExNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_u");
ExecuteNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_d"); await ExNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_d");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part RENAME COLUMN amount TO net_amount"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part RENAME COLUMN amount TO net_amount");
ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_abs)) VIRTUAL"); await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_abs)) VIRTUAL");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_bucket_i CREATE TRIGGER t_payment_delivery_part_bucket_i
AFTER INSERT ON payment_delivery_part_bucket FOR EACH ROW AFTER INSERT ON payment_delivery_part_bucket FOR EACH ROW
BEGIN BEGIN
@@ -656,7 +656,7 @@ namespace Elwig.Helpers {
ON CONFLICT DO UPDATE SET net_amount = net_amount + NEW.amount; ON CONFLICT DO UPDATE SET net_amount = net_amount + NEW.amount;
END; END;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_bucket_u CREATE TRIGGER t_payment_delivery_part_bucket_u
AFTER UPDATE OF amount ON payment_delivery_part_bucket FOR EACH ROW AFTER UPDATE OF amount ON payment_delivery_part_bucket FOR EACH ROW
BEGIN BEGIN
@@ -668,7 +668,7 @@ namespace Elwig.Helpers {
WHERE (year, did, dpnr, avnr) = (NEW.year, NEW.did, NEW.dpnr, NEW.avnr); WHERE (year, did, dpnr, avnr) = (NEW.year, NEW.did, NEW.dpnr, NEW.avnr);
END; END;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_bucket_d CREATE TRIGGER t_payment_delivery_part_bucket_d
AFTER DELETE ON payment_delivery_part_bucket FOR EACH ROW AFTER DELETE ON payment_delivery_part_bucket FOR EACH ROW
BEGIN BEGIN
@@ -678,12 +678,12 @@ namespace Elwig.Helpers {
END; END;
"""); """);
ExecuteNonQuery(cnx, "ALTER TABLE payment_member RENAME COLUMN amount TO net_amount"); await ExNonQuery(cnx, "ALTER TABLE payment_member RENAME COLUMN amount TO net_amount");
ExecuteNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN mod_abs INTEGER NOT NULL DEFAULT 0"); await ExNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN mod_abs INTEGER NOT NULL DEFAULT 0");
ExecuteNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN mod_rel REAL NOT NULL DEFAULT 0"); await ExNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN mod_rel REAL NOT NULL DEFAULT 0");
ExecuteNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_rel)) VIRTUAL"); await ExNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_rel)) VIRTUAL");
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_i CREATE TRIGGER t_payment_delivery_part_i
AFTER INSERT ON payment_delivery_part FOR EACH ROW AFTER INSERT ON payment_delivery_part FOR EACH ROW
BEGIN BEGIN
@@ -692,7 +692,7 @@ namespace Elwig.Helpers {
ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount; ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
END; END;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_u CREATE TRIGGER t_payment_delivery_part_u
AFTER UPDATE OF amount ON payment_delivery_part FOR EACH ROW AFTER UPDATE OF amount ON payment_delivery_part FOR EACH ROW
BEGIN BEGIN
@@ -704,7 +704,7 @@ namespace Elwig.Helpers {
WHERE (year, avnr, mgnr) = (NEW.year, NEW.avnr, (SELECT mgnr FROM delivery WHERE (year, did) = (NEW.year, NEW.did))); WHERE (year, avnr, mgnr) = (NEW.year, NEW.avnr, (SELECT mgnr FROM delivery WHERE (year, did) = (NEW.year, NEW.did)));
END; END;
"""); """);
ExecuteNonQuery(cnx, """ await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_d CREATE TRIGGER t_payment_delivery_part_d
AFTER DELETE ON payment_delivery_part FOR EACH ROW AFTER DELETE ON payment_delivery_part FOR EACH ROW
BEGIN BEGIN
@@ -714,5 +714,106 @@ namespace Elwig.Helpers {
END; END;
"""); """);
} }
private static async Task UpdateDbSchema_9_To_10(SqliteConnection cnx) {
await ExNonQuery(cnx, "UPDATE wine_quality_level SET min_kmw = 10.6 WHERE qualid = 'RSW'");
await ExNonQuery(cnx, "DROP VIEW v_area_commitment_bucket");
await ExNonQuery(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;
""");
}
private static async Task UpdateDbSchema_10_To_11(SqliteConnection cnx) {
// Drop columns, if they exist...
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_1");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_2");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_3");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_4");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_5");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_6");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_7");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_8");
await ExNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_9");
await ExNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_i");
await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_i
AFTER INSERT ON payment_delivery_part FOR EACH ROW
BEGIN
INSERT INTO payment_member (year, avnr, mgnr, net_amount)
SELECT year, NEW.avnr, mgnr, NEW.amount FROM delivery WHERE (year, did) = (NEW.year, NEW.did)
ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
END;
""");
await ExNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_u");
await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_u
AFTER UPDATE ON payment_delivery_part FOR EACH ROW
BEGIN
UPDATE payment_member
SET net_amount = net_amount - OLD.amount
WHERE (year, avnr, mgnr) IN (SELECT year, OLD.avnr, mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did));
INSERT INTO payment_member (year, avnr, mgnr, net_amount)
SELECT year, NEW.avnr, mgnr, NEW.amount FROM delivery WHERE (year, did) = (NEW.year, NEW.did)
ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
END;
""");
await ExNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_d");
await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_d
AFTER DELETE ON payment_delivery_part FOR EACH ROW
BEGIN
UPDATE payment_member
SET net_amount = net_amount - OLD.amount
WHERE (year, avnr, mgnr) IN (SELECT year, OLD.avnr, mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did));
END;
""");
await ExNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_bucket_u");
await ExNonQuery(cnx, """
CREATE TRIGGER t_payment_delivery_part_bucket_u
AFTER UPDATE ON payment_delivery_part_bucket FOR EACH ROW
BEGIN
UPDATE payment_delivery_part
SET net_amount = net_amount - OLD.amount
WHERE (year, did, dpnr, avnr) = (OLD.year, OLD.did, OLD.dpnr, OLD.avnr);
UPDATE payment_delivery_part
SET net_amount = net_amount + NEW.amount
WHERE (year, did, dpnr, avnr) = (NEW.year, NEW.did, NEW.dpnr, NEW.avnr);
END;
""");
await ExNonQuery(cnx, "ALTER TABLE payment_member DROP COLUMN amount");
await ExNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_abs)) VIRTUAL");
}
private static async Task UpdateDbSchema_11_To_12(SqliteConnection cnx) {
await ExNonQuery(cnx, """
CREATE VIEW v_stat_member AS
SELECT year, mgnr,
SUM(weight) AS sum,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen
FROM v_delivery
GROUP BY year, mgnr
ORDER BY year, mgnr;
""");
}
} }
} }
+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 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(); var attrForced = attrVals.Where(a => a.Value.IsStrict && a.Value.FillLower == 0).Select(a => a.Key).ToArray();
using var cnx = await AppDbContext.ConnectAsync(); 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 inserts = new List<(int, int, int, string, int)>();
var deliveries = new List<(int, int, int, string, int, double, string, string?, string[], bool?)>(); 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; int lastMgNr = 0;
Dictionary<string, (int, int)>? rightsAndObligations = null; Dictionary<string, AreaComBucket>? rightsAndObligations = null;
Dictionary<string, int> used = new(); Dictionary<string, int> used = new();
foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden) in deliveries) { foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden) in deliveries) {
if (lastMgNr != mgnr) { if (lastMgNr != mgnr) {
rightsAndObligations = await Context.GetMemberRightsAndObligations(Year, mgnr); rightsAndObligations = await Context.GetMemberAreaCommitmentBuckets(Year, mgnr);
used = new(); used = new();
} }
if ((honorGebunden && gebunden == false) || if ((honorGebunden && gebunden == false) ||
@@ -94,8 +94,8 @@ namespace Elwig.Helpers.Billing {
if (rightsAndObligations.ContainsKey(key)) { if (rightsAndObligations.ContainsKey(key)) {
int i = (c == 0) ? 1 : 2; int i = (c == 0) ? 1 : 2;
var u = used.GetValueOrDefault(key, 0); var u = used.GetValueOrDefault(key, 0);
var vr = Math.Max(0, Math.Min(rightsAndObligations[key].Item1 - u, w)); var vr = Math.Max(0, Math.Min(rightsAndObligations[key].Right - u, w));
var vo = Math.Max(0, Math.Min(rightsAndObligations[key].Item2 - 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; var v = (attributes.Length == c || attributes.Select(a => !attrVals[a].IsStrict ? 2 : attrVals[a].FillLower).Min() == 2) ? vr : vo;
used[key] = u + v; used[key] = u + v;
if (key.Length > 2 && !isStrict) used[key[..2]] = used.GetValueOrDefault(key[..2], 0) + v; if (key.Length > 2 && !isStrict) used[key[..2]] = used.GetValueOrDefault(key[..2], 0) + v;
+44
View File
@@ -0,0 +1,44 @@
using Newtonsoft.Json;
using NJsonSchema;
using System;
using System.Reflection;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Elwig.Helpers.Billing {
public class BillingData {
public static JsonSchema? Schema { get; private set; }
public static async Task Init() {
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Elwig.Schemas.payment_variant.json");
Schema = await JsonSchema.FromJsonAsync(stream ?? throw new ArgumentException("JSON schema not found"));
}
private JsonObject Data;
public BillingData(JsonObject data) {
Data = data;
}
public static JsonObject ParseJson(string json) {
if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first");
try {
var errors = Schema.Validate(json);
if (errors.Count != 0) throw new ArgumentException("Invalid JSON data");
return JsonNode.Parse(json)?.AsObject() ?? throw new ArgumentException("Invalid JSON data");
} catch (JsonReaderException) {
throw new ArgumentException("Invalid JSON data");
}
}
public static BillingData FromJson(string json) {
return new(ParseJson(json));
}
public decimal CalculatePrice(string sortId, string? attrid, string qualId, bool gebunden, double oe, double kmw, bool minQuw) {
return 0m;
}
}
}
+126 -2
View File
@@ -11,6 +11,79 @@ namespace Elwig.Helpers.Billing {
AvNr = avnr; AvNr = avnr;
} }
public async Task Calculate() {
await DeleteInDb();
await SetCalcTime();
await CalculatePrices();
await CalculateModifiers();
await CalculateMemberModifiers();
}
public async Task Commit() {
await Revert();
using var cnx = await AppDbContext.ConnectAsync();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"""
INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers)
SELECT s.year,
COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr,
m.mgnr,
v.avnr,
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
ROUND(lp.amount / POW(10, s.precision - 2)) AS prev_amount,
IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
ROUND(COALESCE(u.total_penalty, 0) / POW(10, 4 - 2)) AS modifiers,
lc.modifiers AS prev_modifiers
FROM season s
JOIN payment_variant v ON v.year = s.year
LEFT JOIN payment_variant l ON l.year = s.year
AND l.avnr = (SELECT avnr
FROM payment_variant
WHERE year = s.year AND NOT test_variant
ORDER BY COALESCE(transfer_date, date) DESC, avnr DESC
LIMIT 1)
LEFT JOIN (SELECT year, MAX(tgnr) AS tgnr FROM credit GROUP BY year) t ON t.year = s.year
JOIN (SELECT DISTINCT year, mgnr FROM delivery) d ON d.year = s.year
JOIN member m ON m.mgnr = d.mgnr
LEFT JOIN payment_member lp ON (lp.year, lp.avnr, lp.mgnr) = (l.year, l.avnr, m.mgnr)
LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (v.year, v.avnr, m.mgnr)
LEFT JOIN credit lc ON (lc.year, lc.avnr, lc.mgnr) = (l.year, l.avnr, m.mgnr)
LEFT JOIN (SELECT year, mgnr,
SUM(COALESCE(IIF(u.weight = 0, -t.penalty_none, 0), 0) +
COALESCE(IIF(u.diff < 0, -t.penalty_amount, 0), 0) +
COALESCE(u.diff * t.penalty_per_kg, 0)) AS total_penalty
FROM v_under_delivery u
JOIN area_commitment_type t ON t.vtrgid = u.bucket
GROUP BY year, mgnr) u ON (u.year, u.mgnr) = (s.year, m.mgnr)
WHERE s.year = {Year} AND v.avnr = {AvNr}
""";
await cmd.ExecuteNonQueryAsync();
}
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync();
}
}
public async Task Revert() {
using var cnx = await AppDbContext.ConnectAsync();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync();
}
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync();
}
}
protected async Task SetCalcTime() {
using var cnx = await AppDbContext.ConnectAsync();
using var cmd = cnx.CreateCommand();
cmd.CommandText = $"UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync();
}
protected async Task DeleteInDb() { protected async Task DeleteInDb() {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
using (var cmd = cnx.CreateCommand()) { using (var cmd = cnx.CreateCommand()) {
@@ -25,10 +98,43 @@ namespace Elwig.Helpers.Billing {
cmd.CommandText = $"DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr})"; cmd.CommandText = $"DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"UPDATE payment_variant SET calc_time = NULL WHERE (year, avnr) = ({Year}, {AvNr})";
await cmd.ExecuteNonQueryAsync();
}
} }
public async Task CalculatePrices() { protected async Task CalculateMemberModifiers() {
await DeleteInDb(); using var cnx = await AppDbContext.ConnectAsync();
if (App.Client.IsMatzen) {
var lastYears = 3;
var multiplier = 0.50;
var modName = "Treue%";
using var cmd = cnx.CreateCommand();
cmd.CommandText = $"""
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
SELECT c.year, {AvNr}, s.mgnr, 0,
ROUND(s.sum * COALESCE(m.abs, 0)),
COALESCE(m.rel, 0)
FROM (SELECT {Year} AS year, mgnr,
ROUND(AVG(sum) * {multiplier}) AS baseline,
COUNT(*) = {lastYears} AND MIN(sum) > 0 AS allowed
FROM v_stat_member
WHERE year > {Year} - {lastYears}
GROUP BY mgnr
HAVING allowed) c
JOIN v_stat_member s ON (s.year, s.mgnr) = (c.year, c.mgnr)
LEFT JOIN modifier m ON m.year = c.year AND m.name LIKE '{modName}'
WHERE sum >= baseline
ON CONFLICT DO UPDATE
SET mod_abs = mod_abs + excluded.mod_abs,
mod_rel = mod_rel + excluded.mod_rel
""";
await cmd.ExecuteNonQueryAsync();
}
}
protected async Task CalculatePrices() {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, bool MinQuw, double Oe, double Kmw)>(); var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, bool MinQuw, double Oe, double Kmw)>();
@@ -52,6 +158,7 @@ namespace Elwig.Helpers.Billing {
var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>(); var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>();
foreach (var part in parts) { foreach (var part in parts) {
var price = !part.MinQuw ? 0.5m : ((part.BktNr == 2 ? 0.8m : (part.BktNr == 1 ? 0.7m : 0.6m)) + ((decimal)(part.Oe - 73) * 0.005m)); // TODO var price = !part.MinQuw ? 0.5m : ((part.BktNr == 2 ? 0.8m : (part.BktNr == 1 ? 0.7m : 0.6m)) + ((decimal)(part.Oe - 73) * 0.005m)); // TODO
price += (AvNr - 1) * 0.2m;
var priceL = Utils.DecToDb(price, 4); var priceL = Utils.DecToDb(price, 4);
var amount = Utils.DecToDb(price * part.Value, 4); var amount = Utils.DecToDb(price * part.Value, 4);
inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, amount)); inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, amount));
@@ -65,5 +172,22 @@ namespace Elwig.Helpers.Billing {
await cmd.ExecuteNonQueryAsync(); await cmd.ExecuteNonQueryAsync();
} }
} }
protected async Task CalculateModifiers() {
using var cnx = await AppDbContext.ConnectAsync();
using var cmd = cnx.CreateCommand();
cmd.CommandText = $"""
INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0), COALESCE(m.rel, 0)
FROM delivery_part d
LEFT JOIN delivery_part_modifier p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
LEFT JOIN modifier m ON m.modid = p.modid
WHERE d.year = {Year}
ON CONFLICT DO UPDATE
SET mod_abs = mod_abs + excluded.mod_abs,
mod_rel = mod_rel + excluded.mod_rel
""";
await cmd.ExecuteNonQueryAsync();
}
} }
} }
-32
View File
@@ -1,32 +0,0 @@
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Helpers.Billing {
public class Transaction {
public readonly Member Member;
public readonly long AmountCent;
public readonly string Currency;
public readonly int Nr;
public Transaction(Member m, decimal amount, string currency, int nr) {
Member = m;
AmountCent = (long)Math.Round(amount * 100);
Currency = currency;
Nr = nr;
}
public static IEnumerable<Transaction> FromPaymentVariant(PaymentVar variant) {
var last = variant.Season.PaymentVariants.Where(v => v.TransferDate != null).OrderBy(v => v.TransferDate).LastOrDefault();
var dict = last?.MemberPayments.ToDictionary(m => m.MgNr, m => m.Amount) ?? new();
return variant.Credits
.OrderBy(c => c.MgNr)
.Select(c => new Transaction(c.Member, c.Amount, variant.Season.CurrencyCode, c.TgNr))
.ToList();
}
public static string FormatAmountCent(long cents) => $"{cents / 100}.{cents % 100:00}";
}
}
+17 -50
View File
@@ -1,8 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using IniParser; using Microsoft.Extensions.Configuration;
using IniParser.Model;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public class Config { public class Config {
@@ -13,7 +12,8 @@ namespace Elwig.Helpers {
public string? DatabaseLog = null; public string? DatabaseLog = null;
public string? Branch = null; public string? Branch = null;
public IList<string?[]> Scales; 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) { public Config(string filename) {
FileName = filename; FileName = filename;
@@ -22,57 +22,24 @@ namespace Elwig.Helpers {
} }
public void Read() { public void Read() {
var parser = new FileIniDataParser(); var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
IniData? ini = null;
try {
ini = parser.ReadFile(FileName, Utils.UTF8);
} catch {}
if (ini == null || !ini.TryGetKey("database.file", out string db)) { DatabaseFile = Utils.GetAbsolutePath(config["database:file"] ?? "database.sqlite3", App.DataPath);
DatabaseFile = App.DataPath + "database.sqlite3"; var log = config["database:log"];
} else if (db.Length > 1 && (db[1] == ':' || db[0] == '/' || db[0] == '\\')) { DatabaseLog = log != null ? Utils.GetAbsolutePath(log, App.DataPath) : null;
DatabaseFile = db; Branch = config["general:branch"];
} else { Debug = trueValues.Contains(config["general:debug"]?.ToLower());
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";
}
var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key);
ScaleList.Clear(); ScaleList.Clear();
Scales = ScaleList; Scales = ScaleList;
if (ini != null) { foreach (var s in scales) {
foreach (var s in ini.Sections.Where(s => s.SectionName.StartsWith("scale."))) { string? scaleLog = config[$"scale.{s}:log"];
string? scaleLog = null; if (scaleLog != null) scaleLog = Utils.GetAbsolutePath(scaleLog, App.DataPath);
if (s.Keys["log"] != null) { ScaleList.Add([
scaleLog = s.Keys["log"]; s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"],
if (scaleLog.Length <= 1 || (scaleLog[1] != ':' && scaleLog[0] != '/' && scaleLog[0] != '\\')) { config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], scaleLog
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
});
}
} }
} }
+51 -59
View File
@@ -1,4 +1,4 @@
using Elwig.Helpers.Billing; using Elwig.Models.Dtos;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -7,32 +7,24 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
public class Ebics : IBankingExporter { public class Ebics(PaymentVar variant, string filename) : IBankingExporter {
public static string FileExtension => "xml"; public static string FileExtension => "xml";
private readonly StreamWriter _writer; private readonly StreamWriter Writer = new(filename, false, Utils.UTF8);
private readonly DateOnly _date; private readonly DateOnly Date = variant.TransferDate ?? throw new ArgumentException("TransferDate has to be set in PaymentVar");
private readonly int _year; private readonly int Year = variant.Year;
private readonly string _name; private readonly string Name = variant.Name;
private readonly int _nr; private readonly int AvNr = variant.AvNr;
public Ebics(PaymentVar variant, string filename) {
_writer = new(filename, false, Utils.UTF8);
_date = variant.TransferDate ?? DateOnly.Parse("2021-01-10"); //throw new ArgumentException("TransferDate has to be set in PaymentVar");
_year = variant.Year;
_name = variant.Name;
_nr = variant.AvNr;
}
public void Dispose() { public void Dispose() {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
_writer.Dispose(); Writer.Dispose();
} }
public ValueTask DisposeAsync() { public ValueTask DisposeAsync() {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
return _writer.DisposeAsync(); return Writer.DisposeAsync();
} }
public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) { public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
@@ -43,66 +35,66 @@ namespace Elwig.Helpers.Export {
progress?.Report(0.0); progress?.Report(0.0);
var nbOfTxs = transactions.Count(); var nbOfTxs = transactions.Count();
int count = nbOfTxs + 2, i = 0; int count = nbOfTxs + 2, i = 0;
var ctrlSum = Transaction.FormatAmountCent(transactions.Sum(tx => tx.AmountCent)); var ctrlSum = transactions.Sum(tx => tx.Amount);
var msgId = $"ELWIG-{App.Client.NameToken}-{_year}-AV{_nr:00}"; var msgId = $"ELWIG-{App.Client.NameToken}-{Year}-AV{AvNr:00}";
var pmtInfId = $"{msgId}-1"; var pmtInfId = $"{msgId}-1";
await _writer.WriteLineAsync($""" await Writer.WriteLineAsync($"""
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09" <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd"> xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd">
<CstmrCdtTrfInitn> <CstmrCdtTrfInitn>
<GrpHdr> <GrpHdr>
<MsgId>{msgId}</MsgId> <MsgId>{msgId}</MsgId>
<CreDtTm>{DateTime.UtcNow:o}</CreDtTm> <CreDtTm>{DateTime.UtcNow:o}</CreDtTm>
<NbOfTxs>{nbOfTxs}</NbOfTxs> <NbOfTxs>{nbOfTxs}</NbOfTxs>
<CtrlSum>{ctrlSum}</CtrlSum> <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
<InitgPty><Nm>{App.Client.NameFull}</Nm></InitgPty> <InitgPty><Nm>{App.Client.NameFull}</Nm></InitgPty>
</GrpHdr> </GrpHdr>
<PmtInf> <PmtInf>
<PmtInfId>{pmtInfId}</PmtInfId> <PmtInfId>{pmtInfId}</PmtInfId>
<PmtMtd>TRF</PmtMtd> <PmtMtd>TRF</PmtMtd>
<NbOfTxs>{nbOfTxs}</NbOfTxs> <NbOfTxs>{nbOfTxs}</NbOfTxs>
<CtrlSum>{ctrlSum}</CtrlSum> <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
<ReqdExctnDt><Dt>{_date:yyyy-MM-dd}</Dt></ReqdExctnDt> <ReqdExctnDt><Dt>{Date:yyyy-MM-dd}</Dt></ReqdExctnDt>
<Dbtr><Nm>{App.Client.NameFull}</Nm></Dbtr> <Dbtr><Nm>{App.Client.NameFull}</Nm></Dbtr>
<DbtrAcct><Id><IBAN>{App.Client.Iban?.Replace(" ", "")}</IBAN></Id></DbtrAcct> <DbtrAcct><Id><IBAN>{App.Client.Iban?.Replace(" ", "")}</IBAN></Id></DbtrAcct>
<DbtrAgt><FinInstnId><BICFI>{App.Client.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></DbtrAgt> <DbtrAgt><FinInstnId><BICFI>{App.Client.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></DbtrAgt>
"""); """);
progress?.Report(100.0 * ++i / count); progress?.Report(100.0 * ++i / count);
foreach (var tx in transactions) { foreach (var tx in transactions) {
var a = (IAddress?)tx.Member.BillingAddress ?? tx.Member; var a = (IAddress?)tx.Member.BillingAddress ?? tx.Member;
var (a1, a2) = Utils.SplitAddress(a.Address); var (a1, a2) = Utils.SplitAddress(a.Address);
var id = $"ELWIG-{App.Client.NameToken}-{_year}-TG{tx.Nr:0000}"; var id = $"ELWIG-{App.Client.NameToken}-{Year}-TG{tx.Nr:0000}";
var info = $"{_name} - Traubengutschrift {_year}/{tx.Nr:000}"; var info = $"{Name} - Traubengutschrift Nr. {Year}/{tx.Nr:000}";
await _writer.WriteLineAsync($""" await Writer.WriteLineAsync($"""
<CdtTrfTxInf> <CdtTrfTxInf>
<PmtId><EndToEndId>{id}</EndToEndId></PmtId> <PmtId><EndToEndId>{id}</EndToEndId></PmtId>
<Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmountCent(tx.AmountCent)}</InstdAmt></Amt> <Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmount(tx.Amount)}</InstdAmt></Amt>
<Cdtr> <Cdtr>
<Nm>{a.Name}</Nm> <Nm>{a.Name}</Nm>
<PstlAdr> <PstlAdr>
<StrtNm>{a1}</StrtNm><BldgNb>{a2}</BldgNb> <StrtNm>{a1}</StrtNm><BldgNb>{a2}</BldgNb>
<PstCd>{a.PostalDest.AtPlz?.Plz}</PstCd><TwnNm>{a.PostalDest.AtPlz?.Ort.Name}</TwnNm> <PstCd>{a.PostalDest.AtPlz?.Plz}</PstCd><TwnNm>{a.PostalDest.AtPlz?.Ort.Name}</TwnNm>
<Ctry>{a.PostalDest.Country.Alpha2}</Ctry> <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>
</PstlAdr> </PstlAdr>
</Cdtr> </Cdtr>
<CdtrAcct><Id><IBAN>{tx.Member.Iban}</IBAN></Id><CdtrAcct> <CdtrAcct><Id><IBAN>{tx.Member.Iban}</IBAN></Id></CdtrAcct>
<CdtrAgt><FinInstnId><BICFI>{tx.Member.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></CdtrAgt> <CdtrAgt><FinInstnId><BICFI>{tx.Member.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></CdtrAgt>
<RmtInf><Ustrd>{info}</Ustrd></RmtInf> <RmtInf><Ustrd>{info}</Ustrd></RmtInf>
</CdtTrfTxInf> </CdtTrfTxInf>
"""); """);
progress?.Report(100.0 * ++i / count); progress?.Report(100.0 * ++i / count);
} }
await _writer.WriteLineAsync(""" await Writer.WriteLineAsync("""
</PmtInf> </PmtInf>
</CstmrCdtTrfInitn> </CstmrCdtTrfInitn>
</Document> </Document>
"""); """);
await _writer.FlushAsync(); await Writer.FlushAsync();
progress?.Report(100.0); progress?.Report(100.0);
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
using Elwig.Helpers.Billing; using Elwig.Models.Dtos;
using System; using System;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
+1 -1
View File
@@ -1,4 +1,4 @@
using Elwig.Helpers.Billing; using Elwig.Models.Dtos;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
/// <summary> /// <summary>
+15
View File
@@ -11,6 +11,7 @@ using Elwig.Dialogs;
using System.Text; using System.Text;
using System.Numerics; using System.Numerics;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using System.IO;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public static partial class Utils { public static partial class Utils {
@@ -47,6 +48,16 @@ namespace Elwig.Helpers {
[GeneratedRegex(@"^(.*?) +([0-9].*)$", RegexOptions.Compiled)] [GeneratedRegex(@"^(.*?) +([0-9].*)$", RegexOptions.Compiled)]
private static partial Regex GeneratedAddressRegex(); private static partial Regex GeneratedAddressRegex();
public static readonly KeyValuePair<string, string>[] PhoneNrTypes = [
new("landline", "Tel.-Nr. (Festnetz)"),
new("mobile", "Tel.-Nr. (mobil)"),
new("fax", "Fax-Nr."),
];
public static string PhoneNrTypeToString(string type) {
return PhoneNrTypes.Where(t => t.Key == type).Select(t => t.Value).FirstOrDefault(type);
}
private static readonly ushort[] Crc16ModbusTable = { private static readonly ushort[] Crc16ModbusTable = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
@@ -345,5 +356,9 @@ namespace Elwig.Helpers {
} }
return output.OrderByDescending(l => l.Count()); 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;
}
} }
} }
+1 -1
View File
@@ -37,7 +37,7 @@ namespace Elwig.Models.Dtos {
private static async Task<IEnumerable<AreaComUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaComUnderDeliveryRowSingle> table, int year) { private static async Task<IEnumerable<AreaComUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaComUnderDeliveryRowSingle> table, int year) {
return await table.FromSqlRaw($""" return await table.FromSqlRaw($"""
SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address, SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address,
c.bucket, c.area, u.min_kg, u.weight c.bucket, c.area, u.min_kg, u.weight
FROM member m FROM member m
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz LEFT JOIN AT_ort o ON o.okz = p.okz
+36 -14
View File
@@ -1,4 +1,5 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
@@ -27,11 +28,11 @@ namespace Elwig.Models.Dtos {
MgNr = mgnr; MgNr = mgnr;
} }
public static async Task<IDictionary<int, CreditNoteData>> ForPaymentVariant(DbSet<CreditNoteRowSingle> table, int year, int avnr) { public static async Task<IDictionary<int, CreditNoteData>> ForPaymentVariant(DbSet<CreditNoteRowSingle> table, DbSet<Season> seasons, int year, int avnr) {
return (await FromDbSet(table, year, avnr)) return (await FromDbSet(table, year, avnr))
.GroupBy( .GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr }, r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
(k, g) => new CreditNoteRow(g)) (k, g) => new CreditNoteRow(g, seasons))
.GroupBy( .GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr }, r => new { r.Year, r.AvNr, r.MgNr, r.TgNr },
(k, g) => new CreditNoteData(g, k.Year, k.TgNr, mgnr: k.MgNr)) (k, g) => new CreditNoteData(g, k.Year, k.TgNr, mgnr: k.MgNr))
@@ -43,17 +44,20 @@ namespace Elwig.Models.Dtos {
var v = avnr?.ToString() ?? "NULL"; var v = avnr?.ToString() ?? "NULL";
var m = mgnr?.ToString() ?? "NULL"; var m = mgnr?.ToString() ?? "NULL";
return await table.FromSqlRaw($""" return await table.FromSqlRaw($"""
SELECT d.year, c.tgnr, p.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, b.bktnr, d.sortid, b.discr, b.value, p.price, p.amount, SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers,
v.name AS variant, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount,
s.name AS variant, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw
FROM v_delivery d FROM v_delivery d
JOIN wine_variety v ON d.sortid = v.sortid JOIN wine_variety s ON s.sortid = d.sortid
LEFT JOIN wine_attribute a ON a.attrid = d.attrid LEFT JOIN wine_attribute a ON a.attrid = d.attrid
JOIN wine_quality_level q ON q.qualid = d.qualid JOIN wine_quality_level q ON q.qualid = d.qualid
LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr) LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr)
LEFT JOIN payment_delivery_part_bucket p ON (p.year, p.did, p.dpnr, p.bktnr) = (b.year, b.did, b.dpnr, b.bktnr) LEFT JOIN payment_variant v ON v.year = d.year
LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, p.avnr, d.mgnr) LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr)
WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (p.avnr = {v} OR {v} IS NULL OR p.avnr IS NULL) AND (d.mgnr = {m} OR {m} IS NULL) LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr, pb.avnr) = (b.year, b.did, b.dpnr, b.bktnr, v.avnr)
ORDER BY d.year, p.avnr, d.mgnr, d.lsnr, d.dpnr LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr)
WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL)
ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
""").ToListAsync(); """).ToListAsync();
} }
} }
@@ -73,28 +77,38 @@ namespace Elwig.Models.Dtos {
public string QualityLevel; public string QualityLevel;
public (double Oe, double Kmw) Gradation; public (double Oe, double Kmw) Gradation;
public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets; public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets;
public decimal? TotalModifiers;
public decimal? Amount;
public CreditNoteRow(IEnumerable<CreditNoteRowSingle> rows) { public CreditNoteRow(IEnumerable<CreditNoteRowSingle> rows, DbSet<Season> seasons) {
var f = rows.First(); var f = rows.First();
Year = f.Year; Year = f.Year;
TgNr = f.TgNr; TgNr = f.TgNr;
MgNr = f.MgNr; MgNr = f.MgNr;
var season = seasons.Find(Year);
LsNr = f.LsNr; LsNr = f.LsNr;
DPNr = f.DPNr; DPNr = f.DPNr;
Variant = f.Variant; Variant = f.Variant;
Attribute = f.Attribute; Attribute = f.Attribute;
Modifiers = Array.Empty<string>(); // TODO var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',')
.Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m))
.Where(m => m != null)
.OrderBy(m => m.Ordering)
.ToList();
Modifiers = modifiers.Select(m => m.Name).ToArray();
QualityLevel = f.QualityLevel; QualityLevel = f.QualityLevel;
Gradation = (f.Oe, f.Kmw); Gradation = (f.Oe, f.Kmw);
Buckets = rows Buckets = rows
.Where(b => b.Value > 0) .Where(b => b.Value > 0)
.OrderByDescending(b => b.BktNr) .OrderByDescending(b => b.BktNr)
// FIXME precision
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value, .Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
b.Price != null ? (decimal?)Utils.DecFromDb((long)b.Price, 4) : null, b.Price != null ? season?.DecFromDb((long)b.Price) : null,
b.Amount != null ? (decimal?)Utils.DecFromDb((long)b.Amount, 4) : null)) b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
.ToArray(); .ToArray();
Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
TotalModifiers = Amount - netAmount;
} }
} }
@@ -114,6 +128,10 @@ namespace Elwig.Models.Dtos {
public string LsNr { get; set; } public string LsNr { get; set; }
[Column("dpnr")] [Column("dpnr")]
public int DPNr { get; set; } public int DPNr { get; set; }
[Column("weight")]
public int Weight { get; set; }
[Column("modifiers")]
public string? Modifiers { get; set; }
[Column("bktnr")] [Column("bktnr")]
public int BktNr { get; set; } public int BktNr { get; set; }
[Column("sortid")] [Column("sortid")]
@@ -126,6 +144,10 @@ namespace Elwig.Models.Dtos {
public long? Price { get; set; } public long? Price { get; set; }
[Column("amount")] [Column("amount")]
public long? Amount { get; set; } public long? Amount { get; set; }
[Column("net_amount")]
public long? NetAmount { get; set; }
[Column("total_amount")]
public long? TotalAmount { get; set; }
[Column("variant")] [Column("variant")]
public string Variant { get; set; } public string Variant { get; set; }
[Column("attribute")] [Column("attribute")]
+26
View File
@@ -0,0 +1,26 @@
using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Models.Dtos {
public class Transaction(Member member, decimal amount, string currency, int nr) {
public readonly Member Member = member;
public readonly decimal Amount = amount;
public readonly string Currency = currency;
public readonly int Nr = nr;
public Transaction(Credit c) : this(c.Member, c.Amount, c.Variant.Season.CurrencyCode, c.TgNr) { }
public static IEnumerable<Transaction> FromPaymentVariant(PaymentVar variant) {
return variant.Credits
.OrderBy(c => c.TgNr)
.Select(c => new Transaction(c))
.ToList();
}
public static string FormatAmountCent(long cents) => $"{cents / 100}.{cents % 100:00}";
public static string FormatAmount(decimal amount) => FormatAmountCent((int)(amount * 100));
}
}
+1 -1
View File
@@ -163,7 +163,7 @@ namespace Elwig.Models.Entities {
[NotMapped] [NotMapped]
public IEnumerable<AreaCom> ActiveAreaCommitments => AreaCommitments public IEnumerable<AreaCom> ActiveAreaCommitments => AreaCommitments
.Where(c => c.YearFrom <= Utils.CurrentNextSeason && (c.YearTo ?? int.MaxValue) >= Utils.CurrentNextSeason); .Where(c => c.YearFrom <= Utils.CurrentYear && (c.YearTo ?? int.MaxValue) >= Utils.CurrentYear);
[InverseProperty("Member")] [InverseProperty("Member")]
public virtual BillingAddr? BillingAddress { get; private set; } public virtual BillingAddr? BillingAddress { get; private set; }
+10 -5
View File
@@ -16,6 +16,14 @@ namespace Elwig.Models.Entities {
[Column("avnr")] [Column("avnr")]
public int AvNr { get; set; } public int AvNr { get; set; }
[Column("net_amount")]
public long NetAmountValue { get; set; }
[NotMapped]
public decimal NetAmount {
get => Variant.Season.DecFromDb(NetAmountValue);
set => NetAmountValue = Variant.Season.DecToDb(value);
}
[Column("mod_abs")] [Column("mod_abs")]
public long ModAbsValue { get; set; } public long ModAbsValue { get; set; }
[NotMapped] [NotMapped]
@@ -33,12 +41,9 @@ namespace Elwig.Models.Entities {
} }
[Column("amount")] [Column("amount")]
public long? AmountValue { get; set; } public long AmountValue { get; private set; }
[NotMapped] [NotMapped]
public decimal? Amount { public decimal Amount => Variant.Season.DecFromDb(AmountValue);
get => AmountValue != null ? Variant.Season.DecFromDb(AmountValue.Value) : null;
set => AmountValue = value != null ? Variant.Season.DecToDb(value.Value) : null;
}
[ForeignKey("Year, AvNr")] [ForeignKey("Year, AvNr")]
public virtual PaymentVar Variant { get; private set; } public virtual PaymentVar Variant { get; private set; }
+26 -5
View File
@@ -13,15 +13,36 @@ namespace Elwig.Models.Entities {
[Column("mgnr")] [Column("mgnr")]
public int MgNr { get; set; } public int MgNr { get; set; }
[Column("amount")]
public long AmountValue { get; set; }
[Column("net_amount")]
public long NetAmountValue { get; set; }
[NotMapped] [NotMapped]
public decimal Amount { public decimal NetAmount {
get => Variant.Season.DecFromDb(AmountValue); get => Variant.Season.DecFromDb(NetAmountValue);
set => AmountValue = Variant.Season.DecToDb(value); set => NetAmountValue = Variant.Season.DecToDb(value);
} }
[Column("mod_abs")]
public long ModAbsValue { get; set; }
[NotMapped]
public decimal ModAbs {
get => Variant.Season.DecFromDb(ModAbsValue);
set => ModAbsValue = Variant.Season.DecToDb(value);
}
[Column("mod_rel")]
public double ModRelValue { get; set; }
[NotMapped]
public decimal ModRel {
get => (decimal)ModRelValue;
set => ModRelValue = (double)value;
}
[Column("amount")]
public long AmountValue { get; private set; }
[NotMapped]
public decimal Amount => Variant.Season.DecFromDb(AmountValue);
[ForeignKey("Year, AvNr")] [ForeignKey("Year, AvNr")]
public virtual PaymentVar Variant { get; private set; } public virtual PaymentVar Variant { get; private set; }
+3
View File
@@ -51,6 +51,9 @@ namespace Elwig.Models.Entities {
[InverseProperty("Variant")] [InverseProperty("Variant")]
public virtual ISet<PaymentMember> MemberPayments { get; private set; } public virtual ISet<PaymentMember> MemberPayments { get; private set; }
[InverseProperty("Variant")]
public virtual ISet<PaymentDeliveryPart> DeliveryPartPayments { get; private set; }
[InverseProperty("Variant")] [InverseProperty("Variant")]
public virtual ISet<Credit> Credits { get; private set; } public virtual ISet<Credit> Credits { get; private set; }
} }
+135
View File
@@ -0,0 +1,135 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["mode"],
"anyOf": [{
"required": ["version", "payment", "curves"],
"additionalProperties": false,
"properties": {
"mode": {"enum": ["elwig"]},
"version": {"enum": [1]},
"payment": {
"type": ["number", "string", "object"],
"pattern": "^curve:[0-9]+$",
"additionalProperties": false,
"properties": {
"default": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
},
"patternProperties": {
"^[A-Z]+$": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
}
},
"quality": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[A-Z]{3}$": {
"type": ["number", "string", "object"],
"pattern": "^curve:[0-9]+$",
"additionalProperties": false,
"properties": {
"default": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
},
"patternProperties": {
"^[A-Z]+$": {
"type": ["number", "string"],
"pattern": "^curve:[0-9]+$"
}
}
}
}
},
"curves": {
"type": "array",
"items": {"$ref": "#/definitions/curve"}
}
}
}, {
"properties": {
"mode": {"enum": ["wgmaster"]},
"AuszahlungSorten": {
"type": "object",
"additionalProperties": false,
"required": ["Kurven"],
"properties": {
"Kurven": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"patternProperties": {
"^[0-9]+(\\.[0-9]+)?oe$": {"type": "number"}
}
}
}
},
"patternProperties": {
"^[A-Z]{2}$": {
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[A-Z]*$": {
"type": "object",
"additionalProperties": false,
"properties": {
"Gebunden": {
"type": "integer",
"minimum": 0
},
"NichtGebunden": {
"type": "integer",
"minimum": 0
}
}
}
}
}
}
}
}
}],
"definitions": {
"curve": {
"type": "object",
"required": ["id", "mode", "data"],
"additionalProperties": false,
"properties": {
"id": {
"type": "integer",
"minimum": 0
},
"mode": {"enum": ["oe", "kmw"]},
"data": {
"anyOf": [
{"type": "number" },
{"$ref": "#/definitions/curve_data"}
]
},
"geb": {
"anyOf": [
{"type": "number"},
{"$ref": "#/definitions/curve_data"}
]
}
}
},
"curve_data": {
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"patternProperties": {
"^([0-9]+(\\.[0-9]+)?)(oe|kmw)$": {"type": "number"}
}
}
}
}
+7 -4
View File
@@ -427,8 +427,9 @@ namespace Elwig.Windows {
} }
protected void TextBox_TextChanged(object sender, RoutedEventArgs? evt) { protected void TextBox_TextChanged(object sender, RoutedEventArgs? evt) {
var input = (TextBox)sender; var input = (Control)sender;
if (SenderIsRequired(input) && input.Text.Length == 0) { var tb = input as TextBox ?? (input as UnitTextBox)?.TextBox;
if (SenderIsRequired(input) && tb?.Text.Length == 0) {
ValidateInput(input, false); ValidateInput(input, false);
ControlUtils.SetInputInvalid(input); ControlUtils.SetInputInvalid(input);
} else { } else {
@@ -472,11 +473,13 @@ namespace Elwig.Windows {
} }
protected void IntegerInput_TextChanged(object sender, TextChangedEventArgs evt) { 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) { 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) { protected void PartialDateInput_TextChanged(object sender, TextChangedEventArgs evt) {
+22 -21
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@@ -45,9 +46,9 @@ namespace Elwig.Windows {
AvNr = avnr; AvNr = avnr;
var v = Context.PaymentVariants.Find(year, avnr); var v = Context.PaymentVariants.Find(year, avnr);
Title = $"{v?.Name} - Lese {year} - Elwig"; Title = $"{v?.Name} - Lese {year} - Elwig";
ExemptInputs = new Control[] { ExemptInputs = [
GraphList, OechsleInput, PriceInput, FreeZoomInput, GradationLinesInput, TooltipInput GraphList, OechsleInput, PriceInput, FreeZoomInput, GradationLinesInput, TooltipInput
}; ];
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { private void Window_Loaded(object sender, RoutedEventArgs evt) {
@@ -60,15 +61,19 @@ namespace Elwig.Windows {
await RefreshGraphListQuery(); await RefreshGraphListQuery();
} }
private async Task RefreshGraphListQuery(bool updateSort = false) { private static JsonObject? ParseData(PaymentVar variant) {
List<PaymentVar> paymentVars = await Context.PaymentVariants.Where(p => p.Year == Year && p.AvNr == AvNr).ToListAsync(); try {
return BillingData.ParseJson(variant.Data);
if (paymentVars.Count != 1) { } catch (ArgumentException) {
return; return null;
} }
}
PaymentVar paymentVar = paymentVars[0]; private async Task RefreshGraphListQuery(bool updateSort = false) {
var data = JsonNode.Parse(paymentVar.Data).AsObject(); var paymentVar = await Context.PaymentVariants.FindAsync(Year, AvNr);
if (paymentVar == null) return;
var data = ParseData(paymentVar);
if (data == null) return;
var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject(); var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject();
if (auszahlungsSorten == null) { if (auszahlungsSorten == null) {
@@ -81,7 +86,7 @@ namespace Elwig.Windows {
return; return;
} }
List<Graph> GraphsList = new(); List<Graph> GraphsList = [];
int i = 1; int i = 1;
foreach (var graph in Graphs) { foreach (var graph in Graphs) {
@@ -97,8 +102,8 @@ namespace Elwig.Windows {
RefreshInputs(); RefreshInputs();
} }
private String ParseContracts(JsonObject auszahlungsSorten, int num) { private string ParseContracts(JsonObject auszahlungsSorten, int num) {
List<string> contracts = new(); List<string> contracts = [];
foreach (var sorte in auszahlungsSorten) { foreach (var sorte in auszahlungsSorten) {
if (sorte.Key == "Kurven") continue; if (sorte.Key == "Kurven") continue;
@@ -111,18 +116,14 @@ namespace Elwig.Windows {
} }
} }
return string.Join("\n", contracts.ToArray()); return string.Join("\n", contracts);
} }
private async Task<bool> RemoveGraph(int num) { private async Task<bool> RemoveGraph(int num) {
List<PaymentVar> paymentVars = await Context.PaymentVariants.Where(p => p.Year == Year && p.AvNr == AvNr).ToListAsync(); var paymentVar = await Context.PaymentVariants.FindAsync(Year, AvNr);
if (paymentVar == null) return false;
if (paymentVars.Count != 1) { var data = ParseData(paymentVar);
return false; if (data == null) return false;
}
PaymentVar paymentVar = paymentVars[0];
var data = JsonNode.Parse(paymentVar.Data).AsObject();
var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject(); var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject();
if (auszahlungsSorten == null) { if (auszahlungsSorten == null) {
+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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -53,8 +54,10 @@
<MenuItem Header="Drucken"> <MenuItem Header="Drucken">
<MenuItem x:Name="Menu_Print_Letterhead" Header="Briefkopf drucken" <MenuItem x:Name="Menu_Print_Letterhead" Header="Briefkopf drucken"
Click="Menu_Print_Letterhead_Click"/> Click="Menu_Print_Letterhead_Click"/>
<MenuItem x:Name="Menu_Show_Memberdata" Header="Stammdatenblatt anzeigen" Click="Menu_Show_Memberdata_Click"/> <MenuItem x:Name="Menu_Show_MemberDataSheet" Header="Stammdatenblatt anzeigen" IsEnabled="False" Tag="Print"
<MenuItem x:Name="Menu_Print_Memberdata" Header="Stammdatenblatt drucken" Click="Menu_Print_Memberdata_Click"/> 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 Header="Briefköpfe drucken">
<MenuItem x:Name="Menu_Print_Letterheads_MgNr" Header="nach MgNr. sortiert" IsEnabled="False" Tag="Print" <MenuItem x:Name="Menu_Print_Letterheads_MgNr" Header="nach MgNr. sortiert" IsEnabled="False" Tag="Print"
Click="Menu_Print_Letterheads_MgNr_Click"/> Click="Menu_Print_Letterheads_MgNr_Click"/>
+17 -20
View File
@@ -15,29 +15,25 @@ using Elwig.Documents;
namespace Elwig.Windows { namespace Elwig.Windows {
public partial class MemberAdminWindow : AdministrationWindow { public partial class MemberAdminWindow : AdministrationWindow {
private List<string> TextFilter = new(); private List<string> TextFilter = [];
private readonly RoutedCommand CtrlF = new(); private readonly RoutedCommand CtrlF = new();
private readonly (ComboBox, TextBox, TextBox)[] PhoneNrInputs; 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(Utils.PhoneNrTypes);
new("landline", "Tel.-Nr. (Festnetz)"),
new("mobile", "Tel.-Nr. (mobil)"),
new("fax", "Fax-Nr."),
};
public MemberAdminWindow() { public MemberAdminWindow() {
InitializeComponent(); InitializeComponent();
CtrlF.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control)); CtrlF.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control));
CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput)); CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
ExemptInputs = new Control[] { ExemptInputs = [
SearchInput, ActiveMemberInput, MemberList, SearchInput, ActiveMemberInput, MemberList,
}; ];
RequiredInputs = new Control[] { RequiredInputs = [
MgNrInput, GivenNameInput, FamilyNameInput, MgNrInput, GivenNameInput, FamilyNameInput,
AddressInput, PlzInput, OrtInput, BillingOrtInput, AddressInput, PlzInput, OrtInput, BillingOrtInput,
BusinessSharesInput, BranchInput, DefaultKgInput BusinessSharesInput, BranchInput, DefaultKgInput
}; ];
PhoneNrInputs = new (ComboBox, TextBox, TextBox)[] { PhoneNrInputs = [
(PhoneNr1TypeInput, PhoneNr1Input, PhoneNr1CommentInput), (PhoneNr1TypeInput, PhoneNr1Input, PhoneNr1CommentInput),
(PhoneNr2TypeInput, PhoneNr2Input, PhoneNr2CommentInput), (PhoneNr2TypeInput, PhoneNr2Input, PhoneNr2CommentInput),
(PhoneNr3TypeInput, PhoneNr3Input, PhoneNr3CommentInput), (PhoneNr3TypeInput, PhoneNr3Input, PhoneNr3CommentInput),
@@ -47,7 +43,7 @@ namespace Elwig.Windows {
(PhoneNr7TypeInput, PhoneNr7Input, PhoneNr7CommentInput), (PhoneNr7TypeInput, PhoneNr7Input, PhoneNr7CommentInput),
(PhoneNr8TypeInput, PhoneNr8Input, PhoneNr8CommentInput), (PhoneNr8TypeInput, PhoneNr8Input, PhoneNr8CommentInput),
(PhoneNr9TypeInput, PhoneNr9Input, PhoneNr9CommentInput), (PhoneNr9TypeInput, PhoneNr9Input, PhoneNr9CommentInput),
}; ];
foreach (var input in PhoneNrInputs) input.Item1.ItemsSource = PhoneNrTypes; foreach (var input in PhoneNrInputs) input.Item1.ItemsSource = PhoneNrTypes;
InitializeDelayTimer(SearchInput, SearchInput_TextChanged); InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
@@ -57,8 +53,9 @@ namespace Elwig.Windows {
private void Window_Loaded(object sender, RoutedEventArgs evt) { private void Window_Loaded(object sender, RoutedEventArgs evt) {
Menu_Print_Letterheads_MgNr.IsEnabled = App.IsPrintingReady; Menu_Print_Letterheads_MgNr.IsEnabled = App.IsPrintingReady;
Menu_Print_Letterheads_Name.IsEnabled = App.IsPrintingReady; Menu_Print_Letterheads_Name.IsEnabled = App.IsPrintingReady;
Menu_Show_Memberdata.IsEnabled = App.IsPrintingReady; Menu_Print_Letterheads_Plz.IsEnabled = App.IsPrintingReady;
Menu_Print_Memberdata.IsEnabled = App.IsPrintingReady; Menu_Show_MemberDataSheet.IsEnabled = App.IsPrintingReady;
Menu_Print_MemberDataSheet.IsEnabled = App.IsPrintingReady;
ActiveMemberInput.IsChecked = true; ActiveMemberInput.IsChecked = true;
UpdatePhoneNrInputVisibility(); UpdatePhoneNrInputVisibility();
@@ -339,7 +336,7 @@ namespace Elwig.Windows {
await PrintLetterheads(2); 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) if (MemberList.SelectedItem is not Member m)
return; return;
Mouse.OverrideCursor = Cursors.AppStarting; Mouse.OverrideCursor = Cursors.AppStarting;
@@ -353,7 +350,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) if (MemberList.SelectedItem is not Member m)
return; return;
Mouse.OverrideCursor = Cursors.AppStarting; Mouse.OverrideCursor = Cursors.AppStarting;
@@ -649,8 +646,8 @@ namespace Elwig.Windows {
Menu_Member_SendEmail.IsEnabled = m.EmailAddresses.Count > 0; Menu_Member_SendEmail.IsEnabled = m.EmailAddresses.Count > 0;
Menu_Print_Letterhead.IsEnabled = true; Menu_Print_Letterhead.IsEnabled = true;
Menu_Show_Memberdata.IsEnabled = true; Menu_Show_MemberDataSheet.IsEnabled = true;
Menu_Print_Memberdata.IsEnabled = true; Menu_Print_MemberDataSheet.IsEnabled = true;
FinishInputFilling(); FinishInputFilling();
} }
@@ -658,8 +655,8 @@ namespace Elwig.Windows {
new protected void ClearInputs(bool validate = false) { new protected void ClearInputs(bool validate = false) {
Menu_Member_SendEmail.IsEnabled = false; Menu_Member_SendEmail.IsEnabled = false;
Menu_Print_Letterhead.IsEnabled = false; Menu_Print_Letterhead.IsEnabled = false;
Menu_Show_Memberdata.IsEnabled = false; Menu_Show_MemberDataSheet.IsEnabled = false;
Menu_Print_Memberdata.IsEnabled = false; Menu_Print_MemberDataSheet.IsEnabled = false;
StatusDeliveriesLastSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason - 1}): -"; StatusDeliveriesLastSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason - 1}): -";
StatusDeliveriesThisSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason}): -"; StatusDeliveriesThisSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason}): -";
StatusAreaCommitment.Text = "Gebundene Fläche: -"; StatusAreaCommitment.Text = "Gebundene Fläche: -";
+11
View File
@@ -85,6 +85,17 @@
<GroupBox Header="Abschluss" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,0,10,10"> <GroupBox Header="Abschluss" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,0,10,10">
<Grid> <Grid>
<Button x:Name="CommitButton" FontSize="12" Width="160" Height="40" Margin="0,0,10,55"
Click="CommitButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Right">
<TextBlock TextAlignment="Center">Traubengutschriften<LineBreak/>erstellen</TextBlock>
</Button>
<Button x:Name="RevertButton" FontSize="12" Width="160" Height="40" Margin="0,0,10,10"
Click="RevertButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Right">
<TextBlock TextAlignment="Center">Traubengutschriften<LineBreak/>löschen</TextBlock>
</Button>
<ProgressBar x:Name="ProgressBar" Margin="10,0,0,74" Height="27" Width="180" <ProgressBar x:Name="ProgressBar" Margin="10,0,0,74" Height="27" Width="180"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/> VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Button x:Name="ShowButton" Content="Vorschau" FontSize="14" Width="180" Margin="10,10,10,42" Height="27" Tag="Print" IsEnabled="False" <Button x:Name="ShowButton" Content="Vorschau" FontSize="14" Width="180" Margin="10,10,10,42" Height="27" Tag="Print" IsEnabled="False"
+33 -4
View File
@@ -30,9 +30,11 @@ namespace Elwig.Windows {
private void Update() { private void Update() {
if (PaymentVariantList.SelectedItem is PaymentVar v) { if (PaymentVariantList.SelectedItem is PaymentVar v) {
var locked = v.TransferDate != null || !v.TestVariant; var locked = !v.TestVariant;
DeleteButton.IsEnabled = !locked; DeleteButton.IsEnabled = !locked;
CalculateButton.IsEnabled = !locked; CalculateButton.IsEnabled = !locked;
CommitButton.IsEnabled = !locked;
RevertButton.IsEnabled = locked;
CopyButton.IsEnabled = true; CopyButton.IsEnabled = true;
EditButton.IsEnabled = true; EditButton.IsEnabled = true;
ShowButton.IsEnabled = true; ShowButton.IsEnabled = true;
@@ -44,6 +46,8 @@ namespace Elwig.Windows {
EditButton.IsEnabled = false; EditButton.IsEnabled = false;
CopyButton.IsEnabled = false; CopyButton.IsEnabled = false;
CalculateButton.IsEnabled = false; CalculateButton.IsEnabled = false;
CommitButton.IsEnabled = false;
RevertButton.IsEnabled = false;
DeleteButton.IsEnabled = false; DeleteButton.IsEnabled = false;
ShowButton.IsEnabled = false; ShowButton.IsEnabled = false;
PrintButton.IsEnabled = false; PrintButton.IsEnabled = false;
@@ -75,7 +79,7 @@ namespace Elwig.Windows {
CalculateButton.IsEnabled = false; CalculateButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting; Mouse.OverrideCursor = Cursors.AppStarting;
var b = new BillingVariant(v.Year, v.AvNr); var b = new BillingVariant(v.Year, v.AvNr);
await b.CalculatePrices(); await b.Calculate();
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
CalculateButton.IsEnabled = true; CalculateButton.IsEnabled = true;
} }
@@ -94,6 +98,30 @@ namespace Elwig.Windows {
await Generate(2); await Generate(2);
} }
private async void CommitButton_Click(object sender, RoutedEventArgs e) {
if (PaymentVariantList.SelectedValue is not PaymentVar v)
return;
CommitButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new BillingVariant(v.Year, v.AvNr);
await b.Commit();
Mouse.OverrideCursor = null;
RevertButton.IsEnabled = true;
await HintContextChange();
}
private async void RevertButton_Click(object sender, RoutedEventArgs e) {
if (PaymentVariantList.SelectedValue is not PaymentVar v)
return;
RevertButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new BillingVariant(v.Year, v.AvNr);
await b.Revert();
Mouse.OverrideCursor = null;
CommitButton.IsEnabled = true;
await HintContextChange();
}
private async Task Generate(int mode) { private async Task Generate(int mode) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) if (PaymentVariantList.SelectedItem is not PaymentVar v)
return; return;
@@ -110,10 +138,11 @@ namespace Elwig.Windows {
members = members.OrderBy(m => m.MgNr); members = members.OrderBy(m => m.MgNr);
IEnumerable<Member> list = await members.ToListAsync(); IEnumerable<Member> list = await members.ToListAsync();
var data = await CreditNoteData.ForPaymentVariant(Context.CreditNoteRows, v.Year, v.AvNr); var data = await CreditNoteData.ForPaymentVariant(Context.CreditNoteRows, Context.Seasons, v.Year, v.AvNr);
var payments = await Context.MemberPayments.Where(p => p.Year == v.Year && p.AvNr == v.AvNr).ToDictionaryAsync(c => c.MgNr); var payments = await Context.MemberPayments.Where(p => p.Year == v.Year && p.AvNr == v.AvNr).ToDictionaryAsync(c => c.MgNr);
await Context.GetMemberAreaCommitmentBuckets(Year, 0);
using var doc = Document.Merge(list.Select(m => using var doc = Document.Merge(list.Select(m =>
new CreditNote(Context, payments[m.MgNr], data[m.MgNr]) new CreditNote(Context, payments[m.MgNr], data[m.MgNr], Context.GetMemberUnderDelivery(Year, m.MgNr).GetAwaiter().GetResult())
)); ));
await doc.Generate(new Progress<double>(v => { await doc.Generate(new Progress<double>(v => {
ProgressBar.Value = v; ProgressBar.Value = v;
+1 -1
View File
@@ -1,7 +1,7 @@
[general] [general]
; Only needed, if more than one branch is stored in database ; Only needed, if more than one branch is stored in database
branch = Zweigstelle ;branch = Zweigstelle
;debug = true ;debug = true
[database] [database]
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>