Compare commits

..

3 Commits

Author SHA1 Message Date
10cb23db9e Tests: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 2m26s
2025-08-11 22:47:24 +02:00
a417d0f2a2 Elwig: Update dependencies 2025-08-11 22:47:12 +02:00
b184d5661b [#70] ElwigData: Sync WbKg
All checks were successful
Test / Run tests (push) Successful in 1m58s
2025-08-11 20:51:01 +02:00
90 changed files with 821 additions and 2662 deletions

View File

@@ -2,229 +2,6 @@
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}
---------------------------------------------
### Neue Funktionen {#v1.0.2.0-features}
* Im Mitglieder-Fenster (`MemberAdminWindow`) können Kontaktdaten der Mitglieder als .vcf-Datei exportiert werden. (01f4480a08, 9dc225d3e4)
### Sonstiges {#v1.0.2.0-misc}
* Wenn ein Serial-/COM-Port-USB-Adapter an- oder abgesteckt wird, wird das nun automatisch erkannt. (e6367da286)
* Abhängigkeiten aktualisiert. (b10c744bf9, 0d513f7bff)
[v1.0.2.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.2.0
[v1.0.1.5][v1.0.1.5] (2025-10-29) {#v1.0.1.5}
---------------------------------------------
### Behobene Fehler {#v1.0.1.5-bugfixes}
* Im Rundschreiben-Fenster (`MailWindow`) kam es zu einem Absturz, wenn man das Fenster über den "Anlieferungsbestätigung"-Knopf im Leseabschluss-Abschnitt geöffnet hat. (af98c32026)
[v1.0.1.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.5
[v1.0.1.4][v1.0.1.4] (2025-10-28) {#v1.0.1.4}
---------------------------------------------
### Behobene Fehler {#v1.0.1.4-bugfixes}
* Im Rundschreiben-Fenster (`MailWindow`) kam es zu einem Absturz, wenn man die Zustelloptionen "Post zusenden an Mitglieder, die keine E-Mail erhalten würden" und "E-Mail zusenden an niemanden" kombiniert hat. (2de8af878b)
### Sonstiges {#v1.0.1.4-misc}
* Im Auszahlungsvariante-Fenster (`ChartWindow`) gibt es keine Fehlermeldung mehr wenn nicht für alle Sorten ein Preis definiert ist, nur noch eine Warnung. (428cd6ddc2)
[v1.0.1.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.4
[v1.0.1.3][v1.0.1.3] (2025-10-13) {#v1.0.1.3}
---------------------------------------------
### Neue Funktionen {#v1.0.1.3-features}
* In der Liste des Lieferungen-Fenster (`DeliveryAdminWindow`) werden
* statt ausschließlich der Sorte auch Attribut und Bewirtschaftungsart angezeigt. (a0d4f19f30)
* Kommentare der Lieferungen (und Teillieferungen) angezeigt. (548aeb2ce9)
### Sonstiges {#v1.0.1.3-misc}
* Verzögerung der Überprüfung auf automatische Updates auf einige Sekunden verlängert. (67ba342c28)
* Verbesserung der Ladezeiten im Anmeldungen-Fenster (`DeliveryAncmtAdminWindow`). (7edd888aa2)
[v1.0.1.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.3
[v1.0.1.2][v1.0.1.2] (2025-09-25) {#v1.0.1.2}
---------------------------------------------
### Behobene Fehler {#v1.0.1.2-bugfixes}
* Beim automatischen Importieren/Synchronisieren wird bei einem Fehlerfall der Benutzer verständigt, aber der Vorgang nicht abgebrochen. (9d02f18bac)
### Sonstiges {#v1.0.1.2-misc}
* Beim Sichern der Datenbank werden Meta-Informationen in der ZIP-Datei gespeichert. (c8a95422af)
[v1.0.1.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.2
[v1.0.1.1][v1.0.1.1] (2025-09-21) {#v1.0.1.1}
---------------------------------------------
### Sonstiges {#v1.0.1.1-misc}
* Eingabe von Sorten und Qualitätsstufen im Übernahme-Fenster (`DeliveryAdminWindows`) verbessert. (e2de7a1f1c, b27b89f599)
[v1.0.1.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.1
[v1.0.1.0][v1.0.1.0] (2025-09-18) {#v1.0.1.0}
---------------------------------------------
### Neue Funktionen {#v1.0.1.0-features}
* Neue Weinsorten gemäß Kürzelliste der Bundeskellereiinspektion hinzugefügt. (a0dcaf7b4f)
### Sonstiges {#v1.0.1.0-misc}
* HTTP-Anfragen haben jetzt das Feld `User-Agent` gesetzt. (844fc5217a)
* Auto-Update-Funktion wird auch beim Erlangen von Netzwerkverbindung ausgeführt. (8bc053053c)
[v1.0.1.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.0
[v1.0.0.6][v1.0.0.6] (2025-09-17) {#v1.0.0.6}
---------------------------------------------
### Behobene Fehler {#v1.0.0.6-bugfixes}
* Die automatische Erkennung des Wiege-Modus hat im WKW nicht funktioniert. (463769b549)
### Sonstiges {#v1.0.0.6-misc}
* Tippfehler im Über-Fenster (`AboutWindow`) behoben. (2c383d0c55)
* `SaveFileDialog` mit Multi-File-Extensions verbessert. (9e02b15ff1)
[v1.0.0.6]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.6
[v1.0.0.5][v1.0.0.5] (2025-09-15) {#v1.0.0.5}
---------------------------------------------
### Neue Funktionen {#v1.0.0.5-features}
* Die Datenbank kann im Haupt-Fenster (`MainWindow`) gesichert und wiederhergestellt werden. (f02598760f)
### Sonstiges {#v1.0.0.5-misc}
* In der WGM ist eine Auswahl eines Zu/-Abschlags im Übernahme-Fenster (`DeliveryAdminWindow`) nun erforderlich. (a9b5317e79)
* In der Konfigurationsdatei kann im `[general]` Block `weighing = gross`, `weighing = net`, oder `weighing = box` angegeben werden. (d7012ebfa1)
* Über-Fenser (`AboutWindow`) hinzugefügt. (3c9b3c2db1)
* Abhängigkeiten aktualisiert. (44dcc5e19f, 98f8907817)
[v1.0.0.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.5
[v1.0.0.4][v1.0.0.4] (2025-09-01) {#v1.0.0.4}
---------------------------------------------
### Behobene Fehler {#v1.0.0.4-bugfixes}
* Absturz beim Verschicken von einzelnen E-Mails außerhalb des Rundschreiben-Fensters (`MailWindow`) behoben. (07f9a0f522)
### Sonstiges {#v1.0.0.4-misc}
* Ladezeit des Fehler-Protokoll-Fensters (`LogWindow`) verbessert. (4653a4f129)
* Im Übernahme-Fenster (`DeliveryAdminWindow`) ist es nun möglich die Qualitätsstufe zu verändern. (104798d4f2)
[v1.0.0.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.4
[v1.0.0.3][v1.0.0.3] (2025-08-11) {#v1.0.0.3}
---------------------------------------------
### Neue Funktionen {#v1.0.0.3-features}
* Im Haupt-Fenster (`MainWindow`) ist es nun möglich die gesamte Datenbank zu hochzuladen bzw. herunterzuladen. ([#69][i69])
### Sonstiges {#v1.0.0.3-misc}
* Die Intigrität von Elwig-Export-Dateien (`.elwig.zip`) und anderen `.zip` Dateien wir nun überprüft. (d3157e4d48)
* Die Herkunftshierarchie wird nun auch automatisch synchronisiert (benötigt für Stamm-KG bei Mitglied, Ried/KG bei Flächenbindung, und Herkunft bei Lieferung). ([#70][i70], b6c03892b1)
* Abhängigkeiten aktualisiert. (1f165055c1, 4e2eca295d)
[v1.0.0.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.3
[i69]: https://git.necronda.net/winzer/elwig/issues/69
[i70]: https://git.necronda.net/winzer/elwig/issues/70
[v1.0.0.2][v1.0.0.2] (2025-08-05) {#v1.0.0.2} [v1.0.0.2][v1.0.0.2] (2025-08-05) {#v1.0.0.2}
--------------------------------------------- ---------------------------------------------
@@ -232,8 +9,6 @@ Changelog
* Explizit native SQLite-Bibliothek hinzugefügt. (77c3f388e7) * Explizit native SQLite-Bibliothek hinzugefügt. (77c3f388e7)
[v1.0.0.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.2
@@ -245,8 +20,6 @@ Changelog
* Abhängigkeiten aktualisiert. (466c8a322c) * Abhängigkeiten aktualisiert. (466c8a322c)
* Angepasste Fehlermeldung, wenn Importieren fehlschlägt. (ab7c7404e2) * Angepasste Fehlermeldung, wenn Importieren fehlschlägt. (ab7c7404e2)
[v1.0.0.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.1

View File

@@ -35,7 +35,7 @@
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="WineVarietyTemplateExpanded"> <DataTemplate x:Key="WineVarietyTemplateExpanded">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SortIdFormat}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/> <TextBlock Text="{Binding SortId}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Name}" Foreground="{Binding Color}"/> <TextBlock Text="{Binding Name}" Foreground="{Binding Color}"/>
<TextBlock Text="{Binding CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/> <TextBlock Text="{Binding CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
</StackPanel> </StackPanel>
@@ -55,12 +55,6 @@
<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

@@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.Data; using System.Data;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -26,7 +25,6 @@ namespace Elwig {
public static bool ForceShutdown { get; private set; } = false; public static bool ForceShutdown { get; private set; } = false;
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) }; private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
public readonly SerialPortWatcher SerialPortWatcher = new();
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig"); public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig");
public static readonly string MailsPath = Path.Combine(DataPath, "mails"); public static readonly string MailsPath = Path.Combine(DataPath, "mails");
@@ -49,10 +47,9 @@ namespace Elwig {
public static string? BranchPhoneNr { get; private set; } public static string? BranchPhoneNr { get; private set; }
public static string? BranchFaxNr { get; private set; } public static string? BranchFaxNr { get; private set; }
public static string? BranchMobileNr { get; private set; } public static string? BranchMobileNr { get; private set; }
public static IList<IScale> Scales { get; private set; }
public static IList<IScale> Scales { get; private set; } = []; public static IList<ICommandScale> CommandScales => Scales.Where(s => s is ICommandScale).Cast<ICommandScale>().ToList();
public static IList<ICommandScale> CommandScales => [.. Scales.Where(s => s is ICommandScale).Cast<ICommandScale>()]; public static IList<IEventScale> EventScales => Scales.Where(s => s is IEventScale).Cast<IEventScale>().ToList();
public static IList<IEventScale> EventScales => [.. Scales.Where(s => s is IEventScale).Cast<IEventScale>()];
public static ClientParameters Client { get; set; } public static ClientParameters Client { get; set; }
public static Dispatcher MainDispatcher { get; private set; } public static Dispatcher MainDispatcher { get; private set; }
@@ -130,18 +127,14 @@ namespace Elwig {
if (Config.UpdateAuto && Config.UpdateUrl != null) { if (Config.UpdateAuto && Config.UpdateUrl != null) {
if (Utils.HasInternetConnectivity()) { if (Utils.HasInternetConnectivity()) {
Utils.RunBackground("Auto Updater", async () => { Utils.RunBackground("Auto Updater", async () => {
await Task.Delay(1000); await Task.Delay(500);
await CheckForUpdates(); await CheckForUpdates();
}); });
} }
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
_autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer); _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
_autoUpdateTimer.Start(); _autoUpdateTimer.Start();
} }
SerialPortWatcher.SerialPortConnected += OnSerialPortConnected;
SerialPortWatcher.SerialPortDisconnected += OnSerialPortDisconnected;
var list = new List<IScale>(); var list = new List<IScale>();
foreach (var s in Config.Scales) { foreach (var s in Config.Scales) {
try { try {
@@ -149,7 +142,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}", "Waagenfehler", MessageBox.Show($"Unable to create scale {s.Id}:\n\n{e.Message}", "Scale Error",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
@@ -157,7 +150,7 @@ namespace Elwig {
if (Config.Branch != null) { if (Config.Branch != null) {
if (!branches.ContainsKey(Config.Branch.ToLower())) { if (!branches.ContainsKey(Config.Branch.ToLower())) {
MessageBox.Show("Ungültige Zweigstelle in Konfigurationsdatei!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show("Invalid branch name in config!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown(); Shutdown();
} else { } else {
SetBranch(branches[Config.Branch.ToLower()]); SetBranch(branches[Config.Branch.ToLower()]);
@@ -165,20 +158,10 @@ namespace Elwig {
} else if (branches.Count == 1) { } else if (branches.Count == 1) {
SetBranch(branches.First().Value); SetBranch(branches.First().Value);
} else { } else {
MessageBox.Show("Erkennen der lokalen Zweigstelle nicht möglich!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show("Unable to determine local branch!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown(); Shutdown();
} }
if (Config.WeighingMode == null) {
if (Client.IsMatzen || Client.IsWolkersdorf) {
Config.WeighingMode = WeighingMode.Net;
} else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
Config.WeighingMode = WeighingMode.Box;
} else if (Client.IsBaden || Client.IsGrInzersdorf) {
Config.WeighingMode = WeighingMode.Gross;
}
}
base.OnStartup(evt); base.OnStartup(evt);
var window = new MainWindow(); var window = new MainWindow();
@@ -186,7 +169,6 @@ namespace Elwig {
} }
private async void Application_Exit(object sender, ExitEventArgs evt) { private async void Application_Exit(object sender, ExitEventArgs evt) {
SerialPortWatcher.Dispose();
foreach (var s in EventScales) { foreach (var s in EventScales) {
s.Dispose(); s.Dispose();
} }
@@ -236,63 +218,6 @@ namespace Elwig {
} }
} }
private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs evt) {
if (!evt.IsAvailable) return;
if (Utils.HasInternetConnectivity()) {
Utils.RunBackground("Auto Updater", async () => {
await Task.Delay(2000);
await CheckForUpdates();
});
}
}
private void OnSerialPortConnected(object? sender, string name) {
for (var i = 0; i < Config.Scales.Count; i++) {
var s = Config.Scales[i];
if (s.Connection?.StartsWith($"serial://{name}:") ?? false) {
if (Scales[i] is InvalidScale) {
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);
}
}
}
UpdateScales();
}
private void OnSerialPortDisconnected(object? sender, string name) {
for (var i = 0; i < Config.Scales.Count; i++) {
var s = Config.Scales[i];
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);
if (Scales[i] is ICommandScale) {
try {
Scales[i].Dispose();
} catch {
// ignore
}
Scales[i] = new InvalidScale(s.Id);
}
}
}
UpdateScales();
}
public static void UpdateScales() {
foreach (Window w in CurrentApp.Windows) {
if (w is DeliveryAdminWindow t && t.ViewModel.IsReceipt) {
t.UpdateScales();
}
}
}
public static async Task CheckForUpdates(bool showAlert = false) { public static async Task CheckForUpdates(bool showAlert = false) {
if (Config.UpdateUrl == null) return; if (Config.UpdateUrl == null) return;
var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl); var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
@@ -317,9 +242,7 @@ namespace Elwig {
public static async Task ReplaceDatabase(string filename) { public static async Task ReplaceDatabase(string filename) {
try { try {
await Task.Run(async () => { await ElwigData.ImportDatabase(filename);
await Database.Import(filename);
});
MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
ForceShutdown = true; ForceShutdown = true;
Current.Shutdown(); Current.Shutdown();

View File

@@ -124,24 +124,19 @@ 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) {
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) { } else if (SelectedItems.Count == 0) {
_textBox.Text = ""; _textBox.Text = "";
AllItemsSelected = false;
} 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,16 +19,6 @@ 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,7 +39,6 @@ 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);
@@ -49,7 +48,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.PublicValueStr})"; MemberModifier = $"{mod.Name} ({mod.ValueStr})";
} 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: 0; margin-top: 10mm;
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: 0; margin-top: 10mm;
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: 0; margin-top: 10mm;
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.PublicValueStr</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.ValueStr</td></tr>
first = false; first = false;
} }
} }

View File

@@ -6,7 +6,6 @@ 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 {
@@ -19,11 +18,10 @@ 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 / (IsDoublePaged ? 2 : 1); public int? Pages => TotalPages / (DoublePaged ? 2 : 1);
public bool ShowFoldMarks = App.Config.Debug; public bool ShowFoldMarks = App.Config.Debug;
public bool IsDoublePaged = false; public bool DoublePaged = false;
public bool IsPreview = false;
public string DocumentsPath; public string DocumentsPath;
public int CurrentNextSeason; public int CurrentNextSeason;
@@ -40,8 +38,15 @@ namespace Elwig.Documents {
CurrentNextSeason = Utils.CurrentNextSeason; CurrentNextSeason = Utils.CurrentNextSeason;
Title = title; Title = title;
Author = c.NameFull; Author = c.NameFull;
Header = ""; 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).ToString(); 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();
Date = DateOnly.FromDateTime(Utils.Today); Date = DateOnly.FromDateTime(Utils.Today);
} }
@@ -99,7 +104,7 @@ namespace Elwig.Documents {
return await Html.CompileRenderAsync(name, this); ; return await Html.CompileRenderAsync(name, this); ;
} }
public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) { public async Task Generate(IProgress<double>? progress = null) {
if (_pdfFile != null) if (_pdfFile != null)
return; return;
progress?.Report(0.0); progress?.Report(0.0);
@@ -109,7 +114,6 @@ 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>();
try {
var n = m.Documents.Count(); var n = m.Documents.Count();
int i = 0; int i = 0;
foreach (var doc in m.Documents) { foreach (var doc in m.Documents) {
@@ -117,8 +121,6 @@ namespace Elwig.Documents {
tmpFiles.Add(doc.PdfPath!); tmpFiles.Add(doc.PdfPath!);
continue; continue;
} }
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var tmpHtml = new TempFile("html"); var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8); await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml); tmpHtmls.Add(tmpHtml);
@@ -127,32 +129,21 @@ namespace Elwig.Documents {
progress?.Report(GenerationProportion * 100 * i / n); progress?.Report(GenerationProportion * 100 * i / n);
} }
progress?.Report(GenerationProportion * 100); 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)))); var pages = await Pdf.Convert(tmpFiles, pdf.FileName, DoublePaged, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages; TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} finally {
foreach (var tmp in tmpHtmls) { foreach (var tmp in tmpHtmls) {
tmp.Dispose(); 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");
try { using (var tmpHtml = new TempFile("html")) {
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, IsDoublePaged, cancelToken); var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, DoublePaged);
TotalPages = pages.Pages; TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} }
_pdfFile = pdf;
} }
progress?.Report(100.0); progress?.Report(100.0);
} }
@@ -164,7 +155,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, IsDoublePaged); await Pdf.Print(PdfPath, copies, DoublePaged);
} }
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.IsDoublePaged) { @if (Model.DoublePaged) {
<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><span class="page"></span>@Raw(Model.IsPreview ? " <b>(vorläufig)</b>" : "")</span> <span class="page"></span>
</div> </div>
<footer>@Raw(Model.Footer)</footer> <footer>@Raw(Model.Footer)</footer>
</div> </div>
@if (Model.IsDoublePaged) { @if (Model.DoublePaged) {
<div class="footer-wrapper left"> <div class="footer-wrapper left">
<div class="pre-footer"> <div class="pre-footer">
<span>@Raw(Model.IsPreview ? "<b>(vorläufig)</b> " : "")<span class="page"></span></span> <span class="page"></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: 0; margin-top: 10mm;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -27,7 +27,6 @@ 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 + r.LowGeb.Weight):N0}") kg</td> <td colspan="2" class="number">@($"{Model.Data.Rows.Sum(r => r.Geb.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.LowGeb.Weight + r.Geb.Weight):N0}") kg</td> <td colspan="2" class="number tborder">@($"{Model.Data.Rows.Sum(r => r.Ungeb.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: 0; margin-top: 10mm;
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: 0; margin-top: 10mm;
margin-bottom: 2mm; margin-bottom: 2mm;
} }

View File

@@ -2,12 +2,12 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon> <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.0.3.0</Version> <Version>1.0.0.2</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,22 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="LinqKit" Version="1.3.9" /> <PackageReference Include="LinqKit" Version="1.3.8" />
<PackageReference Include="MailKit" Version="4.14.1" /> <PackageReference Include="MailKit" Version="4.13.0" />
<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="10.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.8" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3405.78" />
<PackageReference Include="NJsonSchema" Version="11.5.2" /> <PackageReference Include="NJsonSchema" Version="11.4.0" />
<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.0.55" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.1" />
<PackageReference Include="System.IO.Hashing" Version="10.0.2" /> <PackageReference Include="System.IO.Hashing" Version="9.0.8" />
<PackageReference Include="System.IO.Ports" Version="10.0.2" /> <PackageReference Include="System.IO.Ports" Version="9.0.8" />
<PackageReference Include="System.Management" Version="10.0.2" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -10,6 +10,7 @@ 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 {
@@ -66,7 +67,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<MemberDeliveryPerVarietyRowSingle> MemberDeliveryPerVariantRows { get; private set; } public DbSet<MemberDeliveryPerVariantRowSingle> 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; }
@@ -116,6 +117,39 @@ 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();
@@ -214,6 +248,13 @@ namespace Elwig.Helpers {
return c + 1; return c + 1;
} }
public async Task<WineQualLevel> GetWineQualityLevel(double kmw) {
return await WineQualityLevels
.Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
.OrderBy(q => q.MinKmw)
.LastAsync();
}
public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) { public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) {
foreach (var m in Modifiers.Where(m => m.Year == part.Year)) { foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
var mod = new DeliveryPartModifier { var mod = new DeliveryPartModifier {

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 = 36; public static readonly int RequiredSchemaVersion = 32;
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 cnx.ExecuteScalar("PRAGMA application_id") ?? 0; var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "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 cnx.ExecuteScalar("PRAGMA schema_version") ?? 0; var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0;
VersionOffset = (int)(schemaVers % 100); VersionOffset = (int)(schemaVers % 100);
if (VersionOffset != 0) { if (VersionOffset != 0) {
// schema was modified manually/externally // schema was modified manually/externally
@@ -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 cnx.ExecuteScalar("PRAGMA user_version") ?? 0; var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "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 cnx.ExecuteBatch($"PRAGMA user_version = {vers}"); await AppDbContext.ExecuteBatch(cnx, $"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 cnx.ExecuteBatch(""" await AppDbContext.ExecuteBatch(cnx, """
PRAGMA locking_mode = EXCLUSIVE; PRAGMA locking_mode = EXCLUSIVE;
BEGIN EXCLUSIVE; BEGIN EXCLUSIVE;
"""); """);
foreach (var script in toExecute) { foreach (var script in toExecute) {
await cnx.ExecuteEmbeddedScript(asm, script); await AppDbContext.ExecuteEmbeddedScript(cnx, asm, script);
} }
var violations = await cnx.ForeignKeyCheck(); var violations = await AppDbContext.ForeignKeyCheck(cnx);
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}"); await AppDbContext.ExecuteBatch(cnx, $"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}");
if (inserts.Count > 0) { if (inserts.Count > 0) {
await cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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

@@ -20,19 +20,13 @@ namespace Elwig.Helpers.Billing {
Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(ctx, Year, onlyDelivered: false)); Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(ctx, Year, onlyDelivered: false));
} }
public async Task Calculate(bool strictPrices = true, bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) { public async Task Calculate(bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
using var tx = await cnx.BeginTransactionAsync(); using var tx = await cnx.BeginTransactionAsync();
await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx); await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
await DeleteInDb(cnx); await DeleteInDb(cnx);
await SetCalcTime(cnx); await SetCalcTime(cnx);
KeyNotFoundException? exception = null; await CalculatePrices(cnx);
try {
await CalculatePrices(cnx, strictPrices);
} catch (KeyNotFoundException e) {
if (strictPrices) throw;
exception = e;
}
if (Data.ConsiderDelieryModifiers) { if (Data.ConsiderDelieryModifiers) {
await CalculateDeliveryModifiers(cnx); await CalculateDeliveryModifiers(cnx);
} }
@@ -40,28 +34,26 @@ namespace Elwig.Helpers.Billing {
await CalculateMemberModifiers(cnx); await CalculateMemberModifiers(cnx);
} }
await tx.CommitAsync(); await tx.CommitAsync();
if (exception != null)
throw exception;
} }
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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, 0, ROUND(lp.amount / POW(10, s.precision - 2))) AS prev_net_amount, IIF(lc.amount >= 0, ROUND(lp.amount / POW(10, s.precision - 2)), 0) 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, 0, lc.modifiers) AS prev_modifiers IIF(lc.amount >= 0, lc.modifiers, 0) 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 +74,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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 +108,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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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 +130,7 @@ namespace Elwig.Helpers.Billing {
mod_rel = mod_rel + excluded.mod_rel mod_rel = mod_rel + excluded.mod_rel
"""); """);
} }
await cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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
@@ -150,8 +142,7 @@ namespace Elwig.Helpers.Billing {
"""); """);
} }
protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) { protected async Task CalculatePrices(SqliteConnection cnx) {
var invalid = new HashSet<string>();
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>(); var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>();
using (var cmd = cnx.CreateCommand()) { using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $""" cmd.CommandText = $"""
@@ -181,31 +172,21 @@ namespace Elwig.Helpers.Billing {
var payAttrId = (part.Discr is "" or "_") ? null : part.Discr; var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
var attrId = part.AttrAreaCom ? payAttrId : part.AttrId; var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom); var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
decimal price = 0; var price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
try {
price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
} catch (KeyNotFoundException e) {
invalid.Add(e.Message.Split('\'')[1]);
}
var priceL = PaymentVariant.Season.DecToDb(price); var priceL = PaymentVariant.Season.DecToDb(price);
inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value)); inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
} }
var msg = invalid.Count == 0 ? null : "Für folgende Sorten wurde noch keine Preiskurve festgelegt: " + string.Join(", ", invalid); await AppDbContext.ExecuteBatch(cnx, $"""
if (msg != null && strict)
throw new KeyNotFoundException(msg);
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})"))};
"""); """);
if (msg != null)
throw new KeyNotFoundException(msg);
} }
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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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

@@ -19,6 +19,14 @@ namespace Elwig.Helpers {
public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S"; public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
public bool IsGrInzersdorf => IsWeinland; public bool IsGrInzersdorf => IsWeinland;
public bool HasNetWeighing(string? zwstId) => IsMatzen || (IsWinzerkeller && zwstId == "W");
public bool HasNetWeighing(Branch? b) => HasNetWeighing(b?.ZwstId);
public bool HasNetWeighing() => HasNetWeighing(App.ZwstId);
public bool HasBoxWeighing(string? zwstId) => IsWinzerkeller && (zwstId != "W");
public bool HasBoxWeighing(Branch? b) => HasBoxWeighing(b?.ZwstId);
public bool HasBoxWeighing() => HasBoxWeighing(App.ZwstId);
public string NameToken; public string NameToken;
public string NameShort; public string NameShort;
public string Name; public string Name;
@@ -206,15 +214,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 = "WISH"; string mailSendPostal = "MGNR";
switch (MailSendPostal) { switch (MailOrdering) {
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 = "WISH"; string mailSendEmail = "MGNR";
switch (MailSendEmail) { switch (MailOrdering) {
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

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -6,8 +5,6 @@ using Microsoft.Extensions.Configuration;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public enum WeighingMode { Gross, Net, Box }
public record struct ScaleConfig { public record struct ScaleConfig {
public string Id; public string Id;
public string? Type; public string? Type;
@@ -44,7 +41,6 @@ namespace Elwig.Helpers {
public string DatabaseFile = App.DataPath + "database.sqlite3"; public string DatabaseFile = App.DataPath + "database.sqlite3";
public string? DatabaseLog = null; public string? DatabaseLog = null;
public string? Branch = null; public string? Branch = null;
public WeighingMode? WeighingMode;
public string? UpdateUrl = null; public string? UpdateUrl = null;
public bool UpdateAuto = false; public bool UpdateAuto = false;
public string? SyncUrl = null; public string? SyncUrl = null;
@@ -78,8 +74,6 @@ namespace Elwig.Helpers {
DatabaseLog = log != null ? Path.Combine(Path.GetDirectoryName(FileName) ?? App.DataPath, log) : null; DatabaseLog = log != null ? Path.Combine(Path.GetDirectoryName(FileName) ?? App.DataPath, log) : null;
Branch = config["general:branch"]; Branch = config["general:branch"];
Debug = TrueValues.Contains(config["general:debug"]?.ToLower()); Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
var weighing = config["general:weighing"];
WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null;
UpdateUrl = config["update:url"]; UpdateUrl = config["update:url"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower()); UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
SyncUrl = config["sync:url"]; SyncUrl = config["sync:url"];

View File

@@ -15,9 +15,7 @@ namespace Elwig.Helpers.Export {
protected readonly char Separator; protected readonly char Separator;
protected string? Header; protected string? Header;
public Csv(string filename, char separator = ';') : public Csv(string filename, char separator = ';') : this(filename, separator, Utils.UTF8) { }
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);
@@ -60,22 +58,4 @@ 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

@@ -1,243 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Elwig.Helpers.Export {
public static class Database {
private static async Task<(long? ApplicationId, string? UserVersion, long? SchemaVersion, long FileSize)> GetMeta() {
long size = new FileInfo(App.Config.DatabaseFile).Length;
using var cnx = await AppDbContext.ConnectAsync();
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id");
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_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);
}
public static async Task ExportSqlite(string filename, bool zipFile) {
if (zipFile) {
File.Delete(filename);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
await writer.WriteAsync("elwig:1");
}
var (applId, userVers, schemaVers, size) = await GetMeta();
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
var obj = new JsonObject {
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName,
["database"] = new JsonObject {
["application_id"] = applId,
["user_version"] = userVers,
["schema_version"] = schemaVers,
["file_size"] = size,
},
};
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
}
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
} else {
File.Copy(App.Config.DatabaseFile, filename, true);
}
}
public static async Task ExportSql(string filename, bool zipFile) {
if (zipFile) {
File.Delete(filename);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
await writer.WriteAsync("elwig:1");
}
var (applId, userVers, schemaVers, size) = await GetMeta();
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
var obj = new JsonObject {
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName,
["database"] = new JsonObject {
["application_id"] = applId,
["user_version"] = userVers,
["schema_version"] = schemaVers,
["file_size"] = size,
},
};
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
}
var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) {
await ExportSql(writer);
}
} else {
using var stream = File.OpenWrite(filename);
using var writer = new StreamWriter(stream, Utils.UTF8);
await ExportSql(writer);
}
}
public static async Task ExportSql(StreamWriter writer) {
using var cnx = await AppDbContext.ConnectAsync();
var tables = new List<(string Name, string Sql)>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = "SELECT name, sql FROM sqlite_schema WHERE type = 'table'";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
tables.Add((reader.GetString(0), reader.GetString(1)));
}
}
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_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($"-- {Environment.MachineName}, Zwst. {App.BranchName}, {App.Client.Name}");
await writer.WriteLineAsync("BEGIN TRANSACTION;");
await writer.WriteLineAsync("PRAGMA foreign_keys=OFF;");
await writer.WriteLineAsync($"PRAGMA application_id=0x{applId:X8};");
await writer.WriteLineAsync($"PRAGMA user_version=0x{userVers:X8};");
foreach (var t in tables) {
await writer.WriteAsync(t.Sql);
await writer.WriteLineAsync(";");
var columnNames = new List<string>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"PRAGMA table_info({t.Name})";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
columnNames.Add(reader.GetString(1));
}
}
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"SELECT {string.Join(',', columnNames)} FROM {t.Name}";
using var reader = await cmd.ExecuteReaderAsync();
var columns = await reader.GetColumnSchemaAsync();
var values = new object[reader.FieldCount];
while (await reader.ReadAsync()) {
await writer.WriteAsync($"INSERT INTO {t.Name} VALUES (");
reader.GetValues(values);
for (int i = 0; i < columns.Count; i++) {
var c = columns[i];
var v = values[i];
if (i > 0) await writer.WriteAsync(",");
if (v == null || v is DBNull) {
await writer.WriteAsync("NULL");
} else if (c.DataTypeName == "TEXT") {
await writer.WriteAsync($"'{v.ToString()?.Replace("'", "''")}'");
} else {
await writer.WriteAsync(v.ToString()?.Replace(',', '.'));
}
}
await writer.WriteLineAsync(");");
}
}
}
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = "SELECT sql FROM sqlite_schema WHERE type != 'table' AND sql IS NOT NULL";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
await writer.WriteAsync(reader.GetString(0));
await writer.WriteLineAsync(";");
}
}
await writer.WriteLineAsync($"PRAGMA schema_version={schemaVers};");
await writer.WriteLineAsync("PRAGMA foreign_keys=ON;");
await writer.WriteLineAsync("COMMIT;");
await writer.WriteLineAsync("VACUUM;");
}
public static async Task Import(string filename) {
if (filename.EndsWith(".sql")) {
await ImportSql(filename, false);
} else if (filename.EndsWith(".sql.zip")) {
await ImportSql(filename, true);
} else if (filename.EndsWith(".sqlite3")) {
await ImportSqlite(filename, false);
} else if (filename.EndsWith(".sqlite3.zip")) {
await ImportSqlite(filename, true);
} else {
throw new ArgumentException($"Unknown file extension for importing: '{filename}'");
}
}
public static async Task ImportSql(string filename, bool zipFile = false) {
if (zipFile) {
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
await zip.CheckIntegrity();
foreach (var entry in zip.Entries) {
if (entry.Name.EndsWith(".sql")) {
using var stream = entry.Open();
using var reader = new StreamReader(stream, Utils.UTF8);
await ImportSql(reader);
return;
}
}
throw new FileFormatException("ZIP archive has to contain at least one .sql file");
} else {
using var stream = File.Open(filename, FileMode.Open);
using var reader = new StreamReader(stream, Utils.UTF8);
await ImportSql(reader);
}
}
public static async Task ImportSqlite(string filename, bool zipFile = false) {
if (zipFile) {
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
try {
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
await zip.CheckIntegrity();
foreach (var entry in zip.Entries) {
if (entry.Name.EndsWith(".sqlite3")) {
entry.ExtractToFile(newName);
await ImportSqlite(newName);
return;
}
}
throw new FileFormatException("ZIP archive has to contain at least one .sqlite3 file");
} finally {
if (File.Exists(newName)) File.Delete(newName);
}
}
var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3");
File.Move(App.Config.DatabaseFile, oldName, true);
File.Move(filename, App.Config.DatabaseFile, false);
using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch("VACUUM");
}
public static async Task ImportSql(StreamReader reader) {
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
File.Delete(newName);
try {
using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) {
await cnx.ExecuteBatch(await reader.ReadToEndAsync());
}
await ImportSqlite(newName);
} finally {
if (File.Exists(newName)) File.Delete(newName);
}
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@@ -17,6 +18,8 @@ namespace Elwig.Helpers.Export {
public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt"); public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
private static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
public static async Task<string[]> GetImportedFiles() { public static async Task<string[]> GetImportedFiles() {
try { try {
return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8); return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
@@ -74,14 +77,6 @@ namespace Elwig.Helpers.Export {
int? DeliveryNum, string? DeliveryFilters)>(); int? DeliveryNum, string? DeliveryFilters)>();
foreach (var filename in filenames) { foreach (var filename in filenames) {
try {
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
["member"] = [],
["area_commitment"] = [],
["delivery"] = [],
})));
var r = data[^1];
// TODO read encrypted files // TODO read encrypted files
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read); using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
await zip.CheckIntegrity(); await zip.CheckIntegrity();
@@ -89,7 +84,7 @@ namespace Elwig.Helpers.Export {
var version = zip.GetEntry("version"); var version = zip.GetEntry("version");
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) { using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
if (await reader.ReadToEndAsync() != "elwig:1") if (await reader.ReadToEndAsync() != "elwig:1")
throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})"); throw new FileFormatException($"Ungültige Export-Datei ({filename})");
} }
var metaJson = zip.GetEntry("meta.json"); var metaJson = zip.GetEntry("meta.json");
@@ -106,6 +101,13 @@ namespace Elwig.Helpers.Export {
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null, areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null)); deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
["member"] = [],
["area_commitment"] = [],
["delivery"] = [],
})));
var r = data[^1];
var wbKgsJson = zip.GetEntry("wb_kgs.json"); var wbKgsJson = zip.GetEntry("wb_kgs.json");
if (wbKgsJson != null) { if (wbKgsJson != null) {
using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8); using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
@@ -168,25 +170,6 @@ namespace Elwig.Helpers.Export {
r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt)); r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
} }
} }
} catch (Exception exc) when (
exc is InvalidDataException ||
exc is FileFormatException ||
exc is FileNotFoundException ||
exc is IOException) {
data.RemoveAt(data.Count - 1);
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
await AddImportedFiles(Path.GetFileName(filename));
} catch (Exception exc) {
data.RemoveAt(data.Count - 1);
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
if (r == MessageBoxResult.Yes) {
await AddImportedFiles(Path.GetFileName(filename));
}
}
} }
var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>(); var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
@@ -345,7 +328,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 cnx.ExecuteBatch($""" await AppDbContext.ExecuteBatch(cnx, $"""
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)}
@@ -422,6 +405,29 @@ namespace Elwig.Helpers.Export {
}.Export(filename); }.Export(filename);
} }
public static async Task ImportDatabase(string filename) {
var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3");
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
try {
using (var zip = ZipFile.Open(filename, ZipArchiveMode.Read)) {
await zip.CheckIntegrity();
var db = zip.GetEntry("database.sqlite3")!;
db.ExtractToFile(newName, true);
}
File.Move(App.Config.DatabaseFile, oldName, true);
File.Move(newName, App.Config.DatabaseFile, false);
} finally {
if (File.Exists(newName))
File.Delete(newName);
}
}
public static void ExportDatabase(string filename) {
File.Delete(filename);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
}
public class ElwigExport { public class ElwigExport {
public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; } public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; } public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
@@ -466,7 +472,7 @@ namespace Elwig.Helpers.Export {
["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count), ["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()), ["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()),
}; };
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts)); await writer.WriteAsync(obj.ToJsonString(JsonOpts));
} }
// TODO encrypt files // TODO encrypt files
@@ -474,28 +480,28 @@ namespace Elwig.Helpers.Export {
var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var k in WbKgs.Value.WbKgs) { foreach (var k in WbKgs.Value.WbKgs) {
await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(k.ToJson().ToJsonString(JsonOpts));
} }
} }
if (Members != null) { if (Members != null) {
var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var m in Members.Value.Members) { foreach (var m in Members.Value.Members) {
await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(m.ToJson().ToJsonString(JsonOpts));
} }
} }
if (AreaComs != null) { if (AreaComs != null) {
var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var c in AreaComs.Value.AreaComs) { foreach (var c in AreaComs.Value.AreaComs) {
await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(c.ToJson().ToJsonString(JsonOpts));
} }
} }
if (Deliveries != null) { if (Deliveries != null) {
var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var d in Deliveries.Value.Deliveries) { foreach (var d in Deliveries.Value.Deliveries) {
await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts));
} }
} }
} }
@@ -744,8 +750,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,12 +824,12 @@ 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>(),
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts), WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(), WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
}; };
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier { }).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {

View File

@@ -1,69 +0,0 @@
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Elwig.Helpers.Export {
public class VCard : IExporter<Member> {
public static string FileExtension => "vcf";
private readonly StreamWriter _writer;
public VCard(string filename) : this(filename, Utils.UTF8) { }
public VCard(string filename, Encoding encoding) {
_writer = new StreamWriter(filename, false, encoding);
}
public void Dispose() {
GC.SuppressFinalize(this);
_writer.Dispose();
}
public ValueTask DisposeAsync() {
GC.SuppressFinalize(this);
return _writer.DisposeAsync();
}
public async Task ExportAsync(IEnumerable<Member> data, IProgress<double>? progress = null) {
progress?.Report(0.0);
int count = data.Count() + 1, i = 0;
foreach (var row in data) {
var billingAddr = row.BillingAddress != null ? $"ADR;TYPE=work;LANGUAGE=de;LABEL=\"{Escape(row.BillingAddress.FullName)}\\n{Escape(row.BillingAddress.Address)}\\n{row.BillingAddress.PostalDest.AtPlz?.Plz} {Escape(row.BillingAddress.PostalDest.AtPlz?.Ort.Name)}\\nÖsterreich\":;;{Escape(row.BillingAddress.Address)};{Escape(row.BillingAddress.PostalDest.AtPlz?.Ort.Name)};;{row.BillingAddress.PostalDest.AtPlz?.Plz};Österreich\r\n" : null;
var tel = string.Join("", row.TelephoneNumbers
.Where(n => n.Type != "fax")
.Select(n => $"TEL;TYPE={(n.Type == "mobile" ? "cell" : "voice")}:{Escape(n.Number)}\r\n"));
var email = string.Join("", row.EmailAddresses.Select(a => $"EMAIL:{Escape(a.Address)}\r\n"));
await _writer.WriteLineAsync($"""
BEGIN:VCARD
VERSION:4.0
UID:mg{row.MgNr}@{App.Client.NameToken.ToLower()}.elwig.at
NOTE:MgNr. {row.MgNr}
FN:{Escape(row.AdministrativeName)}
N:{Escape(row.Name)};{Escape(row.GivenName)};{Escape(row.MiddleName)};{Escape(row.Prefix)};{Escape(row.Suffix)}
KIND:{(row.IsJuridicalPerson ? "org" : "individual")}
ADR{(billingAddr == null ? "" : ";TYPE=home")};LANGUAGE=de;LABEL="{Escape(row.Address)}\n{row.PostalDest.AtPlz?.Plz} {Escape(row.PostalDest.AtPlz?.Ort.Name)}\nÖsterreich":;;{Escape(row.Address)};{Escape(row.PostalDest.AtPlz?.Ort.Name)};;{row.PostalDest.AtPlz?.Plz};Österreich
{billingAddr}{tel}{email}REV:{row.ModifiedAt.ToUniversalTime():yyyyMMdd\THHmmss\Z}
END:VCARD
""");
progress?.Report(100.0 * ++i / count);
}
await _writer.FlushAsync();
progress?.Report(100.0);
}
public void Export(IEnumerable<Member> data, IProgress<double>? progress = null) {
ExportAsync(data, progress).GetAwaiter().GetResult();
}
private static string? Escape(string? text) {
return text?.Replace("\\", "\\\\").Replace(",", "\\,").Replace(";", "\\;").Replace("\n", "\\n").Replace("\r", "");
}
}
}

View File

@@ -1,5 +1,5 @@
namespace Elwig.Helpers { namespace Elwig.Helpers {
public enum ExportMode { public enum ExportMode {
Show, SaveList, SavePdf, Print, Email, Vcf, Export, Upload Show, SaveList, SavePdf, Print, Email, Export, Upload
} }
} }

View File

@@ -1,17 +1,14 @@
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 {
public static partial class Extensions { static partial class Extensions {
[LibraryImport("msvcrt.dll")] [LibraryImport("msvcrt.dll")]
[UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
@@ -111,39 +108,5 @@ 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,20 @@
using System.Threading.Tasks;
using Elwig.Windows; using Elwig.Windows;
using PdfiumViewer;
using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing.Printing; using System;
using System.IO; using System.IO;
using System.Collections.Generic;
using System.Windows;
using System.Text.RegularExpressions;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection; using PdfiumViewer;
using System.Text.RegularExpressions; using System.Drawing.Printing;
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 = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ? private static readonly string WinziPrint = new string[] { App.InstallPath }
Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Installer/Files/WinziPrint.exe") :
new string[] { App.InstallPath }
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? []) .Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
.Select(x => Path.Combine(x, "WinziPrint.exe")) .Select(x => Path.Combine(x, "WinziPrint.exe"))
.Where(File.Exists) .Where(File.Exists)
@@ -50,45 +46,24 @@ 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, CancellationToken? cancelToken = null, IProgress<double>? progress = null) { public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) {
return await Convert([htmlPath], pdfPath, doublePaged, cancelToken, progress); return await Convert([htmlPath], pdfPath, doublePaged, progress);
} }
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) { public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, 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"));
bool cancelled = false; using var reader = new StreamReader(stream);
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:")) {
var msg = line[6..].Trim(); throw new IOException($"WinziPrint: {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

@@ -1,60 +0,0 @@
using System;
using System.IO.Ports;
using System.Linq;
using System.Management;
namespace Elwig.Helpers {
public sealed class SerialPortWatcher : IDisposable {
private readonly ManagementEventWatcher _deviceArrivalWatcher;
private readonly ManagementEventWatcher _deviceRemovalWatcher;
private string[] _knownPorts;
public event EventHandler<string>? SerialPortConnected;
public event EventHandler<string>? SerialPortDisconnected;
public SerialPortWatcher() {
_knownPorts = SerialPort.GetPortNames();
_deviceArrivalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
_deviceArrivalWatcher.EventArrived += OnDeviceArrived;
_deviceRemovalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
_deviceRemovalWatcher.EventArrived += OnDeviceRemoved;
_deviceArrivalWatcher.Start();
_deviceRemovalWatcher.Start();
}
private void OnDeviceArrived(object sender, EventArrivedEventArgs evt) {
App.MainDispatcher.Invoke(() => {
// "synchronized"
string[] currentPorts = SerialPort.GetPortNames();
var newPorts = currentPorts.Except(_knownPorts).ToArray();
foreach (var port in newPorts)
SerialPortConnected?.Invoke(this, port);
_knownPorts = currentPorts;
});
}
private void OnDeviceRemoved(object sender, EventArrivedEventArgs evt) {
App.MainDispatcher.Invoke(() => {
// "synchronized"
string[] currentPorts = SerialPort.GetPortNames();
var removedPorts = _knownPorts.Except(currentPorts).ToArray();
foreach (var port in removedPorts)
SerialPortDisconnected?.Invoke(this, port);
_knownPorts = currentPorts;
});
}
public void Dispose() {
try {
_deviceArrivalWatcher?.Stop();
_deviceRemovalWatcher?.Stop();
} finally {
_deviceArrivalWatcher?.Dispose();
_deviceRemovalWatcher?.Dispose();
}
}
}
}

View File

@@ -1,43 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.IO.Ports;
using System.Net.Sockets;
using Elwig.Dialogs; using Elwig.Dialogs;
using Elwig.Documents; using System.Text;
using Elwig.Helpers.Billing; using System.Numerics;
using Elwig.Models;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using LinqKit; using Elwig.Helpers.Billing;
using System.Runtime.InteropServices;
using System.Net.Http;
using System.Text.Json.Nodes;
using System.IO;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using MailKit.Security; using MailKit.Security;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using MimeKit;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Net.Sockets;
using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Collections;
using System.Text; using Elwig.Documents;
using System.Text.Json; using MimeKit;
using System.Text.Json.Nodes; using System.Windows.Input;
using System.Text.RegularExpressions; using LinqKit;
using System.Linq.Expressions;
using Elwig.Models;
using Microsoft.Win32;
using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup; using System.Windows.Markup;
namespace Elwig.Helpers { 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 int CurrentYear => DateTime.Now.Year; public static int CurrentYear => DateTime.Now.Year;
public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0); public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
@@ -432,8 +430,6 @@ namespace Elwig.Helpers {
var client = new HttpClient() { var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
}; };
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"Elwig/{App.Version} ({App.Client.NameToken}, {App.BranchName}, {Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Clear();
if (accept != null) if (accept != null)
client.DefaultRequestHeaders.Accept.Add(new(accept)); client.DefaultRequestHeaders.Accept.Add(new(accept));
@@ -502,8 +498,10 @@ namespace Elwig.Helpers {
public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) { public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
if (App.Config.Smtp == null) if (App.Config.Smtp == null)
return false; return false;
return await Task.Run(async () => {
await AddSentMailBody(subject, text, 1); Mouse.OverrideCursor = Cursors.Wait;
var success = await Task.Run(async () => {
SmtpClient? client = null; SmtpClient? client = null;
try { try {
client = await GetSmtpClient(); client = await GetSmtpClient();
@@ -521,11 +519,6 @@ 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;
@@ -536,33 +529,22 @@ namespace Elwig.Helpers {
} }
return true; return true;
}); });
Mouse.OverrideCursor = null;
return success;
} }
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, string, string)? 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.Item1, e.Item2, e.Item3, [doc]);
if (success) if (success)
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", MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "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",
@@ -585,7 +567,9 @@ namespace Elwig.Helpers {
Log = "Application", Log = "Application",
Source = ".NET Runtime", Source = ".NET Runtime",
}; };
return [.. log.Entries.OfType<EventLogEntry>().Where(e => e.InstanceId == 1026).Where(e => e.Message.StartsWith("Application: Elwig.exe"))]; return log.Entries.Cast<EventLogEntry>()
.Where(e => e.Message.StartsWith("Application: Elwig.exe"))
.ToList();
} }
public static int GetEntityIdetifierForPk(params object?[] primaryKey) { public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
@@ -675,9 +659,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)[..19].CompareTo(dt) <= 0) .Where(n => Path.GetFileName(n).CompareTo(dt) <= 0)
.Order() .Order()
.LastOrDefault(); .LastOrDefault();
if (filename == null) if (filename == null)

View File

@@ -15,17 +15,15 @@ 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? log = null, bool required = true) : public AveryEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
base(cnx, null, null, null, log, true, !required) { base(cnx, empty, filling, limit, log) {
ScaleId = id; ScaleId = id;
Model = model; Model = model;
Connection = cnx;
IsReady = true; IsReady = true;
HasFillingClearance = false; HasFillingClearance = false;
Stream.WriteTimeout = -1; Stream.WriteTimeout = -1;
@@ -52,49 +50,19 @@ 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} ({ex.GetType().Name})", "Waagenfehler", MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "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) {
throw new IOException("Connection closed"); return null;
} else if (line.Length > 0 || ch == ' ') { } else if (line.Length > 0 || ch == ' ') {
line += char.ToString((char)ch); line += char.ToString((char)ch);
} }
@@ -103,7 +71,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 FormatException($"Invalid event from scale: '{line}'"); throw new IOException($"Invalid event from scale: '{line}'");
} }
var date = line[ 1.. 9]; var date = line[ 1.. 9];
@@ -113,7 +81,7 @@ namespace Elwig.Helpers.Weighing {
var unit = line[30..32]; var unit = line[30..32];
if (unit != "kg") { if (unit != "kg") {
throw new WeighingException($"Unsupported unit in weighing event: '{unit}'"); throw new IOException($"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 FormatException("Invalid response from scale"); throw new IOException("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 WeighingException($"Waagenfehler {status}: {msg}"); throw new IOException($"Waagenfehler {status}: {msg}");
} else if (status[0] != ' ') { } else if (status[0] != ' ') {
throw new WeighingException($"Invalid response from scale (error code {status})"); throw new IOException($"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 FormatException("Invalid response from scale: Received record has invalid size"); throw new IOException("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,9 +1,8 @@
using System;
using System.IO;
using System.IO.Ports; using System.IO.Ports;
using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using System;
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 {
@@ -28,7 +27,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.Log, config.Required); return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} 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 {
@@ -36,17 +35,10 @@ namespace Elwig.Helpers.Weighing {
} }
} }
protected Scale(string cnx, string? empty, string? filling, int? limit, string? log, bool softFail = false, bool failSilent = false) { protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) {
if (cnx.StartsWith("serial:")) { if (cnx.StartsWith("serial:")) {
try {
Serial = Utils.OpenSerialConnection(cnx); Serial = Utils.OpenSerialConnection(cnx);
} catch (Exception e) { Stream = Serial.BaseStream;
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 FormatException("Invalid response from scale"); throw new IOException("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 WeighingException($"Invalid response from scale (error code {error})"); throw new IOException($"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 WeighingException($"Waagenfehler {error}: {msg}"); throw new IOException($"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 WeighingException($"Fehler {error}: {msg}"); throw new IOException($"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 WeighingException($"Kommunikationsfehler {error}: {msg}"); throw new IOException($"Kommunikationsfehler {error}: {msg}");
} else { } else {
throw new WeighingException($"Invalid response from scale (error code {error})"); throw new IOException($"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 FormatException("Invalid response from scale: Received record has invalid size"); throw new IOException("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 WeighingException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})"); throw new IOException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})");
} else if (unit != "kg") { } else if (unit != "kg") {
throw new WeighingException($"Unsupported unit in weighing response: '{unit}'"); throw new IOException($"Unsupported unit in weighing response: '{unit}'");
} }
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null; identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;

View File

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

View File

@@ -1,59 +0,0 @@
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<MemberDeliveryPerVarietyRow> { public class MemberDeliveryPerVarietyData : DataTable<MemberDeliveryPerVariantRow> {
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,63 +16,104 @@ 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<MemberDeliveryPerVarietyRow> rows, List<string> filterNames) : public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
base("Liefermengen pro Sorte", "Liefermengen pro Mitglied, Sorte, Attribut und Bewirtschaftungsart", string.Join(" / ", filterNames), rows, FieldNames) { base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) {
} }
public static async Task<MemberDeliveryPerVarietyData> FromQuery(IQueryable<DeliveryPart> query, List<string> filterNames) { public static async Task<MemberDeliveryPerVarietyData> ForSeason(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) {
return new((await query return new MemberDeliveryPerVarietyData(
.GroupBy(p => new { (await FromDbSet(table, year)).GroupBy(
p.Delivery.MgNr, r => r.MgNr,
p.Delivery.Member.Name, (k, g) => new MemberDeliveryPerVariantRow(g)
p.Delivery.Member.GivenName, ), year);
p.Delivery.Member.Address, }
p.Delivery.Member.PostalDest.AtPlz!.Plz,
Ort = p.Delivery.Member.PostalDest.AtPlz!.Ort.Name, private static async Task<IEnumerable<MemberDeliveryPerVariantRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) {
p.SortId, return await table.FromSql($"""
p.AttrId, SELECT m.mgnr, m.name AS name_1,
p.CultId, COALESCE(m.prefix || ' ', '') || m.given_name ||
}) COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
.Select(g => new { p.plz, o.name AS ort, m.address,
g.Key, v.bucket, v.weight, v.area
Weight = g.Sum(p => p.Weight), FROM (
}) SELECT c.year AS year,
.ToListAsync()).GroupBy(g => new { c.mgnr AS mgnr,
g.Key.MgNr, c.bucket AS bucket,
g.Key.Name, COALESCE(d.weight, 0) AS weight,
g.Key.GivenName, COALESCE(c.area, 0) AS area
g.Key.Address, FROM v_area_commitment_bucket_strict c
g.Key.Plz, LEFT JOIN v_delivery_bucket_strict d ON (d.year, d.mgnr, d.bucket) = (c.year, c.mgnr, c.bucket)
g.Key.Ort, WHERE c.year = {year}
}).Select(g => new MemberDeliveryPerVarietyRow { UNION
MgNr = g.Key.MgNr, SELECT d.year,
Name1 = g.Key.Name, d.mgnr,
Name2 = g.Key.GivenName, d.bucket,
Address = g.Key.Address, COALESCE(d.weight, 0),
Plz = g.Key.Plz, COALESCE(c.area, 0)
Locality = g.Key.Ort, FROM v_delivery_bucket_strict d
SortIds = [.. g.Select(d => d.Key.SortId)], LEFT JOIN v_area_commitment_bucket_strict c ON (c.year, c.mgnr, c.bucket) = (d.year, d.mgnr, d.bucket)
AttrIds = [.. g.Select(d => d.Key.AttrId)], WHERE d.year = {year}
CultIds = [.. g.Select(d => d.Key.CultId)], ) v
Weights = [.. g.Select(d => d.Weight)], LEFT JOIN member m ON m.mgnr = v.mgnr
}), filterNames); 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 MemberDeliveryPerVarietyRow { public class MemberDeliveryPerVariantRow {
public required int MgNr; public int MgNr;
public required string Name1; public string Name1;
public required string? Name2; public string? Name2;
public required string Address; public string Address;
public required int Plz; public int Plz;
public required string Locality; public string Locality;
public required string[] SortIds; public string[] SortIds;
public required string?[] AttrIds; public string[] AttrIds;
public required string?[] CultIds; public int[] Areas;
public required int[] Weights; public 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

@@ -1,119 +0,0 @@
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

@@ -1,5 +1,4 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -63,11 +62,6 @@ namespace Elwig.Models.Entities {
[Column("comment")] [Column("comment")]
public string? Comment { get; set; } public string? Comment { get; set; }
[NotMapped]
public string[] Comments => [.. Parts.Select(p => p.Comment).Prepend(Comment).Where(c => c != null).Cast<string>()];
[NotMapped]
public string CommentsString => string.Join(" / ", Comments);
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)] [Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; } public long CTime { get; set; }
[NotMapped] [NotMapped]
@@ -114,16 +108,16 @@ namespace Elwig.Models.Entities {
public int Weight => Parts.Select(p => p.Weight).Sum(); public int Weight => Parts.Select(p => p.Weight).Sum();
public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum(); public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
public IEnumerable<RawVaribute> Vaributes => Parts public IEnumerable<string> SortIds => Parts
.GroupBy(p => (p.SortId, p.AttrId, p.CultId)) .GroupBy(p => p.SortId)
.OrderByDescending(g => g.Select(p => p.Weight).Sum()) .OrderByDescending(g => g.Select(p => p.Weight).Sum())
.Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId)); .Select(g => g.Key);
public IEnumerable<RawVaribute> FilteredVaributes => FilteredParts public IEnumerable<string> FilteredSortIds => FilteredParts
.GroupBy(p => (p.SortId, p.AttrId, p.CultId)) .GroupBy(p => p.SortId)
.OrderByDescending(g => g.Select(p => p.Weight).Sum()) .OrderByDescending(g => g.Select(p => p.Weight).Sum())
.Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId)); .Select(g => g.Key);
public string VaributeString => string.Join(", ", Vaributes); public string SortIdString => string.Join(", ", SortIds);
public string FilteredVaributeString => string.Join(", ", FilteredVaributes); public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
public Brush? Color => Parts.Select(p => p.Variety.Color).Distinct().SingleOrDefault(); public Brush? Color => Parts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
public Brush? FilteredColor => FilteredParts.Select(p => p.Variety.Color).Distinct().SingleOrDefault(); public Brush? FilteredColor => FilteredParts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();

View File

@@ -9,11 +9,6 @@ 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; }
@@ -91,27 +86,12 @@ 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

@@ -42,9 +42,7 @@ namespace Elwig.Models.Entities {
[Column("max_weight")] [Column("max_weight")]
public int? MaxWeight { get; set; } public int? MaxWeight { get; set; }
[NotMapped] [NotMapped]
public int AnnouncedWeight => AnnouncedWeightOverride ?? Announcements.Sum(a => a.Weight); public int AnnouncedWeight => Announcements.Sum(a => a.Weight);
[NotMapped]
public int? AnnouncedWeightOverride { get; set; }
[NotMapped] [NotMapped]
public double? Percent => (double)AnnouncedWeight / MaxWeight * 100; public double? Percent => (double)AnnouncedWeight / MaxWeight * 100;

View File

@@ -16,9 +16,6 @@ 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; }
@@ -49,8 +46,6 @@ 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

@@ -11,24 +11,16 @@ namespace Elwig.Models.Entities {
[Column("type")] [Column("type")]
public string Type { get; private set; } = null!; public string Type { get; private set; } = null!;
[Column("max_qualid")]
public string MaxQualId { get; private set; } = null!;
[ForeignKey("MaxQualId")]
public virtual WineQualLevel MaxQualityLevel { get; private set; } = null!;
[Column("name")] [Column("name")]
public string Name { get; private set; } = null!; public string Name { get; private set; } = null!;
[Column("comment")] [Column("comment")]
public string? Comment { get; private set; } public string? Comment { get; private set; }
public string SortIdFormat => IsQuw ? SortId : $"({SortId})";
public string CommentFormat => (Comment != null) ? $" ({Comment})" : ""; public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
public bool IsRed => Type == "R"; public bool IsRed => Type == "R";
public bool IsWhite => Type == "W"; public bool IsWhite => Type == "W";
public bool IsQuw => MaxQualId == "QUW";
public Brush? Color => IsWhite ? Brushes.DarkGreen : IsRed ? Brushes.DarkRed : null; public Brush? Color => IsWhite ? Brushes.DarkGreen : IsRed ? Brushes.DarkRed : null;
public WineVar() { } public WineVar() { }
@@ -36,7 +28,6 @@ namespace Elwig.Models.Entities {
public WineVar(string sortId, string name) { public WineVar(string sortId, string name) {
SortId = sortId; SortId = sortId;
Name = name; Name = name;
MaxQualId = "QUW";
} }
public override string ToString() { public override string ToString() {

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>net10.0-windows</TargetFramework> <TargetFramework>net8.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

@@ -1,17 +0,0 @@
-- schema version 32 to 33
ALTER TABLE wine_variety ADD COLUMN max_qualid TEXT NOT NULL DEFAULT 'QUW';
UPDATE wine_quality_level SET qualid = 'ALW' WHERE qualid = 'AUL';
UPDATE wine_variety SET comment = 'Muscato' WHERE sortid = 'MO';
INSERT INTO wine_variety (sortid, type, max_qualid, name, comment) VALUES
('DR', 'W', 'QUW', 'Donauriesling', NULL),
('DV', 'W', 'QUW', 'Donauveltliner', NULL),
('BN', 'W', 'RSW', 'Bronner', NULL),
('CB', 'W', 'RSW', 'Cabernet Blanc', NULL),
('CJ', 'R', 'RSW', 'Cabernet Jura', NULL),
('JO', 'W', 'RSW', 'Johanniter', NULL),
('OR', 'W', 'RSW', 'Orangetraube', NULL),
('PI', 'R', 'RSW', 'Pinot Nova', NULL),
('RE', 'R', 'RSW', 'Regent', NULL),
('SI', 'W', 'RSW', 'Solaris', NULL);

View File

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

View File

@@ -1,6 +0,0 @@
-- 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

@@ -1,19 +0,0 @@
-- 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,8 +15,10 @@ 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 {
@@ -78,9 +80,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;
@@ -186,54 +188,14 @@ 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.Unloading == DeliveryPart.Pumped); prd = prd.And(p => p.IsLesewagen == true);
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.Unloading != DeliveryPart.Pumped); prd = prd.And(p => p.IsLesewagen == false);
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--);
@@ -521,8 +483,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,
@@ -609,13 +571,6 @@ 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,
}));
} }
} }
@@ -653,13 +608,6 @@ 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,
}));
} }
} }
@@ -694,13 +642,6 @@ 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();
@@ -780,11 +721,9 @@ namespace Elwig.Services {
FileName = subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}.elwig.zip" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip", FileName = subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}.elwig.zip" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
DefaultExt = "elwig.zip", DefaultExt = "elwig.zip",
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip", Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
Title = $"{DeliveryJournal.Name} speichern unter - Elwig", Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
AddExtension = false,
}; };
if (d.ShowDialog() == true) { if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { try {
@@ -792,7 +731,6 @@ namespace Elwig.Services {
.Select(p => p.Delivery) .Select(p => p.Delivery)
.Distinct() .Distinct()
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers) .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) .Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
.AsSplitQuery() .AsSplitQuery()
.ToListAsync(); .ToListAsync();
@@ -813,7 +751,39 @@ 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 () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, 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)
.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 {
@@ -964,52 +934,6 @@ 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,7 +9,9 @@ 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;
@@ -313,22 +315,6 @@ 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--);
@@ -507,42 +493,14 @@ namespace Elwig.Services {
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
} else if (mode == ExportMode.Vcf) {
var d = new SaveFileDialog() {
FileName = "Mitglieder.vcf",
DefaultExt = "vcf",
Filter = "vCard-Datei (*.vcf)|*.vcf",
Title = "Kontakte speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var members = await query
.OrderBy(m => m.MgNr)
.Include(m => m.BillingAddress)
.Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses)
.AsSplitQuery()
.ToListAsync();
using var exporter = new VCard(d.FileName);
await exporter.ExportAsync(members);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Export) { } else if (mode == ExportMode.Export) {
var d = new SaveFileDialog() { var d = new SaveFileDialog() {
FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip", FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
DefaultExt = "elwig.zip", DefaultExt = ".elwig.zip",
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip", Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
Title = $"{MemberList.Name} speichern unter - Elwig", Title = $"{MemberList.Name} speichern unter - Elwig"
AddExtension = false,
}; };
if (d.ShowDialog() == true) { if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { try {
@@ -576,7 +534,46 @@ 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 () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, 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 {
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

@@ -1,277 +0,0 @@
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,28 +169,10 @@ 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

@@ -1,32 +0,0 @@
<Window x:Class="Elwig.Windows.AboutWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Windows"
Title="Über - Elwig" Height="340" Width="460" ResizeMode="NoResize">
<Grid>
<TextBlock Margin="20,10" FontSize="12">
<Bold>Produkt:</Bold> Elwig<LineBreak/>
<Bold>Beschreibung:</Bold> Elektronische Winzergenossenschaftsverwaltung<LineBreak/>
<Bold>Typ:</Bold> Warenwirtschaftssystem (ERP-System)<LineBreak/>
<Bold>Version:</Bold> <Run x:Name="Version">0.0.0.0</Run> (<Hyperlink NavigateUri="https://elwig.at/changelog" RequestNavigate="Hyperlink_RequestNavigate">Änderungsprotokoll</Hyperlink>)<LineBreak/>
<Bold>Lizenz:</Bold> <Hyperlink NavigateUri="https://www.gnu.org/licenses/gpl-3.0.html" RequestNavigate="Hyperlink_RequestNavigate">GNU General Public License 3.0 (GPLv3)</Hyperlink><LineBreak/>
<Bold>Website:</Bold> <Hyperlink NavigateUri="https://elwig.at/" RequestNavigate="Hyperlink_RequestNavigate">https://elwig.at/</Hyperlink><LineBreak/>
<Bold>Entwickler:</Bold> Lorenz Stechauner, Thomas Hilscher<LineBreak/>
<Bold>Kontakt:</Bold> <Hyperlink NavigateUri="mailto:lorenz.stechauner@necronda.net" RequestNavigate="Hyperlink_RequestNavigate">lorenz.stechauner@necronda.net</Hyperlink>, <Hyperlink NavigateUri="mailto:thomas.hilscher@gmail.com" RequestNavigate="Hyperlink_RequestNavigate">thomas.hilscher@gmail.com</Hyperlink><LineBreak/>
<Bold>Quellcode:</Bold> <Hyperlink NavigateUri="https://git.necronda.net/winzer/elwig" RequestNavigate="Hyperlink_RequestNavigate">https://git.necronda.net/winzer/elwig</Hyperlink><LineBreak/>
<Bold>Entwicklungszeitraum:</Bold> 20222025<LineBreak/>
<LineBreak/>
<Bold>Verwendete Technologien:</Bold><LineBreak/>
Programmiersprache: C#<LineBreak/>
Framework: Windows Presentation Framework (WPF)<LineBreak/>
Datenbank: <Hyperlink NavigateUri="https://sqlite.org/" RequestNavigate="Hyperlink_RequestNavigate">SQLite</Hyperlink><LineBreak/>
PDF-Erstellung: <Hyperlink NavigateUri="https://weasyprint.org/" RequestNavigate="Hyperlink_RequestNavigate">WeasyPrint</Hyperlink>, <Hyperlink NavigateUri="https://github.com/toddams/RazorLight" RequestNavigate="Hyperlink_RequestNavigate">RazorLight</Hyperlink>, <Hyperlink NavigateUri="https://github.com/pvginkel/PdfiumViewer" RequestNavigate="Hyperlink_RequestNavigate">PdfiumViewer</Hyperlink><LineBreak/>
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
</TextBlock>
<Image Source="\Resources\Images\Elwig.png" RenderOptions.BitmapScalingMode="HighQuality" Height="64"
HorizontalAlignment="Right" Margin="10" VerticalAlignment="Top"/>
</Grid>
</Window>

View File

@@ -1,17 +0,0 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Navigation;
namespace Elwig.Windows {
public partial class AboutWindow : Window {
public AboutWindow() {
InitializeComponent();
Version.Text = App.Version.ToString();
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) {
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });
}
}
}

View File

@@ -13,8 +13,8 @@ using System.Windows.Input;
namespace Elwig.Windows { namespace Elwig.Windows {
public abstract class AdministrationWindow : ContextWindow { public abstract class AdministrationWindow : ContextWindow {
protected Control[] ExemptInputs { get; set; } protected Control[] ExemptInputs { private get; set; }
protected Control[] RequiredInputs { get; set; } protected Control[] RequiredInputs { private get; set; }
private bool _isEditing; private bool _isEditing;
private bool _isCreating; private bool _isCreating;
@@ -166,10 +166,8 @@ namespace Elwig.Windows {
Valid[input] = false; Valid[input] = false;
} else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) { } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) {
ControlUtils.SetInputInvalid(input); ControlUtils.SetInputInvalid(input);
Valid[input] = false;
} else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) { } else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) {
ControlUtils.SetInputInvalid(input); ControlUtils.SetInputInvalid(input);
Valid[input] = false;
} else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) { } else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) {
ControlUtils.SetInputInvalid(input); ControlUtils.SetInputInvalid(input);
Valid[input] = false; Valid[input] = false;

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="520" MinHeight="400" Width="860" MinWidth="810" Title="Stammdaten - Elwig" Height="500" MinHeight="400" Width="850" MinWidth="810"
Loaded="Window_Loaded"> Loaded="Window_Loaded">
<Window.Resources> <Window.Resources>
<Style TargetType="Label"> <Style TargetType="Label">
@@ -532,10 +532,6 @@
<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,14 +135,12 @@ 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;
} }
@@ -159,7 +157,6 @@ 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

@@ -664,10 +664,8 @@ namespace Elwig.Windows {
try { try {
await Task.Run(async () => { await Task.Run(async () => {
var b = new BillingVariant(PaymentVar.Year, PaymentVar.AvNr); var b = new BillingVariant(PaymentVar.Year, PaymentVar.AvNr);
await b.Calculate(false); await b.Calculate();
}); });
} catch (KeyNotFoundException exc) {
MessageBox.Show(exc.Message, "Noch nicht alle Preise festgelegt", MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error);
} }

View File

@@ -129,10 +129,6 @@
<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)"
@@ -226,7 +222,6 @@
<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")
@@ -270,18 +265,13 @@
</Style> </Style>
</DataGridTextColumn.CellStyle> </DataGridTextColumn.CellStyle>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="Sorte" Binding="{Binding FilteredVaributeString}" Width="60"> <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
<DataGridTextColumn.CellStyle> <DataGridTextColumn.CellStyle>
<Style> <Style>
<Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/> <Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/>
<Setter Property="TextBlock.TextAlignment" Value="Center"/> <Setter Property="TextBlock.TextAlignment" Value="Center"/>
</Style> </Style>
</DataGridTextColumn.CellStyle> </DataGridTextColumn.CellStyle>
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75"> <DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
<DataGridTextColumn.CellStyle> <DataGridTextColumn.CellStyle>
@@ -300,7 +290,6 @@
<DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/> <DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/>
<DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/> <DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/>
<DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/> <DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/>
<DataGridTextColumn Header="Kommentar" Binding="{Binding CommentsString}" Width="150"/>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
@@ -374,9 +363,8 @@
<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="0.875*"/> <RowDefinition Height="*"/>
<RowDefinition Height="0.875*"/> <RowDefinition Height="*"/>
<RowDefinition Height="0.25*"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -410,7 +398,7 @@
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Grid.RowSpan="3" Margin="5,5,5,5"> <GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Margin="5,5,5,5">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/>
@@ -569,7 +557,7 @@
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Sonstiges" Grid.Column="1" Grid.Row="3" Grid.RowSpan="2" Margin="5,5,5,10"> <GroupBox Header="Sonstiges" Grid.Column="1" Grid.Row="3" Margin="5,5,5,10">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/>
@@ -598,31 +586,18 @@
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,135,10,0" Grid.Column="0" Grid.ColumnSpan="2" IsThreeState="True" Content="Handlese" Margin="10,105,0,0" Grid.Column="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="4" Margin="5,5,5,10"> <GroupBox Header="Herkunft" Grid.Column="0" Grid.Row="3" Margin="5,5,5,10">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/>

View File

@@ -8,7 +8,6 @@ using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@@ -35,8 +34,6 @@ namespace Elwig.Windows {
private readonly Button[] WeighingButtons; private readonly Button[] WeighingButtons;
private List<WineQualLevel> WineQualityLevels = [];
public DeliveryAdminWindow(bool receipt = false) { public DeliveryAdminWindow(bool receipt = false) {
InitializeComponent(); InitializeComponent();
CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput)); CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
@@ -92,10 +89,6 @@ namespace Elwig.Windows {
foreach (var s in App.EventScales) { foreach (var s in App.EventScales) {
s.WeighingEvent += Scale_Weighing; s.WeighingEvent += Scale_Weighing;
} }
if (App.Client.IsMatzen) {
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;
@@ -135,7 +128,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.\n\n(Stammdaten -> Saisons -> Neu anlegen...)", MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.",
"Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning); "Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning);
} }
} }
@@ -260,7 +253,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) =>
@@ -280,15 +273,11 @@ 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;
TimeInput.IsReadOnly = false; TimeInput.IsReadOnly = false;
BranchInput.IsEnabled = true; BranchInput.IsEnabled = true;
GerebeltGewogenInput.IsEnabled = true;
if (IsCreating) ViewModel.Time = ""; if (IsCreating) ViewModel.Time = "";
OnSecondPassed(null, null); OnSecondPassed(null, null);
} }
@@ -298,7 +287,6 @@ namespace Elwig.Windows {
DateInput.IsReadOnly = true; DateInput.IsReadOnly = true;
TimeInput.IsReadOnly = true; TimeInput.IsReadOnly = true;
BranchInput.IsEnabled = false; BranchInput.IsEnabled = false;
GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode != WeighingMode.Net;
OnSecondPassed(null, null); OnSecondPassed(null, null);
} }
@@ -313,7 +301,7 @@ namespace Elwig.Windows {
} }
private void InitialDefaultInputs() { private void InitialDefaultInputs() {
if (App.Config.WeighingMode == WeighingMode.Net) { if (App.Client.HasNetWeighing(ViewModel.Branch)) {
GerebeltGewogenInput.IsEnabled = false; GerebeltGewogenInput.IsEnabled = false;
SetDefaultValue(GerebeltGewogenInput, true); SetDefaultValue(GerebeltGewogenInput, true);
} else { } else {
@@ -322,20 +310,15 @@ namespace Elwig.Windows {
UnsetDefaultValue(GerebeltGewogenInput); UnsetDefaultValue(GerebeltGewogenInput);
} }
if (App.Config.WeighingMode == WeighingMode.Box) { if (App.Client.HasBoxWeighing(ViewModel.Branch)) {
SetDefaultValue(UnloadingDumperInput, false); LesewagenInput.IsEnabled = false;
SetDefaultValue(UnloadingPumpedInput, false); SetDefaultValue(LesewagenInput, false);
SetDefaultValue(UnloadingBoxInput, true);
SetDefaultValue(UnloadingOtherInput, false);
UnloadingBoxInput.IsChecked = true;
} else { } else {
UnsetDefaultValue(UnloadingDumperInput); LesewagenInput.IsEnabled = true;
UnsetDefaultValue(UnloadingPumpedInput); UnsetDefaultValue(LesewagenInput);
UnsetDefaultValue(UnloadingBoxInput);
UnsetDefaultValue(UnloadingOtherInput);
} }
if (App.Config.WeighingMode != WeighingMode.Net) { if (!App.Client.HasNetWeighing(ViewModel.Branch)) {
HandPickedInput.IsThreeState = false; HandPickedInput.IsThreeState = false;
UnsetDefaultValue(HandPickedInput); UnsetDefaultValue(HandPickedInput);
} else { } else {
@@ -361,13 +344,13 @@ namespace Elwig.Windows {
ClearOriginalValues(); ClearOriginalValues();
ClearDefaultValues(); ClearDefaultValues();
ViewModel.IsNetWeight = App.Config.WeighingMode == WeighingMode.Net; ViewModel.IsNetWeight = App.Client.HasNetWeighing(ViewModel.Branch);
ViewModel.IsHandPicked = App.Config.WeighingMode != WeighingMode.Net ? true : null; ViewModel.IsLesewagen = false;
ViewModel.Unloading = null; ViewModel.IsHandPicked = !App.Client.HasNetWeighing(ViewModel.Branch) ? true : null;
ViewModel.IsGebunden = null; ViewModel.IsGebunden = null;
InitialDefaultInputs(); InitialDefaultInputs();
//WineQualityLevelInput.IsEnabled = false; // disable wine quality level input in Übernahme WineQualityLevelInput.IsEnabled = false;
ValidateRequiredInputs(); ValidateRequiredInputs();
} }
@@ -495,8 +478,7 @@ namespace Elwig.Windows {
var cultList = await ctx.WineCultivations.OrderBy(a => a.Name).Cast<object>().ToListAsync(); var cultList = await ctx.WineCultivations.OrderBy(a => a.Name).Cast<object>().ToListAsync();
cultList.Insert(0, new NullItem("")); cultList.Insert(0, new NullItem(""));
ControlUtils.RenewItemsSource(CultivationInput, cultList, null, ControlUtils.RenewSourceDefault.First); ControlUtils.RenewItemsSource(CultivationInput, cultList, null, ControlUtils.RenewSourceDefault.First);
WineQualityLevels = await ctx.WineQualityLevels.ToListAsync(); ControlUtils.RenewItemsSource(WineQualityLevelInput, await ctx.WineQualityLevels.ToListAsync());
ControlUtils.RenewItemsSource(WineQualityLevelInput, WineQualityLevels);
ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers
.Where(m => m.Year == y && (!IsCreating || m.IsActive)) .Where(m => m.Year == y && (!IsCreating || m.IsActive))
.OrderBy(m => m.Ordering) .OrderBy(m => m.Ordering)
@@ -1157,7 +1139,6 @@ namespace Elwig.Windows {
DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked; DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked; TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked; BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked;
GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode == WeighingMode.Net || Menu_Settings_EnableFreeEditing.IsChecked;
} }
private void DisableWeighingButtons() { private void DisableWeighingButtons() {
@@ -1177,19 +1158,6 @@ namespace Elwig.Windows {
WeighingDButton.IsEnabled = n > 3 && App.CommandScales[3].IsReady; WeighingDButton.IsEnabled = n > 3 && App.CommandScales[3].IsReady;
} }
public void UpdateScales() {
if (!ViewModel.IsReceipt) return;
foreach (var s in App.EventScales) {
s.WeighingEvent -= Scale_Weighing;
s.WeighingEvent += Scale_Weighing;
}
if (WeighingManualButton.IsEnabled) {
EnableWeighingButtons();
} else {
DisableWeighingButtons();
}
}
private async Task UpdateLsNr() { private async Task UpdateLsNr() {
if (string.IsNullOrEmpty(ViewModel.Date) || ViewModel.Branch == null) { if (string.IsNullOrEmpty(ViewModel.Date) || ViewModel.Branch == null) {
ViewModel.LsNr = ""; ViewModel.LsNr = "";
@@ -1233,7 +1201,6 @@ namespace Elwig.Windows {
AttributeInput.SelectedIndex = 0; AttributeInput.SelectedIndex = 0;
CultivationInput.SelectedIndex = 0; CultivationInput.SelectedIndex = 0;
} }
UpdateWineQualityLevels();
} }
private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) { private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) {
@@ -1247,39 +1214,19 @@ namespace Elwig.Windows {
private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) { private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
if (WineVarietyInput.SelectedItem is WineVar s) if (WineVarietyInput.SelectedItem is WineVar s)
ViewModel.SortId = s.SortId; ViewModel.SortId = s.SortId;
UpdateWineQualityLevels();
if (!ViewModel.WineVar?.IsQuw ?? false) {
App.MainDispatcher.BeginInvoke(() => {
MessageBox.Show("Die eingegebene Sorte darf nicht als Qualitätswein\nübernommen werden!", "Kein Qualitätswein",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
}
}
private WineQualLevel GetWineQualityLevel(double kmw, string? maxQualId = null) {
return WineQualityLevels
.Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
.Where(q => maxQualId == null || q.QualId == "WEI" || q.QualId == maxQualId)
.OrderBy(q => q.MinKmw)
.Last();
} }
private void UpdateWineQualityLevels() { private void UpdateWineQualityLevels() {
using var ctx = new AppDbContext();
if (!GetInputValid(GradationKmwInput)) { if (!GetInputValid(GradationKmwInput)) {
UnsetDefaultValue(WineQualityLevelInput); UnsetDefaultValue(WineQualityLevelInput);
ComboBox_SelectionChanged(WineQualityLevelInput, null); ComboBox_SelectionChanged(WineQualityLevelInput, null);
WineQualityLevelInput.ItemsSource = WineQualityLevels; WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.QualId == "WEI").ToList();
WineQualityLevelInput.SelectedItem = null;
return; return;
} }
var kmw = (double)ViewModel.GradationKmw!; var kmw = (double)ViewModel.GradationKmw!;
var max = ViewModel.WineVar?.MaxQualId; WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.MinKmw == null || q.MinKmw <= kmw).ToList();
var quw = ViewModel.WineVar?.IsQuw ?? true; var qual = ctx.GetWineQualityLevel(kmw).GetAwaiter().GetResult();
WineQualityLevelInput.ItemsSource = WineQualityLevels
.Where(q => q.MinKmw == null || q.MinKmw <= kmw)
.Where(q => quw || q.QualId == "WEI" || q.QualId == max)
.ToList();
var qual = GetWineQualityLevel(kmw, !quw ? max : null);
SetDefaultValue(WineQualityLevelInput, qual); SetDefaultValue(WineQualityLevelInput, qual);
if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) { if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) {
ControlUtils.SelectItem(WineQualityLevelInput, qual); ControlUtils.SelectItem(WineQualityLevelInput, qual);
@@ -1333,73 +1280,42 @@ 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 = ViewModel.Modifiers.ToList(); var mod = ModifiersInput.SelectedItems.Cast<Modifier>().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))
ViewModel.Modifiers.Remove(r); ModifiersInput.SelectedItems.Remove(r);
}); });
} }
} else if (App.Client.IsWinzerkeller) { } else if (App.Client.IsWinzerkeller) {
if (source.Any(m => m.Name.Contains("Lesewagen"))) {
if (mod.Any(m => m.Name.Contains("Lesewagen"))) { if (mod.Any(m => m.Name.Contains("Lesewagen"))) {
ViewModel.IsUnloadingPumped = true; ViewModel.IsLesewagen = true;
} else { } else {
ViewModel.IsUnloadingPumped = false; ViewModel.IsLesewagen = 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 UnloadingInput_Checked(object sender, RoutedEventArgs evt) { private void LesewagenInput_Changed(object sender, RoutedEventArgs evt) {
if (!IsEditing && !IsCreating) return; if (!IsEditing && !IsCreating) return;
var mod = ViewModel.Modifiers.ToList(); var mod = ModifiersInput.SelectedItems.Cast<Modifier>().ToList();
var source = ViewModel.ModifiersSource.ToList(); var source = ModifiersInput.ItemsSource.Cast<Modifier>().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("_")[0]; var kl = mod.Where(m => m.Name.StartsWith("Klasse ")).Select(m => m.ModId).LastOrDefault("A")[0];
if (ViewModel.IsUnloadingPumped && (kl == 'A' || kl == '_')) { if (lw) kl++; else kl--;
kl = 'B'; var newKl = source.FirstOrDefault(m => m.ModId == kl.ToString());
} else if (ViewModel.IsUnloadingDumper && kl == '_') { if (newKl != null) ModifiersInput.SelectedItems.Add(newKl);
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 (source.Any(m => m.Name.Contains("Lesewagen"))) { if (lw && !mod.Any(m => m.Name.Contains("Lesewagen"))) {
if (ViewModel.IsUnloadingPumped && !mod.Any(m => m.Name.Contains("Lesewagen"))) { ModifiersInput.SelectedItems.Add(source.First(m => m.Name.Contains("Lesewagen")));
ViewModel.Modifiers.Add(source.First(m => m.Name.Contains("Lesewagen"))); } else if (!lw && mod.Any(m => m.Name.Contains("Lesewagen"))) {
} else if (!ViewModel.IsUnloadingPumped && mod.Any(m => m.Name.Contains("Lesewagen"))) { ModifiersInput.SelectedItems.Remove(mod.First(m => m.Name.Contains("Lesewagen")));
ViewModel.Modifiers.Remove(mod.First(m => m.Name.Contains("Lesewagen")));
} }
} }
} else if (App.Client.IsBaden) { CheckBox_Changed(sender, evt);
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")));
}
}
}
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() {
@@ -1450,7 +1366,8 @@ namespace Elwig.Windows {
AbgewertetInput.IsChecked = false; AbgewertetInput.IsChecked = false;
return; return;
} }
var defQual = GetWineQualityLevel(ViewModel.GradationKmw!.Value, !(ViewModel.WineVar?.IsQuw ?? true) ? ViewModel.WineVar?.MaxQualId : null); using var ctx = new AppDbContext();
var defQual = ctx.GetWineQualityLevel(double.Parse(GradationKmwInput.Text)).GetAwaiter().GetResult();
AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual; AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
} }
@@ -1472,17 +1389,17 @@ namespace Elwig.Windows {
} }
private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) { private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) { if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked; HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
} }
if (!ViewModel.IsReceipt || App.Config.WeighingMode == WeighingMode.Net) { if (!ViewModel.IsReceipt || App.Client.HasNetWeighing(ViewModel.Branch)) {
GerebeltGewogenInput.IsChecked ??= false; GerebeltGewogenInput.IsChecked ??= false;
} }
CheckBox_Changed(sender, evt); CheckBox_Changed(sender, evt);
} }
private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) { private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) { if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked; GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
} }
CheckBox_Changed(sender, evt); CheckBox_Changed(sender, evt);

View File

@@ -83,19 +83,14 @@ namespace Elwig.Windows {
private async Task RefreshDeliveryScheduleList() { private async Task RefreshDeliveryScheduleList() {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var list = await ctx.DeliverySchedules var deliverySchedules = await ctx.DeliverySchedules
.Where(s => s.Year == ViewModel.FilterSeason) .Where(s => s.Year == ViewModel.FilterSeason)
.Include(s => s.Branch) .Include(s => s.Branch)
.Include(s => s.Announcements)
.OrderBy(s => s.DateString) .OrderBy(s => s.DateString)
.ThenBy(s => s.Branch.Name) .ThenBy(s => s.Branch.Name)
.ThenBy(s => s.Description) .ThenBy(s => s.Description)
.Select(s => new {
Schedule = s,
AnnouncedWeight = s.Announcements.Sum(a => a.Weight)
})
.ToListAsync(); .ToListAsync();
list.ForEach(v => v.Schedule.AnnouncedWeightOverride = v.AnnouncedWeight);
var deliverySchedules = list.Select(v => v.Schedule).ToList();
ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules
.Where(s => !ViewModel.FilterOnlyUpcoming || s.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0) .Where(s => !ViewModel.FilterOnlyUpcoming || s.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0)
.ToList(), DeliveryScheduleList_SelectionChanged, ViewModel.FilterFromAllSchedules ? ControlUtils.RenewSourceDefault.None : ControlUtils.RenewSourceDefault.First); .ToList(), DeliveryScheduleList_SelectionChanged, ViewModel.FilterFromAllSchedules ? ControlUtils.RenewSourceDefault.None : ControlUtils.RenewSourceDefault.First);

View File

@@ -1,9 +1,7 @@
using Elwig.Helpers; using Elwig.Helpers;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input;
namespace Elwig.Windows { namespace Elwig.Windows {
public partial class LogWindow : Window { public partial class LogWindow : Window {
@@ -13,10 +11,9 @@ namespace Elwig.Windows {
WindowState = WindowState.Maximized; WindowState = WindowState.Maximized;
} }
private async void Window_Loaded(object sender, RoutedEventArgs evt) { private void Window_Loaded(object sender, RoutedEventArgs evt) {
Mouse.OverrideCursor = Cursors.Wait; var log = Utils.GetLogEntries();
await Task.Run(async () => { EventList.ItemsSource = log
var list = Utils.GetLogEntries()
.Select(e => new { .Select(e => new {
Event = e, Event = e,
Lines = e.Message.Split('\n').ToArray(), Lines = e.Message.Split('\n').ToArray(),
@@ -35,12 +32,7 @@ namespace Elwig.Windows {
}) })
.OrderByDescending(e => e.Event.TimeGenerated) .OrderByDescending(e => e.Event.TimeGenerated)
.ToList(); .ToList();
await App.MainDispatcher.BeginInvoke(() => {
EventList.ItemsSource = list;
EventList.SelectedIndex = 0; EventList.SelectedIndex = 0;
});
});
Mouse.OverrideCursor = null;
} }
private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) { private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) {

View File

@@ -302,9 +302,6 @@
<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,7 +13,6 @@ 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;
@@ -62,8 +61,6 @@ 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);
@@ -597,32 +594,20 @@ 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;
@@ -677,9 +662,6 @@ 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;
} }
@@ -697,9 +679,6 @@ 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;
} }
@@ -754,27 +733,12 @@ 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;
var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0; var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0;
double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 1 ? PostalNoEmailCount : 0; double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 2 ? PostalNoEmailCount : 0;
double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0; double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0;
double totalNum = printNum + emailNum; double totalNum = printNum + emailNum;
@@ -783,7 +747,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!.IsDoublePaged = false; doc!.DoublePaged = false;
if (doc is BusinessDocument b) { if (doc is BusinessDocument b) {
b.IncludeSender = false; b.IncludeSender = false;
b.Location = location; b.Location = location;
@@ -795,7 +759,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(CancelGeneration.Token, new Progress<double>(v => { await item2.Doc.Generate(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;
})); }));
} }
@@ -804,9 +768,6 @@ 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;
} }
@@ -827,7 +788,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.IsDoublePaged = doublePaged; doc.DoublePaged = doublePaged;
if (doc is BusinessDocument b) if (doc is BusinessDocument b)
b.Location = location; b.Location = location;
}); });
@@ -840,8 +801,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.IsDoublePaged = doublePaged; print.DoublePaged = doublePaged;
await print.Generate(CancelGeneration.Token, new Progress<double>(v => { await print.Generate(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;
@@ -850,9 +811,6 @@ 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;
} }
@@ -864,13 +822,10 @@ 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 && !hasPreviewDocs; PrintButton.IsEnabled = PrintDocument != null;
EmailButton.IsEnabled = EmailDocuments != null && !hasPreviewDocs && App.Config.Smtp != null; EmailButton.IsEnabled = EmailDocuments != null && App.Config.Smtp != null;
} }
private async void PreviewButton_Click(object sender, RoutedEventArgs evt) { private async void PreviewButton_Click(object sender, RoutedEventArgs evt) {
@@ -974,13 +929,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, docs.Select(d => d.Title).ToArray() subject,
docs.Select(d => d.Title).ToArray()
)]); )]);
} }
}); });
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", MessageBox.Show("Erfolgreich alle E-Mails verschickt!", "Rundschreiben verschicken", MessageBoxButton.OK, MessageBoxImage.Information);
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 {
@@ -999,7 +954,7 @@ namespace Elwig.Windows {
AvaiableDocumentsList.SelectedIndex = 1; AvaiableDocumentsList.SelectedIndex = 1;
if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation)) if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation))
return; return;
SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, Year)); SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, (Year, DocumentNonDeliverersInput.IsChecked == true)));
SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1; SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1;
RecipientsDeliveryMembersInput.IsChecked = true; RecipientsDeliveryMembersInput.IsChecked = true;
} }

View File

@@ -31,17 +31,6 @@
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator/> <Separator/>
<MenuItem Header="Datenbank sichern..." Click="Menu_Database_Backup_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEA35;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Datenbank wiederherstellen..." Click="Menu_Database_Restore_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE777;"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click"> <MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE756;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE756;"/>
@@ -64,23 +53,6 @@
</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>
@@ -89,7 +61,7 @@
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem x:Name="HelpMenu" Header="Hilfe"> <MenuItem x:Name="HelpMenu" Header="Hilfe">
<MenuItem Header="Über" Click="Menu_Help_About_Click"> <MenuItem Header="Über">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/>
</MenuItem.Icon> </MenuItem.Icon>
@@ -191,18 +163,16 @@
</Grid> </Grid>
</Button> </Button>
<Button x:Name="SyncButton" Click="SyncButton_Click" <Button x:Name="DownloadButton" Click="DownloadButton_Click"
Margin="375,135,0,0" Padding="1.0,0.5,0,0" Height="30" Width="30" Margin="310,135,0,0" Padding="0.375,0.5,0,0" Height="30" Width="30"
FontFamily="Segoe MDL2 Assets" FontSize="16" Content="&#xE896;" FontFamily="Segoe MDL2 Assets" FontSize="16"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
ToolTip="Geänderte Mitgliederdaten und Lieferungen synchronisieren"> ToolTip="Lieferungen und Mitgliederdaten anderer Zweigstellen herunterladen"/>
<Button.Content> <Button x:Name="UploadButton" Click="UploadButton_Click"
<Grid TextElement.FontFamily="Segoe MDL2 Assets"> Margin="375,135,0,0" Padding="1.0,0.5,0,0" Height="30" Width="30"
<TextBlock x:Name="SyncButton_1" Text="&#xE895;"/> Content="&#xE898;" FontFamily="Segoe MDL2 Assets" FontSize="16"
<TextBlock x:Name="SyncButton_2" Text="" Foreground="DarkOrange"/> HorizontalContentAlignment="Center"
</Grid> ToolTip="Lieferungen dieser Zweigstelle hochladen"/>
</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,30 +2,26 @@ 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;
@@ -33,21 +29,14 @@ 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;
Menu_Sync.IsEnabled = App.Config.SyncUrl != null; DownloadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
SyncButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden; UploadButton.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) {
@@ -70,11 +59,6 @@ namespace Elwig.Windows {
} }
} }
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
var w = new AboutWindow();
w.Show();
}
private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) { private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) {
await App.CheckForUpdates(true); await App.CheckForUpdates(true);
} }
@@ -162,87 +146,92 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
private async void Menu_Database_Backup_Click(object sender, RoutedEventArgs evt) { private async void DownloadButton_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try { try {
var d = new SaveFileDialog() { var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
Title = "Datenbank sichern - Elwig", var files = data
FileName = $"database_{Utils.Today:yyyy-MM-dd}.sql.zip", .Select(f => new {
DefaultExt = "sql.zip", Name = f!["name"]!.AsValue().GetValue<string>(),
Filter = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip", 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,
AddExtension = false, ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
}; Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
if (d.ShowDialog() == true) { Url = f!["url"]!.AsValue().GetValue<string>(),
if (!d.FileName.EndsWith(".sql.zip")) d.FileName += ".sql.zip"; Size = f!["size"]!.AsValue().GetValue<long>(),
Mouse.OverrideCursor = Cursors.Wait; })
await Task.Run(async () => { .Where(f => f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
await Database.ExportSql(d.FileName, true); .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) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} }
});
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
private async void Menu_Database_Restore_Click(object sender, RoutedEventArgs evt) { private async void UploadButton_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try { try {
var d = new OpenFileDialog() { var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
Title = "Datenbank wiederherstellen - Elwig", using var ctx = new AppDbContext();
DefaultExt = "sql.zip", var deliveries = await ctx.Deliveries
Filter = "SQLite-Datenbank (*.sqlite3, *.sqlite3.zip, *.sql, *.sql.zip)|*.sqlite3;*.sqlite3.zip;*.sql;*.sql.zip", .Where(d => d.Year == Utils.CurrentLastSeason && d.ZwstId == App.ZwstId)
}; .Include(d => d.Parts)
if (d.ShowDialog() == true) { .ThenInclude(p => p.PartModifiers)
var res = MessageBox.Show("Soll die Datenbank wirklich unwiederruflich durch die wiederhergestellte Version ersetzt werden?", "Datenbank wiederherstellen", .Include(d => d.Parts)
MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); .ThenInclude(p => p.Kg)
if (res != MessageBoxResult.OK) .ThenInclude(k => k!.Gl)
return; .OrderBy(d => d.DateString)
.ThenBy(d => d.TimeString)
Mouse.OverrideCursor = Cursors.Wait; .ThenBy(d => d.LsNr)
await App.ReplaceDatabase(d.FileName); .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 {
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) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} }
Mouse.OverrideCursor = null;
}
private async void SyncButton_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
});
Mouse.OverrideCursor = null;
}
private async void Menu_Sync_Download_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
});
Mouse.OverrideCursor = null;
}
private async void Menu_Sync_UploadBranchDeliveries_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.UploadBranchDeliveries(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
});
Mouse.OverrideCursor = null;
}
private async void Menu_Sync_UploadModified_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
@@ -254,23 +243,21 @@ namespace Elwig.Windows {
await Task.Run(async () => { await Task.Run(async () => {
try { try {
var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
var files = data var file = data
.Select(f => new { .Select(f => new {
Name = f!["name"]!.AsValue().GetValue<string>(), Name = f!["name"]!.AsValue().GetValue<string>(),
Timestamp = f!["modified"] != null && DateTime.TryParseExact(f!["modified"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null, Timestamp = f!["modified"] != null && DateTime.TryParseExact(f!["modified"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
Url = f!["url"]!.AsValue().GetValue<string>(), Url = f!["url"]!.AsValue().GetValue<string>(),
Size = f!["size"]!.AsValue().GetValue<long>(), Size = f!["size"]!.AsValue().GetValue<long>(),
}) })
.Where(f => f.Name.StartsWith("database.") && f.Name.EndsWith(".zip")) .Where(f => f.Name == "database.sqlite3.zip")
.OrderBy(f => f.Size) .FirstOrDefault();
.ToList();
if (files.Count == 0) { if (file == null) {
MessageBox.Show("Die Datenbank wurde noch nicht vom Hauptgerät hochgeladen!", "Datenbank herunterladen", MessageBox.Show("Die Datenbank wurde noch nicht vom Hauptgerät hochgeladen!", "Datenbank herunterladen",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
return; return;
} }
var file = files[0];
var res = MessageBox.Show($"Es wurde eine komprimierte Datenbank (ca. {file.Size / 1024 / 1024} MB) vom {file.Timestamp:dd.MM.yyyy, HH:mm} gefunden.\n\nWollen Sie wirklich die aktuelle Datenbank unwiederruflich\nlöschen und durch die gefundene ersetzen?\n\nDas kann zu Datenverlust führen!", "Datenbank herunterladen", var res = MessageBox.Show($"Es wurde eine komprimierte Datenbank (ca. {file.Size / 1024 / 1024} MB) vom {file.Timestamp:dd.MM.yyyy, HH:mm} gefunden.\n\nWollen Sie wirklich die aktuelle Datenbank unwiederruflich\nlöschen und durch die gefundene ersetzen?\n\nDas kann zu Datenverlust führen!", "Datenbank herunterladen",
MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
@@ -314,8 +301,8 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { try {
var path = Path.Combine(App.TempPath, "database.sql.zip"); var path = Path.Combine(App.TempPath, "database.sqlite3.zip");
await Database.ExportSql(path, true); ElwigData.ExportDatabase(path);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen der gesamten Datenbank erfolgreich!", "Datenbank hochladen", MessageBox.Show($"Hochladen der gesamten Datenbank erfolgreich!", "Datenbank hochladen",
MessageBoxButton.OK, MessageBoxImage.Information); MessageBoxButton.OK, MessageBoxImage.Information);
@@ -356,42 +343,9 @@ namespace Elwig.Windows {
App.FocusMailWindow(); App.FocusMailWindow();
} }
protected async override Task OnRenewContext(AppDbContext ctx) { protected override Task OnRenewContext(AppDbContext ctx) {
SeasonInput_TextChanged(null, null); SeasonInput_TextChanged(null, null);
CheckSync(); return Task.CompletedTask;
}
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) {
@@ -574,7 +528,7 @@ namespace Elwig.Windows {
App.HintContextChange(); App.HintContextChange();
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var tbl = await MemberDeliveryYieldsPerVarietyData.ForSeason(ctx.MemberDeliveryPerVariantRows, year); var tbl = await MemberDeliveryPerVarietyData.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

@@ -164,14 +164,6 @@
<MenuItem x:Name="Menu_Export_UploadAll" Header="...von allen Mitgliedern hochladen" <MenuItem x:Name="Menu_Export_UploadAll" Header="...von allen Mitgliedern hochladen"
Click="Menu_Export_UploadAll_Click"/> Click="Menu_Export_UploadAll_Click"/>
</MenuItem> </MenuItem>
<MenuItem Header="Kontakte">
<MenuItem x:Name="Menu_Contacts_Selected" Header="...von ausgewähltem Mitglied speichern..." IsEnabled="False"
Click="Menu_Contacts_Selected_Click"/>
<MenuItem x:Name="Menu_Contacts_Filters" Header="...aus Filtern speichern..."
Click="Menu_Contacts_Filters_Click"/>
<MenuItem x:Name="Menu_Contacts_All" Header="...von allen Mitgliedern speichern..."
Click="Menu_Contacts_All_Click"/>
</MenuItem>
</Menu> </Menu>
<Grid Grid.Row="1" Margin="5,0,0,0"> <Grid Grid.Row="1" Margin="5,0,0,0">

View File

@@ -319,11 +319,9 @@ namespace Elwig.Windows {
if (MemberList.SelectedItem is Member m) { if (MemberList.SelectedItem is Member m) {
Menu_Export_ExportSelected.IsEnabled = !IsEditing && !IsCreating; Menu_Export_ExportSelected.IsEnabled = !IsEditing && !IsCreating;
Menu_Export_UploadSelected.IsEnabled = !IsEditing && !IsCreating && App.Config.SyncUrl != null; Menu_Export_UploadSelected.IsEnabled = !IsEditing && !IsCreating && App.Config.SyncUrl != null;
Menu_Contacts_Selected.IsEnabled = !IsEditing && !IsCreating;
} else { } else {
Menu_Export_ExportSelected.IsEnabled = false; Menu_Export_ExportSelected.IsEnabled = false;
Menu_Export_UploadSelected.IsEnabled = false; Menu_Export_UploadSelected.IsEnabled = false;
Menu_Contacts_Selected.IsEnabled = false;
} }
} }
@@ -651,13 +649,6 @@ namespace Elwig.Windows {
await ViewModel.GenerateMemberList(MemberService.ExportSubject.Selected, ExportMode.Upload); await ViewModel.GenerateMemberList(MemberService.ExportSubject.Selected, ExportMode.Upload);
} }
private async void Menu_Contacts_All_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateMemberList(MemberService.ExportSubject.All, ExportMode.Vcf);
private async void Menu_Contacts_Filters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateMemberList(MemberService.ExportSubject.FromFilters, ExportMode.Vcf);
private async void Menu_Contacts_Selected_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateMemberList(MemberService.ExportSubject.Selected, ExportMode.Vcf);
private async void Menu_List_Order_Click(object sender, RoutedEventArgs evt) { private async void Menu_List_Order_Click(object sender, RoutedEventArgs evt) {
Menu_List.IsSubmenuOpen = true; Menu_List.IsSubmenuOpen = true;
if (sender == Menu_List_OrderMgNr) { if (sender == Menu_List_OrderMgNr) {

View File

@@ -55,9 +55,6 @@ 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();
} }
@@ -122,11 +119,6 @@ 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,35 +5,27 @@
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="1*" MinHeight="50"/> <RowDefinition Height="3*" MinHeight="100"/>
<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_member" Grid.Row="1" Grid.RowSpan="3" <TextBox x:Name="QueryInput" Text="SELECT * FROM v_delivery"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,5,120,5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,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" Grid.Row="1" <Button x:Name="QueryButton" Content="Abfragen"
HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,5,10,0" HorizontalAlignment="Right" VerticalAlignment="Stretch" Margin="10,10,10,5"
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="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> <GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<DataGrid x:Name="DataList" Grid.Row="5" <DataGrid x:Name="DataList" Grid.Row="2"
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,10 +1,7 @@
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;
@@ -16,12 +13,7 @@ namespace Elwig.Windows {
private ICommand? _enterCommand; private ICommand? _enterCommand;
public ICommand EnterCommand => _enterCommand ??= new ActionCommand(async () => { public ICommand EnterCommand => _enterCommand ??= new ActionCommand(async () => {
await DisplayQuery(); await ExecuteQuery();
});
private ICommand? _saveCommand;
public ICommand SaveCommand => _saveCommand ??= new ActionCommand(async () => {
await SaveQuery();
}); });
@@ -30,45 +22,33 @@ namespace Elwig.Windows {
} }
private async void QueryButton_Click(object sender, RoutedEventArgs evt) { private async void QueryButton_Click(object sender, RoutedEventArgs evt) {
await DisplayQuery(); await ExecuteQuery();
} }
private async void SaveButton_Click(object sender, RoutedEventArgs evt) { private async Task ExecuteQuery() {
await SaveQuery();
}
private async Task DisplayQuery() {
try { try {
Mouse.OverrideCursor = Cursors.Wait; await ExecuteQuery(QueryInput.Text);
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 SaveQuery() { private async Task ExecuteQuery(string sqlQuery) {
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[]>();
using var cnx = await AppDbContext.ConnectAsync(); IList<DbColumn> header;
using var cmd = cnx.CreateCommand();
using (var cnx = await AppDbContext.ConnectAsync()) {
var cmd = cnx.CreateCommand();
cmd.CommandText = sqlQuery; cmd.CommandText = sqlQuery;
using var reader = await cmd.ExecuteReaderAsync(); using var reader = await cmd.ExecuteReaderAsync();
var header = await reader.GetColumnSchemaAsync(); header = await reader.GetColumnSchemaAsync();
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
var values = new object[reader.FieldCount]; var values = new object[reader.FieldCount];
reader.GetValues(values); reader.GetValues(values);
rows.Add(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));
@@ -83,27 +63,5 @@ 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

@@ -5,43 +5,3 @@ Elwig
**El**ektronische **Wi**nzer**g**enossenschaftsverwaltung (Electronic Management for Vintners' Cooperatives). **El**ektronische **Wi**nzer**g**enossenschaftsverwaltung (Electronic Management for Vintners' Cooperatives).
Further information may be found on [the website](https://elwig.at). Further information may be found on [the website](https://elwig.at).
About
=====
**Product:** Elwig
**Description:** Electronic Management for Vintners' Cooperatives
**Type:** ERP system
**Version:** 1.0.3.0 ([Changelog](./CHANGELOG.md))
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/
**Source code:** https://git.necronda.net/winzer/elwig
**Developement period:** 20222025
**Technology Stack:**
Language: C#
Framework: Windows Presentation Framework (WPF)
Database: [SQLite](https://sqlite.org/)
PDF creation: [WeasyPrint](https://weasyprint.org/), [RazorLight](https://github.com/toddams/RazorLight), [PdfiumViewer](https://github.com/pvginkel/PdfiumViewer)
Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
Über
====
**Produkt:** Elwig
**Beschreibung:** Elektronische Winzergenossenschaftsverwaltung
**Typ:** Warenwirtschaftssystem (ERP-System)
**Version:** 1.0.3.0 ([Änderungsprotokoll](./CHANGELOG.md))
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/
**Quellcode:** https://git.necronda.net/winzer/elwig
**Entwicklungszeitraum:** 20222025
**Verwendete Technologien:**
Programmiersprache: C#
Framework: Windows Presentation Framework (WPF)
Datenbank: [SQLite](https://sqlite.org/)
PDF-Erstellung: [WeasyPrint](https://weasyprint.org/), [RazorLight](https://github.com/toddams/RazorLight), [PdfiumViewer](https://github.com/pvginkel/PdfiumViewer)
Paketierung: [WiX Toolset](https://www.firegiant.com/wixtoolset/)

View File

@@ -13,7 +13,7 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Installer\Installer.wixproj" /> <ProjectReference Include="..\Installer\Installer.wixproj" />
<PackageReference Include="WixToolset.Bal.wixext" Version="6.0.2" /> <PackageReference Include="WixToolset.Bal.wixext" Version="6.0.1" />
<PackageReference Include="WixToolset.Util.wixext" Version="6.0.2" /> <PackageReference Include="WixToolset.Util.wixext" Version="6.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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 Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, 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 cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql"); await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql"); await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.E2EInsert.sql"); await AppDbContext.ExecuteEmbeddedScript(cnx, 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\net10.0-windows\Elwig.exe"); public static readonly string ApplicationPath = Path.GetFullPath(@"..\..\..\..\Elwig\bin\Debug\net8.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>net10.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@@ -19,12 +19,12 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.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="6.1.0" /> <PackageReference Include="NUnit3TestAdapter" Version="5.1.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.11.2"> <PackageReference Include="NUnit.Analyzers" Version="4.10.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

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 Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentInsert.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, 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 Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentDelete.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, 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 Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingInsert.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, 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 Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingDelete.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, 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 Connection.ExecuteBatch(""" await AppDbContext.ExecuteBatch(Connection, """
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 Connection!.ExecuteBatch($""" return AppDbContext.ExecuteBatch(Connection!, $"""
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}');
"""); """);
@@ -188,7 +188,7 @@ namespace Tests.UnitTests.HelperTests {
Assert.That(payment["GV"], Is.EqualTo(10_000)); Assert.That(payment["GV"], Is.EqualTo(10_000));
}); });
await b.Calculate(true, false, false, false); await b.Calculate(false, false, false);
var prices = await GetMemberDeliveryPrices(year, mgnr); var prices = await GetMemberDeliveryPrices(year, mgnr);
Assert.Multiple(() => { Assert.Multiple(() => {
Assert.That(prices, Has.Count.EqualTo(7)); Assert.That(prices, Has.Count.EqualTo(7));
@@ -234,7 +234,7 @@ namespace Tests.UnitTests.HelperTests {
Assert.That(payment["GV"], Is.EqualTo(8_000)); Assert.That(payment["GV"], Is.EqualTo(8_000));
}); });
await b.Calculate(true, true, false, false); await b.Calculate(true, false, false);
var prices = await GetMemberDeliveryPrices(year, mgnr); var prices = await GetMemberDeliveryPrices(year, mgnr);
Assert.Multiple(() => { Assert.Multiple(() => {
Assert.That(prices, Has.Count.EqualTo(6)); Assert.That(prices, Has.Count.EqualTo(6));

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 Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceInsert.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, 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 Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceDelete.sql"); await AppDbContext.ExecuteEmbeddedScript(Connection, 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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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<FormatException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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<FormatException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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<FormatException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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<FormatException>(async () => await Scale!.Weigh()); Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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<FormatException>(async () => await ScaleA!.Weigh()); Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(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";
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh()); IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
} }
} }
} }

View File

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