Compare commits

...

31 Commits

Author SHA1 Message Date
e9e90b1e98 Bump version to 1.0.3.0
All checks were successful
Test / Run tests (push) Successful in 3m4s
Deploy / Build and Deploy (push) Successful in 2m8s
2026-01-16 14:43:52 +01:00
640dbf705e Printing/Pdf: Fix detection of WinziPrint.exe to isolate tests properly
All checks were successful
Test / Run tests (push) Successful in 2m11s
2026-01-16 01:26:35 +01:00
42121fe7da Elwig: Update dependencies
Some checks failed
Test / Run tests (push) Has been cancelled
2026-01-16 00:35:50 +01:00
d45c3f867f Tests: Update dependencies 2026-01-16 00:35:38 +01:00
a90be2644d [#50] MailWindow: Add button to cancel document generation
Some checks failed
Test / Run tests (push) Has been cancelled
2026-01-16 00:22:11 +01:00
01739ba42e ClientParameters: Fix storing of MailSendPostal and MailSendEmail 2026-01-16 00:20:15 +01:00
7bea4d9ee0 BillingVariant: Fix NULL/0 error introduced in c7a2f2241d
All checks were successful
Test / Run tests (push) Successful in 2m12s
2026-01-15 17:56:12 +01:00
b6497fd422 PaymentVariantsWindow: Select last variant by default and ask user before deleting
All checks were successful
Test / Run tests (push) Successful in 2m33s
2026-01-15 14:03:00 +01:00
f141485537 Documents: Only show header and footer on BusinessDocuments 2026-01-15 14:01:21 +01:00
b31603554a Document: Add IsPreview to indicate that a document may only be viewed by internal staff
All checks were successful
Test / Run tests (push) Successful in 2m51s
2026-01-15 13:26:44 +01:00
90def81cc5 PaymentVariantSummary: Fix statistical sum for geb weight
All checks were successful
Test / Run tests (push) Successful in 2m52s
2026-01-14 09:15:27 +01:00
87903227b5 [#66] Services: Add filters for import/export/upload 2026-01-03 20:49:00 +01:00
6d6776f0f9 DeliveryAdminWindow: Complete tooltip in search bar and warning when no season is found 2026-01-03 20:48:39 +01:00
bf3cc2ea1e [#71] Weighing: Fix reconnection behaviour when COM port is connected/disconnected 2026-01-03 20:48:39 +01:00
c1697dc4f3 [#66] MainWindow: Merge both sync buttons into a single one 2026-01-03 20:47:51 +01:00
3b335a568e AbbDbContext: Move SqliteConnection extensions to Extensions class 2026-01-03 20:47:45 +01:00
beacdab54f [#66] Services: Add SyncService 2026-01-03 20:47:14 +01:00
da05a49e10 DeliveryService: Add modifiers when splitting delivery parts
All checks were successful
Test / Run tests (push) Successful in 2m31s
2025-12-18 18:12:14 +01:00
5cec5b3556 QueryWindow: Allow users to export query result to csv file
All checks were successful
Test / Run tests (push) Successful in 1m47s
2025-12-16 16:55:52 +01:00
9af498287d Database: Add v_member view
All checks were successful
Test / Run tests (push) Successful in 2m32s
2025-12-16 15:58:45 +01:00
452f246f24 [#73] DeliveryAdminWindow: Add Liefermengen Excel output 2025-12-15 00:03:45 +01:00
e97c29db43 Dtos: Rename MemberDeliveryPerVarietyData to MemberDeliveryYieldsPerVarietyData 2025-12-14 23:14:40 +01:00
3419113dec DeliveryAdminWindow: Restrict handling of modifiers to specific clients
All checks were successful
Test / Run tests (push) Successful in 2m19s
2025-12-13 08:30:10 +01:00
bf6297f63b DeliveryPart: Add Unloading type instead of IsLesewagen
All checks were successful
Test / Run tests (push) Successful in 1m54s
2025-12-12 12:35:48 +01:00
f228ba3019 AppDbContext: Fix silent fail in ExecuteBatch() 2025-12-11 16:08:11 +01:00
495aa8a691 DeliveryNote: Add option to redact modifier value
All checks were successful
Test / Run tests (push) Successful in 2m26s
2025-12-11 13:05:40 +01:00
811916a8b9 Controls/CheckComboBox: Apply AllItemsSelectedContent only when more than 1 items are selected 2025-12-11 13:05:40 +01:00
3b6333a6a2 Tests: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 2m22s
2025-11-29 12:34:33 +01:00
9b37330362 Elwig: Update dependencies 2025-11-29 12:34:33 +01:00
889a17b21c Upgrade to .NET 10 2025-11-29 12:34:33 +01:00
ac6d559e5d Helpers/Utils: Fix mail log for single mails 2025-11-29 12:34:29 +01:00
74 changed files with 1515 additions and 597 deletions

View File

@@ -2,6 +2,47 @@
Changelog Changelog
========= =========
[v1.0.3.0][v1.0.3.0] (2026-01-16) {#v1.0.3.0}
---------------------------------------------
### Neue Funktionen {#v1.0.3.0-features}
* Bei Zu-/Abschlägen ist es nun möglich den konkreten Wert dieser auf Lieferscheinen nicht anzuzeigen (Stammdaten -> Saisons). (495aa8a691)
* Bei Teillieferungen kann nun die Art der Anlieferung angegeben werden: Planenwagen/Kipper, Lesewagen, Kiste(n). (bf6297f63b, 3419113dec, 6d6776f0f9)
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) können nun die Gesamtmengen der gefilterten Lieferungen nach Mitglied und Sorte als Excel-Liste exportiert werden ("Liefermengen"). ([#73][i73], e97c29db43)
* Im Datenbankabfragen-Fenster (`QueryWindow`) gibt es die Möglichkeit die Ergebnisse als CSV-Datei zu exportieren. (9af498287d, 5cec5b3556)
* PDF-Dokumente wurden überarbeitet:
* Traubengutschriften können nur noch ausgedruckt bzw. verschickt werden, wenn die zugehörige Auszahlungsvariante festgesetzt ist. (b31603554a)
* Auf Dokumenten für interne Verwendung (alle außer an Mitglieder adressierte) wurden Kopf- und Fußzeile auf ein Minimum beschränkt. (f141485537)
* Das Synchronisieren zwischen Zweigstellen ist für den Benutzer nun wesentlich vereinfacht. Geänderte Daten von Mitgliedern, Flächenbindungen oder Lieferungen werden beim Synchronisieren automatsich hochgeladen bzw. heruntergeladen. ([#66][i66], 3b335a568e)
* Im Rundschreiben-Fenster (`MailWindow`) wurde ein Knopf zum Abbrechen des Generierens hinzugefügt. ([#50][i50])
### Behobene Fehler {#v1.0.3.0-bugfixes}
* Einzelne (nicht über das Rundschreiben-Fenster) verschickte E-Mails werde korrekt im Ausgangsprotokoll angeführt. (ac6d559e5d)
* Bei automatischen Datenbank-Updates sind auftretende Fehler ignoriert worden. (f228ba3019)
* Beim Aufteilen von Lieferungen sind Zu-/Abschläge nicht auf neu erstellen Teillieferungen übernommen worden. (da05a49e10)
* Das Verbinden und Trennen von Waagen mittels Serial-/COM-Port ist nun bei laufendem Programm möglich. ([#71][i71])
* In den Auszahlungsvarianten-Daten (`PaymentVariantSummary`) ist die statistische Summe der gebundenen und ungebundenen Menge ohne die Spalte _attributlos gebunden_ berechnet worden. (90def81cc5)
* Seit [v0.13.7](#v0.13.7) (2025-01-21) wurde auch bei der ersten Auszahlung der Saison ein "Bisher berücksichtigt: € 0,00" auf den Traubengutschriften angeführt. (7bea4d9ee0)
* Das Speichern der Parameter `MAIL_SEND_POSTAL` und `MAIL_SEND_EMAIL` war fehlerhaft. (01739ba42e)
* Bessere Isolation der automatisierten Dokumenten-Tests. (640dbf705e)
### Sonstiges {#v1.0.3.0-misc}
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) wird bei Zu-/Abschlägen nur "Alle" angezeigt, wenn mehr als 1 ausgewählt sind. (811916a8b9)
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) wird die letzte Variante standardmäßig ausgewählt und beim Löschen wird um Bestätigung gebeten. (b6497fd422)
* Abhängigkeiten aktualisiert. (889a17b21c, 9b37330362, 3b6333a6a2, d45c3f867f, 42121fe7da)
[v1.0.3.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.0
[i50]: https://git.necronda.net/winzer/elwig/issues/50
[i66]: https://git.necronda.net/winzer/elwig/issues/60
[i71]: https://git.necronda.net/winzer/elwig/issues/71
[i73]: https://git.necronda.net/winzer/elwig/issues/73
[v1.0.2.0][v1.0.2.0] (2025-11-10) {#v1.0.2.0} [v1.0.2.0][v1.0.2.0] (2025-11-10) {#v1.0.2.0}
--------------------------------------------- ---------------------------------------------

View File

@@ -55,6 +55,12 @@
<TextBlock Text="{Binding ValueStr}"/> <TextBlock Text="{Binding ValueStr}"/>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="PublicModifierTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" MinWidth="250" Margin="0,0,10,0"/>
<TextBlock Text="{Binding PublicValueStr}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="WineAttributeTemplate"> <DataTemplate x:Key="WineAttributeTemplate">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">

View File

@@ -149,7 +149,7 @@ namespace Elwig {
} catch (Exception e) { } catch (Exception e) {
list.Add(new InvalidScale(s.Id)); list.Add(new InvalidScale(s.Id));
if (s.Required) if (s.Required)
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagen-Fehler", MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
@@ -249,14 +249,18 @@ namespace Elwig {
private void OnSerialPortConnected(object? sender, string name) { private void OnSerialPortConnected(object? sender, string name) {
for (var i = 0; i < Config.Scales.Count; i++) { for (var i = 0; i < Config.Scales.Count; i++) {
var s = Config.Scales[i]; var s = Config.Scales[i];
if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is InvalidScale) { if (s.Connection?.StartsWith($"serial://{name}:") ?? false) {
try { if (Scales[i] is InvalidScale) {
Scales[i] = Scale.FromConfig(s); try {
Scales[i] = Scale.FromConfig(s);
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception e) {
Scales[i] = new InvalidScale(s.Id);
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error);
}
} else if (Scales[i] is IEventScale) {
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception e) {
Scales[i] = new InvalidScale(s.Id);
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagen-Fehler",
MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
} }
@@ -268,12 +272,14 @@ namespace Elwig {
var s = Config.Scales[i]; var s = Config.Scales[i];
if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) { if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) {
MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning); MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning);
try { if (Scales[i] is ICommandScale) {
Scales[i].Dispose(); try {
} catch { Scales[i].Dispose();
// ignore } catch {
// ignore
}
Scales[i] = new InvalidScale(s.Id);
} }
Scales[i] = new InvalidScale(s.Id);
} }
} }
UpdateScales(); UpdateScales();

View File

@@ -124,19 +124,24 @@ namespace Elwig.Controls {
SelectItemsReverse(); SelectItemsReverse();
var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null; var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null;
if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) { if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
_textBox.Text = AllItemsSelectedContent;
AllItemsSelected = true; AllItemsSelected = true;
} else if (SelectedItems.Count == 0) { } else if (SelectedItems.Count == 0) {
_textBox.Text = "";
AllItemsSelected = false; AllItemsSelected = false;
} else {
AllItemsSelected = null;
}
if (SelectedItems.Count > 1 && SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
_textBox.Text = AllItemsSelectedContent;
} else if (SelectedItems.Count == 0) {
_textBox.Text = "";
} else { } else {
_textBox.Text = string.Join(Delimiter, _textBox.Text = string.Join(Delimiter,
dmp == null ? SelectedItems.Cast<object>() : dmp == null ? SelectedItems.Cast<object>() :
SelectedItems.Cast<object>() SelectedItems.Cast<object>()
.Select(i => i.GetType().GetProperty(dmp)?.GetValue(i)) .Select(i => i.GetType().GetProperty(dmp)?.GetValue(i))
); );
AllItemsSelected = null;
} }
RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, evt.RemovedItems, evt.AddedItems)); RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, evt.RemovedItems, evt.AddedItems));
} }

View File

@@ -19,6 +19,16 @@ namespace Elwig.Documents {
Member = m; Member = m;
Location = App.BranchLocation; Location = App.BranchLocation;
IncludeSender = includeSender; IncludeSender = includeSender;
var c = App.Client;
Header = $"<div class='name'>{c.Name}</div><div class='suffix'>{c.NameSuffix}</div><div class='type'>{c.NameTypeFull}</div>";
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
.Item(c.NameFull).NextLine()
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
.Item(c.EmailAddress != 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("Bio-KSt.", c.OrganicAuthority).NextLine()
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
.ToString();
var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " <i>(pauschaliert)</i>"); var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " <i>(pauschaliert)</i>");
Aside = $"<table><colgroup><col span='1' style='width: 22.5mm;'/><col span='1' style='width: 42.5mm;'/></colgroup>" + Aside = $"<table><colgroup><col span='1' style='width: 22.5mm;'/><col span='1' style='width: 42.5mm;'/></colgroup>" +
$"<thead><tr><th colspan='2'>Mitglied</th></tr></thead><tbody>" + $"<thead><tr><th colspan='2'>Mitglied</th></tr></thead><tbody>" +

View File

@@ -39,6 +39,7 @@ namespace Elwig.Documents {
Data = data; Data = data;
Payment = p; Payment = p;
Credit = p.Credit; Credit = p.Credit;
IsPreview = Payment == null || Credit == null;
var season = p.Variant.Season; var season = p.Variant.Season;
if (considerCustomModifiers) { if (considerCustomModifiers) {
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr); CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
@@ -48,7 +49,7 @@ namespace Elwig.Documents {
if (CustomPayment?.ModComment != null) { if (CustomPayment?.ModComment != null) {
MemberModifier = CustomPayment.ModComment; MemberModifier = CustomPayment.ModComment;
} else if (mod != null) { } else if (mod != null) {
MemberModifier = $"{mod.Name} ({mod.ValueStr})"; MemberModifier = $"{mod.Name} ({mod.PublicValueStr})";
} else { } else {
MemberModifier = "Sonstige Zu-/Abschläge"; MemberModifier = "Sonstige Zu-/Abschläge";
} }

View File

@@ -2,7 +2,7 @@
h1 { h1 {
text-align: center; text-align: center;
font-size: 24pt; font-size: 24pt;
margin-top: 10mm; margin-top: 0;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -2,7 +2,7 @@
h1 { h1 {
text-align: center; text-align: center;
font-size: 24pt; font-size: 24pt;
margin-top: 10mm; margin-top: 0;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -2,7 +2,7 @@
h1 { h1 {
text-align: center; text-align: center;
font-size: 24pt; font-size: 24pt;
margin-top: 10mm; margin-top: 0;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -55,7 +55,7 @@
@if (part.Modifiers.Count() > 0) { @if (part.Modifiers.Count() > 0) {
var first = true; var first = true;
foreach (var mod in part.Modifiers) { foreach (var mod in part.Modifiers) {
<tr class="tight @(first ? "first" : "")"><td></td><td>@Raw(first ? "<i>Zu-/Abschläge:</i>" : "")</td><td colspan="3"><b>@mod.Name</b></td><td style="white-space: pre;">@mod.ValueStr</td></tr> <tr class="tight @(first ? "first" : "")"><td></td><td>@Raw(first ? "<i>Zu-/Abschläge:</i>" : "")</td><td colspan="3"><b>@mod.Name</b></td><td style="white-space: pre;">@mod.PublicValueStr</td></tr>
first = false; first = false;
} }
} }

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Elwig.Helpers.Printing; using Elwig.Helpers.Printing;
using MimeKit; using MimeKit;
using System.Threading;
namespace Elwig.Documents { namespace Elwig.Documents {
public abstract partial class Document : IDisposable { public abstract partial class Document : IDisposable {
@@ -18,10 +19,11 @@ namespace Elwig.Documents {
protected string? _pdfPath; protected string? _pdfPath;
protected string? PdfPath => _pdfPath ?? _pdfFile?.FilePath; protected string? PdfPath => _pdfPath ?? _pdfFile?.FilePath;
public int? TotalPages { get; private set; } public int? TotalPages { get; private set; }
public int? Pages => TotalPages / (DoublePaged ? 2 : 1); public int? Pages => TotalPages / (IsDoublePaged ? 2 : 1);
public bool ShowFoldMarks = App.Config.Debug; public bool ShowFoldMarks = App.Config.Debug;
public bool DoublePaged = false; public bool IsDoublePaged = false;
public bool IsPreview = false;
public string DocumentsPath; public string DocumentsPath;
public int CurrentNextSeason; public int CurrentNextSeason;
@@ -38,15 +40,8 @@ namespace Elwig.Documents {
CurrentNextSeason = Utils.CurrentNextSeason; CurrentNextSeason = Utils.CurrentNextSeason;
Title = title; Title = title;
Author = c.NameFull; Author = c.NameFull;
Header = $"<div class='name'>{c.Name}</div><div class='suffix'>{c.NameSuffix}</div><div class='type'>{c.NameTypeFull}</div>"; Header = "";
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ") Footer = Utils.GenerateFooter("<br/>", " \u00b7 ").Item(c.NameFull).ToString();
.Item(c.NameFull).NextLine()
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
.Item(c.EmailAddress != 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("Bio-KSt.", c.OrganicAuthority).NextLine()
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
.ToString();
Date = DateOnly.FromDateTime(Utils.Today); Date = DateOnly.FromDateTime(Utils.Today);
} }
@@ -104,7 +99,7 @@ namespace Elwig.Documents {
return await Html.CompileRenderAsync(name, this); ; return await Html.CompileRenderAsync(name, this); ;
} }
public async Task Generate(IProgress<double>? progress = null) { public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
if (_pdfFile != null) if (_pdfFile != null)
return; return;
progress?.Report(0.0); progress?.Report(0.0);
@@ -114,36 +109,50 @@ namespace Elwig.Documents {
var pdf = new TempFile("pdf"); var pdf = new TempFile("pdf");
var tmpHtmls = new List<TempFile>(); var tmpHtmls = new List<TempFile>();
var tmpFiles = new List<string>(); var tmpFiles = new List<string>();
var n = m.Documents.Count(); try {
int i = 0; var n = m.Documents.Count();
foreach (var doc in m.Documents) { int i = 0;
if (doc is PdfDocument) { foreach (var doc in m.Documents) {
tmpFiles.Add(doc.PdfPath!); if (doc is PdfDocument) {
continue; tmpFiles.Add(doc.PdfPath!);
continue;
}
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
i++;
progress?.Report(GenerationProportion * 100 * i / n);
}
progress?.Report(GenerationProportion * 100);
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, IsDoublePaged, cancelToken, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} finally {
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
} }
var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
i++;
progress?.Report(GenerationProportion * 100 * i / n);
} }
progress?.Report(GenerationProportion * 100);
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, DoublePaged, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages;
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
}
_pdfFile = pdf;
} else { } else {
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var pdf = new TempFile("pdf"); var pdf = new TempFile("pdf");
using (var tmpHtml = new TempFile("html")) { try {
using var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8); await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
progress?.Report(50.0); progress?.Report(50.0);
var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, DoublePaged); var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, IsDoublePaged, cancelToken);
TotalPages = pages.Pages; TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} }
_pdfFile = pdf;
} }
progress?.Report(100.0); progress?.Report(100.0);
} }
@@ -155,7 +164,7 @@ namespace Elwig.Documents {
public async Task Print(int copies = 1) { public async Task Print(int copies = 1) {
if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet"); if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
await Pdf.Print(PdfPath, copies, DoublePaged); await Pdf.Print(PdfPath, copies, IsDoublePaged);
} }
public void Show() { public void Show() {

View File

@@ -10,7 +10,7 @@
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.css" /> <link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Page.css" /> <link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Page.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Table.css" /> <link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Table.css" />
@if (Model.DoublePaged) { @if (Model.IsDoublePaged) {
<style> <style>
@@page :left { @@page :left {
margin: 25mm 25mm 35mm 20mm; margin: 25mm 25mm 35mm 20mm;
@@ -34,14 +34,14 @@
<div class="pre-footer"> <div class="pre-footer">
<span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span> <span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
<span class="doc-id">@Model.DocumentId</span> <span class="doc-id">@Model.DocumentId</span>
<span class="page"></span> <span><span class="page"></span>@Raw(Model.IsPreview ? " <b>(vorläufig)</b>" : "")</span>
</div> </div>
<footer>@Raw(Model.Footer)</footer> <footer>@Raw(Model.Footer)</footer>
</div> </div>
@if (Model.DoublePaged) { @if (Model.IsDoublePaged) {
<div class="footer-wrapper left"> <div class="footer-wrapper left">
<div class="pre-footer"> <div class="pre-footer">
<span class="page"></span> <span>@Raw(Model.IsPreview ? "<b>(vorläufig)</b> " : "")<span class="page"></span></span>
<span class="doc-id">@Model.DocumentId</span> <span class="doc-id">@Model.DocumentId</span>
<span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span> <span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
</div> </div>

View File

@@ -2,7 +2,7 @@
h1 { h1 {
text-align: center; text-align: center;
font-size: 24pt; font-size: 24pt;
margin-top: 10mm; margin-top: 0;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -27,6 +27,7 @@ namespace Elwig.Documents {
Data = data; Data = data;
CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code; CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code;
MemberNum = v.Credits.Count; MemberNum = v.Credits.Count;
IsPreview = MemberNum == 0;
DeliveryNum = v.DeliveryPartPayments.DistinctBy(p => p.DeliveryPart.Delivery).Count(); DeliveryNum = v.DeliveryPartPayments.DistinctBy(p => p.DeliveryPart.Delivery).Count();
DeliveryPartNum = v.DeliveryPartPayments.Count; DeliveryPartNum = v.DeliveryPartPayments.Count;
ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult(); ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult();

View File

@@ -164,14 +164,14 @@
<td class="number">@Utils.GetSign(considered)</td> <td class="number">@Utils.GetSign(considered)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(considered):N2}")</td> <td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(considered):N2}")</td>
<th class="lborder">Menge (gebunden):</th> <th class="lborder">Menge (gebunden):</th>
<td colspan="2" class="number">@($"{Model.Data.Rows.Sum(r => r.Geb.Weight):N0}") kg</td> <td colspan="2" class="number">@($"{Model.Data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0}") kg</td>
</tr> </tr>
<tr> <tr>
<th colspan="2">Auszahlungsbetrag:</th> <th colspan="2">Auszahlungsbetrag:</th>
<td class="number tborder"></td> <td class="number tborder"></td>
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{totalSum:N2}")</td> <td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{totalSum:N2}")</td>
<th class="lborder">Gesamtmenge:</th> <th class="lborder">Gesamtmenge:</th>
<td colspan="2" class="number tborder">@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight + r.Geb.Weight):N0}") kg</td> <td colspan="2" class="number tborder">@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0}") kg</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -2,7 +2,7 @@
h1 { h1 {
text-align: center; text-align: center;
font-size: 24pt; font-size: 24pt;
margin-top: 10mm; margin-top: 0;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -2,7 +2,7 @@
h1 { h1 {
text-align: center; text-align: center;
font-size: 24pt; font-size: 24pt;
margin-top: 10mm; margin-top: 0;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -2,12 +2,12 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon> <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.0.2.0</Version> <Version>1.0.3.0</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages> <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
@@ -22,23 +22,23 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="LinqKit" Version="1.3.8" /> <PackageReference Include="LinqKit" Version="1.3.9" />
<PackageReference Include="MailKit" Version="4.14.1" /> <PackageReference Include="MailKit" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.10" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.2" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3595.46" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
<PackageReference Include="NJsonSchema" Version="11.5.2" /> <PackageReference Include="NJsonSchema" Version="11.5.2" />
<PackageReference Include="PdfiumViewer" Version="2.13.0" /> <PackageReference Include="PdfiumViewer" Version="2.13.0" />
<PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" /> <PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
<PackageReference Include="RazorLight" Version="2.3.1" /> <PackageReference Include="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.1.57" /> <PackageReference Include="ScottPlot.WPF" Version="5.1.57" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
<PackageReference Include="System.IO.Hashing" Version="9.0.10" /> <PackageReference Include="System.IO.Hashing" Version="10.0.2" />
<PackageReference Include="System.IO.Ports" Version="9.0.10" /> <PackageReference Include="System.IO.Ports" Version="10.0.2" />
<PackageReference Include="System.Management" Version="9.0.10" /> <PackageReference Include="System.Management" Version="10.0.2" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.10" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -10,7 +10,6 @@ using Microsoft.Data.Sqlite;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Collections.Generic; using System.Collections.Generic;
using Elwig.Models.Dtos; using Elwig.Models.Dtos;
using System.Reflection;
using System.Data; using System.Data;
namespace Elwig.Helpers { namespace Elwig.Helpers {
@@ -67,7 +66,7 @@ namespace Elwig.Helpers {
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; } public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; } public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; } public DbSet<MemberDeliveryPerVarietyRowSingle> MemberDeliveryPerVariantRows { get; private set; }
public DbSet<MemberAreaComsRowSingle> MemberAreaComsRows { get; private set; } public DbSet<MemberAreaComsRowSingle> MemberAreaComsRows { get; private set; }
public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; } public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; } public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
@@ -117,39 +116,6 @@ namespace Elwig.Helpers {
return cnx; return cnx;
} }
public static async Task ExecuteBatch(SqliteConnection cnx, string sql) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = sql;
await (await cmd.ExecuteReaderAsync()).CloseAsync();
}
public static async Task ExecuteEmbeddedScript(SqliteConnection cnx, Assembly asm, string name) {
using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource");
using var reader = new StreamReader(stream);
await ExecuteBatch(cnx, await reader.ReadToEndAsync());
}
public static async Task<object?> ExecuteScalar(SqliteConnection cnx, string sql) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = sql;
return await cmd.ExecuteScalarAsync();
}
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(SqliteConnection cnx) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA foreign_key_check";
using var reader = await cmd.ExecuteReaderAsync();
var list = new List<(string, long, string, long)>();
while (await reader.ReadAsync()) {
var table = reader.GetString(0);
var rowid = reader.GetInt64(1);
var parent = reader.GetString(2);
var fkid = reader.GetInt64(3);
list.Add((table, rowid, parent, fkid));
}
return [.. list];
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlite(ConnectionString); optionsBuilder.UseSqlite(ConnectionString);
optionsBuilder.UseLazyLoadingProxies(); optionsBuilder.UseLazyLoadingProxies();

View File

@@ -9,17 +9,17 @@ namespace Elwig.Helpers {
public static class AppDbUpdater { public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat! // Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 33; public static readonly int RequiredSchemaVersion = 36;
private static int VersionOffset = 0; private static int VersionOffset = 0;
public static async Task<Version> CheckDb() { public static async Task<Version> CheckDb() {
using var cnx = AppDbContext.Connect(); using var cnx = AppDbContext.Connect();
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0; var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})"); if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0; var schemaVers = (long?)await cnx.ExecuteScalar("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
@@ -27,12 +27,12 @@ namespace Elwig.Helpers {
} }
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion); await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0; var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF)); var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
if (App.Version > v) { if (App.Version > v) {
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision; long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision;
await AppDbContext.ExecuteBatch(cnx, $"PRAGMA user_version = {vers}"); await cnx.ExecuteBatch($"PRAGMA user_version = {vers}");
} }
return v; return v;
@@ -67,20 +67,20 @@ namespace Elwig.Helpers {
if (toExecute.Count == 0) if (toExecute.Count == 0)
return; return;
await AppDbContext.ExecuteBatch(cnx, """ await cnx.ExecuteBatch("""
PRAGMA locking_mode = EXCLUSIVE; PRAGMA locking_mode = EXCLUSIVE;
BEGIN EXCLUSIVE; BEGIN EXCLUSIVE;
"""); """);
foreach (var script in toExecute) { foreach (var script in toExecute) {
await AppDbContext.ExecuteEmbeddedScript(cnx, asm, script); await cnx.ExecuteEmbeddedScript(asm, script);
} }
var violations = await AppDbContext.ForeignKeyCheck(cnx); var violations = await cnx.ForeignKeyCheck();
if (violations.Length > 0) { if (violations.Length > 0) {
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}"))); .Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
} }
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
COMMIT; COMMIT;
VACUUM; VACUUM;
PRAGMA schema_version = {toVersion * 100 + VersionOffset}; PRAGMA schema_version = {toVersion * 100 + VersionOffset};

View File

@@ -27,7 +27,7 @@ namespace Elwig.Helpers.Billing {
public async Task FinishSeason() { public async Task FinishSeason() {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
UPDATE season UPDATE season
SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year}) SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year})
WHERE year = {Year}; WHERE year = {Year};
@@ -37,7 +37,7 @@ namespace Elwig.Helpers.Billing {
public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceBs = 0, int allowanceKgPerBs = 0, double allowanceRel = 0, int addMinBs = 1) { public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceBs = 0, int allowanceKgPerBs = 0, double allowanceRel = 0, int addMinBs = 1) {
if (addMinBs < 1) addMinBs = 1; if (addMinBs < 1) addMinBs = 1;
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
UPDATE member UPDATE member
SET business_shares = member.business_shares - h.business_shares SET business_shares = member.business_shares - h.business_shares
FROM member_history h FROM member_history h
@@ -66,7 +66,7 @@ namespace Elwig.Helpers.Billing {
public async Task UnAdjustBusinessShares() { public async Task UnAdjustBusinessShares() {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
UPDATE member UPDATE member
SET business_shares = member.business_shares - h.business_shares SET business_shares = member.business_shares - h.business_shares
FROM member_history h FROM member_history h
@@ -157,9 +157,9 @@ namespace Elwig.Helpers.Billing {
lastMgNr = mgnr; lastMgNr = mgnr;
} }
await AppDbContext.ExecuteBatch(cnx, $"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}"); await cnx.ExecuteBatch($"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}");
if (inserts.Count > 0) { if (inserts.Count > 0) {
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))} VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
ON CONFLICT DO UPDATE ON CONFLICT DO UPDATE
@@ -237,7 +237,7 @@ namespace Elwig.Helpers.Billing {
if (needed == 0) break; if (needed == 0) break;
} }
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
VALUES {string.Join(",\n ", posChanges.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '', {i.Item4})"))} VALUES {string.Join(",\n ", posChanges.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '', {i.Item4})"))}
ON CONFLICT DO UPDATE ON CONFLICT DO UPDATE

View File

@@ -47,21 +47,21 @@ namespace Elwig.Helpers.Billing {
public async Task Commit() { public async Task Commit() {
await Revert(); await Revert();
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers) INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers)
SELECT s.year, SELECT s.year,
COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr, COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr,
m.mgnr, m.mgnr,
v.avnr, v.avnr,
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount, ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
IIF(lc.amount >= 0, ROUND(lp.amount / POW(10, s.precision - 2)), 0) AS prev_net_amount, IIF(lc.amount < 0, 0, ROUND(lp.amount / POW(10, s.precision - 2))) AS prev_net_amount,
IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat, IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) + ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) + ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) + ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) +
IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0) IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
AS modifiers, AS modifiers,
IIF(lc.amount >= 0, lc.modifiers, 0) AS prev_modifiers IIF(lc.amount < 0, 0, lc.modifiers) AS prev_modifiers
FROM season s FROM season s
JOIN payment_variant v ON v.year = s.year JOIN payment_variant v ON v.year = s.year
LEFT JOIN payment_variant l ON l.year = s.year LEFT JOIN payment_variant l ON l.year = s.year
@@ -82,27 +82,27 @@ namespace Elwig.Helpers.Billing {
LEFT JOIN payment_custom x ON (x.year, x.mgnr) = (s.year, m.mgnr) LEFT JOIN payment_custom x ON (x.year, x.mgnr) = (s.year, m.mgnr)
WHERE s.year = {Year} AND v.avnr = {AvNr}; WHERE s.year = {Year} AND v.avnr = {AvNr};
"""); """);
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr}); UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
"""); """);
} }
public async Task Revert() { public async Task Revert() {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr}); DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr});
UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr}); UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr});
"""); """);
} }
protected async Task SetCalcTime(SqliteConnection cnx) { protected async Task SetCalcTime(SqliteConnection cnx) {
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr}) UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr})
"""); """);
} }
protected async Task DeleteInDb(SqliteConnection cnx) { protected async Task DeleteInDb(SqliteConnection cnx) {
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr}); DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr});
DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr}); DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr});
DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr}); DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr});
@@ -116,7 +116,7 @@ namespace Elwig.Helpers.Billing {
var multiplier = 0.50; var multiplier = 0.50;
var includePredecessor = true; var includePredecessor = true;
var modName = "Treue%"; var modName = "Treue%";
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel) INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
SELECT c.year, {AvNr}, s.mgnr, 0, SELECT c.year, {AvNr}, s.mgnr, 0,
ROUND(s.sum * COALESCE(m.abs, 0)), ROUND(s.sum * COALESCE(m.abs, 0)),
@@ -138,7 +138,7 @@ namespace Elwig.Helpers.Billing {
mod_rel = mod_rel + excluded.mod_rel mod_rel = mod_rel + excluded.mod_rel
"""); """);
} }
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel) INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
SELECT x.year, {AvNr}, x.mgnr, 0, COALESCE(x.mod_abs * POW(10, s.precision - 2), 0), COALESCE(x.mod_rel, 0) SELECT x.year, {AvNr}, x.mgnr, 0, COALESCE(x.mod_abs * POW(10, s.precision - 2), 0), COALESCE(x.mod_rel, 0)
FROM payment_custom x FROM payment_custom x
@@ -194,7 +194,7 @@ namespace Elwig.Helpers.Billing {
var msg = invalid.Count == 0 ? null : "Für folgende Sorten wurde noch keine Preiskurve festgelegt: " + string.Join(", ", invalid); var msg = invalid.Count == 0 ? null : "Für folgende Sorten wurde noch keine Preiskurve festgelegt: " + string.Join(", ", invalid);
if (msg != null && strict) if (msg != null && strict)
throw new KeyNotFoundException(msg); throw new KeyNotFoundException(msg);
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount) INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
VALUES {string.Join(",\n ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))}; VALUES {string.Join(",\n ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))};
"""); """);
@@ -205,7 +205,7 @@ namespace Elwig.Helpers.Billing {
protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) { protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
var netMod = Data.NetWeightModifier.ToString().Replace(',', '.'); var netMod = Data.NetWeightModifier.ToString().Replace(',', '.');
var grossMod = Data.GrossWeightModifier.ToString().Replace(',', '.'); var grossMod = Data.GrossWeightModifier.ToString().Replace(',', '.');
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel) INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, 0, IIF(d.net_weight, {netMod}, {grossMod}) SELECT d.year, d.did, d.dpnr, {AvNr}, 0, 0, IIF(d.net_weight, {netMod}, {grossMod})
FROM delivery_part d FROM delivery_part d

View File

@@ -206,15 +206,15 @@ namespace Elwig.Helpers {
case 1: orderingMemberList = "NAME"; break; case 1: orderingMemberList = "NAME"; break;
case 2: orderingMemberList = "KG"; break; case 2: orderingMemberList = "KG"; break;
} }
string mailSendPostal = "MGNR"; string mailSendPostal = "WISH";
switch (MailOrdering) { switch (MailSendPostal) {
case 0: mailSendPostal = "NONE"; break; case 0: mailSendPostal = "NONE"; break;
case 1: mailSendPostal = "NO_EMAIL"; break; case 1: mailSendPostal = "NO_EMAIL"; break;
case 2: mailSendPostal = "WISH"; break; case 2: mailSendPostal = "WISH"; break;
case 3: mailSendPostal = "ALL"; break; case 3: mailSendPostal = "ALL"; break;
} }
string mailSendEmail = "MGNR"; string mailSendEmail = "WISH";
switch (MailOrdering) { switch (MailSendEmail) {
case 0: mailSendEmail = "NONE"; break; case 0: mailSendEmail = "NONE"; break;
case 1: mailSendEmail = "WISH"; break; case 1: mailSendEmail = "WISH"; break;
case 2: mailSendEmail = "ALL"; break; case 2: mailSendEmail = "ALL"; break;

View File

@@ -15,7 +15,9 @@ namespace Elwig.Helpers.Export {
protected readonly char Separator; protected readonly char Separator;
protected string? Header; protected string? Header;
public Csv(string filename, char separator = ';') : this(filename, separator, Utils.UTF8) { } public Csv(string filename, char separator = ';') :
this(filename, separator, Utils.UTF8) {
}
public Csv(string filename, char separator, Encoding encoding) { public Csv(string filename, char separator, Encoding encoding) {
_writer = new StreamWriter(filename, false, encoding); _writer = new StreamWriter(filename, false, encoding);
@@ -58,4 +60,22 @@ namespace Elwig.Helpers.Export {
public abstract string FormatRow(T row); public abstract string FormatRow(T row);
} }
public class CsvSimple : Csv<IEnumerable<object?>> {
public CsvSimple(string filename, char separator, Encoding encoding) :
base(filename, separator, encoding) {
}
public CsvSimple(string filename, char separator = ';') :
base(filename, separator) {
}
public override string FormatRow(IEnumerable<object?> row) {
return string.Join(Separator, row.Select(i => {
var str = $"{i}";
return str.Contains(Separator) || str.Contains('\n') ? $"\"{str.Replace("\"", "\"\"")}\"" : str;
}));
}
}
} }

View File

@@ -11,9 +11,9 @@ namespace Elwig.Helpers.Export {
private static async Task<(long? ApplicationId, string? UserVersion, long? SchemaVersion, long FileSize)> GetMeta() { private static async Task<(long? ApplicationId, string? UserVersion, long? SchemaVersion, long FileSize)> GetMeta() {
long size = new FileInfo(App.Config.DatabaseFile).Length; long size = new FileInfo(App.Config.DatabaseFile).Length;
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id"); var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id");
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version"); var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version");
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version"); var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version");
return (applId, userVers != null ? $"{userVers >> 24}.{(userVers >> 16) & 0xFF}.{(userVers >> 8) & 0xFF}.{userVers & 0xFF}" : null, schemaVers, size); return (applId, userVers != null ? $"{userVers >> 24}.{(userVers >> 16) & 0xFF}.{(userVers >> 8) & 0xFF}.{userVers & 0xFF}" : null, schemaVers, size);
} }
@@ -100,9 +100,9 @@ namespace Elwig.Helpers.Export {
} }
} }
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0; var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0; var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0; var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
await writer.WriteLineAsync($"-- Elwig database dump, {DateTime.Now:yyyy-MM-dd, HH:mm:ss}"); await writer.WriteLineAsync($"-- Elwig database dump, {DateTime.Now:yyyy-MM-dd, HH:mm:ss}");
await writer.WriteLineAsync($"-- {Environment.MachineName}, Zwst. {App.BranchName}, {App.Client.Name}"); await writer.WriteLineAsync($"-- {Environment.MachineName}, Zwst. {App.BranchName}, {App.Client.Name}");
@@ -224,7 +224,7 @@ namespace Elwig.Helpers.Export {
File.Move(filename, App.Config.DatabaseFile, false); File.Move(filename, App.Config.DatabaseFile, false);
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteBatch(cnx, "VACUUM"); await cnx.ExecuteBatch("VACUUM");
} }
public static async Task ImportSql(StreamReader reader) { public static async Task ImportSql(StreamReader reader) {
@@ -232,7 +232,7 @@ namespace Elwig.Helpers.Export {
File.Delete(newName); File.Delete(newName);
try { try {
using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) { using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) {
await AppDbContext.ExecuteBatch(cnx, await reader.ReadToEndAsync()); await cnx.ExecuteBatch(await reader.ReadToEndAsync());
} }
await ImportSqlite(newName); await ImportSqlite(newName);
} finally { } finally {

View File

@@ -345,7 +345,7 @@ namespace Elwig.Helpers.Export {
$"mtime = {((DateTimeOffset)m.ModifiedAt.ToUniversalTime()).ToUnixTimeSeconds()} " + $"mtime = {((DateTimeOffset)m.ModifiedAt.ToUniversalTime()).ToUnixTimeSeconds()} " +
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2});")); $"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2});"));
using var cnx = AppDbContext.Connect(); using var cnx = AppDbContext.Connect();
await AppDbContext.ExecuteBatch(cnx, $""" await cnx.ExecuteBatch($"""
BEGIN; BEGIN;
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS'; UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
{string.Join("\n", updateStmts)} {string.Join("\n", updateStmts)}
@@ -744,8 +744,8 @@ namespace Elwig.Helpers.Export {
}; };
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck; if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked; if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
if (p.IsLesewagen != null) obj["lesewagen"] = p.IsLesewagen;
if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden; if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden;
if (p.Unloading != null) obj["unloading"] = p.Unloading;
if (p.Temperature != null) obj["temperature"] = p.Temperature; if (p.Temperature != null) obj["temperature"] = p.Temperature;
if (p.Acid != null) obj["acid"] = p.Acid; if (p.Acid != null) obj["acid"] = p.Acid;
if (p.ScaleId != null) obj["scale_id"] = p.ScaleId; if (p.ScaleId != null) obj["scale_id"] = p.ScaleId;
@@ -818,8 +818,8 @@ namespace Elwig.Helpers.Export {
Comment = p["comment"]?.AsValue().GetValue<string>(), Comment = p["comment"]?.AsValue().GetValue<string>(),
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false, IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(), IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
IsLesewagen = p["lesewagen"]?.AsValue().GetValue<bool>(),
IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(), IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(),
Unloading = p["unloading"]?.AsValue().GetValue<string>() ?? ((p["lesewagen"]?.AsValue().GetValue<bool>() ?? false) ? DeliveryPart.Pumped : null),
Temperature = p["temperature"]?.AsValue().GetValue<double>(), Temperature = p["temperature"]?.AsValue().GetValue<double>(),
Acid = p["acid"]?.AsValue().GetValue<double>(), Acid = p["acid"]?.AsValue().GetValue<double>(),
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(), ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),

View File

@@ -1,14 +1,17 @@
using Microsoft.Data.Sqlite;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.IO.Hashing; using System.IO.Hashing;
using System.Net.Http; using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Helpers { namespace Elwig.Helpers {
static partial class Extensions { public static partial class Extensions {
[LibraryImport("msvcrt.dll")] [LibraryImport("msvcrt.dll")]
[UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
@@ -108,5 +111,39 @@ namespace Elwig.Helpers {
throw new InvalidDataException($"CRC-32 mismatch in '{entry.FullName}'"); throw new InvalidDataException($"CRC-32 mismatch in '{entry.FullName}'");
} }
} }
public static async Task ExecuteBatch(this SqliteConnection cnx, string sql) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = sql;
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.NextResultAsync()) ;
}
public static async Task ExecuteEmbeddedScript(this SqliteConnection cnx, Assembly asm, string name) {
using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource");
using var reader = new StreamReader(stream);
await ExecuteBatch(cnx, await reader.ReadToEndAsync());
}
public static async Task<object?> ExecuteScalar(this SqliteConnection cnx, string sql) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = sql;
return await cmd.ExecuteScalarAsync();
}
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(this SqliteConnection cnx) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA foreign_key_check";
using var reader = await cmd.ExecuteReaderAsync();
var list = new List<(string, long, string, long)>();
while (await reader.ReadAsync()) {
var table = reader.GetString(0);
var rowid = reader.GetInt64(1);
var parent = reader.GetString(2);
var fkid = reader.GetInt64(3);
list.Add((table, rowid, parent, fkid));
}
return [.. list];
}
} }
} }

View File

@@ -1,24 +1,28 @@
using System.Threading.Tasks;
using Elwig.Windows; using Elwig.Windows;
using System.Diagnostics; using PdfiumViewer;
using System; using System;
using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows; using System.Diagnostics;
using System.Text.RegularExpressions; using System.Drawing.Printing;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using PdfiumViewer; using System.Reflection;
using System.Drawing.Printing; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Printing { namespace Elwig.Helpers.Printing {
public static class Pdf { public static class Pdf {
private static readonly string WinziPrint = new string[] { App.InstallPath } private static readonly string WinziPrint = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? []) Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Installer/Files/WinziPrint.exe") :
.Select(x => Path.Combine(x, "WinziPrint.exe")) new string[] { App.InstallPath }
.Where(File.Exists) .Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
.FirstOrDefault() ?? throw new FileNotFoundException("WiniPrint executable not found"); .Select(x => Path.Combine(x, "WinziPrint.exe"))
.Where(File.Exists)
.FirstOrDefault() ?? throw new FileNotFoundException("WiniPrint executable not found");
private static Process? WinziPrintProc; private static Process? WinziPrintProc;
public static bool IsReady => WinziPrintProc != null; public static bool IsReady => WinziPrintProc != null;
@@ -46,24 +50,45 @@ namespace Elwig.Helpers.Printing {
return Task.CompletedTask; return Task.CompletedTask;
} }
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) { public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
return await Convert([htmlPath], pdfPath, doublePaged, progress); return await Convert([htmlPath], pdfPath, doublePaged, cancelToken, progress);
} }
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) { public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
if (WinziPrintProc == null) throw new InvalidOperationException("The WinziPrint process has not been initialized yet"); if (WinziPrintProc == null) throw new InvalidOperationException("The WinziPrint process has not been initialized yet");
progress?.Report(0.0); progress?.Report(0.0);
using var client = new TcpClient("127.0.0.1", 30983); using var client = new TcpClient("127.0.0.1", 30983);
using var stream = client.GetStream(); using var stream = client.GetStream();
string cnxId;
using var reader = new StreamReader(stream);
var first = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
if (first.StartsWith("id:")) {
cnxId = first[3..].Trim();
} else {
throw new IOException("Invalid response from WinziPrint");
}
await stream.WriteAsync(Utils.UTF8.GetBytes( await stream.WriteAsync(Utils.UTF8.GetBytes(
"-e utf-8;-p;" + (doublePaged ? "-2;" : "") + "-e utf-8;-p;" + (doublePaged ? "-2;" : "") +
$"{string.Join(';', htmlPath)};{pdfPath}" + $"{string.Join(';', htmlPath)};{pdfPath}" +
"\r\n")); "\r\n"));
using var reader = new StreamReader(stream); bool cancelled = false;
while (true) { while (true) {
if (!cancelled && (cancelToken?.IsCancellationRequested ?? false)) {
try {
using var cancelClient = new TcpClient("127.0.0.1", 30983);
using var cancelStream = cancelClient.GetStream();
using var cancelReader = new StreamReader(cancelStream);
await cancelReader.ReadLineAsync();
await cancelStream.WriteAsync(Utils.UTF8.GetBytes($"cancel;{cnxId}\r\n"));
} catch { }
cancelled = true;
}
var line = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint"); var line = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
if (line.StartsWith("error:")) { if (line.StartsWith("error:")) {
throw new IOException($"WinziPrint: {line[6..].Trim()}"); var msg = line[6..].Trim();
if (msg == "aborted")
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
throw new IOException($"WinziPrint: {msg}");
} else if (line.StartsWith("progress:")) { } else if (line.StartsWith("progress:")) {
var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray(); var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray();
progress?.Report(100.0 * parts[0] / parts[1]); progress?.Report(100.0 * parts[0] / parts[1]);

View File

@@ -17,28 +17,34 @@ namespace Elwig.Helpers {
public SerialPortWatcher() { public SerialPortWatcher() {
_knownPorts = SerialPort.GetPortNames(); _knownPorts = SerialPort.GetPortNames();
_deviceArrivalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2"); _deviceArrivalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
_deviceArrivalWatcher.EventArrived += (s, e) => OnDeviceArrived(); _deviceArrivalWatcher.EventArrived += OnDeviceArrived;
_deviceRemovalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3"); _deviceRemovalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
_deviceRemovalWatcher.EventArrived += (s, e) => OnDeviceRemoved(); _deviceRemovalWatcher.EventArrived += OnDeviceRemoved;
_deviceArrivalWatcher.Start(); _deviceArrivalWatcher.Start();
_deviceRemovalWatcher.Start(); _deviceRemovalWatcher.Start();
} }
private void OnDeviceArrived() { private void OnDeviceArrived(object sender, EventArrivedEventArgs evt) {
string[] currentPorts = SerialPort.GetPortNames(); App.MainDispatcher.Invoke(() => {
var newPorts = currentPorts.Except(_knownPorts).ToArray(); // "synchronized"
foreach (var port in newPorts) string[] currentPorts = SerialPort.GetPortNames();
SerialPortConnected?.Invoke(this, port); var newPorts = currentPorts.Except(_knownPorts).ToArray();
_knownPorts = currentPorts; foreach (var port in newPorts)
SerialPortConnected?.Invoke(this, port);
_knownPorts = currentPorts;
});
} }
private void OnDeviceRemoved() { private void OnDeviceRemoved(object sender, EventArrivedEventArgs evt) {
string[] currentPorts = SerialPort.GetPortNames(); App.MainDispatcher.Invoke(() => {
var removedPorts = _knownPorts.Except(currentPorts).ToArray(); // "synchronized"
foreach (var port in removedPorts) string[] currentPorts = SerialPort.GetPortNames();
SerialPortDisconnected?.Invoke(this, port); var removedPorts = _knownPorts.Except(currentPorts).ToArray();
_knownPorts = currentPorts; foreach (var port in removedPorts)
SerialPortDisconnected?.Invoke(this, port);
_knownPorts = currentPorts;
});
} }
public void Dispose() { public void Dispose() {

View File

@@ -36,6 +36,7 @@ namespace Elwig.Helpers {
public static partial class Utils { public static partial class Utils {
public static readonly Encoding UTF8 = new UTF8Encoding(false, true); public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
public static readonly Encoding UTF8BOM = new UTF8Encoding(true, true);
public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
public static int CurrentYear => DateTime.Now.Year; public static int CurrentYear => DateTime.Now.Year;
@@ -502,6 +503,7 @@ namespace Elwig.Helpers {
if (App.Config.Smtp == null) if (App.Config.Smtp == null)
return false; return false;
return await Task.Run(async () => { return await Task.Run(async () => {
await AddSentMailBody(subject, text, 1);
SmtpClient? client = null; SmtpClient? client = null;
try { try {
client = await GetSmtpClient(); client = await GetSmtpClient();
@@ -519,6 +521,11 @@ namespace Elwig.Helpers {
} }
msg.Body = body; msg.Body = body;
await client!.SendAsync(msg); await client!.SendAsync(msg);
await AddSentMails([(
"email", member.MgNr, member.AdministrativeName,
member.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
subject, docs.Select(d => d.Title).ToArray()
)]);
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
return false; return false;
@@ -533,15 +540,29 @@ namespace Elwig.Helpers {
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) { public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) {
if (mode == ExportMode.Print && !App.Config.Debug) { if (mode == ExportMode.Print && !App.Config.Debug) {
if (doc.IsPreview) {
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!",
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
await doc.Generate(); await doc.Generate();
await doc.Print(); await doc.Print();
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) { } else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
if (doc.IsPreview) {
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!",
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
await doc.Generate(); await doc.Generate();
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]); var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
if (success) if (success)
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt", MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mail im Posteingang des Empfängers aufscheint.", "E-Mail verschickt",
MessageBoxButton.OK, MessageBoxImage.Information); MessageBoxButton.OK, MessageBoxImage.Information);
} else if (mode == ExportMode.SavePdf) { } else if (mode == ExportMode.SavePdf) {
if (doc.IsPreview) {
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!",
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Warning);
}
var d = new SaveFileDialog() { var d = new SaveFileDialog() {
FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf", FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf",
DefaultExt = "pdf", DefaultExt = "pdf",
@@ -654,9 +675,9 @@ namespace Elwig.Helpers {
} }
public static async Task<string?> FindSentMailBody(DateTime target) { public static async Task<string?> FindSentMailBody(DateTime target) {
var dt = $"{target:yyyy-MM-dd_HH-mm-ss}_"; var dt = $"{target:yyyy-MM-dd_HH-mm-ss}";
var filename = Directory.GetFiles(App.MailsPath, "????-??-??_??-??-??_*.txt") var filename = Directory.GetFiles(App.MailsPath, "????-??-??_??-??-??_*.txt")
.Where(n => Path.GetFileName(n).CompareTo(dt) <= 0) .Where(n => Path.GetFileName(n)[..19].CompareTo(dt) <= 0)
.Order() .Order()
.LastOrDefault(); .LastOrDefault();
if (filename == null) if (filename == null)

View File

@@ -15,15 +15,17 @@ namespace Elwig.Helpers.Weighing {
public bool IsReady { get; private set; } public bool IsReady { get; private set; }
public bool HasFillingClearance { get; private set; } public bool HasFillingClearance { get; private set; }
public event IEventScale.EventHandler<WeighingEventArgs> WeighingEvent; public event IEventScale.EventHandler<WeighingEventArgs>? WeighingEvent;
private bool IsRunning = true; private bool IsRunning = true;
private readonly Thread BackgroundThread; private readonly Thread BackgroundThread;
private readonly string Connection;
public AveryEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) : public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) :
base(cnx, empty, filling, limit, log) { base(cnx, null, null, null, log, true, !required) {
ScaleId = id; ScaleId = id;
Model = model; Model = model;
Connection = cnx;
IsReady = true; IsReady = true;
HasFillingClearance = false; HasFillingClearance = false;
Stream.WriteTimeout = -1; Stream.WriteTimeout = -1;
@@ -50,19 +52,49 @@ namespace Elwig.Helpers.Weighing {
var data = await Receive(); var data = await Receive();
if (data != null) if (data != null)
RaiseWeighingEvent(new WeighingEventArgs(data.Value)); RaiseWeighingEvent(new WeighingEventArgs(data.Value));
} catch (ThreadInterruptedException) {
// ignore
} catch (IOException) {
await Task.Delay(500);
await Reconnect();
} catch (TimeoutException) {
await Task.Delay(500);
await Reconnect();
} catch (Exception ex) { } catch (Exception ex) {
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "Waagenfehler", MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
} }
protected async Task Reconnect() {
try { Reader.Close(); } catch { }
try { Stream.Close(); } catch { }
try { Serial?.Close(); } catch { }
while (IsRunning) {
try {
if (Connection.StartsWith("serial:")) {
Serial = Utils.OpenSerialConnection(Connection);
Stream = Serial.BaseStream;
} else if (Connection.StartsWith("tcp:")) {
Tcp = Utils.OpenTcpConnection(Connection);
Stream = Tcp.GetStream();
}
Reader = new(Stream, Encoding.ASCII, false, 512);
break;
} catch {
// ignore
}
await Task.Delay(1000);
}
}
protected async Task<WeighingResult?> Receive() { protected async Task<WeighingResult?> Receive() {
var line = ""; var line = "";
while (line.Length < 33) { while (line.Length < 33) {
var ch = Reader.Read(); var ch = Reader.Read();
if (ch == -1) { if (ch == -1) {
return null; throw new IOException("Connection closed");
} else if (line.Length > 0 || ch == ' ') { } else if (line.Length > 0 || ch == ' ') {
line += char.ToString((char)ch); line += char.ToString((char)ch);
} }
@@ -71,7 +103,7 @@ namespace Elwig.Helpers.Weighing {
if (line == null || line == "") { if (line == null || line == "") {
return null; return null;
} else if (line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') { } else if (line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
throw new IOException($"Invalid event from scale: '{line}'"); throw new FormatException($"Invalid event from scale: '{line}'");
} }
var date = line[ 1.. 9]; var date = line[ 1.. 9];
@@ -81,7 +113,7 @@ namespace Elwig.Helpers.Weighing {
var unit = line[30..32]; var unit = line[30..32];
if (unit != "kg") { if (unit != "kg") {
throw new IOException($"Unsupported unit in weighing event: '{unit}'"); throw new WeighingException($"Unsupported unit in weighing event: '{unit}'");
} }
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null; identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;

View File

@@ -36,7 +36,7 @@ namespace Elwig.Helpers.Weighing {
var line = await Reader.ReadUntilAsync('\x03'); var line = await Reader.ReadUntilAsync('\x03');
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n"); if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
if (line == null || line.Length < 4 || !line.StartsWith('\x02')) { if (line == null || line.Length < 4 || !line.StartsWith('\x02')) {
throw new IOException("Invalid response from scale"); throw new FormatException("Invalid response from scale");
} }
var status = line[1..3]; var status = line[1..3];
@@ -45,9 +45,9 @@ namespace Elwig.Helpers.Weighing {
switch (status[1]) { switch (status[1]) {
case 'M': msg = "Waage in Bewegung"; break; case 'M': msg = "Waage in Bewegung"; break;
} }
throw new IOException($"Waagenfehler {status}: {msg}"); throw new WeighingException($"Waagenfehler {status}: {msg}");
} else if (status[0] != ' ') { } else if (status[0] != ' ') {
throw new IOException($"Invalid response from scale (error code {status})"); throw new WeighingException($"Invalid response from scale (error code {status})");
} }
return line[1..^1]; return line[1..^1];
@@ -57,7 +57,7 @@ namespace Elwig.Helpers.Weighing {
await SendCommand(incIdentNr ? '\x05' : '?'); await SendCommand(incIdentNr ? '\x05' : '?');
string record = await ReceiveResponse(); string record = await ReceiveResponse();
if (record.Length != 45) if (record.Length != 45)
throw new IOException("Invalid response from scale: Received record has invalid size"); throw new FormatException("Invalid response from scale: Received record has invalid size");
var line = record[2..]; var line = record[2..];
var brutto = line[ 0.. 7].Trim(); var brutto = line[ 0.. 7].Trim();

View File

@@ -1,8 +1,9 @@
using System.IO.Ports;
using System.IO;
using System.Net.Sockets;
using System; using System;
using System.IO;
using System.IO.Ports;
using System.Net.Sockets;
using System.Text; using System.Text;
using System.Windows;
namespace Elwig.Helpers.Weighing { namespace Elwig.Helpers.Weighing {
public abstract class Scale : IDisposable { public abstract class Scale : IDisposable {
@@ -27,7 +28,7 @@ namespace Elwig.Helpers.Weighing {
if (config.Type == "SysTec-IT") { if (config.Type == "SysTec-IT") {
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log); return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else if (config.Type == "Avery-Async") { } else if (config.Type == "Avery-Async") {
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log); return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Log, config.Required);
} else if (config.Type == "Gassner") { } else if (config.Type == "Gassner") {
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log); return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else { } else {
@@ -35,10 +36,17 @@ namespace Elwig.Helpers.Weighing {
} }
} }
protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) { protected Scale(string cnx, string? empty, string? filling, int? limit, string? log, bool softFail = false, bool failSilent = false) {
if (cnx.StartsWith("serial:")) { if (cnx.StartsWith("serial:")) {
Serial = Utils.OpenSerialConnection(cnx); try {
Stream = Serial.BaseStream; Serial = Utils.OpenSerialConnection(cnx);
} catch (Exception e) {
if (!softFail) throw;
if (!failSilent)
MessageBox.Show($"Verbindung zu Waage konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
Stream = Serial?.BaseStream ?? Stream.Null;
} else if (cnx.StartsWith("tcp:")) { } else if (cnx.StartsWith("tcp:")) {
Tcp = Utils.OpenTcpConnection(cnx); Tcp = Utils.OpenTcpConnection(cnx);
Stream = Tcp.GetStream(); Stream = Tcp.GetStream();

View File

@@ -34,14 +34,14 @@ namespace Elwig.Helpers.Weighing {
var line = await Reader.ReadUntilAsync("\r\n"); var line = await Reader.ReadUntilAsync("\r\n");
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line); if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) { if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
throw new IOException("Invalid response from scale"); throw new FormatException("Invalid response from scale");
} }
var error = line[1..3]; var error = line[1..3];
string msg = $"Unbekannter Fehler (Fehler code {error})"; string msg = $"Unbekannter Fehler (Fehler code {error})";
if (error[0] == '0') { if (error[0] == '0') {
if (error[1] != '0') { if (error[1] != '0') {
throw new IOException($"Invalid response from scale (error code {error})"); throw new WeighingException($"Invalid response from scale (error code {error})");
} }
} else if (error[0] == '1') { } else if (error[0] == '1') {
switch (error[1]) { switch (error[1]) {
@@ -52,21 +52,21 @@ namespace Elwig.Helpers.Weighing {
case '6': msg = "Drucker nicht bereit"; break; case '6': msg = "Drucker nicht bereit"; break;
case '7': msg = "Druckmuster enthält ungültiges Kommando"; break; case '7': msg = "Druckmuster enthält ungültiges Kommando"; break;
} }
throw new IOException($"Waagenfehler {error}: {msg}"); throw new WeighingException($"Waagenfehler {error}: {msg}");
} else if (error[0] == '2') { } else if (error[0] == '2') {
switch (error[1]) { switch (error[1]) {
case '0': msg = "Brutto negativ"; break; case '0': msg = "Brutto negativ"; break;
} }
throw new IOException($"Fehler {error}: {msg}"); throw new WeighingException($"Fehler {error}: {msg}");
} else if (error[0] == '3') { } else if (error[0] == '3') {
switch (error[1]) { switch (error[1]) {
case '1': msg = "Übertragunsfehler"; break; case '1': msg = "Übertragunsfehler"; break;
case '2': msg = "Ungültiger Befehl"; break; case '2': msg = "Ungültiger Befehl"; break;
case '3': msg = "Ungültiger Parameter"; break; case '3': msg = "Ungültiger Parameter"; break;
} }
throw new IOException($"Kommunikationsfehler {error}: {msg}"); throw new WeighingException($"Kommunikationsfehler {error}: {msg}");
} else { } else {
throw new IOException($"Invalid response from scale (error code {error})"); throw new WeighingException($"Invalid response from scale (error code {error})");
} }
return line[1..^3]; return line[1..^3];
@@ -76,7 +76,7 @@ namespace Elwig.Helpers.Weighing {
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}"); await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}");
string record = await ReceiveResponse(); string record = await ReceiveResponse();
if (record.Length != 62) if (record.Length != 62)
throw new IOException("Invalid response from scale: Received record has invalid size"); throw new FormatException("Invalid response from scale: Received record has invalid size");
var line = record[2..]; var line = record[2..];
var status = line[ 0.. 2]; var status = line[ 0.. 2];
@@ -94,9 +94,9 @@ namespace Elwig.Helpers.Weighing {
var crc16 = line[52..60].Trim(); var crc16 = line[52..60].Trim();
if (Utils.CalcCrc16Modbus(record[..54]) != ushort.Parse(crc16)) { if (Utils.CalcCrc16Modbus(record[..54]) != ushort.Parse(crc16)) {
throw new IOException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})"); throw new WeighingException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})");
} else if (unit != "kg") { } else if (unit != "kg") {
throw new IOException($"Unsupported unit in weighing response: '{unit}'"); throw new WeighingException($"Unsupported unit in weighing response: '{unit}'");
} }
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null; identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;

View File

@@ -0,0 +1,6 @@
using System;
namespace Elwig.Helpers.Weighing {
public class WeighingException(string? message = null) : Exception(message) {
}
}

View File

@@ -0,0 +1,59 @@
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class MemberDeliveryData : DataTable<MemberDeliveryRow> {
private static readonly (string, string, string?, int?)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("Weight", "Geliefert", "kg", 22),
];
public MemberDeliveryData(IEnumerable<MemberDeliveryRow> rows, List<string> filterNames) :
base("Liefermengen Gesamt", "Liefermengen pro Mitglied", string.Join(" / ", filterNames), rows, FieldNames) {
}
public static async Task<MemberDeliveryData> FromQuery(IQueryable<DeliveryPart> query, List<string> filterNames) {
return new((await query
.GroupBy(p => new {
p.Delivery.MgNr,
p.Delivery.Member.Name,
p.Delivery.Member.GivenName,
p.Delivery.Member.Address,
p.Delivery.Member.PostalDest.AtPlz!.Plz,
Ort = p.Delivery.Member.PostalDest.AtPlz!.Ort.Name,
})
.Select(g => new {
g.Key,
Weight = g.Sum(p => p.Weight),
}).ToListAsync())
.Select(g => new MemberDeliveryRow {
MgNr = g.Key.MgNr,
Name1 = g.Key.Name,
Name2 = g.Key.GivenName,
Address = g.Key.Address,
Plz = g.Key.Plz,
Locality = g.Key.Ort,
Weight = g.Weight,
}), filterNames);
}
}
public class MemberDeliveryRow {
public required int MgNr;
public required string Name1;
public required string? Name2;
public required string Address;
public required int Plz;
public required string Locality;
public required int Weight;
}
}

View File

@@ -1,13 +1,13 @@
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Models.Dtos { namespace Elwig.Models.Dtos {
public class MemberDeliveryPerVarietyData : DataTable<MemberDeliveryPerVariantRow> { public class MemberDeliveryPerVarietyData : DataTable<MemberDeliveryPerVarietyRow> {
private static readonly (string, string, string?, int)[] FieldNames = [ private static readonly (string, string, string?, int?)[] FieldNames = [
("MgNr", "MgNr.", null, 12), ("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40), ("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40), ("Name2", "Vorname", null, 40),
@@ -16,104 +16,63 @@ namespace Elwig.Models.Dtos {
("Locality", "Ort", null, 60), ("Locality", "Ort", null, 60),
("SortIds", "Sorte", null, 12), ("SortIds", "Sorte", null, 12),
("AttrIds", "Attribut", null, 16), ("AttrIds", "Attribut", null, 16),
("CultIds", "Bewirt.", null, 16),
("Weights", "Geliefert", "kg", 22), ("Weights", "Geliefert", "kg", 22),
("Areas", "Fläche", "m²", 22),
("Yields", "Ertrag", "kg/ha", 22),
]; ];
public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) : public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVarietyRow> rows, List<string> filterNames) :
base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) { base("Liefermengen pro Sorte", "Liefermengen pro Mitglied, Sorte, Attribut und Bewirtschaftungsart", string.Join(" / ", filterNames), rows, FieldNames) {
} }
public static async Task<MemberDeliveryPerVarietyData> ForSeason(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) { public static async Task<MemberDeliveryPerVarietyData> FromQuery(IQueryable<DeliveryPart> query, List<string> filterNames) {
return new MemberDeliveryPerVarietyData( return new((await query
(await FromDbSet(table, year)).GroupBy( .GroupBy(p => new {
r => r.MgNr, p.Delivery.MgNr,
(k, g) => new MemberDeliveryPerVariantRow(g) p.Delivery.Member.Name,
), year); p.Delivery.Member.GivenName,
} p.Delivery.Member.Address,
p.Delivery.Member.PostalDest.AtPlz!.Plz,
private static async Task<IEnumerable<MemberDeliveryPerVariantRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) { Ort = p.Delivery.Member.PostalDest.AtPlz!.Ort.Name,
return await table.FromSql($""" p.SortId,
SELECT m.mgnr, m.name AS name_1, p.AttrId,
COALESCE(m.prefix || ' ', '') || m.given_name || p.CultId,
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2, })
p.plz, o.name AS ort, m.address, .Select(g => new {
v.bucket, v.weight, v.area g.Key,
FROM ( Weight = g.Sum(p => p.Weight),
SELECT c.year AS year, })
c.mgnr AS mgnr, .ToListAsync()).GroupBy(g => new {
c.bucket AS bucket, g.Key.MgNr,
COALESCE(d.weight, 0) AS weight, g.Key.Name,
COALESCE(c.area, 0) AS area g.Key.GivenName,
FROM v_area_commitment_bucket_strict c g.Key.Address,
LEFT JOIN v_delivery_bucket_strict d ON (d.year, d.mgnr, d.bucket) = (c.year, c.mgnr, c.bucket) g.Key.Plz,
WHERE c.year = {year} g.Key.Ort,
UNION }).Select(g => new MemberDeliveryPerVarietyRow {
SELECT d.year, MgNr = g.Key.MgNr,
d.mgnr, Name1 = g.Key.Name,
d.bucket, Name2 = g.Key.GivenName,
COALESCE(d.weight, 0), Address = g.Key.Address,
COALESCE(c.area, 0) Plz = g.Key.Plz,
FROM v_delivery_bucket_strict d Locality = g.Key.Ort,
LEFT JOIN v_area_commitment_bucket_strict c ON (c.year, c.mgnr, c.bucket) = (d.year, d.mgnr, d.bucket) SortIds = [.. g.Select(d => d.Key.SortId)],
WHERE d.year = {year} AttrIds = [.. g.Select(d => d.Key.AttrId)],
) v CultIds = [.. g.Select(d => d.Key.CultId)],
LEFT JOIN member m ON m.mgnr = v.mgnr Weights = [.. g.Select(d => d.Weight)],
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest }), filterNames);
LEFT JOIN AT_ort o ON o.okz = p.okz
ORDER BY m.mgnr, v.bucket
""").ToListAsync();
} }
} }
public class MemberDeliveryPerVariantRow { public class MemberDeliveryPerVarietyRow {
public int MgNr; public required int MgNr;
public string Name1; public required string Name1;
public string? Name2; public required string? Name2;
public string Address; public required string Address;
public int Plz; public required int Plz;
public string Locality; public required string Locality;
public string[] SortIds; public required string[] SortIds;
public string[] AttrIds; public required string?[] AttrIds;
public int[] Areas; public required string?[] CultIds;
public int[] Weights; public required int[] Weights;
public int?[] Yields => Weights.Zip(Areas).Select(i => i.Second > 0 ? (int?)i.First * 10_000 / i.Second : null).ToArray();
public MemberDeliveryPerVariantRow(IEnumerable<MemberDeliveryPerVariantRowSingle> rows) {
var f = rows.First();
MgNr = f.MgNr;
Name1 = f.Name1;
Name2 = f.Name2;
Address = f.Address;
Plz = f.Plz;
Locality = f.Locality.Split(",")[0];
SortIds = rows.Select(r => r.VtrgId[..2]).ToArray();
AttrIds = rows.Select(r => r.VtrgId[2..]).ToArray();
Areas = rows.Select(r => r.Area).ToArray();
Weights = rows.Select(r => r.Weight).ToArray();
}
}
[Keyless]
public class MemberDeliveryPerVariantRowSingle {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]
public int Plz { get; set; }
[Column("ort")]
public required string Locality { get; set; }
[Column("bucket")]
public required string VtrgId { get; set; }
[Column("area")]
public int Area { get; set; }
[Column("weight")]
public int Weight { get; set; }
} }
} }

View File

@@ -0,0 +1,119 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class MemberDeliveryYieldsPerVarietyData : DataTable<MemberDeliveryYieldsPerVarietyRow> {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SortIds", "Sorte", null, 12),
("AttrIds", "Attribut", null, 16),
("Weights", "Geliefert", "kg", 22),
("Areas", "Fläche", "m²", 22),
("Yields", "Ertrag", "kg/ha", 22),
];
public MemberDeliveryYieldsPerVarietyData(IEnumerable<MemberDeliveryYieldsPerVarietyRow> rows, int year) :
base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) {
}
public static async Task<MemberDeliveryYieldsPerVarietyData> ForSeason(DbSet<MemberDeliveryPerVarietyRowSingle> table, int year) {
return new MemberDeliveryYieldsPerVarietyData(
(await FromDbSet(table, year)).GroupBy(
r => r.MgNr,
(k, g) => new MemberDeliveryYieldsPerVarietyRow(g)
), year);
}
private static async Task<IEnumerable<MemberDeliveryPerVarietyRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVarietyRowSingle> table, int year) {
return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address,
v.bucket, v.weight, v.area
FROM (
SELECT c.year AS year,
c.mgnr AS mgnr,
c.bucket AS bucket,
COALESCE(d.weight, 0) AS weight,
COALESCE(c.area, 0) AS area
FROM v_area_commitment_bucket_strict c
LEFT JOIN v_delivery_bucket_strict d ON (d.year, d.mgnr, d.bucket) = (c.year, c.mgnr, c.bucket)
WHERE c.year = {year}
UNION
SELECT d.year,
d.mgnr,
d.bucket,
COALESCE(d.weight, 0),
COALESCE(c.area, 0)
FROM v_delivery_bucket_strict d
LEFT JOIN v_area_commitment_bucket_strict c ON (c.year, c.mgnr, c.bucket) = (d.year, d.mgnr, d.bucket)
WHERE d.year = {year}
) v
LEFT JOIN member m ON m.mgnr = v.mgnr
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz
ORDER BY m.mgnr, v.bucket
""").ToListAsync();
}
}
public class MemberDeliveryYieldsPerVarietyRow {
public int MgNr;
public string Name1;
public string? Name2;
public string Address;
public int Plz;
public string Locality;
public string[] SortIds;
public string[] AttrIds;
public int[] Areas;
public int[] Weights;
public int?[] Yields => Weights.Zip(Areas).Select(i => i.Second > 0 ? (int?)i.First * 10_000 / i.Second : null).ToArray();
public MemberDeliveryYieldsPerVarietyRow(IEnumerable<MemberDeliveryPerVarietyRowSingle> rows) {
var f = rows.First();
MgNr = f.MgNr;
Name1 = f.Name1;
Name2 = f.Name2;
Address = f.Address;
Plz = f.Plz;
Locality = f.Locality.Split(",")[0];
SortIds = rows.Select(r => r.VtrgId[..2]).ToArray();
AttrIds = rows.Select(r => r.VtrgId[2..]).ToArray();
Areas = rows.Select(r => r.Area).ToArray();
Weights = rows.Select(r => r.Weight).ToArray();
}
}
[Keyless]
public class MemberDeliveryPerVarietyRowSingle {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]
public int Plz { get; set; }
[Column("ort")]
public required string Locality { get; set; }
[Column("bucket")]
public required string VtrgId { get; set; }
[Column("area")]
public int Area { get; set; }
[Column("weight")]
public int Weight { get; set; }
}
}

View File

@@ -9,6 +9,11 @@ using System.Text.Json.Nodes;
namespace Elwig.Models.Entities { namespace Elwig.Models.Entities {
[Table("delivery_part"), PrimaryKey("Year", "DId", "DPNr")] [Table("delivery_part"), PrimaryKey("Year", "DId", "DPNr")]
public class DeliveryPart : IDelivery { public class DeliveryPart : IDelivery {
public const string Dumper = "dumper";
public const string Pumped = "pumped";
public const string Box = "box";
[Column("year")] [Column("year")]
public int Year { get; set; } public int Year { get; set; }
@@ -86,12 +91,27 @@ namespace Elwig.Models.Entities {
[Column("hand_picked")] [Column("hand_picked")]
public bool? IsHandPicked { get; set; } public bool? IsHandPicked { get; set; }
[Column("lesewagen")]
public bool? IsLesewagen { get; set; }
[Column("gebunden")] [Column("gebunden")]
public bool? IsGebunden { get; set; } public bool? IsGebunden { get; set; }
[Column("unloading")]
public string? Unloading { get; set; }
[NotMapped]
public bool IsDumper {
get => Unloading == Dumper;
set => Unloading = value ? Dumper : Unloading;
}
[NotMapped]
public bool IsPumped {
get => Unloading == Pumped;
set => Unloading = value ? Pumped : Unloading;
}
[NotMapped]
public bool IsBox {
get => Unloading == Box;
set => Unloading = value ? Box : Unloading;
}
[Column("temperature")] [Column("temperature")]
public double? Temperature { get; set; } public double? Temperature { get; set; }

View File

@@ -16,6 +16,9 @@ namespace Elwig.Models.Entities {
[Column("active")] [Column("active")]
public bool IsActive { get; set; } public bool IsActive { get; set; }
[Column("redacted")]
public bool IsRedacted { get; set; }
[Column("ordering")] [Column("ordering")]
public int Ordering { get; set; } public int Ordering { get; set; }
@@ -46,6 +49,8 @@ namespace Elwig.Models.Entities {
(Rel != null) ? $"{Utils.GetSign(Rel.Value)}{(Math.Abs(Rel.Value) < 0.1m ? "\u2007" : "")}{Math.Abs(Rel.Value):0.00##\u00a0%}" : (Rel != null) ? $"{Utils.GetSign(Rel.Value)}{(Math.Abs(Rel.Value) < 0.1m ? "\u2007" : "")}{Math.Abs(Rel.Value):0.00##\u00a0%}" :
""; "";
public string? PublicValueStr => IsRedacted ? null : ValueStr;
public override string ToString() { public override string ToString() {
return Name; return Name;
} }

View File

@@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublishDir>bin\Publish</PublishDir> <PublishDir>bin\Publish</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId> <_TargetId>Folder</_TargetId>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile> <PublishSingleFile>false</PublishSingleFile>

View File

@@ -0,0 +1,3 @@
-- schema version 33 to 34
ALTER TABLE modifier ADD COLUMN redacted INTEGER NOT NULL CHECK (redacted IN (TRUE, FALSE)) DEFAULT FALSE;

View File

@@ -0,0 +1,6 @@
-- schema version 34 to 35
ALTER TABLE delivery_part ADD COLUMN unloading TEXT DEFAULT NULL;
UPDATE delivery_part SET unloading = 'pumped' WHERE lesewagen;
UPDATE delivery_part SET unloading = 'box' WHERE (SELECT zwstid IN ('H','S') FROM delivery d WHERE (d.year, d.did) = (delivery_part.year, delivery_part.did));
ALTER TABLE delivery_part DROP COLUMN lesewagen;

View File

@@ -0,0 +1,19 @@
-- schema version 35 to 36
CREATE VIEW v_member AS
SELECT m.mgnr, m.name,
(COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '')) AS other_names,
m.address, p.plz, o.name AS locality,
a.name AS billing_name, a.address AS billing_address, p2.plz AS billing_plz, o2.name AS billing_locality,
k.name AS default_kg,
GROUP_CONCAT(e.address, ', ') AS email_addresses
FROM member m
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz
LEFT JOIN member_billing_address a ON a.mgnr = m.mgnr
LEFT JOIN AT_plz_dest p2 ON p2.id = a.postal_dest
LEFT JOIN AT_ort o2 ON o2.okz = p2.okz
LEFT JOIN AT_kg k ON k.kgnr = m.default_kgnr
LEFT JOIN member_email_address e ON e.mgnr = m.mgnr
GROUP BY m.mgnr
ORDER BY m.mgnr;

View File

@@ -15,10 +15,8 @@ using LinqKit;
using System.Globalization; using System.Globalization;
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.IO;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Windows.Controls; using System.Windows.Controls;
using System.Net.Http;
namespace Elwig.Services { namespace Elwig.Services {
public static class DeliveryService { public static class DeliveryService {
@@ -80,9 +78,9 @@ namespace Elwig.Services {
vm.PartComment = p.Comment ?? ""; vm.PartComment = p.Comment ?? "";
vm.TemperatureString = (p.Temperature != null) ? $"{p.Temperature:N1}" : ""; vm.TemperatureString = (p.Temperature != null) ? $"{p.Temperature:N1}" : "";
vm.AcidString = (p.Acid != null) ? $"{p.Acid:N1}" : ""; vm.AcidString = (p.Acid != null) ? $"{p.Acid:N1}" : "";
vm.IsLesewagen = p.IsLesewagen ?? false;
vm.IsHandPicked = p.IsHandPicked; vm.IsHandPicked = p.IsHandPicked;
vm.IsGebunden = p.IsGebunden; vm.IsGebunden = p.IsGebunden;
vm.Unloading = p.Unloading;
vm.ScaleId = p.ScaleId; vm.ScaleId = p.ScaleId;
vm.WeighingData = p.WeighingData; vm.WeighingData = p.WeighingData;
@@ -188,14 +186,54 @@ namespace Elwig.Services {
prd = prd.And(p => p.IsNetWeight == true); prd = prd.And(p => p.IsNetWeight == true);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("gerebelt gewogen"); filterNames.Add("gerebelt gewogen");
} else if (e.Length >= 5 && e.Length <= 11 && "planenwagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.Unloading == DeliveryPart.Dumper);
filter.RemoveAt(i--);
filterNames.Add("Planenw./Kipper");
} else if (e.Length >= 6 && e.Length <= 12 && "!planenwagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.Unloading != DeliveryPart.Dumper);
filter.RemoveAt(i--);
filterNames.Add("kein Planenw./Kipper");
} else if (e.Length >= 4 && e.Length <= 6 && "kipper".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.Unloading == DeliveryPart.Dumper);
filter.RemoveAt(i--);
filterNames.Add("Planenw./Kipper");
} else if (e.Length >= 5 && e.Length <= 7 && "!kipper".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.Unloading != DeliveryPart.Dumper);
filter.RemoveAt(i--);
filterNames.Add("kein Planenw./Kipper");
} else if (e.Length >= 5 && e.Length <= 9 && "lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 5 && e.Length <= 9 && "lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.IsLesewagen == true); prd = prd.And(p => p.Unloading == DeliveryPart.Pumped);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Lesewagen"); filterNames.Add("Lesewagen");
} else if (e.Length >= 6 && e.Length <= 10 && "!lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 6 && e.Length <= 10 && "!lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.IsLesewagen == false); prd = prd.And(p => p.Unloading != DeliveryPart.Pumped);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("kein Lesewagen"); filterNames.Add("kein Lesewagen");
} else if (e.Length >= 5 && e.Length <= 6 && "kisten".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.Unloading == DeliveryPart.Box);
filter.RemoveAt(i--);
filterNames.Add("Kisten");
} else if (e.Length >= 6 && e.Length <= 7 && "!kisten".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.Unloading != DeliveryPart.Box);
filter.RemoveAt(i--);
filterNames.Add("keine Kisten");
} else if ("upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => (p.Delivery.XTime == null || p.Delivery.MTime > p.Delivery.XTime) && (p.Delivery.ITime == null || p.Delivery.MTime > p.Delivery.ITime));
filter.RemoveAt(i--);
filterNames.Add("geändert seit letztem Export");
} else if ("!upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => !((p.Delivery.XTime == null || p.Delivery.MTime > p.Delivery.XTime) && (p.Delivery.ITime == null || p.Delivery.MTime > p.Delivery.ITime)));
filter.RemoveAt(i--);
filterNames.Add("unverändert seit letztem Export");
} else if (">import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.ITime != null && p.Delivery.MTime > p.Delivery.ITime);
filter.RemoveAt(i--);
filterNames.Add("geändert seit letztem Import");
} else if ("<import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.Delivery.MTime <= p.Delivery.ITime);
filter.RemoveAt(i--);
filterNames.Add("unverändert seit letztem Import");
} else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) { } else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
filterVar.Add(e.ToUpper()); filterVar.Add(e.ToUpper());
filter.RemoveAt(i--); filter.RemoveAt(i--);
@@ -483,8 +521,8 @@ namespace Elwig.Services {
IsNetWeight = vm.IsNetWeight, IsNetWeight = vm.IsNetWeight,
IsHandPicked = vm.IsHandPicked, IsHandPicked = vm.IsHandPicked,
IsLesewagen = vm.IsLesewagen,
IsGebunden = vm.IsGebunden, IsGebunden = vm.IsGebunden,
Unloading = vm.Unloading,
Temperature = vm.Temperature, Temperature = vm.Temperature,
Acid = vm.Acid, Acid = vm.Acid,
Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment, Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment,
@@ -571,6 +609,13 @@ namespace Elwig.Services {
s.DPNr = dpnr++; s.DPNr = dpnr++;
s.Weight = w; s.Weight = w;
ctx.Add(s); ctx.Add(s);
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
Year = s.Year,
DId = s.DId,
DPNr = s.DPNr,
ModId = m.ModId,
}));
} }
} }
@@ -608,6 +653,13 @@ namespace Elwig.Services {
s.DPNr = dpnr++; s.DPNr = dpnr++;
s.Weight = w; s.Weight = w;
ctx.Add(s); ctx.Add(s);
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
Year = s.Year,
DId = s.DId,
DPNr = s.DPNr,
ModId = m.ModId,
}));
} }
} }
@@ -642,6 +694,13 @@ namespace Elwig.Services {
n.QualId = "WEI"; n.QualId = "WEI";
n.HkId = "OEST"; n.HkId = "OEST";
ctx.Add(n); ctx.Add(n);
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
Year = n.Year,
DId = n.DId,
DPNr = n.DPNr,
ModId = m.ModId,
}));
} }
} }
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
@@ -754,40 +813,7 @@ namespace Elwig.Services {
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) { } else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
var path = Path.Combine(App.TempPath, filename);
var list = await query
.Select(p => p.Delivery)
.Distinct()
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
.Include(d => d.Parts).ThenInclude(p => p.Rd)
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
.AsSplitQuery()
.ToListAsync();
var wbKgs = list
.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!)
.DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr)
.ToList();
if (list.Count == 0) {
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
await ElwigData.Export(path, list, wbKgs, filterNames);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} else { } else {
@@ -938,6 +964,52 @@ namespace Elwig.Services {
} }
} }
public static async Task GenerateDeliveryDataList(this DeliveryAdminViewModel vm, ExportSubject subject, ExportMode mode) {
using var ctx = new AppDbContext();
IQueryable<DeliveryPart> query;
List<string> filterNames = [];
if (subject == ExportSubject.FromFilters) {
var (f, _, q, _, _) = await vm.GetFilters(ctx);
query = q;
filterNames.AddRange(f);
} else if (subject == ExportSubject.FromSeason) {
var year = vm.FilterSeason ?? Utils.CurrentLastSeason;
query = ctx.DeliveryParts
.Where(p => p.Year == year);
filterNames.Add($"{year}");
} else {
throw new ArgumentException("Invalid value for ExportSubject");
}
query = query
.OrderBy(p => p.Delivery.MgNr)
.ThenBy(p => p.SortId)
.ThenBy(p => p.AttrId)
.ThenBy(p => p.CultId);
var d = new SaveFileDialog() {
FileName = $"Liefermengen.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Liefermengen speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ods = new OdsFile(d.FileName);
var tblTotal = await MemberDeliveryData.FromQuery(query, filterNames);
var tbl = await MemberDeliveryPerVarietyData.FromQuery(query, filterNames);
await ods.AddTable(tblTotal);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) { private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
var tb = new TextBlock() { var tb = new TextBlock() {
Text = text, Text = text,

View File

@@ -9,9 +9,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
@@ -315,6 +313,22 @@ namespace Elwig.Services {
memberQuery = memberQuery.Where(m => !m.ContactViaPost); memberQuery = memberQuery.Where(m => !m.ContactViaPost);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("nicht Kontaktart Post"); filterNames.Add("nicht Kontaktart Post");
} else if ("upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(p => (p.XTime == null || p.MTime > p.XTime) && (p.ITime == null || p.MTime > p.ITime));
filter.RemoveAt(i--);
filterNames.Add("geändert seit letztem Export");
} else if ("!upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(p => !((p.XTime == null || p.MTime > p.XTime) && (p.ITime == null || p.MTime > p.ITime)));
filter.RemoveAt(i--);
filterNames.Add("unverändert seit letztem Export");
} else if (">import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(p => p.MTime > p.ITime);
filter.RemoveAt(i--);
filterNames.Add("geändert seit letztem Import");
} else if ("<import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(p => p.MTime <= p.ITime);
filter.RemoveAt(i--);
filterNames.Add("unverändert seit letztem Import");
} else if (e.All(char.IsAsciiDigit) && mgnr.ContainsKey(e)) { } else if (e.All(char.IsAsciiDigit) && mgnr.ContainsKey(e)) {
filterMgNr.Add(int.Parse(e)); filterMgNr.Add(int.Parse(e));
filter.RemoveAt(i--); filter.RemoveAt(i--);
@@ -562,46 +576,7 @@ namespace Elwig.Services {
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) { } else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
var path = Path.Combine(App.TempPath, filename);
var members = await query
.OrderBy(m => m.MgNr)
.Include(m => m.BillingAddress)
.Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses)
.Include(m => m.DefaultWbKg!.Gl)
.AsSplitQuery()
.ToListAsync();
var areaComs = await query
.SelectMany(m => m.AreaCommitments)
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
.Include(c => c.Rd)
.Include(c => c.Kg.Gl)
.ToListAsync();
var wbKgs = members
.Where(m => m.DefaultWbKg != null)
.Select(m => m.DefaultWbKg!)
.Union(areaComs.Select(c => c.Kg))
.Distinct()
.OrderBy(k => k.KgNr)
.ToList();
if (members.Count == 0) {
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} else { } else {

View File

@@ -0,0 +1,277 @@
using Elwig.Helpers;
using Elwig.Helpers.Export;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Services {
public static class SyncService {
public static readonly Expression<Func<Member, bool>> ChangedMembers = (m) => ((m.XTime == null && m.MTime > 1751328000) || m.MTime > m.XTime) && (m.ITime == null || m.MTime > m.ITime);
public static readonly Expression<Func<Delivery, bool>> ChangedDeliveries = (d) => ((d.XTime == null && d.MTime > 1751328000) || d.MTime > d.XTime) && (d.ITime == null || d.MTime > d.ITime);
public static async Task Upload(string url, string username, string password, IQueryable<Member> query, IEnumerable<string> filterNames) {
try {
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
var path = Path.Combine(App.TempPath, filename);
var members = await query
.OrderBy(m => m.MgNr)
.Include(m => m.BillingAddress)
.Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses)
.Include(m => m.DefaultWbKg!.Gl)
.AsSplitQuery()
.ToListAsync();
var areaComs = await query
.SelectMany(m => m.AreaCommitments)
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
.Include(c => c.Rd)
.Include(c => c.Kg.Gl)
.ToListAsync();
var wbKgs = members
.Where(m => m.DefaultWbKg != null)
.Select(m => m.DefaultWbKg!)
.Union(areaComs.Select(c => c.Kg))
.Distinct()
.OrderBy(k => k.KgNr)
.ToList();
if (members.Count == 0) {
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
var exportedAt = DateTime.Now;
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
await Utils.UploadExportData(path, url, username, password);
await UpdateExportedAt(members, [], exportedAt);
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public static async Task Upload(string url, string username, string password, IQueryable<DeliveryPart> query, IEnumerable<string> filterNames) {
try {
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
var path = Path.Combine(App.TempPath, filename);
var list = await query
.Select(p => p.Delivery)
.Distinct()
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.Include(d => d.Parts).ThenInclude(p => p.Rd)
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
.AsSplitQuery()
.ToListAsync();
var wbKgs = list
.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!)
.DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr)
.ToList();
if (list.Count == 0) {
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
var exportedAt = DateTime.Now;
await ElwigData.Export(path, list, wbKgs, filterNames);
await Utils.UploadExportData(path, url, username, password);
await UpdateExportedAt([], list, exportedAt);
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public static async Task UploadModified(string url, string username, string password) {
try {
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
List<Member> members;
List<AreaCom> areaComs;
List<Delivery> deliveries;
using (var ctx = new AppDbContext()) {
members = await ctx.Members
.Where(ChangedMembers)
.Include(m => m.BillingAddress)
.Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses)
.Include(m => m.DefaultWbKg!.Gl)
.OrderBy(m => m.MgNr)
.AsSplitQuery()
.ToListAsync();
areaComs = await ctx.Members
.Where(ChangedMembers)
.SelectMany(m => m.AreaCommitments)
.Include(c => c.Rd)
.Include(c => c.Kg.Gl)
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
.ToListAsync();
deliveries = await ctx.Deliveries
.Where(ChangedDeliveries)
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.Include(d => d.Parts).ThenInclude(p => p.Rd)
.Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
.AsSplitQuery()
.ToListAsync();
}
var wbKgs = members
.Where(m => m.DefaultWbKg != null)
.Select(m => m.DefaultWbKg!)
.Union(areaComs.Select(c => c.Kg))
.Union(deliveries.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!))
.DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr)
.ToList();
if (members.Count == 0 && deliveries.Count == 0) {
MessageBox.Show("Es gibt keine geänderten Mitglieder oder Lieferungen, die hochgeladen werden könnten!", "Mitglieder und Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Information);
} else {
var exportedAt = DateTime.Now;
await (new ElwigData.ElwigExport {
Members = (members, ["geändert seit letztem Export"]),
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
Deliveries = (deliveries, ["geändert seit letzem Export"]),
WbKgs = (wbKgs, ["von exportierten Mitgliedern, Flächenbindungen und Lieferungen"]),
}).Export(path);
await Utils.UploadExportData(path, url, username, password);
await UpdateExportedAt(members, deliveries, exportedAt);
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern und {deliveries.Count:N0} Lieferungen erfolgreich!", "Mitglieder und Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public static async Task UploadBranchDeliveries(string url, string username, string password, int? year = null) {
try {
year ??= Utils.CurrentLastSeason;
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
using var ctx = new AppDbContext();
var deliveries = await ctx.Deliveries
.Where(d => d.Year == year && d.ZwstId == App.ZwstId)
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.Include(d => d.Parts).ThenInclude(p => p.Rd)
.Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
.AsSplitQuery()
.ToListAsync();
var wbKgs = deliveries
.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!)
.DistinctBy(k => k.KgNr)
.ToList();
if (deliveries.Count == 0) {
MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
var exportedAt = DateTime.Now;
await ElwigData.Export(path, deliveries, wbKgs, [$"{year}", $"Zweigstelle {App.BranchName}"]);
await Utils.UploadExportData(path, url, username, password);
await UpdateExportedAt([], deliveries, exportedAt);
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public static async Task<List<(string Name, string Url)>> GetFilesToImport(string url, string username, string password) {
var data = await Utils.GetExportMetaData(url, username, password);
var files = data
.Select(f => new {
Name = f!["name"]!.AsValue().GetValue<string>(),
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
Url = f!["url"]!.AsValue().GetValue<string>(),
Size = f!["size"]!.AsValue().GetValue<long>(),
})
.Where(f => f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
.ToList();
var imported = await ElwigData.GetImportedFiles();
return [.. files
.Where(f => f.Device != Environment.MachineName && !imported.Contains(f.Name))
.Select(f => (f.Name, f.Url))
];
}
public static async Task Download(string url, string username, string password) {
try {
var import = await GetFilesToImport(url, username, password);
var paths = new List<string>();
using (var client = Utils.GetHttpClient(username, password)) {
foreach (var f in import) {
var filename = Path.Combine(App.TempPath, f.Name);
using var stream = new FileStream(filename, FileMode.Create);
await client.DownloadAsync(f.Url, stream);
paths.Add(filename);
}
}
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static async Task UpdateExportedAt(IEnumerable<Member> member, IEnumerable<Delivery> deliveries, DateTime dateTime) {
var timestamp = ((DateTimeOffset)dateTime.ToUniversalTime()).ToUnixTimeSeconds();
var mgnrs = string.Join(",", member.Select(m => $"{m.MgNr}").Append("0"));
var dids = string.Join(",", deliveries.Select(d => $"({d.Year},{d.DId})").Append("(0,0)"));
using (var cnx = await AppDbContext.ConnectAsync()) {
await cnx.ExecuteBatch($"""
BEGIN;
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
UPDATE member SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
UPDATE area_commitment SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
UPDATE delivery SET xtime = {timestamp} WHERE (year, did) IN ({dids});
UPDATE delivery_part SET xtime = {timestamp} WHERE (year, did) IN ({dids});
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
COMMIT;
""");
}
App.HintContextChange();
}
public static async Task<bool> ChangesAvailable(AppDbContext ctx, string url, string username, string password) {
return await ctx.Members.AnyAsync(ChangedMembers) || await ctx.Deliveries.AnyAsync(ChangedDeliveries) || (Utils.HasInternetConnectivity() && (await GetFilesToImport(url, username, password)).Count > 0);
}
}
}

View File

@@ -169,10 +169,28 @@ namespace Elwig.ViewModels {
set => AcidString = $"{value:0.0}"; set => AcidString = $"{value:0.0}";
} }
[ObservableProperty] [ObservableProperty]
private bool _isLesewagen;
[ObservableProperty]
private bool? _isHandPicked; private bool? _isHandPicked;
public string? Unloading {
get => IsUnloadingDumper ? DeliveryPart.Dumper : IsUnloadingPumped ? DeliveryPart.Pumped : IsUnloadingBox ? DeliveryPart.Box : null;
set {
switch (value) {
case DeliveryPart.Dumper: IsUnloadingDumper = true; break;
case DeliveryPart.Pumped: IsUnloadingPumped = true; break;
case DeliveryPart.Box: IsUnloadingBox = true; break;
default: IsUnloadingOther = true; break;
}
}
}
[ObservableProperty]
private bool _isUnloadingDumper;
[ObservableProperty]
private bool _isUnloadingPumped;
[ObservableProperty]
private bool _isUnloadingBox;
[ObservableProperty]
private bool _isUnloadingOther;
[ObservableProperty] [ObservableProperty]
private string _statusMembers = "-"; private string _statusMembers = "-";
[ObservableProperty] [ObservableProperty]

View File

@@ -8,7 +8,7 @@
xmlns:local="clr-namespace:Elwig.Windows" xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls"
mc:Ignorable="d" mc:Ignorable="d"
Title="Stammdaten - Elwig" Height="500" MinHeight="400" Width="850" MinWidth="810" Title="Stammdaten - Elwig" Height="520" MinHeight="400" Width="860" MinWidth="810"
Loaded="Window_Loaded"> Loaded="Window_Loaded">
<Window.Resources> <Window.Resources>
<Style TargetType="Label"> <Style TargetType="Label">
@@ -532,6 +532,10 @@
<CheckBox x:Name="SeasonModifierActiveInput" Content="In Übernahme-Fenster anzeigen" <CheckBox x:Name="SeasonModifierActiveInput" Content="In Übernahme-Fenster anzeigen"
Grid.Column="1" Grid.ColumnSpan="2" Margin="10,134,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,134,10,10" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="SeasonModifier_Changed" Unchecked="SeasonModifier_Changed"/> Checked="SeasonModifier_Changed" Unchecked="SeasonModifier_Changed"/>
<CheckBox x:Name="SeasonModifierRedactedInput" Content="Wert auf Lieferschein verbergen"
Grid.Column="1" Grid.ColumnSpan="2" Margin="10,154,10,10" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="SeasonModifier_Changed" Unchecked="SeasonModifier_Changed"/>
</Grid> </Grid>
</GroupBox> </GroupBox>
</Grid> </Grid>

View File

@@ -135,12 +135,14 @@ namespace Elwig.Windows {
SeasonModifierRelInput.Text = ""; SeasonModifierRelInput.Text = "";
SeasonModifierAbsInput.Text = ""; SeasonModifierAbsInput.Text = "";
SeasonModifierActiveInput.IsChecked = false; SeasonModifierActiveInput.IsChecked = false;
SeasonModifierRedactedInput.IsChecked = false;
} else { } else {
SeasonModifierIdInput.Text = mod.ModId; SeasonModifierIdInput.Text = mod.ModId;
SeasonModifierNameInput.Text = mod.Name; SeasonModifierNameInput.Text = mod.Name;
SeasonModifierRelInput.Text = (mod.Rel * 100)?.ToString() ?? ""; SeasonModifierRelInput.Text = (mod.Rel * 100)?.ToString() ?? "";
SeasonModifierAbsInput.Text = mod.Abs?.ToString() ?? ""; SeasonModifierAbsInput.Text = mod.Abs?.ToString() ?? "";
SeasonModifierActiveInput.IsChecked = mod.IsActive; SeasonModifierActiveInput.IsChecked = mod.IsActive;
SeasonModifierRedactedInput.IsChecked = mod.IsRedacted;
} }
_modUpdate = false; _modUpdate = false;
} }
@@ -157,6 +159,7 @@ namespace Elwig.Windows {
mod.Rel = decimal.TryParse(SeasonModifierRelInput.Text, out var vRel) ? vRel / 100 : null; mod.Rel = decimal.TryParse(SeasonModifierRelInput.Text, out var vRel) ? vRel / 100 : null;
mod.AbsValue = decimal.TryParse(SeasonModifierAbsInput.Text, out var vAbs) ? Utils.DecToDb(vAbs, s.Precision) : null; mod.AbsValue = decimal.TryParse(SeasonModifierAbsInput.Text, out var vAbs) ? Utils.DecToDb(vAbs, s.Precision) : null;
mod.IsActive = SeasonModifierActiveInput.IsChecked ?? false; mod.IsActive = SeasonModifierActiveInput.IsChecked ?? false;
mod.IsRedacted = SeasonModifierRedactedInput.IsChecked ?? false;
CollectionViewSource.GetDefaultView(_modList).Refresh(); CollectionViewSource.GetDefaultView(_modList).Refresh();
UpdateButtons(); UpdateButtons();

View File

@@ -129,6 +129,10 @@
<MenuItem x:Name="Menu_DeliveryDepreciationList_PrintSeason" Header="...von Saison drucken" <MenuItem x:Name="Menu_DeliveryDepreciationList_PrintSeason" Header="...von Saison drucken"
Click="Menu_DeliveryDepreciationList_PrintSeason_Click"/> Click="Menu_DeliveryDepreciationList_PrintSeason_Click"/>
</MenuItem> </MenuItem>
<MenuItem Header="Liefermengen" x:Name="Menu_DeliveryDataList">
<MenuItem x:Name="Menu_DeliveryDataList_SaveFilters" Header="...aus Filtern speichern... (Excel)"
Click="Menu_DeliveryDataList_SaveFilters_Click"/>
</MenuItem>
<MenuItem Header="Statistik" x:Name="Menu_Statistics"> <MenuItem Header="Statistik" x:Name="Menu_Statistics">
<MenuItem Header="Qualitätsstatistik..." x:Name="Menu_Statistics_WineQuality"> <MenuItem Header="Qualitätsstatistik..." x:Name="Menu_Statistics_WineQuality">
<MenuItem x:Name="Menu_Statistics_WineQuality_ShowFilters" Header="...aus Filtern anzeigen (PDF)" <MenuItem x:Name="Menu_Statistics_WineQuality_ShowFilters" Header="...aus Filtern anzeigen (PDF)"
@@ -222,6 +226,7 @@
<Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/> <Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/>
<Bold>Handwiegung</Bold>: handw[iegung], !Handw[iegung] (alle ohne Handwiegung)<LineBreak/> <Bold>Handwiegung</Bold>: handw[iegung], !Handw[iegung] (alle ohne Handwiegung)<LineBreak/>
<Bold>Handlese</Bold>: Handl[ese], !handl[ese] (alle ohne Handlese)<LineBreak/> <Bold>Handlese</Bold>: Handl[ese], !handl[ese] (alle ohne Handlese)<LineBreak/>
<Bold>Anlieferung</Bold>: Plane[nwagen]/Kipp[er], !plane[nwagen]/!kipp[er], Lesew[agen], !lesew[agen], kiste[n], !kiste[n]<LineBreak/>
<Bold>Gebunden</Bold>: geb[unden], ungeb[unden], !geb[unden], !ungeb[unden]<LineBreak/> <Bold>Gebunden</Bold>: geb[unden], ungeb[unden], !geb[unden], !ungeb[unden]<LineBreak/>
<Bold>Gerebelt</Bold>: gerebelt, !Gerebelt (nicht gerebelt gewogen)<LineBreak/> <Bold>Gerebelt</Bold>: gerebelt, !Gerebelt (nicht gerebelt gewogen)<LineBreak/>
<Bold>Freitext</Bold>: z.B. Lieferscheinnummern, Anmerkung, "quw" (sucht nach dem Text "quw") <Bold>Freitext</Bold>: z.B. Lieferscheinnummern, Anmerkung, "quw" (sucht nach dem Text "quw")
@@ -369,8 +374,9 @@
<Grid Grid.Row="1" Grid.Column="2"> <Grid Grid.Row="1" Grid.Column="2">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="0.625*"/> <RowDefinition Height="0.625*"/>
<RowDefinition Height="*"/> <RowDefinition Height="0.875*"/>
<RowDefinition Height="*"/> <RowDefinition Height="0.875*"/>
<RowDefinition Height="0.25*"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -404,7 +410,7 @@
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Margin="5,5,5,5"> <GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Grid.RowSpan="3" Margin="5,5,5,5">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/>
@@ -563,7 +569,7 @@
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Sonstiges" Grid.Column="1" Grid.Row="3" Margin="5,5,5,10"> <GroupBox Header="Sonstiges" Grid.Column="1" Grid.Row="3" Grid.RowSpan="2" Margin="5,5,5,10">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/>
@@ -592,18 +598,31 @@
TextChanged="TemperatureAcidInput_TextChanged" LostFocus="TemperatureAcidInput_LostFocus" TextChanged="TemperatureAcidInput_TextChanged" LostFocus="TemperatureAcidInput_LostFocus"
Grid.Column="1" Margin="0,100,10,10" VerticalAlignment="Top"/> Grid.Column="1" Margin="0,100,10,10" VerticalAlignment="Top"/>
<CheckBox x:Name="LesewagenInput" IsChecked="{Binding IsLesewagen, Mode=TwoWay}"
Content="Lesewagen" Margin="10,75,0,0" Grid.Column="2"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="LesewagenInput_Changed" Unchecked="LesewagenInput_Changed"/>
<CheckBox x:Name="HandPickedInput" IsChecked="{Binding IsHandPicked, Mode=TwoWay}" <CheckBox x:Name="HandPickedInput" IsChecked="{Binding IsHandPicked, Mode=TwoWay}"
Content="Handlese" Margin="10,105,0,0" Grid.Column="2" IsThreeState="True" Content="Handlese" Margin="10,135,10,0" Grid.Column="0" Grid.ColumnSpan="2" IsThreeState="True"
VerticalAlignment="Top" HorizontalAlignment="Left" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="HandPickedInput_Changed" Unchecked="HandPickedInput_Changed" Indeterminate="HandPickedInput_Changed"/> Checked="HandPickedInput_Changed" Unchecked="HandPickedInput_Changed" Indeterminate="HandPickedInput_Changed"/>
<RadioButton x:Name="UnloadingDumperInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingDumper, Mode=TwoWay}"
Content="Planenw./Kipper" Margin="10,75,0,0" Grid.Column="2"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
<RadioButton x:Name="UnloadingPumpedInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingPumped, Mode=TwoWay}"
Content="Lesewagen" Margin="10,95,0,0" Grid.Column="2"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
<RadioButton x:Name="UnloadingBoxInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingBox, Mode=TwoWay}"
Content="Kiste(n)" Margin="10,115,0,0" Grid.Column="2"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
<RadioButton x:Name="UnloadingOtherInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingOther, Mode=TwoWay}"
Content="Andere/Unbek." Margin="10,135,0,0" Grid.Column="2"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Herkunft" Grid.Column="0" Grid.Row="3" Margin="5,5,5,10"> <GroupBox Header="Herkunft" Grid.Column="0" Grid.Row="4" Margin="5,5,5,10">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/>

View File

@@ -95,6 +95,7 @@ namespace Elwig.Windows {
if (App.Client.IsMatzen) { if (App.Client.IsMatzen) {
RequiredInputs = [.. RequiredInputs, ModifiersInput]; RequiredInputs = [.. RequiredInputs, ModifiersInput];
} }
ModifiersInput.ItemTemplate = (DataTemplate)ModifiersInput.FindResource("PublicModifierTemplate");
} else { } else {
WeighingManualButton.Visibility = Visibility.Hidden; WeighingManualButton.Visibility = Visibility.Hidden;
WeighingAButton.Visibility = Visibility.Hidden; WeighingAButton.Visibility = Visibility.Hidden;
@@ -134,7 +135,7 @@ namespace Elwig.Windows {
NewDeliveryButton_Click(null, null); NewDeliveryButton_Click(null, null);
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
if (ctx.Seasons.Find(Utils.CurrentYear) == null) { if (ctx.Seasons.Find(Utils.CurrentYear) == null) {
MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.", MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.\n\n(Stammdaten -> Saisons -> Neu anlegen...)",
"Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning); "Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning);
} }
} }
@@ -259,7 +260,7 @@ namespace Elwig.Windows {
await App.Client.UpdateValues(); await App.Client.UpdateValues();
} }
private async void Menu_Statistic_Locality_SaveFilters_Click(object sender, RoutedEventArgs evt)=> private async void Menu_Statistic_Locality_SaveFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateLocalityStatistics(DeliveryService.ExportSubject.FromFilters); await ViewModel.GenerateLocalityStatistics(DeliveryService.ExportSubject.FromFilters);
private async void Menu_DeliveryDepreciationList_SaveFilters_Click(object sender, RoutedEventArgs evt) => private async void Menu_DeliveryDepreciationList_SaveFilters_Click(object sender, RoutedEventArgs evt) =>
@@ -279,6 +280,9 @@ namespace Elwig.Windows {
private async void Menu_DeliveryDepreciationList_PrintSeason_Click(object sender, RoutedEventArgs evt) => private async void Menu_DeliveryDepreciationList_PrintSeason_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromSeason, ExportMode.Print); await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromSeason, ExportMode.Print);
private async void Menu_DeliveryDataList_SaveFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDataList(DeliveryService.ExportSubject.FromFilters, ExportMode.SaveList);
private void Menu_Settings_EnableFreeEditing_Checked(object sender, RoutedEventArgs evt) { private void Menu_Settings_EnableFreeEditing_Checked(object sender, RoutedEventArgs evt) {
if (IsEditing || IsCreating) { if (IsEditing || IsCreating) {
DateInput.IsReadOnly = false; DateInput.IsReadOnly = false;
@@ -319,11 +323,16 @@ namespace Elwig.Windows {
} }
if (App.Config.WeighingMode == WeighingMode.Box) { if (App.Config.WeighingMode == WeighingMode.Box) {
LesewagenInput.IsEnabled = false; SetDefaultValue(UnloadingDumperInput, false);
SetDefaultValue(LesewagenInput, false); SetDefaultValue(UnloadingPumpedInput, false);
SetDefaultValue(UnloadingBoxInput, true);
SetDefaultValue(UnloadingOtherInput, false);
UnloadingBoxInput.IsChecked = true;
} else { } else {
LesewagenInput.IsEnabled = true; UnsetDefaultValue(UnloadingDumperInput);
UnsetDefaultValue(LesewagenInput); UnsetDefaultValue(UnloadingPumpedInput);
UnsetDefaultValue(UnloadingBoxInput);
UnsetDefaultValue(UnloadingOtherInput);
} }
if (App.Config.WeighingMode != WeighingMode.Net) { if (App.Config.WeighingMode != WeighingMode.Net) {
@@ -353,8 +362,8 @@ namespace Elwig.Windows {
ClearDefaultValues(); ClearDefaultValues();
ViewModel.IsNetWeight = App.Config.WeighingMode == WeighingMode.Net; ViewModel.IsNetWeight = App.Config.WeighingMode == WeighingMode.Net;
ViewModel.IsLesewagen = false;
ViewModel.IsHandPicked = App.Config.WeighingMode != WeighingMode.Net ? true : null; ViewModel.IsHandPicked = App.Config.WeighingMode != WeighingMode.Net ? true : null;
ViewModel.Unloading = null;
ViewModel.IsGebunden = null; ViewModel.IsGebunden = null;
InitialDefaultInputs(); InitialDefaultInputs();
@@ -1324,42 +1333,73 @@ namespace Elwig.Windows {
private void ModifiersInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) { private void ModifiersInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
if (!IsEditing && !IsCreating) return; if (!IsEditing && !IsCreating) return;
var mod = ModifiersInput.SelectedItems.Cast<Modifier>().ToList(); var mod = ViewModel.Modifiers.ToList();
var source = ViewModel.ModifiersSource.ToList();
if (App.Client.IsMatzen) { if (App.Client.IsMatzen) {
var kl = mod.Where(m => m.Name.StartsWith("Klasse ")); var kl = mod.Where(m => m.Name.StartsWith("Klasse "));
if (kl.Count() > 1) { if (kl.Count() > 1) {
App.MainDispatcher.BeginInvoke(() => { App.MainDispatcher.BeginInvoke(() => {
foreach (var r in kl.Take(kl.Count() - 1)) foreach (var r in kl.Take(kl.Count() - 1))
ModifiersInput.SelectedItems.Remove(r); ViewModel.Modifiers.Remove(r);
}); });
} }
} else if (App.Client.IsWinzerkeller) { } else if (App.Client.IsWinzerkeller) {
if (mod.Any(m => m.Name.Contains("Lesewagen"))) { if (source.Any(m => m.Name.Contains("Lesewagen"))) {
ViewModel.IsLesewagen = true; if (mod.Any(m => m.Name.Contains("Lesewagen"))) {
} else { ViewModel.IsUnloadingPumped = true;
ViewModel.IsLesewagen = false; } else {
ViewModel.IsUnloadingPumped = false;
}
}
} else if (App.Client.IsBaden) {
if (source.Any(m => m.Name.Contains("Kiste"))) {
if (mod.Any(m => m.Name.Contains("Kiste"))) {
ViewModel.IsUnloadingBox = true;
} else {
ViewModel.IsUnloadingBox = false;
}
} }
} }
} }
private void LesewagenInput_Changed(object sender, RoutedEventArgs evt) { private void UnloadingInput_Checked(object sender, RoutedEventArgs evt) {
if (!IsEditing && !IsCreating) return; if (!IsEditing && !IsCreating) return;
var mod = ModifiersInput.SelectedItems.Cast<Modifier>().ToList(); var mod = ViewModel.Modifiers.ToList();
var source = ModifiersInput.ItemsSource.Cast<Modifier>().ToList(); var source = ViewModel.ModifiersSource.ToList();
var lw = LesewagenInput.IsChecked == true;
if (App.Client.IsMatzen) { if (App.Client.IsMatzen) {
var kl = mod.Where(m => m.Name.StartsWith("Klasse ")).Select(m => m.ModId).LastOrDefault("A")[0]; var kl = mod.Where(m => m.Name.StartsWith("Klasse ")).Select(m => m.ModId).LastOrDefault("_")[0];
if (lw) kl++; else kl--; if (ViewModel.IsUnloadingPumped && (kl == 'A' || kl == '_')) {
var newKl = source.FirstOrDefault(m => m.ModId == kl.ToString()); kl = 'B';
if (newKl != null) ModifiersInput.SelectedItems.Add(newKl); } else if (ViewModel.IsUnloadingDumper && kl == '_') {
kl = 'A';
} else {
kl = '_';
}
var newKl = source.FirstOrDefault(m => m?.ModId == kl.ToString(), null);
if (newKl != null) ViewModel.Modifiers.Add(newKl);
} else if (App.Client.IsWinzerkeller) { } else if (App.Client.IsWinzerkeller) {
if (lw && !mod.Any(m => m.Name.Contains("Lesewagen"))) { if (source.Any(m => m.Name.Contains("Lesewagen"))) {
ModifiersInput.SelectedItems.Add(source.First(m => m.Name.Contains("Lesewagen"))); if (ViewModel.IsUnloadingPumped && !mod.Any(m => m.Name.Contains("Lesewagen"))) {
} else if (!lw && mod.Any(m => m.Name.Contains("Lesewagen"))) { ViewModel.Modifiers.Add(source.First(m => m.Name.Contains("Lesewagen")));
ModifiersInput.SelectedItems.Remove(mod.First(m => m.Name.Contains("Lesewagen"))); } else if (!ViewModel.IsUnloadingPumped && mod.Any(m => m.Name.Contains("Lesewagen"))) {
ViewModel.Modifiers.Remove(mod.First(m => m.Name.Contains("Lesewagen")));
}
}
} else if (App.Client.IsBaden) {
if (source.Any(m => m.Name.Contains("Kiste"))) {
if (ViewModel.IsUnloadingBox && !mod.Any(m => m.Name.Contains("Kiste"))) {
ViewModel.Modifiers.Add(source.First(m => m.Name.Contains("Kiste")));
} else if (!ViewModel.IsUnloadingBox && mod.Any(m => m.Name.Contains("Kiste"))) {
ViewModel.Modifiers.Remove(mod.First(m => m.Name.Contains("Kiste")));
}
} }
} }
CheckBox_Changed(sender, evt); RadioButton_Changed(sender, evt);
}
private void UnloadingInput_Unchecked(object sender, RoutedEventArgs evt) {
if (!IsEditing && !IsCreating) return;
RadioButton_Changed(sender, evt);
} }
private void UpdateWineOrigin() { private void UpdateWineOrigin() {

View File

@@ -302,6 +302,9 @@
<Button x:Name="GenerateButton" Content="Generieren" <Button x:Name="GenerateButton" Content="Generieren"
Grid.Row="0" Grid.Column="0" FontSize="14" Grid.Row="0" Grid.Column="0" FontSize="14"
Click="GenerateButton_Click"/> Click="GenerateButton_Click"/>
<Button x:Name="AbortButton" Content="Abbrechen" Visibility="Hidden" IsEnabled="False"
Grid.Row="0" Grid.Column="0" FontSize="14"
Click="AbortButton_Click"/>
<ProgressBar x:Name="ProgressBar" <ProgressBar x:Name="ProgressBar"
Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True"/> Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True"/>

View File

@@ -13,6 +13,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -61,6 +62,8 @@ namespace Elwig.Windows {
protected Dictionary<Member, List<Document>>? PrintMemberDocuments; protected Dictionary<Member, List<Document>>? PrintMemberDocuments;
protected Dictionary<Member, List<Document>>? EmailDocuments; protected Dictionary<Member, List<Document>>? EmailDocuments;
private CancellationTokenSource? CancelGeneration;
public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow)); public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow));
public int PostalAllCount { public int PostalAllCount {
get => (int)GetValue(PostalAllCountProperty); get => (int)GetValue(PostalAllCountProperty);
@@ -594,20 +597,32 @@ namespace Elwig.Windows {
} }
private void Window_Closed(object sender, EventArgs evt) { private void Window_Closed(object sender, EventArgs evt) {
CancelGeneration?.Dispose();
DisposeDocs(); DisposeDocs();
} }
private async void AbortButton_Click(object sender, RoutedEventArgs evt) {
AbortButton.IsEnabled = false;
CancelGeneration?.Cancel();
}
private async void GenerateButton_Click(object sender, RoutedEventArgs evt) { private async void GenerateButton_Click(object sender, RoutedEventArgs evt) {
LockInputs(); LockInputs();
PreviewButton.IsEnabled = false; PreviewButton.IsEnabled = false;
PrintButton.IsEnabled = false; PrintButton.IsEnabled = false;
EmailButton.IsEnabled = false; EmailButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
AbortButton.IsEnabled = true;
AbortButton.Visibility = Visibility.Visible;
GenerateButton.IsEnabled = false; GenerateButton.IsEnabled = false;
GenerateButton.Visibility = Visibility.Hidden;
DisposeDocs(); DisposeDocs();
await UpdateClientParameters(); await UpdateClientParameters();
CancelGeneration?.Dispose();
CancelGeneration = new();
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var doublePaged = DoublePagedInput.IsChecked == true; var doublePaged = DoublePagedInput.IsChecked == true;
@@ -662,6 +677,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -679,6 +697,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -733,6 +754,21 @@ namespace Elwig.Windows {
}).ToList() }).ToList()
}).ToList(); }).ToList();
var hasPreviewDocs = memberDocs.Any(m => m.Docs.Any(d => d.Doc.IsPreview));
if (hasPreviewDocs) {
var res = MessageBox.Show("Einige der ausgewählten Dokumente sind nur als vorläufig zu betrachten und können daher nicht verschickt/ausgedruckt werden!\n\nDies könnte an einer noch nicht festgesetzen Auszahlungsvariante liegen.\n\nSoll mit dem Generieren fortgefahren werden?",
"Vorläufige Dokumente", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (res != MessageBoxResult.OK) {
UnlockInputs();
GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null;
return;
}
}
var printMode = PostalAllInput.IsChecked == true ? 3 : var printMode = PostalAllInput.IsChecked == true ? 3 :
PostalWishInput.IsChecked == true ? 2 : PostalWishInput.IsChecked == true ? 2 :
PostalNoEmailInput.IsChecked == true ? 1 : 0; PostalNoEmailInput.IsChecked == true ? 1 : 0;
@@ -747,7 +783,7 @@ namespace Elwig.Windows {
.ToDictionary(d => d.Member, m => { .ToDictionary(d => d.Member, m => {
var docs = m.Docs.Select(d => d.Doc).ToList(); var docs = m.Docs.Select(d => d.Doc).ToList();
foreach (var doc in docs) { foreach (var doc in docs) {
doc!.DoublePaged = false; doc!.IsDoublePaged = false;
if (doc is BusinessDocument b) { if (doc is BusinessDocument b) {
b.IncludeSender = false; b.IncludeSender = false;
b.Location = location; b.Location = location;
@@ -759,7 +795,7 @@ namespace Elwig.Windows {
try { try {
foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) { foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) {
foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) { foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) {
await item2.Doc.Generate(new Progress<double>(v => { await item2.Doc.Generate(CancelGeneration.Token, new Progress<double>(v => {
ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum; ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum;
})); }));
} }
@@ -768,6 +804,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -788,7 +827,7 @@ namespace Elwig.Windows {
docs.Insert(0, new Letterhead(m.Member)); docs.Insert(0, new Letterhead(m.Member));
} }
docs.ForEach(doc => { docs.ForEach(doc => {
doc.DoublePaged = doublePaged; doc.IsDoublePaged = doublePaged;
if (doc is BusinessDocument b) if (doc is BusinessDocument b)
b.Location = location; b.Location = location;
}); });
@@ -801,8 +840,8 @@ namespace Elwig.Windows {
if (printDocs.Count > 0) { if (printDocs.Count > 0) {
try { try {
var print = Document.Merge(printDocs); var print = Document.Merge(printDocs);
print.DoublePaged = doublePaged; print.IsDoublePaged = doublePaged;
await print.Generate(new Progress<double>(v => { await print.Generate(CancelGeneration.Token, new Progress<double>(v => {
ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum; ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum;
})); }));
PrintDocument = print; PrintDocument = print;
@@ -811,6 +850,9 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
@@ -822,10 +864,13 @@ namespace Elwig.Windows {
UnlockInputs(); UnlockInputs();
GenerateButton.IsEnabled = true; GenerateButton.IsEnabled = true;
GenerateButton.Visibility = Visibility.Visible;
AbortButton.IsEnabled = false;
AbortButton.Visibility = Visibility.Hidden;
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
PreviewButton.IsEnabled = true; PreviewButton.IsEnabled = true;
PrintButton.IsEnabled = PrintDocument != null; PrintButton.IsEnabled = PrintDocument != null && !hasPreviewDocs;
EmailButton.IsEnabled = EmailDocuments != null && App.Config.Smtp != null; EmailButton.IsEnabled = EmailDocuments != null && !hasPreviewDocs && App.Config.Smtp != null;
} }
private async void PreviewButton_Click(object sender, RoutedEventArgs evt) { private async void PreviewButton_Click(object sender, RoutedEventArgs evt) {
@@ -929,13 +974,13 @@ namespace Elwig.Windows {
await Utils.AddSentMails([( await Utils.AddSentMails([(
"email", m.MgNr, m.AdministrativeName, "email", m.MgNr, m.AdministrativeName,
m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(), m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
subject, subject, docs.Select(d => d.Title).ToArray()
docs.Select(d => d.Title).ToArray()
)]); )]);
} }
}); });
MessageBox.Show("Erfolgreich alle E-Mails verschickt!", "Rundschreiben verschicken", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show("Erfolgreich alle E-Mails verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mails in den Posteingängen der Empfänger aufscheinen.", "Rundschreiben verschicken",
MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} finally { } finally {

View File

@@ -64,6 +64,23 @@
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="Synchronisieren" x:Name="Menu_Sync" IsEnabled="false">
<MenuItem x:Name="Menu_Sync_Download" Header="Mitgliederdaten und Lieferungen herunterladen" Click="Menu_Sync_Download_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE896;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Sync_UploadBranchDeliveries" Header="Lieferungen dieser Saison/Zweigstelle hochladen" Click="Menu_Sync_UploadBranchDeliveries_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE898;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Sync_UploadModified" Header="Geänderte Mitglieder und Lieferungen hochladen" Click="Menu_Sync_UploadModified_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xECC5;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Waage"> <MenuItem Header="Waage">
<MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click"> <MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click">
<MenuItem.Icon> <MenuItem.Icon>
@@ -174,16 +191,18 @@
</Grid> </Grid>
</Button> </Button>
<Button x:Name="DownloadButton" Click="DownloadButton_Click" <Button x:Name="SyncButton" Click="SyncButton_Click"
Margin="310,135,0,0" Padding="0.375,0.5,0,0" Height="30" Width="30"
Content="&#xE896;" FontFamily="Segoe MDL2 Assets" FontSize="16"
HorizontalContentAlignment="Center"
ToolTip="Lieferungen und Mitgliederdaten anderer Zweigstellen herunterladen"/>
<Button x:Name="UploadButton" Click="UploadButton_Click"
Margin="375,135,0,0" Padding="1.0,0.5,0,0" Height="30" Width="30" Margin="375,135,0,0" Padding="1.0,0.5,0,0" Height="30" Width="30"
Content="&#xE898;" FontFamily="Segoe MDL2 Assets" FontSize="16" FontFamily="Segoe MDL2 Assets" FontSize="16"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
ToolTip="Lieferungen dieser Zweigstelle hochladen"/> ToolTip="Geänderte Mitgliederdaten und Lieferungen synchronisieren">
<Button.Content>
<Grid TextElement.FontFamily="Segoe MDL2 Assets">
<TextBlock x:Name="SyncButton_1" Text="&#xE895;"/>
<TextBlock x:Name="SyncButton_2" Text="" Foreground="DarkOrange"/>
</Grid>
</Button.Content>
</Button>
<Expander x:Name="SeasonFinish" Header="Leseabschluss" SnapsToDevicePixels="True" <Expander x:Name="SeasonFinish" Header="Leseabschluss" SnapsToDevicePixels="True"
Expanded="SeasonFinish_Expanded" Collapsed="SeasonFinish_Collapsed" Expanded="SeasonFinish_Expanded" Collapsed="SeasonFinish_Collapsed"

View File

@@ -2,26 +2,30 @@ using Elwig.Helpers;
using Elwig.Helpers.Billing; using Elwig.Helpers.Billing;
using Elwig.Helpers.Export; using Elwig.Helpers.Export;
using Elwig.Models.Dtos; using Elwig.Models.Dtos;
using Elwig.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.NetworkInformation;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading;
namespace Elwig.Windows { namespace Elwig.Windows {
public partial class MainWindow : ContextWindow { public partial class MainWindow : ContextWindow {
private readonly DispatcherTimer _syncTimer = new() { Interval = TimeSpan.FromHours(1) };
public MainWindow() { public MainWindow() {
InitializeComponent(); InitializeComponent();
var v = Assembly.GetExecutingAssembly().GetName().Version; var v = Assembly.GetExecutingAssembly().GetName().Version;
@@ -29,14 +33,21 @@ namespace Elwig.Windows {
if (App.Client.Client == null) VersionField.Text += " (Unbekannt)"; if (App.Client.Client == null) VersionField.Text += " (Unbekannt)";
Menu_Help_Update.IsEnabled = App.Config.UpdateUrl != null; Menu_Help_Update.IsEnabled = App.Config.UpdateUrl != null;
Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null; Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null;
DownloadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden; Menu_Sync.IsEnabled = App.Config.SyncUrl != null;
UploadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden; SyncButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
Menu_Database_Upload.IsEnabled = App.Config.SyncUrl != null; Menu_Database_Upload.IsEnabled = App.Config.SyncUrl != null;
Menu_Database_Download.IsEnabled = App.Config.SyncUrl != null; Menu_Database_Download.IsEnabled = App.Config.SyncUrl != null;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { private void Window_Loaded(object sender, RoutedEventArgs evt) {
SeasonInput.Value = Utils.CurrentLastSeason; SeasonInput.Value = Utils.CurrentLastSeason;
if (Utils.HasInternetConnectivity()) {
CheckSync(200);
}
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
_syncTimer.Tick += new EventHandler(OnSyncTimer);
_syncTimer.Start();
} }
private void Window_Closing(object sender, CancelEventArgs evt) { private void Window_Closing(object sender, CancelEventArgs evt) {
@@ -195,92 +206,43 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
private async void DownloadButton_Click(object sender, RoutedEventArgs evt) { private async void SyncButton_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null) if (App.Config.SyncUrl == null)
return; return;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
var files = data
.Select(f => new {
Name = f!["name"]!.AsValue().GetValue<string>(),
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
Url = f!["url"]!.AsValue().GetValue<string>(),
Size = f!["size"]!.AsValue().GetValue<long>(),
})
.Where(f => f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
.ToList();
var imported = await ElwigData.GetImportedFiles();
var import = files
.Where(f => f.Device != Environment.MachineName && !imported.Contains(f.Name))
.ToList();
var paths = new List<string>();
using (var client = Utils.GetHttpClient(App.Config.SyncUsername, App.Config.SyncPassword)) {
foreach (var f in import) {
var filename = Path.Combine(App.TempPath, f.Name);
using var stream = new FileStream(filename, FileMode.Create);
await client.DownloadAsync(f.Url, stream);
paths.Add(filename);
}
}
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
private async void UploadButton_Click(object sender, RoutedEventArgs evt) { private async void Menu_Sync_Download_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null) if (App.Config.SyncUrl == null)
return; return;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip"); });
using var ctx = new AppDbContext(); Mouse.OverrideCursor = null;
var deliveries = await ctx.Deliveries }
.Where(d => d.Year == Utils.CurrentLastSeason && d.ZwstId == App.ZwstId)
.Include(d => d.Parts) private async void Menu_Sync_UploadBranchDeliveries_Click(object sender, RoutedEventArgs evt) {
.ThenInclude(p => p.PartModifiers) if (App.Config.SyncUrl == null)
.Include(d => d.Parts) return;
.ThenInclude(p => p.Kg) Mouse.OverrideCursor = Cursors.Wait;
.ThenInclude(k => k!.Gl) await Task.Run(async () => {
.OrderBy(d => d.DateString) await SyncService.UploadBranchDeliveries(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
.ThenBy(d => d.TimeString) });
.ThenBy(d => d.LsNr) Mouse.OverrideCursor = null;
.AsSplitQuery() }
.ToListAsync();
var wbKgs = deliveries private async void Menu_Sync_UploadModified_Click(object sender, RoutedEventArgs evt) {
.SelectMany(d => d.Parts) if (App.Config.SyncUrl == null)
.Where(p => p.Kg != null) return;
.Select(p => p.Kg!) Mouse.OverrideCursor = Cursors.Wait;
.DistinctBy(k => k.KgNr) await Task.Run(async () => {
.ToList(); await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
if (deliveries.Count == 0) {
MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
await ElwigData.Export(path, deliveries, wbKgs, [$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
@@ -394,9 +356,42 @@ namespace Elwig.Windows {
App.FocusMailWindow(); App.FocusMailWindow();
} }
protected override Task OnRenewContext(AppDbContext ctx) { protected async override Task OnRenewContext(AppDbContext ctx) {
SeasonInput_TextChanged(null, null); SeasonInput_TextChanged(null, null);
return Task.CompletedTask; CheckSync();
}
private void OnSyncTimer(object? sender, EventArgs? evt) {
if (Utils.HasInternetConnectivity()) {
CheckSync();
}
}
private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs evt) {
if (!evt.IsAvailable) return;
if (Utils.HasInternetConnectivity()) {
CheckSync(1000);
}
}
private async void CheckSync(int delay = 0) {
if (App.Config.SyncUrl == null) return;
Utils.RunBackground("Daten Synchronisieren", async () => {
await Task.Delay(delay);
var ch = false;
using (var ctx = new AppDbContext()) {
ch = await SyncService.ChangesAvailable(ctx, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
}
await App.MainDispatcher.BeginInvoke(() => {
if (ch) {
SyncButton_1.Text = "\uEA6A";
SyncButton_2.Text = "\uEA81";
} else {
SyncButton_1.Text = "\uE895";
SyncButton_2.Text = "";
}
});
});
} }
private void SeasonFinish_Expanded(object sender, RoutedEventArgs evt) { private void SeasonFinish_Expanded(object sender, RoutedEventArgs evt) {
@@ -579,7 +574,7 @@ namespace Elwig.Windows {
App.HintContextChange(); App.HintContextChange();
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var tbl = await MemberDeliveryPerVarietyData.ForSeason(ctx.MemberDeliveryPerVariantRows, year); var tbl = await MemberDeliveryYieldsPerVarietyData.ForSeason(ctx.MemberDeliveryPerVariantRows, year);
using var ods = new OdsFile(d.FileName); using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl); await ods.AddTable(tbl);
} catch (Exception exc) { } catch (Exception exc) {

View File

@@ -55,6 +55,9 @@ namespace Elwig.Windows {
.OrderBy(v => v.AvNr) .OrderBy(v => v.AvNr)
.Include(v => v.Season.Currency) .Include(v => v.Season.Currency)
.ToListAsync()); .ToListAsync());
if (PaymentVariantList.SelectedItem == null && PaymentVariantList.Items.Count > 0) {
PaymentVariantList.SelectedIndex = PaymentVariantList.Items.Count - 1;
}
Update(); Update();
} }
@@ -119,6 +122,11 @@ namespace Elwig.Windows {
private async void DeleteButton_Click(object sender, RoutedEventArgs evt) { private async void DeleteButton_Click(object sender, RoutedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v || !v.TestVariant) if (PaymentVariantList.SelectedItem is not PaymentVar v || !v.TestVariant)
return; return;
var res = MessageBox.Show(
$"Soll die Auszahlungsvariante \"{v.Name}\" wirklich unwiderruflich gelöscht werden?",
"Auszahlungsvariante löschen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (res != MessageBoxResult.OK)
return;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
try { try {
await PaymentVariantService.DeletePaymentVariant(v.Year, v.AvNr); await PaymentVariantService.DeletePaymentVariant(v.Year, v.AvNr);

View File

@@ -5,27 +5,35 @@
Title="Datenbankabfragen - Elwig" Height="450" Width="800" MinWidth="400" MinHeight="300"> Title="Datenbankabfragen - Elwig" Height="450" Width="800" MinWidth="400" MinHeight="300">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="1*" MinHeight="100"/>
<RowDefinition Height="5"/> <RowDefinition Height="5"/>
<RowDefinition Height="3*" MinHeight="100"/> <RowDefinition Height="1*" MinHeight="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="1*" MinHeight="50"/>
<RowDefinition Height="5"/>
<RowDefinition Height="6*" MinHeight="100"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBox x:Name="QueryInput" Text="SELECT * FROM v_delivery" <TextBox x:Name="QueryInput" Text="SELECT * FROM v_member" Grid.Row="1" Grid.RowSpan="3"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,120,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,5,120,5"
FontFamily="Cascadia Code Light" FontSize="13"> FontFamily="Cascadia Code Light" FontSize="13">
<TextBox.InputBindings> <TextBox.InputBindings>
<KeyBinding Key="Return" Modifiers="Control" Command="{Binding EnterCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:QueryWindow}}}" /> <KeyBinding Key="Return" Modifiers="Control" Command="{Binding EnterCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:QueryWindow}}}" />
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:QueryWindow}}}" />
</TextBox.InputBindings> </TextBox.InputBindings>
</TextBox> </TextBox>
<Button x:Name="QueryButton" Content="Abfragen" <Button x:Name="QueryButton" Content="Abfragen" Grid.Row="1"
HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,10,10,5" HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,5,10,0"
Click="QueryButton_Click" Width="100" Click="QueryButton_Click" Width="100"
FontSize="14"/> FontSize="14"/>
<Button x:Name="SaveButton" Content="Speichern" Grid.Row="3"
HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,0,10,5"
Click="SaveButton_Click" Width="100"
FontSize="14"/>
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> <GridSplitter Grid.Row="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<DataGrid x:Name="DataList" Grid.Row="2" <DataGrid x:Name="DataList" Grid.Row="5"
AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Extended" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Extended"
CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,5,10,10"/> HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,5,10,10"/>

View File

@@ -1,7 +1,10 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Common; using System.Data.Common;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -13,7 +16,12 @@ namespace Elwig.Windows {
private ICommand? _enterCommand; private ICommand? _enterCommand;
public ICommand EnterCommand => _enterCommand ??= new ActionCommand(async () => { public ICommand EnterCommand => _enterCommand ??= new ActionCommand(async () => {
await ExecuteQuery(); await DisplayQuery();
});
private ICommand? _saveCommand;
public ICommand SaveCommand => _saveCommand ??= new ActionCommand(async () => {
await SaveQuery();
}); });
@@ -22,33 +30,45 @@ namespace Elwig.Windows {
} }
private async void QueryButton_Click(object sender, RoutedEventArgs evt) { private async void QueryButton_Click(object sender, RoutedEventArgs evt) {
await ExecuteQuery(); await DisplayQuery();
} }
private async Task ExecuteQuery() { private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
await SaveQuery();
}
private async Task DisplayQuery() {
try { try {
await ExecuteQuery(QueryInput.Text); Mouse.OverrideCursor = Cursors.Wait;
await DisplayQuery(QueryInput.Text);
Mouse.OverrideCursor = null;
} catch (Exception e) { } catch (Exception e) {
Mouse.OverrideCursor = null;
MessageBox.Show(e.Message, "Fehler beim Ausführen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(e.Message, "Fehler beim Ausführen", MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
private async Task ExecuteQuery(string sqlQuery) { private async Task SaveQuery() {
await SaveQuery(QueryInput.Text);
}
private static async Task<(IList<DbColumn>, IEnumerable<object[]>)> ExecuteQuery(string sqlQuery) {
var rows = new List<object[]>(); var rows = new List<object[]>();
IList<DbColumn> header; using var cnx = await AppDbContext.ConnectAsync();
using var cmd = cnx.CreateCommand();
using (var cnx = await AppDbContext.ConnectAsync()) { cmd.CommandText = sqlQuery;
using var cmd = cnx.CreateCommand(); using var reader = await cmd.ExecuteReaderAsync();
cmd.CommandText = sqlQuery; var header = await reader.GetColumnSchemaAsync();
using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) {
header = await reader.GetColumnSchemaAsync(); var values = new object[reader.FieldCount];
while (await reader.ReadAsync()) { reader.GetValues(values);
var values = new object[reader.FieldCount]; rows.Add(values);
reader.GetValues(values);
rows.Add(values);
}
} }
return (header, rows);
}
private async Task DisplayQuery(string sqlQuery) {
var (header, rows) = await ExecuteQuery(sqlQuery);
var styleRight = new Style(); var styleRight = new Style();
styleRight.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right)); styleRight.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right));
@@ -63,5 +83,27 @@ namespace Elwig.Windows {
} }
DataList.ItemsSource = rows; DataList.ItemsSource = rows;
} }
private static async Task SaveQuery(string sqlQuery) {
var d = new SaveFileDialog() {
FileName = $"Abfrage.csv",
DefaultExt = "csv",
Filter = "CSV-Datei (*.csv)|*.csv",
Title = $"Datenbank Abfrage speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var (header, rows) = await ExecuteQuery(sqlQuery);
using var csv = new CsvSimple(d.FileName, ';', Utils.UTF8BOM);
await csv.ExportAsync(rows.Prepend([.. header.Select(h => h.ColumnName)]));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler beim Ausführen", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
} }
} }

Binary file not shown.

View File

@@ -13,7 +13,7 @@ About
**Product:** Elwig **Product:** Elwig
**Description:** Electronic Management for Vintners' Cooperatives **Description:** Electronic Management for Vintners' Cooperatives
**Type:** ERP system **Type:** ERP system
**Version:** 1.0.2.0 ([Changelog](./CHANGELOG.md)) **Version:** 1.0.3.0 ([Changelog](./CHANGELOG.md))
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE) **License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/ **Website:** https://elwig.at/
**Source code:** https://git.necronda.net/winzer/elwig **Source code:** https://git.necronda.net/winzer/elwig
@@ -33,7 +33,7 @@ Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
**Produkt:** Elwig **Produkt:** Elwig
**Beschreibung:** Elektronische Winzergenossenschaftsverwaltung **Beschreibung:** Elektronische Winzergenossenschaftsverwaltung
**Typ:** Warenwirtschaftssystem (ERP-System) **Typ:** Warenwirtschaftssystem (ERP-System)
**Version:** 1.0.2.0 ([Änderungsprotokoll](./CHANGELOG.md)) **Version:** 1.0.3.0 ([Änderungsprotokoll](./CHANGELOG.md))
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE) **Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/ **Website:** https://elwig.at/
**Quellcode:** https://git.necronda.net/winzer/elwig **Quellcode:** https://git.necronda.net/winzer/elwig

View File

@@ -14,8 +14,8 @@ namespace Tests {
public async Task Setup_1_Database() { public async Task Setup_1_Database() {
AppDbContext.ConnectionStringOverride = $"Data Source=ElwigTestDB; Mode=Memory; Foreign Keys=True; Cache=Shared"; AppDbContext.ConnectionStringOverride = $"Data Source=ElwigTestDB; Mode=Memory; Foreign Keys=True; Cache=Shared";
Connection = await AppDbContext.ConnectAsync(); Connection = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
} }
[OneTimeSetUp] [OneTimeSetUp]

View File

@@ -16,9 +16,9 @@ namespace Tests.E2ETests {
public static async Task SetupDatabase() { public static async Task SetupDatabase() {
if (File.Exists(Utils.TestDatabasePath)) File.Delete(Utils.TestDatabasePath); if (File.Exists(Utils.TestDatabasePath)) File.Delete(Utils.TestDatabasePath);
using var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{Utils.TestDatabasePath}\"; Mode=ReadWriteCreate; Foreign Keys=True; Cache=Default; Pooling=False"); using var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{Utils.TestDatabasePath}\"; Mode=ReadWriteCreate; Foreign Keys=True; Cache=Default; Pooling=False");
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql"); await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql"); await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.E2EInsert.sql"); await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.E2EInsert.sql");
} }
[OneTimeTearDown] [OneTimeTearDown]

View File

@@ -6,7 +6,7 @@ namespace Tests.E2ETests {
public const int WINDOW_OPEN_SLEEP = 2000; public const int WINDOW_OPEN_SLEEP = 2000;
public static readonly string ApplicationPath = Path.GetFullPath(@"..\..\..\..\Elwig\bin\Debug\net8.0-windows\Elwig.exe"); public static readonly string ApplicationPath = Path.GetFullPath(@"..\..\..\..\Elwig\bin\Debug\net10.0-windows\Elwig.exe");
public static readonly string ConfigPath = Path.GetFullPath(@"..\..\..\..\Tests\config.test.ini"); public static readonly string ConfigPath = Path.GetFullPath(@"..\..\..\..\Tests\config.test.ini");
public static readonly string TestDatabasePath = Path.GetFullPath(@"..\..\..\..\Tests\ElwigTestDB.sqlite3"); public static readonly string TestDatabasePath = Path.GetFullPath(@"..\..\..\..\Tests\ElwigTestDB.sqlite3");

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@@ -19,11 +19,11 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Appium.WebDriver" Version="4.4.5" /> <PackageReference Include="Appium.WebDriver" Version="4.4.5" />
<PackageReference Include="NReco.PdfRenderer" Version="1.6.0" /> <PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
<PackageReference Include="NUnit" Version="4.4.0" /> <PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" /> <PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.11.2"> <PackageReference Include="NUnit.Analyzers" Version="4.11.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -12,13 +12,13 @@ namespace Tests.UnitTests.DocumentTests {
[OneTimeSetUp] [OneTimeSetUp]
public async Task SetupDatabase() { public async Task SetupDatabase() {
Connection = await AppDbContext.ConnectAsync(); Connection = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentInsert.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentInsert.sql");
} }
[OneTimeTearDown] [OneTimeTearDown]
public async Task TeardownDatabase() { public async Task TeardownDatabase() {
if (Connection == null) return; if (Connection == null) return;
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentDelete.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentDelete.sql");
await Connection.DisposeAsync(); await Connection.DisposeAsync();
Connection = null; Connection = null;
} }

View File

@@ -24,13 +24,13 @@ namespace Tests.UnitTests.HelperTests {
[OneTimeSetUp] [OneTimeSetUp]
public async Task SetupDatabase() { public async Task SetupDatabase() {
Connection = await AppDbContext.ConnectAsync(); Connection = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingInsert.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingInsert.sql");
} }
[OneTimeTearDown] [OneTimeTearDown]
public async Task TeardownDatabase() { public async Task TeardownDatabase() {
if (Connection == null) return; if (Connection == null) return;
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingDelete.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingDelete.sql");
await Connection.DisposeAsync(); await Connection.DisposeAsync();
Connection = null; Connection = null;
} }
@@ -72,7 +72,7 @@ namespace Tests.UnitTests.HelperTests {
[TearDown] [TearDown]
public async Task CleanupDatabasePayment() { public async Task CleanupDatabasePayment() {
if (Connection == null) return; if (Connection == null) return;
await AppDbContext.ExecuteBatch(Connection, """ await Connection.ExecuteBatch("""
DELETE FROM credit; DELETE FROM credit;
DELETE FROM payment_variant; DELETE FROM payment_variant;
DELETE FROM delivery_part_bucket; DELETE FROM delivery_part_bucket;
@@ -115,7 +115,7 @@ namespace Tests.UnitTests.HelperTests {
} }
private Task InsertPaymentVariant(int year, int avnr, string data) { private Task InsertPaymentVariant(int year, int avnr, string data) {
return AppDbContext.ExecuteBatch(Connection!, $""" return Connection!.ExecuteBatch($"""
INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, data) INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, data)
VALUES ({year}, {avnr}, 'Test', '2021-01-15', NULL, TRUE, NULL, '{data}'); VALUES ({year}, {avnr}, 'Test', '2021-01-15', NULL, TRUE, NULL, '{data}');
"""); """);

View File

@@ -11,13 +11,13 @@ namespace Tests.UnitTests.ServiceTests {
[OneTimeSetUp] [OneTimeSetUp]
public async Task SetupDatabase() { public async Task SetupDatabase() {
Connection = await AppDbContext.ConnectAsync(); Connection = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceInsert.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceInsert.sql");
} }
[OneTimeTearDown] [OneTimeTearDown]
public async Task TeardownDatabase() { public async Task TeardownDatabase() {
if (Connection == null) return; if (Connection == null) return;
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceDelete.sql"); await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceDelete.sql");
await Connection.DisposeAsync(); await Connection.DisposeAsync();
Connection = null; Connection = null;
} }

View File

@@ -71,7 +71,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_03_Moving() { public void Test_03_Moving() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "moving"; Mock.Error = "moving";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung")); Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
} }
@@ -79,7 +79,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_04_Overloaded() { public void Test_04_Overloaded() {
Mock.Weight = 10_000; Mock.Weight = 10_000;
Mock.Error = "overloaded"; Mock.Error = "overloaded";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Überlast")); Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
} }
@@ -87,14 +87,14 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_05_InvalidResponse() { public void Test_05_InvalidResponse() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "invalid"; Mock.Error = "invalid";
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
} }
[Test] [Test]
public void Test_06_InvalidCrc() { public void Test_06_InvalidCrc() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "crc"; Mock.Error = "crc";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum")); Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
} }
@@ -102,7 +102,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_07_InvalidUnit() { public void Test_07_InvalidUnit() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "unit"; Mock.Error = "unit";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
} }
} }
} }

View File

@@ -100,7 +100,7 @@ namespace Tests.UnitTests.WeighingTests {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Tare = 41; Mock.Tare = 41;
Mock.Error = "moving"; Mock.Error = "moving";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung")); Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
} }
@@ -109,7 +109,7 @@ namespace Tests.UnitTests.WeighingTests {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Tare = 41; Mock.Tare = 41;
Mock.Error = "invalid"; Mock.Error = "invalid";
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
} }
} }
} }

View File

@@ -71,7 +71,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_03_Moving() { public void Test_03_Moving() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "moving"; Mock.Error = "moving";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung")); Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
} }
@@ -79,7 +79,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_04_Overloaded() { public void Test_04_Overloaded() {
Mock.Weight = 10_000; Mock.Weight = 10_000;
Mock.Error = "overloaded"; Mock.Error = "overloaded";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Überlast")); Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
} }
@@ -87,14 +87,14 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_05_InvalidResponse() { public void Test_05_InvalidResponse() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "invalid"; Mock.Error = "invalid";
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
} }
[Test] [Test]
public void Test_06_InvalidCrc() { public void Test_06_InvalidCrc() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "crc"; Mock.Error = "crc";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum")); Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
} }
@@ -102,7 +102,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_07_InvalidUnit() { public void Test_07_InvalidUnit() {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Error = "unit"; Mock.Error = "unit";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
} }
} }
} }

View File

@@ -100,7 +100,7 @@ namespace Tests.UnitTests.WeighingTests {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Tare = 41; Mock.Tare = 41;
Mock.Error = "moving"; Mock.Error = "moving";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung")); Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
} }
@@ -109,7 +109,7 @@ namespace Tests.UnitTests.WeighingTests {
Mock.Weight = 1_000; Mock.Weight = 1_000;
Mock.Tare = 41; Mock.Tare = 41;
Mock.Error = "invalid"; Mock.Error = "invalid";
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
} }
} }
} }

View File

@@ -91,7 +91,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_03_Moving() { public void Test_03_Moving() {
MockA.Weight = 1_000; MockA.Weight = 1_000;
MockA.Error = "moving"; MockA.Error = "moving";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung")); Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
} }
@@ -99,7 +99,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_04_Overloaded() { public void Test_04_Overloaded() {
MockA.Weight = 10_000; MockA.Weight = 10_000;
MockA.Error = "overloaded"; MockA.Error = "overloaded";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Überlast")); Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
} }
@@ -107,14 +107,14 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_05_InvalidResponse() { public void Test_05_InvalidResponse() {
MockA.Weight = 1_000; MockA.Weight = 1_000;
MockA.Error = "invalid"; MockA.Error = "invalid";
Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh()); Assert.ThrowsAsync<FormatException>(async () => await ScaleA!.Weigh());
} }
[Test] [Test]
public void Test_06_InvalidCrc() { public void Test_06_InvalidCrc() {
MockA.Weight = 1_000; MockA.Weight = 1_000;
MockA.Error = "crc"; MockA.Error = "crc";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum")); Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
} }
@@ -122,7 +122,7 @@ namespace Tests.UnitTests.WeighingTests {
public void Test_07_InvalidUnit() { public void Test_07_InvalidUnit() {
MockA.Weight = 1_000; MockA.Weight = 1_000;
MockA.Error = "unit"; MockA.Error = "unit";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh()); var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
} }
} }
} }

View File

@@ -1 +1 @@
curl --fail -s -L "https://elwig.at/files/create.sql?v=33" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql" curl --fail -s -L "https://elwig.at/files/create.sql?v=36" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"