Compare commits

..

2 Commits

Author SHA1 Message Date
lorenz.stechauner 99ef5c7539 Bump version to 1.0.0.0
Test / Run tests (push) Successful in 1m50s
2025-06-03 21:18:07 +02:00
lorenz.stechauner a95edb5bba Add GNU GPLv3 as license 2025-04-24 16:06:23 +02:00
171 changed files with 2038 additions and 5179 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
name: Deploy
on:
push:
tags: ["v[0-9]+.[0-9]+.[0-9]+.[0-9]+"]
tags: ["v([0-9]+.)?[0-9]+.[0-9]+.[0-9]+"]
jobs:
deploy:
name: Build and Deploy
+4 -287
View File
@@ -2,305 +2,22 @@
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}
---------------------------------------------
### Sonstiges {#v1.0.0.2-misc}
* Explizit native SQLite-Bibliothek hinzugefügt. (77c3f388e7)
[v1.0.0.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.2
[v1.0.0.1][v1.0.0.1] (2025-08-05) {#v1.0.0.1}
---------------------------------------------
### Sonstiges {#v1.0.0.1-misc}
* Abhängigkeiten aktualisiert. (466c8a322c)
* Angepasste Fehlermeldung, wenn Importieren fehlschlägt. (ab7c7404e2)
[v1.0.0.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.1
[v1.0.0.0][v1.0.0.0] (2025-07-30) {#v1.0.0.0}
[v1.0.0.0][v1.0.0.0] (2025-XX-YY) {#v1.0.0.0}
---------------------------------------------
### Neue Funktionen {#v1.0.0.0-features}
* Es wird nun gespeichert, wann Mitglieder/Flächenbindungen/Lieferungen importiert bzw. exporiert wurden. (4234c7f994)
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) wird der Auszahlungsbetrag für nicht-festgesetzte Varianten nun überschlagsmäßig angezeigt. (913129f155)
* Wenn eine Variante im Auszahlungsvariante-Fenster (`ChartWindow`) gespeichert wird, wird sie nun automatisch auch berechnet. (c6c8fd9b68)
* Überall wo Weinsorten vorkommen werden diese jetzt rot oder grün eingefärbt angezeigt. ([#61][i61])
* Das Flächenbindungs-Fenster (`AreaComAdminWindow`) wurde überarbeitet. (53d82604a1, 267aa3d47c, [#64][i64])
* Auf allen Dokumenten wird der Bio-Kontrollstellen-Code in der Fußzeile angeführt (sofern dieser in den Stammdaten gesetzt wurde). (fad1e28c06)
* Something.
### Behobene Fehler {#v1.0.0.0-bugfixes}
* Bei Traubengutschriften (`CreditNote`) wurde der Rebelzuschlag immer angeführt, auch wenn dieser in der zugrundeliegenden Berechnung nicht berücksichtigt wurde. (336aef5c70)
* In den Variantendaten einer Auszahlungsvariante (`PaymentVariantSummary`) wurde neben den Spalten _gebunden_ und _ungebunden_ noch _attributlos gebunden_ hinzugefügt. Ohne diese neue Spalte wären die Werte der anderen beiden falsch. ([#58][i58])
* Das erste Laden des Ausgangs-Protokoll-Fensters (`MailLogWindow`) hat nicht funktioniert. ([#65][i65])
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) und im Mitglieder-Fenster (`MemberAdminWindow`) wird der Tool-Tip für Gewicht/Gradation mit korrektem Layout angezeigt. (e9f389b885)
* Bei Traubengutschriften (`CreditNote`) werden längere Freitexte vollständig angezeigt statt abgeschnitten. ([#62][i62])
* Something.
### Sonstiges {#v1.0.0.0-misc}
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) wird angeführt, dass "Zu- und Abschläge bei Lieferungen" auch den Rebelzuschlag umfassen. (0dff3986b7)
* Im gesamten Program wird an passenden Stellen statt "Gewicht" nun "Menge" verwendet. (0f3ce39f35)
* Verbesserungen der Code-Qualität und der Leistung. ([#59][i59], b580e1bf79, 623f55f5b0, 3493ff6df1, 267797b55d, 41811925be, [#57][i57], 38d0ff969d, e6746f76b1, 7e9a27c75d)
* Im Mitglieder-Fenster (`MemberAdminWindow`) werden die Flächenbindungen jetzt für die momentane Saison angezeigt statt für das aktuelle Jahr. (953532cae4)
* Abhängigkeiten aktualisiert. (4b27ebf81b, 7153bfab6f, 9b48242f0e)
* GPLv3 Lizenz hinzugefügt. (93e920f61a)
* Something.
[v1.0.0.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.0
[i57]: https://git.necronda.net/winzer/elwig/issues/57
[i58]: https://git.necronda.net/winzer/elwig/issues/58
[i59]: https://git.necronda.net/winzer/elwig/issues/59
[i61]: https://git.necronda.net/winzer/elwig/issues/61
[i62]: https://git.necronda.net/winzer/elwig/issues/62
[i64]: https://git.necronda.net/winzer/elwig/issues/64
[i65]: https://git.necronda.net/winzer/elwig/issues/65
[v0.13.9][v0.13.9] (2025-05-05) {#v0.13.9}
------------------------------------------
### Sonstiges {#v0.13.9-misc}
* Abhängigkeiten aktualisiert. (bf0db37872, 4af2fa256e, d1c07ee92a, 41c5288fc5)
* Automatisches Aktualisieren der Synchroisations-URL (`https://elwig.at/clients/` -> `https://sync.elwig.at/`). (e50e7337e6)
[v0.13.9]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.9
+3 -17
View File
@@ -30,37 +30,23 @@
<DataTemplate x:Key="WineVarietyTemplateCollapsed">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Foreground="{Binding Color}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="WineVarietyTemplateExpanded">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SortIdFormat}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Name}" Foreground="{Binding Color}"/>
<TextBlock Text="{Binding SortId}" MinWidth="36" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AreaCommitmentTypeTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding WineVar.Name}" Foreground="{Binding WineVar.Color}"/>
<TextBlock Text="{Binding WineAttr.Name}" Margin="5,0,0,0"/>
<TextBlock Text="{Binding Discriminator}" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ModifierTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" MinWidth="250" Margin="0,0,10,0"/>
<TextBlock Text="{Binding ValueStr}"/>
</StackPanel>
</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">
<StackPanel Orientation="Horizontal">
+25 -119
View File
@@ -1,22 +1,20 @@
using Elwig.Dialogs;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Helpers.Printing;
using Elwig.Helpers.Weighing;
using Elwig.Models.Entities;
using Elwig.Windows;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.IO;
using Elwig.Helpers;
using Elwig.Helpers.Weighing;
using System.Collections.Generic;
using System.Windows.Threading;
using System.Reflection;
using Elwig.Helpers.Printing;
using Elwig.Windows;
using Elwig.Dialogs;
using System.Threading.Tasks;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using System.Text;
namespace Elwig {
public partial class App : Application {
@@ -26,15 +24,11 @@ namespace Elwig {
public static bool ForceShutdown { get; private set; } = false;
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 = @"C:\ProgramData\Elwig\";
public static readonly string MailsPath = Path.Combine(DataPath, "mails");
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Elwig");
public static readonly string DocumentsPath = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Elwig/Documents") :
Path.Combine(InstallPath, "resources/Documents");
public static readonly string ExePath = @"C:\Program Files\Elwig\";
public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");
public static Config Config { get; private set; } = new(ConfigPath);
@@ -49,10 +43,9 @@ namespace Elwig {
public static string? BranchPhoneNr { get; private set; }
public static string? BranchFaxNr { get; private set; }
public static string? BranchMobileNr { get; private set; }
public static IList<IScale> Scales { get; private set; } = [];
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>()];
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<IEventScale> EventScales => Scales.Where(s => s is IEventScale).Cast<IEventScale>().ToList();
public static ClientParameters Client { get; set; }
public static Dispatcher MainDispatcher { get; private set; }
@@ -130,18 +123,14 @@ namespace Elwig {
if (Config.UpdateAuto && Config.UpdateUrl != null) {
if (Utils.HasInternetConnectivity()) {
Utils.RunBackground("Auto Updater", async () => {
await Task.Delay(1000);
await Task.Delay(500);
await CheckForUpdates();
});
}
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
_autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
_autoUpdateTimer.Start();
}
SerialPortWatcher.SerialPortConnected += OnSerialPortConnected;
SerialPortWatcher.SerialPortDisconnected += OnSerialPortDisconnected;
var list = new List<IScale>();
foreach (var s in Config.Scales) {
try {
@@ -149,7 +138,7 @@ namespace Elwig {
} catch (Exception e) {
list.Add(new InvalidScale(s.Id));
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);
}
}
@@ -157,7 +146,7 @@ namespace Elwig {
if (Config.Branch != null) {
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();
} else {
SetBranch(branches[Config.Branch.ToLower()]);
@@ -165,20 +154,10 @@ namespace Elwig {
} else if (branches.Count == 1) {
SetBranch(branches.First().Value);
} 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();
}
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);
var window = new MainWindow();
@@ -186,7 +165,6 @@ namespace Elwig {
}
private async void Application_Exit(object sender, ExitEventArgs evt) {
SerialPortWatcher.Dispose();
foreach (var s in EventScales) {
s.Dispose();
}
@@ -219,12 +197,10 @@ namespace Elwig {
var ch = CurrentLastWrite;
if (ch > CurrentApp.LastChanged)
CurrentApp.LastChanged = ch;
MainDispatcher.Invoke(() => {
foreach (Window w in CurrentApp.Windows) {
if (w is not ContextWindow c) continue;
MainDispatcher.BeginInvoke(c.HintContextChange);
}
});
foreach (Window w in CurrentApp.Windows) {
if (w is not ContextWindow c) continue;
MainDispatcher.BeginInvoke(c.HintContextChange);
}
}
private void OnAutoUpdateTimer(object? sender, EventArgs? evt) {
@@ -236,63 +212,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) {
if (Config.UpdateUrl == null) return;
var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
@@ -315,19 +234,6 @@ namespace Elwig {
}
}
public static async Task ReplaceDatabase(string filename) {
try {
await Task.Run(async () => {
await Database.Import(filename);
});
MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
ForceShutdown = true;
Current.Shutdown();
} catch (Exception exc) {
MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static T FocusWindow<T>(Func<T> constructor, Predicate<T>? selector = null) where T : Window {
foreach (Window w in CurrentApp.Windows) {
if (w is T t && (selector == null || selector(t))) {
+3 -8
View File
@@ -124,24 +124,19 @@ namespace Elwig.Controls {
SelectItemsReverse();
var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null;
if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
_textBox.Text = AllItemsSelectedContent;
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) {
_textBox.Text = "";
AllItemsSelected = false;
} else {
_textBox.Text = string.Join(Delimiter,
dmp == null ? SelectedItems.Cast<object>() :
SelectedItems.Cast<object>()
.Select(i => i.GetType().GetProperty(dmp)?.GetValue(i))
);
AllItemsSelected = null;
}
RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, evt.RemovedItems, evt.AddedItems));
}
@@ -1,4 +1,4 @@
<Window x:Class="Elwig.Dialogs.AreaComTransferDialog"
<Window x:Class="Elwig.Dialogs.AreaComDialog"
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"
@@ -51,7 +51,7 @@
<CheckBox x:Name="CopyYearToInput" Content="Beginn der Laufzeit von Vorgänger übernehmen" Margin="0,80,0,0"
HorizontalAlignment="Center" VerticalAlignment="Top"
Checked="CopyYearToInput_Changed" Unchecked="CopyYearToInput_Changed"
IsChecked="{Binding MaintainYearFrom}"/>
IsChecked="{Binding MaintainYearTo}"/>
<TextBlock x:Name="DescBlock1" Margin="0,105,0,0" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Top">
@@ -3,16 +3,16 @@ using System.Windows;
using System.Windows.Controls;
namespace Elwig.Dialogs {
public partial class AreaComTransferDialog : Window {
public partial class AreaComDialog : Window {
public int CancelSeason { get; set; }
public int SuccessorSeason => CancelSeason + 1;
public bool MaintainYearFrom { get; set; }
public bool MaintainYearTo { get; set; }
public string AreaComNum { get; set; }
public string Area { get; set; }
public AreaComTransferDialog(string name, int areaComNum, int area) {
public AreaComDialog(string name, int areaComNum, int area) {
CancelSeason = Utils.FollowingSeason - 1;
AreaComNum = $"{areaComNum:N0}";
Area = $"{area:N0}";
@@ -29,7 +29,7 @@ namespace Elwig.Dialogs {
SeasonLabel.Margin = new(0, 40, 100, 0);
}
public AreaComTransferDialog(string name, string successorName, int areaComNum, int area) {
public AreaComDialog(string name, string successorName, int areaComNum, int area) {
CancelSeason = Utils.FollowingSeason - 1;
AreaComNum = $"{areaComNum:N0}";
Area = $"{area:N0}";
@@ -43,13 +43,13 @@ namespace Elwig.Dialogs {
CancelSeason = (int)SeasonInput.Value!;
CancelSeason1.Text = $"{CancelSeason}";
CancelSeason2.Text = $"{CancelSeason}";
TransferSeason.Text = MaintainYearFrom ? "" : $"{SuccessorSeason}";
DescBlock1Season.Text = MaintainYearFrom ? "dem originalen Beginn der FB" : "inkl. Saison ";
TransferSeason.Text = MaintainYearTo ? "" : $"{SuccessorSeason}";
DescBlock1Season.Text = MaintainYearTo ? "dem originalen Beginn der FB" : "inkl. Saison ";
}
private void CopyYearToInput_Changed(object sender, RoutedEventArgs evt) {
TransferSeason.Text = MaintainYearFrom ? "" : $"{SuccessorSeason}";
DescBlock1Season.Text = MaintainYearFrom ? "dem originalen Beginn der FB" : "inkl. Saison ";
TransferSeason.Text = MaintainYearTo ? "" : $"{SuccessorSeason}";
DescBlock1Season.Text = MaintainYearTo ? "dem originalen Beginn der FB" : "inkl. Saison ";
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
-71
View File
@@ -1,71 +0,0 @@
<Window x:Class="Elwig.Dialogs.AreaComModifyDialog"
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.Dialogs"
xmlns:ctrl="clr-namespace:Elwig.Controls"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Flächenbindungen bearbeiten" Height="220" Width="450">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Padding" Value="2,4,2,4"/>
<Setter Property="Height" Value="25"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<Grid>
<TextBlock x:Name="QuestionBlock1" TextAlignment="Center" Margin="0,10,0,0"
HorizontalAlignment="Center" VerticalAlignment="Top">
Soll die Flächenbindung (<Run Text="{Binding Area}"/> m², <Run Text="{Binding OrigYearFrom}"/><Run Text="{Binding OrigYearTo}"/>) rückwirkend bearbeitet werden,<LineBreak/>
oder erst ab der angegebenen Saison?
</TextBlock>
<TextBlock x:Name="QuestionBlock2" TextAlignment="Center" Margin="0,10,0,0" Visibility="Hidden"
HorizontalAlignment="Center" VerticalAlignment="Top">
Soll die Flächenbindung (<Run Text="{Binding Area}"/> m², <Run Text="{Binding OrigYearFrom}"/><Run Text="{Binding OrigYearTo}"/>) rückwirkend gelöscht werden,<LineBreak/>
oder erst ab der angegebenen Saison?
</TextBlock>
<Label x:Name="SeasonLabel" Content="Saison:" Margin="0,50,100,0"
HorizontalAlignment="Center" VerticalAlignment="Top"/>
<ctrl:IntegerUpDown x:Name="SeasonInput" Width="56" Height="25" Margin="0,50,0,0" FontSize="14"
Minimum="1900" Maximum="9999"
HorizontalAlignment="Center" VerticalAlignment="Top"
TextChanged="SeasonInput_TextChanged"/>
<CheckBox x:Name="RetroactiveInput" Content="Rückwirkend bearbeiten" Margin="0,80,0,0"
HorizontalAlignment="Center" VerticalAlignment="Top"
Checked="RetroactiveInput_Changed" Unchecked="RetroactiveInput_Changed"
IsChecked="{Binding ModifyRetroactively}"/>
<TextBlock x:Name="DescBlock1" Margin="0,105,0,0" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Top">
Die Flächenbindung bleibt bis inkl. Saison <Bold><Run x:Name="CancelSeason1"/></Bold> unverändert,<LineBreak/>
und wird ab inkl. Saison <Bold><Run x:Name="NewSeason1"/></Bold> in geänderter Form übernommen.
</TextBlock>
<TextBlock x:Name="DescBlock2" Margin="0,105,0,0" TextAlignment="Center" Visibility="Hidden"
HorizontalAlignment="Center" VerticalAlignment="Top">
Die Flächenbindung bleibt bis inklusive Saison <Bold><Run x:Name="CancelSeason2"/></Bold> gültig.
</TextBlock>
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" Grid.Column="1"
Click="ConfirmButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" Grid.Column="1" IsCancel="True"/>
</Grid>
</Window>
-62
View File
@@ -1,62 +0,0 @@
using Elwig.Helpers;
using System.Windows;
using System.Windows.Controls;
namespace Elwig.Dialogs {
public partial class AreaComModifyDialog : Window {
public int? YearTo { get; set; }
public bool ModifyRetroactively { get; set; }
public string OrigYearFrom { get; set; }
public string OrigYearTo { get; set; }
public string Area { get; set; }
public AreaComModifyDialog(int? yearFrom, int? yearTo, int area, bool delete) {
Area = $"{area:N0}";
OrigYearFrom = $"{yearFrom}";
OrigYearTo = $"{yearTo}";
InitializeComponent();
Title = delete ? "Flächenbindung löschen" : "Flächenbindung bearbeiten";
RetroactiveInput.Content = delete ? "Rückwirkend löschen" : "Rückwirkend bearbeiten";
if (delete) {
QuestionBlock1.Visibility = Visibility.Hidden;
QuestionBlock2.Visibility = Visibility.Visible;
DescBlock1.Visibility = Visibility.Hidden;
DescBlock2.Visibility = Visibility.Visible;
}
if ((yearTo.HasValue && yearTo < Utils.CurrentNextSeason) || (yearFrom.HasValue && yearFrom >= Utils.CurrentNextSeason)) {
RetroactiveInput.IsChecked = true;
} else {
SeasonInput.Text = $"{Utils.CurrentNextSeason}";
}
}
private void SeasonInput_TextChanged(object sender, TextChangedEventArgs evt) {
YearTo = SeasonInput.Value! - 1;
CancelSeason1.Text = $"{YearTo}";
CancelSeason2.Text = $"{YearTo}";
NewSeason1.Text = $"{YearTo + 1}";
}
private void RetroactiveInput_Changed(object sender, RoutedEventArgs evt) {
if (ModifyRetroactively) {
SeasonInput.IsEnabled = false;
SeasonInput.Text = "";
YearTo = null;
DescBlock1.Visibility = Visibility.Hidden;
DescBlock2.Visibility = Visibility.Hidden;
} else {
SeasonInput.IsEnabled = true;
SeasonInput.Text = $"{Utils.CurrentNextSeason}";
DescBlock1.Visibility = QuestionBlock1.Visibility;
DescBlock2.Visibility = QuestionBlock2.Visibility;
}
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
DialogResult = true;
Close();
}
}
}
-10
View File
@@ -19,16 +19,6 @@ namespace Elwig.Documents {
Member = m;
Location = App.BranchLocation;
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>");
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>" +
+1 -1
View File
@@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.BusinessDocument>
@model Elwig.Documents.BusinessDocument
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\BusinessDocument.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\BusinessDocument.css"/>
<div class="info-wrapper">
<div class="address-wrapper">
<div class="sender">
+1 -2
View File
@@ -39,7 +39,6 @@ namespace Elwig.Documents {
Data = data;
Payment = p;
Credit = p.Credit;
IsPreview = Payment == null || Credit == null;
var season = p.Variant.Season;
if (considerCustomModifiers) {
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
@@ -49,7 +48,7 @@ namespace Elwig.Documents {
if (CustomPayment?.ModComment != null) {
MemberModifier = CustomPayment.ModComment;
} else if (mod != null) {
MemberModifier = $"{mod.Name} ({mod.PublicValueStr})";
MemberModifier = $"{mod.Name} ({mod.ValueStr})";
} else {
MemberModifier = "Sonstige Zu-/Abschläge";
}
+3 -15
View File
@@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.CreditNote>
@model Elwig.Documents.CreditNote
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\CreditNote.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\CreditNote.css"/>
<main>
<h1>@Model.Title</h1>
<table class="credit">
@@ -136,10 +136,7 @@
}
</tbody>
<tbody style="break-inside: avoid;">
@{
decimal penalty = 0;
string? comment = null;
}
@{ decimal penalty = 0; }
@if (Model.MemberUnderDeliveries != null && Model.MemberUnderDeliveries.Count() > 0) {
<tr class="small">
@@ -161,13 +158,7 @@
penalty += Model.MemberAutoBusinessSharesAmount;
}
@if (Model.CustomPayment?.Amount != null) {
comment = Model.CustomPayment.Comment;
string text = (Model.CustomPayment.Amount.Value < 0 ? "Weitere Abzüge" : "Weitere Zuschläge") + (comment != null ? "*" : "");
if (comment != null && comment!.Length <= 30) {
text = comment;
comment = null;
}
@Raw(FormatRow(text, Model.CustomPayment.Amount.Value, add: true));
@Raw(FormatRow(Model.CustomPayment.Comment ?? ((Model.CustomPayment.Amount.Value) < 0 ? "Weitere Abzüge" : "Weitere Zuschläge"), Model.CustomPayment.Amount.Value, add: true));
penalty += Model.CustomPayment.Amount.Value;
}
@@ -185,9 +176,6 @@
}
</tbody>
</table>
@if (comment != null) {
<p>* @comment</p>
}
<p>Überweisung erfolgt auf Konto @(Elwig.Helpers.Utils.FormatIban(Model.Member.Iban ?? "-")).</p>
<div style="margin-top: 1em;">
@if (Model.Text != null) {
+2 -2
View File
@@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryAncmtList>
@model Elwig.Documents.DeliveryAncmtList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryAncmtList.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryAncmtList.css" />
<main>
<h1>Anmeldeliste</h1>
<h2>@Model.Filter</h2>
@@ -24,7 +24,7 @@
<th rowspan="2" style="text-align: left;">Ort</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="2">Anmldg.</th>
<th>Menge</th>
<th>Gewicht</th>
</tr>
<tr>
<th class="unit">[kg]</th>
+1 -1
View File
@@ -2,7 +2,7 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-top: 10mm;
margin-bottom: 2mm;
}
+2 -2
View File
@@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryConfirmation>
@model Elwig.Documents.DeliveryConfirmation
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryConfirmation.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryConfirmation.css"/>
<main>
<h1>@Model.Title</h1>
<table class="delivery-confirmation">
@@ -30,7 +30,7 @@
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
<th colspan="2">Gradation</th>
<th colspan="2">Flächenbindung</th>
<th>Menge</th>
<th>Gewicht</th>
<th rowspan="3" style="padding: 0;">
<svg width="10" height="40" xmlns="http://www.w3.org/2000/svg">
<text x="-40" y="4" transform="rotate(270)" font-size="8pt" font-style="italic" font-family="Times New Roman"
@@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryDepreciationList>
@model Elwig.Documents.DeliveryDepreciationList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryDepreciationList.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryDepreciationList.css" />
<main>
<h1>Abwertungsliste</h1>
<h2>@Model.Filter</h2>
@@ -27,7 +27,7 @@
<th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="2" style="text-align: left;">Attr./Bewirt.</th>
<th colspan="2">Gradation</th>
<th>Menge</th>
<th>Gewicht</th>
</tr>
<tr>
<th class="unit">[°Oe]</th>
+1 -1
View File
@@ -2,7 +2,7 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-top: 10mm;
margin-bottom: 2mm;
}
+2 -2
View File
@@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryJournal>
@model Elwig.Documents.DeliveryJournal
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryJournal.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryJournal.css"/>
<main>
<h1>Lieferjournal</h1>
<h2>@Model.Filter</h2>
@@ -29,7 +29,7 @@
<th rowspan="2" style="text-align: left;">Mitglied</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th colspan="2">Gradation</th>
<th>Menge</th>
<th>Gewicht</th>
</tr>
<tr>
<th class="unit">[°Oe]</th>
+1 -1
View File
@@ -2,7 +2,7 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-top: 10mm;
margin-bottom: 2mm;
}
+3 -3
View File
@@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.DeliveryNote>
@model Elwig.Documents.DeliveryNote
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryNote.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryNote.css" />
<main>
<h1>@Model.Title</h1>
<table class="delivery large">
@@ -24,7 +24,7 @@
<th class="main" rowspan="2" colspan="2">Attribut</th>
<th class="main" rowspan="2">Qualitätsstufe</th>
<th colspan="2">Gradation</th>
<th>Menge</th>
<th>Gewicht</th>
</tr>
<tr>
<th class="unit">[°Oe]</th>
@@ -55,7 +55,7 @@
@if (part.Modifiers.Count() > 0) {
var first = true;
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;
}
}
+37 -46
View File
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using Elwig.Helpers.Printing;
using MimeKit;
using System.Threading;
namespace Elwig.Documents {
public abstract partial class Document : IDisposable {
@@ -19,13 +18,12 @@ namespace Elwig.Documents {
protected string? _pdfPath;
protected string? PdfPath => _pdfPath ?? _pdfFile?.FilePath;
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 IsDoublePaged = false;
public bool IsPreview = false;
public bool DoublePaged = false;
public string DocumentsPath;
public string DataPath;
public int CurrentNextSeason;
public string? DocumentId;
public string Title;
@@ -36,12 +34,19 @@ namespace Elwig.Documents {
public Document(string title) {
var c = App.Client;
DocumentsPath = App.DocumentsPath;
DataPath = App.DataPath;
CurrentNextSeason = Utils.CurrentNextSeason;
Title = title;
Author = c.NameFull;
Header = "";
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ").Item(c.NameFull).ToString();
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("UID", c.UstIdNr).NextLine()
.Item("BIC", c.Bic).Item("IBAN", c.Iban)
.ToString();
Date = DateOnly.FromDateTime(Utils.Today);
}
@@ -99,7 +104,7 @@ namespace Elwig.Documents {
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)
return;
progress?.Report(0.0);
@@ -109,50 +114,36 @@ namespace Elwig.Documents {
var pdf = new TempFile("pdf");
var tmpHtmls = new List<TempFile>();
var tmpFiles = new List<string>();
try {
var n = m.Documents.Count();
int i = 0;
foreach (var doc in m.Documents) {
if (doc is PdfDocument) {
tmpFiles.Add(doc.PdfPath!);
continue;
}
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
i++;
progress?.Report(GenerationProportion * 100 * i / n);
}
progress?.Report(GenerationProportion * 100);
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, IsDoublePaged, cancelToken, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} finally {
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
var n = m.Documents.Count();
int i = 0;
foreach (var doc in m.Documents) {
if (doc is PdfDocument) {
tmpFiles.Add(doc.PdfPath!);
continue;
}
var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
i++;
progress?.Report(GenerationProportion * 100 * i / n);
}
progress?.Report(GenerationProportion * 100);
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, DoublePaged, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages;
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
}
_pdfFile = pdf;
} else {
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
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);
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;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
}
_pdfFile = pdf;
}
progress?.Report(100.0);
}
@@ -164,7 +155,7 @@ namespace Elwig.Documents {
public async Task Print(int copies = 1) {
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() {
+7 -7
View File
@@ -7,10 +7,10 @@
<title>@Model.Title</title>
<meta name="author" value="@Model.Author"/>
<meta charset="UTF-8"/>
<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.Table.css" />
@if (Model.IsDoublePaged) {
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.Page.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.Table.css"/>
@if (Model.DoublePaged) {
<style>
@@page :left {
margin: 25mm 25mm 35mm 20mm;
@@ -34,14 +34,14 @@
<div class="pre-footer">
<span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</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>
<footer>@Raw(Model.Footer)</footer>
</div>
@if (Model.IsDoublePaged) {
@if (Model.DoublePaged) {
<div class="footer-wrapper left">
<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="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
</div>
+1 -1
View File
@@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.MemberDataSheet>
@model Elwig.Documents.MemberDataSheet
@{ Layout = "BusinessDocument"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\MemberDataSheet.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\MemberDataSheet.css" />
<main>
<h1>@Model.Title</h1>
<table class="member border">
+1 -1
View File
@@ -2,7 +2,7 @@
@inherits TemplatePage<Elwig.Documents.MemberList>
@model Elwig.Documents.MemberList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\MemberList.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\MemberList.css"/>
<main>
<h1>Mitgliederliste</h1>
<h2>@Model.Filter</h2>
+1 -1
View File
@@ -2,7 +2,7 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-top: 10mm;
margin-bottom: 2mm;
}
-1
View File
@@ -27,7 +27,6 @@ namespace Elwig.Documents {
Data = data;
CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code;
MemberNum = v.Credits.Count;
IsPreview = MemberNum == 0;
DeliveryNum = v.DeliveryPartPayments.DistinctBy(p => p.DeliveryPart.Delivery).Count();
DeliveryPartNum = v.DeliveryPartPayments.Count;
ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult();
+17 -26
View File
@@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.PaymentVariantSummary>
@model Elwig.Documents.PaymentVariantSummary
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\PaymentVariantSummary.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\PaymentVariantSummary.css" />
<main>
<h1>Auszahlungsvariante Lese @Model.Variant.Year</h1>
<h2>@Model.Variant.Name</h2>
@@ -111,8 +111,8 @@
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(payed):N2}")</td>
@{
var weiRows = Model.Data.Rows.Where(r => r.QualityLevel == "Wein");
var minWei = weiRows.Min(r => r.Ungeb.MinPrice);
var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice);
var minWei = weiRows.Min(r => r.Ungeb.Price);
var maxWei = weiRows.Max(r => r.Ungeb.Price);
}
<th class="lborder tborder">Preis (abgewertet):</th>
<td colspan="2" class="center tborder">@(minWei != maxWei ? $"{minWei:N4}{maxWei:N4}" : $"{minWei:N4}") @Model.CurrencySymbol/kg</td>
@@ -123,8 +123,8 @@
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{netSum:N2}")</td>
@{
var quwRows = Model.Data.Rows.Where(r => r.QualityLevel != "Wein");
var minPrice = quwRows.Min(r => r.Ungeb.MinPrice);
var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice);
var minPrice = quwRows.Min(r => r.Ungeb.Price);
var maxPrice = quwRows.Max(r => r.Ungeb.Price);
}
<th class="lborder">Preis (ungeb., nicht abgew.):</th>
<td colspan="2" class="center">@(minPrice != maxPrice ? $"{minPrice:N4}{maxPrice:N4}" : $"{minPrice:N4}") @Model.CurrencySymbol/kg</td>
@@ -135,8 +135,8 @@
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(vat):N2}")</td>
@{
var gebRows = Model.Data.Rows
.Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null)
.Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice);
.Where(r => r.Geb.Price != null && r.Ungeb.Price != null)
.Select(r => r.Geb.Price - r.Ungeb.Price);
var minGeb = gebRows.Min();
var maxGeb = gebRows.Max();
}
@@ -164,14 +164,14 @@
<td class="number">@Utils.GetSign(considered)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(considered):N2}")</td>
<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>
<th colspan="2">Auszahlungsbetrag:</th>
<td class="number tborder"></td>
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{totalSum:N2}")</td>
<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>
</tbody>
</table>
@@ -219,22 +219,19 @@
</table>
<table class="payment-variant-data">
<colgroup>
<col style="width: 30mm;"/>
<col style="width: 20mm;"/>
<col style="width: 25mm;"/>
<col style="width: 20mm;"/>
<col style="width: 25mm;"/>
<col style="width: 20mm;"/>
<col style="width: 25mm;"/>
<col style="width: 19mm;"/>
<col style="width: 18mm;"/>
<col style="width: 15mm;"/>
<col style="width: 18mm;"/>
<col style="width: 15mm;"/>
<col style="width: 18mm;"/>
<col style="width: 15mm;"/>
<col style="width: 22mm;"/>
</colgroup>
<thead>
<tr>
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
<th>Gradation</th>
<th colspan="2">ungebunden</th>
<th colspan="2">attributlos gebunden</th>
<th colspan="2">gebunden</th>
<th>Gesamt</th>
</tr>
@@ -244,8 +241,6 @@
<th>[@(Model.CurrencySymbol)/kg]</th>
<th>[kg]</th>
<th>[@(Model.CurrencySymbol)/kg]</th>
<th>[kg]</th>
<th>[@(Model.CurrencySymbol)/kg]</th>
<th>[@(Model.CurrencySymbol)]</th>
</tr>
</thead>
@@ -263,8 +258,6 @@
<th colspan="2">@hdr</th>
<td class="number">@($"{rows.Sum(r => r.Ungeb.Weight):N0}")</td>
<td></td>
<td class="number">@($"{rows.Sum(r => r.LowGeb.Weight):N0}")</td>
<td></td>
<td class="number">@($"{rows.Sum(r => r.Geb.Weight):N0}")</td>
<td></td>
<td class="number">@($"{rows.Sum(r => r.Amount):N2}")</td>
@@ -274,11 +267,9 @@
<td>@(row.QualityLevel)</td>
<td class="center">@($"{row.Oe:N0}")</td>
<td class="number">@(row.Ungeb.Weight != 0 ? $"{row.Ungeb.Weight:N0}" : "-")</td>
<td class="number">@(row.Ungeb.MaxPrice != null ? $"{row.Ungeb.MaxPrice:N4}" : "-")</td>
<td class="number">@(row.LowGeb.Weight != 0 ? $"{row.LowGeb.Weight:N0}" : "-")</td>
<td class="number">@(row.LowGeb.MaxPrice != null ? $"{row.LowGeb.MaxPrice:N4}" : "-")</td>
<td class="number">@(row.Ungeb.Price != null ? $"{row.Ungeb.Price:N4}" : "-")</td>
<td class="number">@(row.Geb.Weight != 0 ? $"{row.Geb.Weight:N0}" : "-")</td>
<td class="number">@(row.Geb.MaxPrice != null ? $"{row.Geb.MaxPrice:N4}" : "-")</td>
<td class="number">@(row.Geb.Price != null ? $"{row.Geb.Price:N4}" : "-")</td>
<td class="number">@($"{row.Amount:N2}")</td>
</tr>
lastHdr = hdr;
+1 -1
View File
@@ -2,7 +2,7 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-top: 10mm;
margin-bottom: 2mm;
}
+1 -1
View File
@@ -3,7 +3,7 @@
@inherits TemplatePage<Elwig.Documents.WineQualityStatistics>
@model Elwig.Documents.WineQualityStatistics
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\WineQualityStatistics.css" />
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\WineQualityStatistics.css"/>
<main>
<h1>Qualitätsstatistik</h1>
<h2>@Model.Filter</h2>
+1 -1
View File
@@ -2,7 +2,7 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-top: 10mm;
margin-bottom: 2mm;
}
+16 -15
View File
@@ -2,12 +2,12 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.0.3.0</Version>
<Version>1.0.0.0</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
@@ -20,25 +20,26 @@
<EmbeddedResource Include="Resources\Sql\*" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="call fetch-resources.bat" />
</Target>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="LinqKit" Version="1.3.9" />
<PackageReference Include="MailKit" Version="4.14.1" />
<PackageReference Include="LinqKit" Version="1.3.8" />
<PackageReference Include="MailKit" Version="4.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.2" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
<PackageReference Include="NJsonSchema" Version="11.5.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.4" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<PackageReference Include="NJsonSchema" Version="11.2.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="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.1.57" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
<PackageReference Include="System.IO.Hashing" Version="10.0.2" />
<PackageReference Include="System.IO.Ports" Version="10.0.2" />
<PackageReference Include="System.Management" Version="10.0.2" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.2" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.55" />
<PackageReference Include="System.IO.Ports" Version="9.0.4" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.4" />
</ItemGroup>
</Project>
+42 -1
View File
@@ -10,6 +10,7 @@ using Microsoft.Data.Sqlite;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Elwig.Models.Dtos;
using System.Reflection;
using System.Data;
namespace Elwig.Helpers {
@@ -66,7 +67,7 @@ namespace Elwig.Helpers {
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { 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<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
@@ -116,6 +117,39 @@ namespace Elwig.Helpers {
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) {
optionsBuilder.UseSqlite(ConnectionString);
optionsBuilder.UseLazyLoadingProxies();
@@ -214,6 +248,13 @@ namespace Elwig.Helpers {
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) {
foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
var mod = new DeliveryPartModifier {
+9 -9
View File
@@ -9,17 +9,17 @@ namespace Elwig.Helpers {
public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 36;
public static readonly int RequiredSchemaVersion = 31;
private static int VersionOffset = 0;
public static async Task<Version> CheckDb() {
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})");
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);
if (VersionOffset != 0) {
// schema was modified manually/externally
@@ -27,12 +27,12 @@ namespace Elwig.Helpers {
}
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));
if (App.Version > v) {
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;
@@ -67,20 +67,20 @@ namespace Elwig.Helpers {
if (toExecute.Count == 0)
return;
await cnx.ExecuteBatch("""
await AppDbContext.ExecuteBatch(cnx, """
PRAGMA locking_mode = EXCLUSIVE;
BEGIN EXCLUSIVE;
""");
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) {
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
}
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
COMMIT;
VACUUM;
PRAGMA schema_version = {toVersion * 100 + VersionOffset};
+6 -6
View File
@@ -27,7 +27,7 @@ namespace Elwig.Helpers.Billing {
public async Task FinishSeason() {
using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
UPDATE season
SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery 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) {
if (addMinBs < 1) addMinBs = 1;
using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
UPDATE member
SET business_shares = member.business_shares - h.business_shares
FROM member_history h
@@ -66,7 +66,7 @@ namespace Elwig.Helpers.Billing {
public async Task UnAdjustBusinessShares() {
using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
UPDATE member
SET business_shares = member.business_shares - h.business_shares
FROM member_history h
@@ -157,9 +157,9 @@ namespace Elwig.Helpers.Billing {
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) {
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
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})"))}
ON CONFLICT DO UPDATE
@@ -237,7 +237,7 @@ namespace Elwig.Helpers.Billing {
if (needed == 0) break;
}
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
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})"))}
ON CONFLICT DO UPDATE
+15 -34
View File
@@ -20,19 +20,13 @@ namespace Elwig.Helpers.Billing {
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 tx = await cnx.BeginTransactionAsync();
await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
await DeleteInDb(cnx);
await SetCalcTime(cnx);
KeyNotFoundException? exception = null;
try {
await CalculatePrices(cnx, strictPrices);
} catch (KeyNotFoundException e) {
if (strictPrices) throw;
exception = e;
}
await CalculatePrices(cnx);
if (Data.ConsiderDelieryModifiers) {
await CalculateDeliveryModifiers(cnx);
}
@@ -40,28 +34,26 @@ namespace Elwig.Helpers.Billing {
await CalculateMemberModifiers(cnx);
}
await tx.CommitAsync();
if (exception != null)
throw exception;
}
public async Task Commit() {
await Revert();
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)
SELECT s.year,
COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr,
m.mgnr,
v.avnr,
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
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,
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.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) +
IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
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
JOIN payment_variant v ON v.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)
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});
""");
}
public async Task Revert() {
using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr});
UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr});
""");
}
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})
""");
}
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 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 includePredecessor = true;
var modName = "Treue%";
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
SELECT c.year, {AvNr}, s.mgnr, 0,
ROUND(s.sum * COALESCE(m.abs, 0)),
@@ -138,7 +130,7 @@ namespace Elwig.Helpers.Billing {
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)
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
@@ -150,8 +142,7 @@ namespace Elwig.Helpers.Billing {
""");
}
protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) {
var invalid = new HashSet<string>();
protected async Task CalculatePrices(SqliteConnection cnx) {
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()) {
cmd.CommandText = $"""
@@ -181,31 +172,21 @@ namespace Elwig.Helpers.Billing {
var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
decimal price = 0;
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 price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
var priceL = PaymentVariant.Season.DecToDb(price);
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);
if (msg != null && strict)
throw new KeyNotFoundException(msg);
await cnx.ExecuteBatch($"""
await AppDbContext.ExecuteBatch(cnx, $"""
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})"))};
""");
if (msg != null)
throw new KeyNotFoundException(msg);
}
protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
var netMod = Data.NetWeightModifier.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)
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, 0, IIF(d.net_weight, {netMod}, {grossMod})
FROM delivery_part d
-2
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
namespace Elwig.Helpers.Billing {
public class GraphEntry {
@@ -41,7 +40,6 @@ namespace Elwig.Helpers.Billing {
public string VaributeStringSimple => (Abgewertet ? "Abgew.: " : "") + (Vaributes.Count != 0 ? (Vaributes.Count >= 25 ? "Restliche Sorten" : string.Join(", ", Vaributes.Select(c => c.Listing))) : "-");
public string VaributeString => Vaributes.Count != 0 ? string.Join("\n", Vaributes.Select(c => c.FullName)) : "-";
public string VaributeStringChange => (Abgewertet ? "A." : "") + string.Join(",", Vaributes.Select(c => c.Listing));
public Brush? Color => Vaributes.Select(v => v.Variety?.Color).Distinct().SingleOrDefault();
private readonly int Precision;
public GraphEntry(int id, int precision, BillingData.CurveMode mode) {
+12 -7
View File
@@ -19,6 +19,14 @@ namespace Elwig.Helpers {
public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
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 NameShort;
public string Name;
@@ -47,7 +55,6 @@ namespace Elwig.Helpers {
public string? Bic;
public string? UstIdNr;
public string? LfbisNr;
public string? OrganicAuthority;
public string? PhoneNr;
public string? FaxNr;
@@ -106,7 +113,6 @@ namespace Elwig.Helpers {
UstIdNr = parameters.GetValueOrDefault("CLIENT_USTIDNR");
Bic = parameters.GetValueOrDefault("CLIENT_BIC");
Iban = parameters.GetValueOrDefault("CLIENT_IBAN");
OrganicAuthority = parameters.GetValueOrDefault("CLIENT_ORGANIC_AUTHORITY");
switch (parameters.GetValueOrDefault("MODE_DELIVERYNOTE_STATS", "SHORT")?.ToUpper()) {
case "NONE": ModeDeliveryNoteStats = 0; break;
@@ -206,15 +212,15 @@ namespace Elwig.Helpers {
case 1: orderingMemberList = "NAME"; break;
case 2: orderingMemberList = "KG"; break;
}
string mailSendPostal = "WISH";
switch (MailSendPostal) {
string mailSendPostal = "MGNR";
switch (MailOrdering) {
case 0: mailSendPostal = "NONE"; break;
case 1: mailSendPostal = "NO_EMAIL"; break;
case 2: mailSendPostal = "WISH"; break;
case 3: mailSendPostal = "ALL"; break;
}
string mailSendEmail = "WISH";
switch (MailSendEmail) {
string mailSendEmail = "MGNR";
switch (MailOrdering) {
case 0: mailSendEmail = "NONE"; break;
case 1: mailSendEmail = "WISH"; break;
case 2: mailSendEmail = "ALL"; break;
@@ -251,7 +257,6 @@ namespace Elwig.Helpers {
("CLIENT_USTIDNR", UstIdNr),
("CLIENT_BIC", Bic),
("CLIENT_IBAN", Iban),
("CLIENT_ORGANIC_AUTHORITY", OrganicAuthority),
("MODE_DELIVERYNOTE_STATS", deliveryNoteStats),
("MODE_WINEQUALITYSTATISTICS", modeWineQualityStatistics),
("ORDERING_MEMBERLIST", orderingMemberList),
-6
View File
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -6,8 +5,6 @@ using Microsoft.Extensions.Configuration;
namespace Elwig.Helpers {
public enum WeighingMode { Gross, Net, Box }
public record struct ScaleConfig {
public string Id;
public string? Type;
@@ -44,7 +41,6 @@ namespace Elwig.Helpers {
public string DatabaseFile = App.DataPath + "database.sqlite3";
public string? DatabaseLog = null;
public string? Branch = null;
public WeighingMode? WeighingMode;
public string? UpdateUrl = null;
public bool UpdateAuto = false;
public string? SyncUrl = null;
@@ -78,8 +74,6 @@ namespace Elwig.Helpers {
DatabaseLog = log != null ? Path.Combine(Path.GetDirectoryName(FileName) ?? App.DataPath, log) : null;
Branch = config["general:branch"];
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"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
SyncUrl = config["sync:url"];
+1 -21
View File
@@ -15,9 +15,7 @@ namespace Elwig.Helpers.Export {
protected readonly char Separator;
protected string? Header;
public Csv(string filename, char separator = ';') :
this(filename, separator, Utils.UTF8) {
}
public Csv(string filename, char separator = ';') : this(filename, separator, Utils.UTF8) { }
public Csv(string filename, char separator, Encoding encoding) {
_writer = new StreamWriter(filename, false, encoding);
@@ -60,22 +58,4 @@ namespace Elwig.Helpers.Export {
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;
}));
}
}
}
-243
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);
}
}
}
}
+117 -291
View File
@@ -1,14 +1,14 @@
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.Json.Nodes;
using System.IO;
using System.Threading.Tasks;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System;
using System.Text.Json.Nodes;
using System.Linq;
using System.Windows;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
namespace Elwig.Helpers.Export {
public static class ElwigData {
@@ -17,6 +17,8 @@ namespace Elwig.Helpers.Export {
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() {
try {
return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
@@ -38,7 +40,6 @@ namespace Elwig.Helpers.Export {
Dictionary<string, int> currentLsNrs;
Dictionary<int, List<WbRd>> currentWbRde;
Dictionary<int, AT_Kg> kgs;
List<WbGl> currentWbGls;
using (var ctx = new AppDbContext()) {
branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId);
@@ -50,7 +51,6 @@ namespace Elwig.Helpers.Export {
currentWbRde = await ctx.WbRde
.GroupBy(r => r.KgNr)
.ToDictionaryAsync(g => g.Key, g => g.ToList());
currentWbGls = await ctx.WbGls.ToListAsync();
kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr);
}
@@ -61,12 +61,9 @@ namespace Elwig.Helpers.Export {
List<MemberEmailAddr> EmailAddresses,
List<AreaCom> AreaCommitments,
List<WbRd> Riede,
List<WbKg> WbKgs,
List<WbGl> WbGls,
List<Delivery> Deliveries,
List<DeliveryPart> DeliveryParts,
List<DeliveryPartModifier> Modifiers,
Dictionary<string, List<(int Id1, int Id2, DateTime CreatedAt, DateTime ModifiedAt)>> Timestamps)>();
List<DeliveryPartModifier> Modifiers)>();
var metaData = new List<(string FileName, string ZwstId, string Device,
int? MemberNum, string? MemberFilters,
@@ -74,117 +71,71 @@ namespace Elwig.Helpers.Export {
int? DeliveryNum, string? DeliveryFilters)>();
foreach (var filename in filenames) {
try {
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
["member"] = [],
["area_commitment"] = [],
["delivery"] = [],
})));
var r = data[^1];
// TODO read encrypted files
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
// TODO read encrypted files
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
await zip.CheckIntegrity();
var version = zip.GetEntry("version");
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
if (await reader.ReadToEndAsync() != "elwig:1")
throw new FileFormatException($"Ungültige Export-Datei ({filename})");
}
var version = zip.GetEntry("version");
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
if (await reader.ReadToEndAsync() != "elwig:1")
throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})");
var metaJson = zip.GetEntry("meta.json");
var meta = await JsonNode.ParseAsync(metaJson!.Open());
var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
metaData.Add((Path.GetFileName(filename),
meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
data.Add(new([], [], [], [], [], [], [], new([], [])));
var r = data[^1];
var membersJson = zip.GetEntry("members.json");
if (membersJson != null) {
using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (m, b, telNrs, emailAddrs) = obj.ToMember(kgs);
r.Members.Add(m);
if (b != null) r.BillingAddresses.Add(b);
r.TelephoneNumbers.AddRange(telNrs);
r.EmailAddresses.AddRange(emailAddrs);
}
}
var metaJson = zip.GetEntry("meta.json");
var meta = await JsonNode.ParseAsync(metaJson!.Open());
var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
metaData.Add((Path.GetFileName(filename),
meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
var wbKgsJson = zip.GetEntry("wb_kgs.json");
if (wbKgsJson != null) {
using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (k, g) = obj.ToWbKg(currentWbGls);
r.WbKgs.Add(k);
if (g != null) {
currentWbGls[g.GlNr] = g;
r.WbGls.Add(g);
}
var areaComsJson = zip.GetEntry("area_commitments.json");
if (areaComsJson != null) {
using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (areaCom, wbrd) = obj.ToAreaCom(kgs, currentWbRde);
r.AreaCommitments.Add(areaCom);
if (wbrd != null) {
currentWbRde[wbrd.KgNr].Add(wbrd);
r.Riede.Add(wbrd);
}
}
}
var membersJson = zip.GetEntry("members.json");
if (membersJson != null) {
using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
r.Members.Add(m);
if (b != null) r.BillingAddresses.Add(b);
r.TelephoneNumbers.AddRange(telNrs);
r.EmailAddresses.AddRange(emailAddrs);
if (timestamps.HasValue)
r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
var areaComsJson = zip.GetEntry("area_commitments.json");
if (areaComsJson != null) {
using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
r.AreaCommitments.Add(areaCom);
if (wbrd != null) {
r.Riede.Add(wbrd);
}
if (timestamps.HasValue)
r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
var deliveriesJson = zip.GetEntry("deliveries.json");
if (deliveriesJson != null) {
using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
r.Deliveries.Add(d);
r.DeliveryParts.AddRange(parts);
r.Modifiers.AddRange(mods);
r.Riede.AddRange(rde);
if (timestamps.HasValue)
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 deliveriesJson = zip.GetEntry("deliveries.json");
if (deliveriesJson != null) {
using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (d, parts, mods) = obj.ToDelivery(currentLsNrs, currentDids);
r.Deliveries.Add(d);
r.DeliveryParts.AddRange(parts);
r.Modifiers.AddRange(mods);
}
}
}
@@ -193,18 +144,12 @@ namespace Elwig.Helpers.Export {
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int Imported, int NotImported, string Filters)>();
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers), meta) in data.Zip(metaData)) {
var branch = branches[meta.ZwstId];
var device = meta.Device;
using var ctx = new AppDbContext();
var kgnrs = wbKgs.Select(k => k.KgNr).ToList();
var duplicateKgNrs = await ctx.WbKgs
.Where(k => kgnrs.Contains(k.KgNr))
.Select(k => k.KgNr)
.ToListAsync();
var mgnrs = members.Select(m => m.MgNr).ToList();
var duplicateMgNrs = await ctx.Members
.Where(m => mgnrs.Contains(m.MgNr))
@@ -258,12 +203,6 @@ namespace Elwig.Helpers.Export {
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
}
if (importDuplicateMembers || importNewMembers || importDuplicateDeliveries || importNewDeliveries) {
ctx.AddRange(wbGls);
ctx.UpdateRange(wbKgs.Where(k => duplicateKgNrs.Contains(k.KgNr)));
ctx.AddRange(wbKgs.Where(k => !duplicateKgNrs.Contains(k.KgNr)));
}
if (importDuplicateMembers) {
ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr)));
@@ -333,26 +272,6 @@ namespace Elwig.Helpers.Export {
}
await ctx.SaveChangesAsync();
var primaryKeys = new Dictionary<string, string>() {
["member"] = "mgnr, 0",
["area_commitment"] = "fbnr, 0",
["delivery"] = "year, did",
};
var updateStmts = timestamps
.SelectMany(e => e.Value.Select(m => $"UPDATE {e.Key} " +
$"SET ctime = {((DateTimeOffset)m.CreatedAt.ToUniversalTime()).ToUnixTimeSeconds()}, " +
$"mtime = {((DateTimeOffset)m.ModifiedAt.ToUniversalTime()).ToUnixTimeSeconds()} " +
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2});"));
using var cnx = AppDbContext.Connect();
await cnx.ExecuteBatch($"""
BEGIN;
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
{string.Join("\n", updateStmts)}
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
COMMIT;
""");
await AddImportedFiles(Path.GetFileName(meta.FileName));
}
App.HintContextChange();
@@ -383,9 +302,9 @@ namespace Elwig.Helpers.Export {
"Importieren erfolgreich",
MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\nEvtl. muss die Datenbank manuell auf dieses Gerät kopieren werden.\n\n" + exc.Message;
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(str, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
GC.Collect();
GC.WaitForPendingFinalizers();
@@ -400,30 +319,26 @@ namespace Elwig.Helpers.Export {
) == MessageBoxResult.Yes;
}
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<string> filters) {
return new ElwigExport {
Members = (members, filters),
WbKgs = (wbKgs, ["von exportierten Mitgliedern"]),
Members = (members, filters)
}.Export(filename);
}
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<string> filters) {
return new ElwigExport {
Members = (members, filters),
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]),
}.Export(filename);
}
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
return new ElwigExport {
Deliveries = (deliveries, filters),
WbKgs = (wbKgs, ["von exportierten Lieferungen"]),
Deliveries = (deliveries, filters)
}.Export(filename);
}
public class ElwigExport {
public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
public (IEnumerable<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
@@ -444,12 +359,6 @@ namespace Elwig.Helpers.Export {
["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName,
};
if (WbKgs != null) {
obj["wb_kgs"] = new JsonObject {
["count"] = WbKgs.Value.WbKgs.Count(),
["filters"] = new JsonArray(WbKgs.Value.Filters.Select(f => (JsonNode)f).ToArray()),
};
}
if (Members != null)
obj["members"] = new JsonObject {
["count"] = Members.Value.Members.Count(),
@@ -466,69 +375,34 @@ namespace Elwig.Helpers.Export {
["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
["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
if (WbKgs != null) {
var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var k in WbKgs.Value.WbKgs) {
await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.JsonOpts));
}
}
if (Members != null) {
var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
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) {
var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
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) {
var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var d in Deliveries.Value.Deliveries) {
await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts));
await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts));
}
}
}
}
public static JsonObject ToJson(this WbKg k) {
return new JsonObject {
["kgnr"] = k.KgNr,
["großlage"] = k.Gl?.Name,
};
}
public static (WbKg, WbGl?) ToWbKg(this JsonNode json, List<WbGl> gls) {
var grosslage = json["großlage"]?.AsValue().GetValue<string>();
WbGl? gl = null;
bool newGl = false;
if (grosslage != null) {
gl = gls.FirstOrDefault(g => g.Name == grosslage);
if (gl == null) {
newGl = true;
gl = new WbGl {
GlNr = (gls.Count == 0 ? 1 : gls.Max(g => g.GlNr)) + 1,
Name = grosslage,
};
gls[gl.GlNr] = gl;
}
}
return (new WbKg {
KgNr = json["kgnr"]!.AsValue().GetValue<int>(),
GlNr = gl?.GlNr,
}, newGl ? gl : null);
}
public static JsonObject ToJson(this Member m) {
return new JsonObject {
["mgnr"] = m.MgNr,
@@ -586,19 +460,15 @@ namespace Elwig.Helpers.Export {
return obj;
}).ToArray()),
["comment"] = m.Comment,
["created_at"] = $"{m.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{m.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs) {
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs) {
var mgnr = json["mgnr"]!.AsValue().GetValue<int>();
var kgnr = json["default_kgnr"]?.AsValue().GetValue<int>();
if (kgnr != null && !kgs.Values.Any(k => k.WbKg?.KgNr == kgnr)) {
throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten → Herkunftshierarchie)");
}
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new Member {
MgNr = mgnr,
PredecessorMgNr = json["predecessor_mgnr"]?.AsValue().GetValue<int>(),
@@ -632,7 +502,6 @@ namespace Elwig.Helpers.Export {
ContactViaPost = json["contact_postal"]?.AsValue().GetValue<bool>() ?? false,
ContactViaEmail = json["contact_email"]?.AsValue().GetValue<bool>() ?? false,
Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now,
}, json["billing_address"] is JsonObject a ? new BillingAddr {
MgNr = mgnr,
FullName = a["name"]!.AsValue().GetValue<string>(),
@@ -650,10 +519,7 @@ namespace Elwig.Helpers.Export {
Nr = i + 1,
Address = a["address"]!.AsValue().GetValue<string>(),
Comment = a["comment"]?.AsValue().GetValue<string>(),
}).ToList(),
createdAt == null || modifiedAt == null ? null :
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
}).ToList());
}
public static JsonObject ToJson(this AreaCom c) {
@@ -669,32 +535,27 @@ namespace Elwig.Helpers.Export {
["year_from"] = c.YearFrom,
["year_to"] = c.YearTo,
["comment"] = c.Comment,
["created_at"] = $"{c.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{c.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) {
public static (AreaCom, WbRd?) ToAreaCom(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
var ried = json["ried"]?.AsValue().GetValue<string>();
WbRd? rd = null;
bool newRd = false;
if (ried != null) {
var rde = riede.GetValueOrDefault(kgnr, []);
var rde = riede[kgnr] ?? throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten → Herkunftshierarchie)");
rd = rde.FirstOrDefault(r => r.Name == ried);
if (rd == null) {
newRd = true;
rd = new WbRd {
KgNr = kgnr,
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
RdNr = (rde.Count == 0 ? 0 : rde.Max(r => r.RdNr)) + 1,
Name = ried,
};
rde.Add(rd);
riede[rd.KgNr] = rde;
}
}
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaCom {
FbNr = json["fbnr"]!.AsValue().GetValue<int>(),
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
@@ -703,15 +564,11 @@ namespace Elwig.Helpers.Export {
Area = json["area"]!.AsValue().GetValue<int>(),
KgNr = kgnr,
GstNr = json["gstnr"]?.AsValue().GetValue<string>() ?? "-",
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(),
RdNr = rd?.RdNr,
YearFrom = json["year_from"]?.AsValue().GetValue<int>(),
YearTo = json["year_to"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now,
}, newRd ? rd : null,
createdAt == null || modifiedAt == null ? null :
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
}, newRd ? rd : null);
}
public static JsonObject ToJson(this Delivery d) {
@@ -734,18 +591,16 @@ namespace Elwig.Helpers.Export {
["qualid"] = p.QualId,
["hkid"] = p.HkId,
["kgnr"] = p.KgNr,
["ried"] = p.Rd?.Name,
["rdnr"] = p.RdNr,
["net_weight"] = p.IsNetWeight,
["manual_weighing"] = p.IsManualWeighing,
["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
["comment"] = p.Comment,
["created_at"] = $"{p.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{p.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
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.Unloading != null) obj["unloading"] = p.Unloading;
if (p.Temperature != null) obj["temperature"] = p.Temperature;
if (p.Acid != null) obj["acid"] = p.Acid;
if (p.ScaleId != null) obj["scale_id"] = p.ScaleId;
@@ -754,12 +609,10 @@ namespace Elwig.Helpers.Export {
return obj;
}).ToArray()),
["comment"] = d.Comment,
["created_at"] = $"{d.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{d.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>, List<WbRd>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids) {
var year = json["year"]!.AsValue().GetValue<int>();
var lsnr = json["lsnr"]!.AsValue().GetValue<string>();
var did = currentLsNrs.GetValueOrDefault(lsnr, -1);
@@ -768,9 +621,6 @@ namespace Elwig.Helpers.Export {
did = ++currentDids[year];
}
currentLsNrs[lsnr] = did;
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
var wbRde = new List<WbRd>();
return (new Delivery {
Year = year,
DId = did,
@@ -781,61 +631,37 @@ namespace Elwig.Helpers.Export {
LsNr = lsnr,
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now,
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => {
var kgnr = p["kgnr"]!.AsValue().GetValue<int>();
var ried = p["ried"]?.AsValue().GetValue<string>();
WbRd? rd = null;
if (ried != null) {
var rde = riede.GetValueOrDefault(kgnr, []);
rd = rde.FirstOrDefault(r => r.Name == ried);
if (rd == null) {
rd = new WbRd {
KgNr = kgnr,
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
Name = ried,
};
rde.Add(rd);
riede[rd.KgNr] = rde;
wbRde.Add(rd);
}
}
return new DeliveryPart {
Year = year,
DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
SortId = p["sortid"]!.AsValue().GetValue<string>(),
AttrId = p["attrid"]?.AsValue().GetValue<string>(),
CultId = p["cultid"]?.AsValue().GetValue<string>(),
Weight = p["weight"]!.AsValue().GetValue<int>(),
Kmw = p["kmw"]!.AsValue().GetValue<double>(),
QualId = p["qualid"]!.AsValue().GetValue<string>(),
HkId = p["hkid"]!.AsValue().GetValue<string>(),
KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
RdNr = rd?.RdNr ?? p["rdnr"]?.AsValue().GetValue<int>(),
IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
Comment = p["comment"]?.AsValue().GetValue<string>(),
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
IsHandPicked = p["hand_picked"]?.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>(),
Acid = p["acid"]?.AsValue().GetValue<double>(),
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
};
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
Year = year,
DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
SortId = p["sortid"]!.AsValue().GetValue<string>(),
AttrId = p["attrid"]?.AsValue().GetValue<string>(),
CultId = p["cultid"]?.AsValue().GetValue<string>(),
Weight = p["weight"]!.AsValue().GetValue<int>(),
Kmw = p["kmw"]!.AsValue().GetValue<double>(),
QualId = p["qualid"]!.AsValue().GetValue<string>(),
HkId = p["hkid"]!.AsValue().GetValue<string>(),
KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
RdNr = p["rdnr"]?.AsValue().GetValue<int>(),
IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
Comment = p["comment"]?.AsValue().GetValue<string>(),
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
IsLesewagen = p["lesewagen"]?.AsValue().GetValue<bool>(),
IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(),
Temperature = p["temperature"]?.AsValue().GetValue<double>(),
Acid = p["acid"]?.AsValue().GetValue<double>(),
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
Year = year,
DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
ModId = m!.AsValue().GetValue<string>(),
})).ToList(),
wbRde,
createdAt == null || modifiedAt == null ? null :
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
})).ToList());
}
}
}
+1 -1
View File
@@ -317,7 +317,7 @@ namespace Elwig.Helpers.Export {
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{SecurityElement.Escape(data.ToString())}</text:p></{ct}>";
}
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-columns-repeated=\"{colSpan - 1}\"/>\r\n" : "");
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-rows-repeated=\"{colSpan - 1}\"/>\r\n" : "");
}
}
}
-69
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", "");
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
namespace Elwig.Helpers {
public enum ExportMode {
Show, SaveList, SavePdf, Print, Email, Vcf, Export, Upload
Show, SaveList, SavePdf, Print, Email, Export, Upload
}
}
+1 -51
View File
@@ -1,17 +1,12 @@
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.IO.Hashing;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Elwig.Helpers {
public static partial class Extensions {
static partial class Extensions {
[LibraryImport("msvcrt.dll")]
[UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
@@ -100,50 +95,5 @@ namespace Elwig.Helpers {
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
progress.Report(100.0);
}
public static async Task CheckIntegrity(this ZipArchive zip) {
var crc = new Crc32();
foreach (var entry in zip.Entries) {
crc.Reset();
using var stream = entry.Open();
await crc.AppendAsync(stream);
if (crc.GetCurrentHashAsUInt32() != entry.Crc32)
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];
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ namespace Elwig.Helpers.Printing {
public static async Task Init(Action? evtHandler = null) {
var e = new RazorLightEngineBuilder()
.UseFileSystemProject(App.DocumentsPath)
.UseFileSystemProject(App.DataPath + "resources")
.UseMemoryCachingProvider()
.Build();
+17 -42
View File
@@ -1,28 +1,24 @@
using System.Threading.Tasks;
using Elwig.Windows;
using PdfiumViewer;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Printing;
using System;
using System.IO;
using System.Collections.Generic;
using System.Windows;
using System.Text.RegularExpressions;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using PdfiumViewer;
using System.Drawing.Printing;
namespace Elwig.Helpers.Printing {
public static class Pdf {
private static readonly string WinziPrint = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Installer/Files/WinziPrint.exe") :
new string[] { App.InstallPath }
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
.Select(x => Path.Combine(x, "WinziPrint.exe"))
.Where(File.Exists)
.FirstOrDefault() ?? throw new FileNotFoundException("WiniPrint executable not found");
private static readonly string WinziPrint = new string[] { App.ExePath }
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
.Select(x => Path.Combine(x, "WinziPrint.exe"))
.Where(File.Exists)
.FirstOrDefault() ?? throw new FileNotFoundException("WiniPrint executable not found");
private static Process? WinziPrintProc;
public static bool IsReady => WinziPrintProc != null;
@@ -50,45 +46,24 @@ namespace Elwig.Helpers.Printing {
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) {
return await Convert([htmlPath], pdfPath, doublePaged, cancelToken, progress);
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, 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");
progress?.Report(0.0);
using var client = new TcpClient("127.0.0.1", 30983);
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(
"-e utf-8;-p;" + (doublePaged ? "-2;" : "") +
$"{string.Join(';', htmlPath)};{pdfPath}" +
"\r\n"));
bool cancelled = false;
using var reader = new StreamReader(stream);
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");
if (line.StartsWith("error:")) {
var msg = line[6..].Trim();
if (msg == "aborted")
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
throw new IOException($"WinziPrint: {msg}");
throw new IOException($"WinziPrint: {line[6..].Trim()}");
} else if (line.StartsWith("progress:")) {
var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray();
progress?.Report(100.0 * parts[0] / parts[1]);
-60
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();
}
}
}
}
+63 -87
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.Documents;
using Elwig.Helpers.Billing;
using Elwig.Models;
using System.Text;
using System.Numerics;
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.Security;
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.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Collections;
using Elwig.Documents;
using MimeKit;
using System.Windows.Input;
using LinqKit;
using System.Linq.Expressions;
using Elwig.Models;
using Microsoft.Win32;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
namespace Elwig.Helpers {
public static partial class Utils {
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 CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
@@ -432,13 +430,12 @@ namespace Elwig.Helpers {
var client = new HttpClient() {
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();
if (accept != null)
client.DefaultRequestHeaders.Accept.Add(new(accept));
if (username != null || password != null)
client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(Utils.UTF8.GetBytes($"{username}:{password}")));
client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(
Utils.UTF8.GetBytes($"{username}:{password}")));
return client;
}
@@ -457,19 +454,15 @@ namespace Elwig.Helpers {
}
public static async Task UploadExportData(string zip, string url, string username, string password) {
if (url.StartsWith("https://elwig.at/clients/"))
url = "https://sync.elwig.at/" + url[25..];
if (!url.EndsWith('/')) url += "/";
using var client = GetHttpClient(username, password, accept: "application/json");
using var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
content.Headers.ContentType = new("application/zip");
using var res = await client.PutAsync(url + Path.GetFileName(zip), content);
res.EnsureSuccessStatusCode();
}
public static async Task<JsonArray> GetExportMetaData(string url, string username, string password) {
if (url.StartsWith("https://elwig.at/clients/"))
url = "https://sync.elwig.at/" + url[25..];
using var client = GetHttpClient(username, password, accept: "application/json");
using var res = await client.GetAsync(url);
res.EnsureSuccessStatusCode();
@@ -502,67 +495,48 @@ namespace Elwig.Helpers {
public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
if (App.Config.Smtp == null)
return false;
return await Task.Run(async () => {
await AddSentMailBody(subject, text, 1);
SmtpClient? client = null;
try {
client = await GetSmtpClient();
using var msg = new MimeMessage();
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address)));
msg.Subject = subject;
var body = new Multipart("mixed") {
new TextPart("plain") { Text = text }
};
foreach (var doc in docs) {
var name = NormalizeFileName(doc.Title);
body.Add(doc.AsEmailAttachment($"{name}.pdf"));
}
msg.Body = body;
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) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
} finally {
if (client != null)
await client.DisconnectAsync(true);
client?.Dispose();
SmtpClient? client = null;
try {
Mouse.OverrideCursor = Cursors.AppStarting;
client = await GetSmtpClient();
using var msg = new MimeMessage();
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address)));
msg.Subject = subject;
var body = new Multipart("mixed") {
new TextPart("plain") { Text = text }
};
foreach (var doc in docs) {
var name = NormalizeFileName(doc.Title);
body.Add(doc.AsEmailAttachment($"{name}.pdf"));
}
return true;
});
msg.Body = body;
await client!.SendAsync(msg);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
} finally {
if (client != null)
await client.DisconnectAsync(true);
client?.Dispose();
Mouse.OverrideCursor = null;
}
return true;
}
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 (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.Print();
} 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();
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
var success = await SendEmail(e.Item1, e.Item2, e.Item3, [doc]);
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);
} 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() {
FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf",
DefaultExt = "pdf",
@@ -585,7 +559,9 @@ namespace Elwig.Helpers {
Log = "Application",
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) {
@@ -675,9 +651,9 @@ namespace Elwig.Helpers {
}
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")
.Where(n => Path.GetFileName(n)[..19].CompareTo(dt) <= 0)
.Where(n => Path.GetFileName(n).CompareTo(dt) <= 0)
.Order()
.LastOrDefault();
if (filename == null)
-50
View File
@@ -628,55 +628,5 @@ namespace Elwig.Helpers {
return new(true, null);
}
public static ValidationResult CheckOrganicAuthorityCode(TextBox input, bool required) {
string text = "";
int pos = input.CaretIndex;
for (int i = 0, v = 0; i < input.Text.Length; i++) {
char ch = char.ToUpper(input.Text[i]);
if (v < 2 && char.IsAsciiLetter(ch)) {
v++;
text += ch;
} else if ((v == 2 || v == 6) && ch == '-') {
v++;
text += ch;
} else if (v >= 2 && char.IsLetterOrDigit(ch)) {
if (text.StartsWith("AT")) {
if (v == 3 && ch == 'B' || v == 4 && ch == 'I' || v == 5 && ch == 'O') {
v++;
text += ch;
} else if (v > 6 && char.IsAsciiDigit(ch)) {
v++;
text += ch;
}
} else {
v++;
text += ch;
}
}
if (i == input.CaretIndex - 1) {
pos = text.Length;
} else if (text.StartsWith("AT") && v >= 10) {
break;
}
}
input.Text = text;
input.CaretIndex = pos;
if (text.Length == 0)
return required ? new(false, "Bio-Kontrollstellen-Code ist nicht optional") : new(true, null);
if (text.StartsWith("AT")) {
if (text.Length != 10) {
return new(false, "Bio-Kontrollstellen-Code ist ungültig");
}
} else {
return new(false, "Not implemented yet");
}
return new(true, null);
}
}
}
+7 -39
View File
@@ -15,17 +15,15 @@ namespace Elwig.Helpers.Weighing {
public bool IsReady { 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 readonly Thread BackgroundThread;
private readonly string Connection;
public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) :
base(cnx, null, null, null, log, true, !required) {
public AveryEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
base(cnx, empty, filling, limit, log) {
ScaleId = id;
Model = model;
Connection = cnx;
IsReady = true;
HasFillingClearance = false;
Stream.WriteTimeout = -1;
@@ -52,49 +50,19 @@ namespace Elwig.Helpers.Weighing {
var data = await Receive();
if (data != null)
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) {
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);
}
}
}
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() {
var line = "";
while (line.Length < 33) {
var ch = Reader.Read();
if (ch == -1) {
throw new IOException("Connection closed");
return null;
} else if (line.Length > 0 || ch == ' ') {
line += char.ToString((char)ch);
}
@@ -103,7 +71,7 @@ namespace Elwig.Helpers.Weighing {
if (line == null || line == "") {
return null;
} 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];
@@ -113,7 +81,7 @@ namespace Elwig.Helpers.Weighing {
var unit = line[30..32];
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;
+4 -4
View File
@@ -36,7 +36,7 @@ namespace Elwig.Helpers.Weighing {
var line = await Reader.ReadUntilAsync('\x03');
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
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];
@@ -45,9 +45,9 @@ namespace Elwig.Helpers.Weighing {
switch (status[1]) {
case 'M': msg = "Waage in Bewegung"; break;
}
throw new WeighingException($"Waagenfehler {status}: {msg}");
throw new IOException($"Waagenfehler {status}: {msg}");
} 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];
@@ -57,7 +57,7 @@ namespace Elwig.Helpers.Weighing {
await SendCommand(incIdentNr ? '\x05' : '?');
string record = await ReceiveResponse();
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 brutto = line[ 0.. 7].Trim();
+6 -14
View File
@@ -1,9 +1,8 @@
using System;
using System.IO;
using System.IO.Ports;
using System.IO;
using System.Net.Sockets;
using System;
using System.Text;
using System.Windows;
namespace Elwig.Helpers.Weighing {
public abstract class Scale : IDisposable {
@@ -28,7 +27,7 @@ namespace Elwig.Helpers.Weighing {
if (config.Type == "SysTec-IT") {
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} 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") {
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} 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:")) {
try {
Serial = Utils.OpenSerialConnection(cnx);
} catch (Exception e) {
if (!softFail) throw;
if (!failSilent)
MessageBox.Show($"Verbindung zu Waage konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
Stream = Serial?.BaseStream ?? Stream.Null;
Serial = Utils.OpenSerialConnection(cnx);
Stream = Serial.BaseStream;
} else if (cnx.StartsWith("tcp:")) {
Tcp = Utils.OpenTcpConnection(cnx);
Stream = Tcp.GetStream();
+9 -9
View File
@@ -34,14 +34,14 @@ namespace Elwig.Helpers.Weighing {
var line = await Reader.ReadUntilAsync("\r\n");
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
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];
string msg = $"Unbekannter Fehler (Fehler code {error})";
if (error[0] == '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') {
switch (error[1]) {
@@ -52,21 +52,21 @@ namespace Elwig.Helpers.Weighing {
case '6': msg = "Drucker nicht bereit"; 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') {
switch (error[1]) {
case '0': msg = "Brutto negativ"; break;
}
throw new WeighingException($"Fehler {error}: {msg}");
throw new IOException($"Fehler {error}: {msg}");
} else if (error[0] == '3') {
switch (error[1]) {
case '1': msg = "Übertragunsfehler"; break;
case '2': msg = "Ungültiger Befehl"; break;
case '3': msg = "Ungültiger Parameter"; break;
}
throw new WeighingException($"Kommunikationsfehler {error}: {msg}");
throw new IOException($"Kommunikationsfehler {error}: {msg}");
} 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];
@@ -76,7 +76,7 @@ namespace Elwig.Helpers.Weighing {
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}");
string record = await ReceiveResponse();
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 status = line[ 0.. 2];
@@ -94,9 +94,9 @@ namespace Elwig.Helpers.Weighing {
var crc16 = line[52..60].Trim();
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") {
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;
@@ -1,6 +0,0 @@
using System;
namespace Elwig.Helpers.Weighing {
public class WeighingException(string? message = null) : Exception(message) {
}
}
+6 -6
View File
@@ -27,14 +27,14 @@ namespace Elwig.Models.Dtos {
MgNr = mgnr;
}
public static async Task<IDictionary<int, CreditNoteDeliveryData>> ForPaymentVariant(DbSet<CreditNoteDeliveryRowSingle> table, DbSet<PaymentVar> paymentVariants, int year, int avnr) {
var variant = await paymentVariants.FindAsync(year, avnr);
public static async Task<IDictionary<int, CreditNoteDeliveryData>> ForPaymentVariant(DbSet<CreditNoteDeliveryRowSingle> table, DbSet<Season> seasons, int year, int avnr) {
var variant = (await seasons.FindAsync(year))?.PaymentVariants.Where(v => v.AvNr == avnr).SingleOrDefault();
BillingData? varData = null;
try { varData = variant != null ? BillingData.FromJson(variant.Data) : null; } catch { }
return (await FromDbSet(table, year, avnr))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
(k, g) => new CreditNoteDeliveryRow(g, variant, varData))
(k, g) => new CreditNoteDeliveryRow(g, seasons, varData?.NetWeightModifier ?? 0.0, varData?.GrossWeightModifier ?? 0.0))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr },
(k, g) => new CreditNoteDeliveryData(g, k.Year, k.TgNr, mgnr: k.MgNr))
@@ -86,13 +86,13 @@ namespace Elwig.Models.Dtos {
public decimal? Amount;
public double WeighingModifier;
public CreditNoteDeliveryRow(IEnumerable<CreditNoteDeliveryRowSingle> rows, PaymentVar paymentVariant, BillingData? varData) {
public CreditNoteDeliveryRow(IEnumerable<CreditNoteDeliveryRowSingle> rows, DbSet<Season> seasons, double netWeightModifier, double grossWeightModifier) {
var f = rows.First();
Year = f.Year;
TgNr = f.TgNr;
AvNr = f.AvNr;
MgNr = f.MgNr;
var season = paymentVariant.Season;
var season = seasons.Find(Year);
LsNr = f.LsNr;
DPNr = f.DPNr;
@@ -115,7 +115,7 @@ namespace Elwig.Models.Dtos {
b.Price != null ? season?.DecFromDb((long)b.Price) : null,
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
.ToArray();
WeighingModifier = (varData == null || !varData.ConsiderDelieryModifiers) ? 0 : f.NetWeight ? varData.NetWeightModifier : varData.GrossWeightModifier;
WeighingModifier = f.NetWeight ? netWeightModifier : grossWeightModifier;
Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
var amt = netAmount * (decimal)(1.0 + WeighingModifier);
+8 -8
View File
@@ -17,9 +17,9 @@ namespace Elwig.Models.Dtos {
("Name2", "Vorname", null, 40),
("DefaultKg", "Ort", null, 40),
("SortId", "Sorte", null, 10),
("Weight", "Menge", "kg", 20),
("CreatedAt", "Angemeldet", null, 35),
("ModifiedAt", "Geändert", null, 35),
("Weight", "Gewicht", "kg", 20),
("CreatedTimestamp", "Angemeldet", null, 35),
("ModifiedTimestamp", "Geändert", null, 35),
("Status", "Status", null, 20),
];
@@ -47,8 +47,8 @@ namespace Elwig.Models.Dtos {
public string? DefaultKg;
public string SortId;
public string Variety;
public DateTime CreatedAt;
public DateTime? ModifiedAt;
public DateTime CreatedTimestamp;
public DateTime? ModifiedTimestamp;
public int Weight;
public string? Status;
@@ -65,10 +65,10 @@ namespace Elwig.Models.Dtos {
DefaultKg = m.DefaultKg?.Name;
SortId = a.SortId;
Variety = a.Variety.Name;
CreatedAt = a.CreatedAt;
ModifiedAt = a.ModifiedAt == a.CreatedAt ? null : a.ModifiedAt;
CreatedTimestamp = a.CreatedTimestamp;
ModifiedTimestamp = a.ModifiedTimestamp == a.CreatedTimestamp ? null : a.ModifiedTimestamp;
Weight = a.Weight;
Status = s.AncmtTo == null ? null : a.CreatedAt >= s.AncmtTo ? "verspät." : "ok";
Status = s.AncmtTo == null ? null : a.CreatedTimestamp >= s.AncmtTo ? "verspät." : "ok";
}
}
}
@@ -16,7 +16,7 @@ namespace Elwig.Models.Dtos {
("QualityLevel", "Qualitätsstufe", null, 25),
("Gradation", "Gradation", "°Oe|°KMW", 32),
("Buckets", "Flächenbindung", "|kg", 36),
("Weight", "Menge", "kg", 16),
("Weight", "Gewicht", "kg", 16),
];
private readonly int MgNr;
+1 -1
View File
@@ -24,7 +24,7 @@ namespace Elwig.Models.Dtos {
("CultId", "Bewirt.", null, 15),
("QualId", "Qualität", null, 15),
("Gradation", "Gradation", "°Oe|°KMW", 40),
("Weight", "Menge", "kg", 20),
("Weight", "Gewicht", "kg", 20),
("IsNetWeight", "Gerebelt", null, 20),
("HkId", "Herkunft", null, 20),
("Modifiers", "Zu-/Abschläge", null, 40),
-59
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;
}
}
@@ -1,13 +1,13 @@
using Elwig.Models.Entities;
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 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),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
@@ -16,63 +16,104 @@ namespace Elwig.Models.Dtos {
("Locality", "Ort", null, 60),
("SortIds", "Sorte", null, 12),
("AttrIds", "Attribut", null, 16),
("CultIds", "Bewirt.", null, 16),
("Weights", "Geliefert", "kg", 22),
("Areas", "Fläche", "m²", 22),
("Yields", "Ertrag", "kg/ha", 22),
];
public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVarietyRow> rows, List<string> filterNames) :
base("Liefermengen pro Sorte", "Liefermengen pro Mitglied, Sorte, Attribut und Bewirtschaftungsart", string.Join(" / ", filterNames), rows, FieldNames) {
public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) {
}
public static async Task<MemberDeliveryPerVarietyData> 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,
p.SortId,
p.AttrId,
p.CultId,
})
.Select(g => new {
g.Key,
Weight = g.Sum(p => p.Weight),
})
.ToListAsync()).GroupBy(g => new {
g.Key.MgNr,
g.Key.Name,
g.Key.GivenName,
g.Key.Address,
g.Key.Plz,
g.Key.Ort,
}).Select(g => new MemberDeliveryPerVarietyRow {
MgNr = g.Key.MgNr,
Name1 = g.Key.Name,
Name2 = g.Key.GivenName,
Address = g.Key.Address,
Plz = g.Key.Plz,
Locality = g.Key.Ort,
SortIds = [.. g.Select(d => d.Key.SortId)],
AttrIds = [.. g.Select(d => d.Key.AttrId)],
CultIds = [.. g.Select(d => d.Key.CultId)],
Weights = [.. g.Select(d => d.Weight)],
}), filterNames);
public static async Task<MemberDeliveryPerVarietyData> ForSeason(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) {
return new MemberDeliveryPerVarietyData(
(await FromDbSet(table, year)).GroupBy(
r => r.MgNr,
(k, g) => new MemberDeliveryPerVariantRow(g)
), year);
}
private static async Task<IEnumerable<MemberDeliveryPerVariantRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVariantRowSingle> 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 MemberDeliveryPerVarietyRow {
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 string[] SortIds;
public required string?[] AttrIds;
public required string?[] CultIds;
public required int[] Weights;
public class MemberDeliveryPerVariantRow {
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 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; }
}
}
@@ -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; }
}
}
+15 -32
View File
@@ -18,9 +18,8 @@ namespace Elwig.Models.Dtos {
("Cultivation", "Bewirt.", null, 20),
("QualityLevel", "Qualitätsstufe", null, 30),
("Oe", "Gradation", "°Oe", 20),
("Ungeb", "ungebunden", "kg|€/kg|€/kg", 60),
("LowGeb", "attributlos gebunden", "kg|€/kg|€/kg", 60),
("Geb", "gebunden", "kg|€/kg|€/kg", 60),
("Ungeb", "ungebunden", "kg|€/kg", 40),
("Geb", "gebunden", "kg|€/kg", 40),
("Amount", "Gesamt", "€", 25),
];
@@ -31,9 +30,8 @@ namespace Elwig.Models.Dtos {
string? Cultivation,
string QualityLevel,
double Oe,
(int Weight, decimal? MinPrice, decimal? MaxPrice) Ungeb,
(int Weight, decimal? MinPrice, decimal? MaxPrice) LowGeb,
(int Weight, decimal? MinPrice, decimal? MaxPrice) Geb,
(int Weight, decimal? Price) Ungeb,
(int Weight, decimal? Price) Geb,
decimal Amount
);
@@ -44,9 +42,8 @@ namespace Elwig.Models.Dtos {
public static async Task<PaymentVariantSummaryData> ForPaymentVariant(PaymentVar v, DbSet<PaymentVariantSummaryRow> table) {
return new(v, (await FromDbSet(table, v.Year, v.AvNr))
.Select(r => new PaymentRow(r.Type, r.Variety, r.Attribute, r.Cultivation, r.QualityLevel, r.Oe,
(r.WeightUngeb, r.MinPriceUngeb != null ? Utils.DecFromDb(r.MinPriceUngeb.Value, v.Season.Precision) : null, r.MaxPriceUngeb != null ? Utils.DecFromDb(r.MaxPriceUngeb.Value, v.Season.Precision) : null),
(r.WeightLowGeb, r.MinPriceLowGeb != null ? Utils.DecFromDb(r.MinPriceLowGeb.Value, v.Season.Precision) : null, r.MaxPriceLowGeb != null ? Utils.DecFromDb(r.MaxPriceLowGeb.Value, v.Season.Precision) : null),
(r.WeightGeb, r.MinPriceGeb != null ? Utils.DecFromDb(r.MinPriceGeb.Value, v.Season.Precision) : null, r.MaxPriceGeb != null ? Utils.DecFromDb(r.MaxPriceGeb.Value, v.Season.Precision) : null),
(r.WeightUngeb, r.PriceUngeb != null ? Utils.DecFromDb(r.PriceUngeb.Value, v.Season.Precision) : null),
(r.WeightGeb, r.PriceGeb != null ? Utils.DecFromDb(r.PriceGeb.Value, v.Season.Precision) : null),
Utils.DecFromDb(r.Amount, v.Season.Precision)))
.ToArray());
}
@@ -60,23 +57,19 @@ namespace Elwig.Models.Dtos {
q.name AS quality_level,
ROUND(kmw * (4.54 + 0.022 * kmw)) AS oe,
SUM(IIF(w.discr = '_', w.value, 0)) AS weight_ungeb,
MIN(IIF(w.discr = '_', b.price, NULL)) AS min_price_ungeb,
MAX(IIF(w.discr = '_', b.price, NULL)) AS max_price_ungeb,
SUM(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), w.value, 0)) AS weight_lowgeb,
MIN(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS min_price_lowgeb,
MAX(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS max_price_lowgeb,
SUM(IIF(w.discr = COALESCE(p.attrid, ''), w.value, 0)) AS weight_geb,
MIN(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS min_price_geb,
MAX(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS max_price_geb,
MAX(IIF(w.discr = '_', b.price, NULL)) AS price_ungeb,
SUM(IIF(w.discr != '_', w.value, 0)) AS weight_geb,
MAX(IIF(w.discr != '_', b.price, NULL)) AS price_geb,
SUM(b.amount) AS amount
FROM payment_delivery_part_bucket b
LEFT JOIN delivery_part_bucket w ON (w.year, w.did, w.dpnr, w.bktnr) = (b.year, b.did, b.dpnr, b.bktnr)
LEFT JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (b.year, b.did, b.dpnr)
LEFT JOIN delivery d ON (d.year, d.did) = (p.year, p.did)
LEFT JOIN wine_variety v ON v.sortid = p.sortid
LEFT JOIN wine_attribute a ON a.attrid = p.attrid
LEFT JOIN wine_cultivation c ON c.cultid = p.cultid
LEFT JOIN wine_quality_level q ON q.qualid = p.qualid
WHERE p.year = {year} AND b.avnr = {avnr}
WHERE d.year = {year} AND b.avnr = {avnr}
GROUP BY variety, attribute, cultivation, q.min_kmw, oe
ORDER BY variety, attribute, cultivation, q.min_kmw, oe
""").ToListAsync();
@@ -99,22 +92,12 @@ namespace Elwig.Models.Dtos {
public double Oe { get; set; }
[Column("weight_ungeb")]
public int WeightUngeb { get; set; }
[Column("min_price_ungeb")]
public long? MinPriceUngeb { get; set; }
[Column("max_price_ungeb")]
public long? MaxPriceUngeb { get; set; }
[Column("weight_lowgeb")]
public int WeightLowGeb { get; set; }
[Column("min_price_lowgeb")]
public long? MinPriceLowGeb { get; set; }
[Column("max_price_lowgeb")]
public long? MaxPriceLowGeb { get; set; }
[Column("price_ungeb")]
public long? PriceUngeb { get; set; }
[Column("weight_geb")]
public int WeightGeb { get; set; }
[Column("min_price_geb")]
public long? MinPriceGeb { get; set; }
[Column("max_price_geb")]
public long? MaxPriceGeb { get; set; }
[Column("price_geb")]
public long? PriceGeb { get; set; }
[Column("amount")]
public long Amount { get; set; }
}
+1 -1
View File
@@ -14,7 +14,7 @@ namespace Elwig.Models.Dtos {
("CultId", "Bewirt.", null, 15),
("QualId", "Qual.", null, 15),
("Geb", "gebunden", null, 20),
("Weight", "Menge", "kg", 20),
("Weight", "Gewicht", "kg", 20),
];
public WeightBreakdownData(IEnumerable<WeightBreakdownRow> rows, int year, string name) :
@@ -17,7 +17,7 @@ namespace Elwig.Models.Dtos {
("Members", "Mitgl.", "#", 15),
("Deliveries", "Lfrg.", "#", 15),
("Parts", "Teill.", "#", 15),
("Weight", "Menge", "kg", 20),
("Weight", "Gewicht", "kg", 20),
("Gradation", "Gradation", "°Oe|°KMW", 30),
];
+4 -26
View File
@@ -41,36 +41,14 @@ namespace Elwig.Models.Entities {
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("MgNr")]
public virtual Member Member { get; private set; } = null!;
+4 -26
View File
@@ -84,36 +84,14 @@ namespace Elwig.Models.Entities {
}
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year, AvNr, MgNr")]
public virtual PaymentMember Payment { get; private set; } = null!;
+12 -44
View File
@@ -1,11 +1,9 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Windows.Media;
using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute;
namespace Elwig.Models.Entities {
@@ -63,42 +61,15 @@ namespace Elwig.Models.Entities {
[Column("comment")]
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)]
public long CTime { get; set; }
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year")]
public virtual Season Season { get; private set; } = null!;
@@ -114,19 +85,16 @@ namespace Elwig.Models.Entities {
public int Weight => Parts.Select(p => p.Weight).Sum();
public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
public IEnumerable<RawVaribute> Vaributes => Parts
.GroupBy(p => (p.SortId, p.AttrId, p.CultId))
public IEnumerable<string> SortIds => Parts
.GroupBy(p => p.SortId)
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
.Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
public IEnumerable<RawVaribute> FilteredVaributes => FilteredParts
.GroupBy(p => (p.SortId, p.AttrId, p.CultId))
.Select(g => g.Key);
public IEnumerable<string> FilteredSortIds => FilteredParts
.GroupBy(p => p.SortId)
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
.Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
public string VaributeString => string.Join(", ", Vaributes);
public string FilteredVaributeString => string.Join(", ", FilteredVaributes);
public Brush? Color => Parts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
public Brush? FilteredColor => FilteredParts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
.Select(g => g.Key);
public string SortIdString => string.Join(", ", SortIds);
public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
public IEnumerable<string> Modifiers => Parts
.SelectMany(p => p.Modifiers)
+4 -26
View File
@@ -27,36 +27,14 @@ namespace Elwig.Models.Entities {
public required string Type { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year, DsNr")]
public virtual DeliverySchedule Schedule { get; private set; } = null!;
+7 -49
View File
@@ -9,11 +9,6 @@ using System.Text.Json.Nodes;
namespace Elwig.Models.Entities {
[Table("delivery_part"), PrimaryKey("Year", "DId", "DPNr")]
public class DeliveryPart : IDelivery {
public const string Dumper = "dumper";
public const string Pumped = "pumped";
public const string Box = "box";
[Column("year")]
public int Year { get; set; }
@@ -91,27 +86,12 @@ namespace Elwig.Models.Entities {
[Column("hand_picked")]
public bool? IsHandPicked { get; set; }
[Column("lesewagen")]
public bool? IsLesewagen { get; set; }
[Column("gebunden")]
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")]
public double? Temperature { get; set; }
@@ -149,36 +129,14 @@ namespace Elwig.Models.Entities {
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[InverseProperty(nameof(DeliveryPartModifier.Part))]
public virtual ICollection<DeliveryPartModifier> PartModifiers { get; private set; } = null!;
+1 -3
View File
@@ -42,9 +42,7 @@ namespace Elwig.Models.Entities {
[Column("max_weight")]
public int? MaxWeight { get; set; }
[NotMapped]
public int AnnouncedWeight => AnnouncedWeightOverride ?? Announcements.Sum(a => a.Weight);
[NotMapped]
public int? AnnouncedWeightOverride { get; set; }
public int AnnouncedWeight => Announcements.Sum(a => a.Weight);
[NotMapped]
public double? Percent => (double)AnnouncedWeight / MaxWeight * 100;
+4 -26
View File
@@ -145,36 +145,14 @@ namespace Elwig.Models.Entities {
public AT_Kg? DefaultKg => DefaultWbKg?.AtKg;
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("ZwstId")]
public virtual Branch? Branch { get; private set; }
-5
View File
@@ -16,9 +16,6 @@ namespace Elwig.Models.Entities {
[Column("active")]
public bool IsActive { get; set; }
[Column("redacted")]
public bool IsRedacted { get; set; }
[Column("ordering")]
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%}" :
"";
public string? PublicValueStr => IsRedacted ? null : ValueStr;
public override string ToString() {
return Name;
}
+4 -26
View File
@@ -46,36 +46,14 @@ namespace Elwig.Models.Entities {
public required string Data { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year")]
public virtual Season Season { get; private set; } = null!;
+2 -2
View File
@@ -6,10 +6,10 @@ namespace Elwig.Models.Entities {
[Table("wb_gl"), PrimaryKey("GlNr")]
public class WbGl {
[Column("glnr")]
public int GlNr { get; set; }
public int GlNr { get; private set; }
[Column("name")]
public string Name { get; set; } = null!;
public string Name { get; private set; } = null!;
[InverseProperty(nameof(WbKg.Gl))]
public virtual ICollection<WbKg> Kgs { get; private set; } = null!;
+1 -1
View File
@@ -15,7 +15,7 @@ namespace Elwig.Models.Entities {
public virtual AT_Kg AtKg { get; private set; } = null!;
[ForeignKey("GlNr")]
public virtual WbGl? Gl { get; private set; } = null!;
public virtual WbGl Gl { get; private set; } = null!;
[InverseProperty(nameof(WbRd.Kg))]
public virtual ICollection<WbRd> Rds { get; private set; } = null!;
-11
View File
@@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using System.Windows.Media;
namespace Elwig.Models.Entities {
[Table("wine_variety"), PrimaryKey("SortId")]
@@ -11,32 +10,22 @@ namespace Elwig.Models.Entities {
[Column("type")]
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")]
public string Name { get; private set; } = null!;
[Column("comment")]
public string? Comment { get; private set; }
public string SortIdFormat => IsQuw ? SortId : $"({SortId})";
public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
public bool IsRed => Type == "R";
public bool IsWhite => Type == "W";
public bool IsQuw => MaxQualId == "QUW";
public Brush? Color => IsWhite ? Brushes.DarkGreen : IsRed ? Brushes.DarkRed : null;
public WineVar() { }
public WineVar(string sortId, string name) {
SortId = sortId;
Name = name;
MaxQualId = "QUW";
}
public override string ToString() {
@@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublishDir>bin\Publish</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net10.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>false</PublishSingleFile>
-33
View File
@@ -1,33 +0,0 @@
-- schema version 31 to 32
INSERT INTO client_parameter (param, value) VALUES ('ENABLE_TIME_TRIGGERS', '1');
ALTER TABLE member ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE member ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE area_commitment ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE area_commitment ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE delivery_announcement ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE delivery_announcement ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE delivery ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE delivery ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE delivery_part ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE delivery_part ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE payment_variant ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE payment_variant ADD COLUMN itime INTEGER DEFAULT NULL;
ALTER TABLE credit ADD COLUMN xtime INTEGER DEFAULT NULL;
ALTER TABLE credit ADD COLUMN itime INTEGER DEFAULT NULL;
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = REPLACE(REPLACE(sql,
' WHEN',
' WHEN (SELECT value FROM client_parameter WHERE param = ''ENABLE_TIME_TRIGGERS'') = 1 AND'),
'FOR EACH ROW' || char(10) ||
'BEGIN',
'FOR EACH ROW' || char(10) ||
' WHEN (SELECT value FROM client_parameter WHERE param = ''ENABLE_TIME_TRIGGERS'') = 1' || char(10) ||
'BEGIN')
WHERE type = 'trigger' AND name LIKE '%time%';
PRAGMA writable_schema = OFF;
PRAGMA schema_version = 3101;
-17
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);
-3
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;
-6
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;
-19
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;
+9 -17
View File
@@ -106,13 +106,12 @@ namespace Elwig.Services {
}
public static async Task<int> UpdateAreaCommitment(this AreaComAdminViewModel vm, int? oldFbNr) {
int newFbNr = vm.FbNr!.Value;
int newFbNr = (int)vm.FbNr!;
return await Task.Run(async () => {
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
var a = new AreaCom {
FbNr = oldFbNr ?? newFbNr,
MgNr = vm.MgNr!.Value,
MgNr = (int)vm.MgNr!,
YearFrom = vm.YearFrom,
YearTo = vm.YearTo,
VtrgId = vm.AreaComType!.VtrgId,
@@ -120,8 +119,8 @@ namespace Elwig.Services {
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
KgNr = vm.Kg!.KgNr,
RdNr = vm.Rd?.RdNr,
GstNr = vm.GstNr?.Trim() ?? "-",
Area = vm.Area!.Value,
GstNr = vm.GstNr!.Trim(),
Area = (int)vm.Area!,
};
if (vm.Rd?.RdNr == 0) {
@@ -141,9 +140,11 @@ namespace Elwig.Services {
if (newFbNr != a.FbNr) {
await ctx.Database.ExecuteSqlAsync($"UPDATE area_commitment SET fbnr = {newFbNr} WHERE fbnr = {oldFbNr}");
}
}
return newFbNr;
});
App.HintContextChange();
return newFbNr;
}
private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
@@ -252,14 +253,5 @@ namespace Elwig.Services {
}
return grid;
}
public static async Task DeleteAreaCom(int fbnr) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
var l = (await ctx.AreaCommitments.FindAsync(fbnr))!;
ctx.Remove(l);
await ctx.SaveChangesAsync();
});
}
}
}
+22 -25
View File
@@ -37,8 +37,8 @@ namespace Elwig.Services {
vm.DeliverySchedule = (DeliverySchedule?)ControlUtils.GetItemFromSourceWithPk(vm.DeliveryScheduleSource, a.Year, a.DsNr);
vm.SortId = a.SortId;
vm.Weight = a.Weight;
vm.StatusAncmtCreated = $"{a.CreatedAt:dd.MM.yyyy, HH:mm} ({a.Type})";
vm.StatusAncmtModified = a.ModifiedAt != a.CreatedAt ? $"{a.ModifiedAt:dd.MM.yyyy, HH:mm}" : "-";
vm.StatusAncmtCreated = $"{a.CreatedTimestamp:dd.MM.yyyy, HH:mm} ({a.Type})";
vm.StatusAncmtModified = a.ModifiedTimestamp != a.CreatedTimestamp ? $"{a.ModifiedTimestamp:dd.MM.yyyy, HH:mm}" : "-";
}
public static async Task<(List<string>, IQueryable<DeliveryAncmt>, List<string>)> GetFilters(this DeliveryAncmtAdminViewModel vm, AppDbContext ctx) {
@@ -211,8 +211,7 @@ namespace Elwig.Services {
int newMgNr = vm.MgNr!.Value;
string newSortId = vm.SortId!;
return await Task.Run(async () => {
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
var a = new DeliveryAncmt {
Year = oldYear ?? year,
DsNr = oldDsNr ?? dsnr,
@@ -233,9 +232,11 @@ namespace Elwig.Services {
if (oldDsNr != null && (oldYear != year || oldDsNr != dsnr || oldMgNr != newMgNr || oldSortId != newSortId)) {
await ctx.Database.ExecuteSqlAsync($"UPDATE delivery_announcement SET year = {year}, dsnr = {dsnr}, mgnr = {newMgNr}, sortid = {newSortId} WHERE (year, dsnr, mgnr, sortid) = ({a.Year}, {a.DsNr}, {a.MgNr}, {a.SortId})");
}
}
return (year, dsnr, newMgNr, newSortId);
});
App.HintContextChange();
return (year, dsnr, newMgNr, newSortId);
}
public static async Task GenerateDeliveryAncmtList(this DeliveryAncmtAdminViewModel vm, ExportSubject subject, ExportMode mode) {
@@ -268,29 +269,25 @@ namespace Elwig.Services {
Title = $"{DeliveryAncmtList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryAncmtListData.FromQuery(query, filterNames);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await DeliveryAncmtListData.FromQuery(query, filterNames);
using var doc = new DeliveryAncmtList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await DeliveryAncmtListData.FromQuery(query, filterNames);
using var doc = new DeliveryAncmtList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
@@ -329,7 +326,7 @@ namespace Elwig.Services {
var weight = await deliveryAncmts.SumAsync(p => p.Weight);
text = $"{weight:N0} kg";
AddToolTipRow(grid, 0, "Menge", null, weight, null, weight);
AddToolTipRow(grid, 0, "Gewicht", null, weight, null, weight);
if (await deliveryAncmts.AnyAsync()) {
var attrGroups = await deliveryAncmts
+4 -3
View File
@@ -152,8 +152,7 @@ namespace Elwig.Services {
public static async Task UpdateDeliverySchedule(this DeliveryScheduleAdminViewModel vm, int? oldYear, int? oldDsNr) {
int year = vm.Date!.Value.Year;
await Task.Run(async () => {
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
var s = new DeliverySchedule {
Year = oldYear ?? year,
DsNr = oldDsNr ?? await ctx.NextDsNr(year),
@@ -182,7 +181,9 @@ namespace Elwig.Services {
.ToList(), vm.MainVarieties.Select(v => (v, 1)).Union(vm.OtherVarieties.Select(v => (v, 2))).ToList());
await ctx.SaveChangesAsync();
});
}
App.HintContextChange();
}
}
}
+140 -244
View File
@@ -15,8 +15,10 @@ using LinqKit;
using System.Globalization;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using System.IO;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Windows.Controls;
using System.Net.Http;
namespace Elwig.Services {
public static class DeliveryService {
@@ -78,9 +80,9 @@ namespace Elwig.Services {
vm.PartComment = p.Comment ?? "";
vm.TemperatureString = (p.Temperature != null) ? $"{p.Temperature:N1}" : "";
vm.AcidString = (p.Acid != null) ? $"{p.Acid:N1}" : "";
vm.IsLesewagen = p.IsLesewagen ?? false;
vm.IsHandPicked = p.IsHandPicked;
vm.IsGebunden = p.IsGebunden;
vm.Unloading = p.Unloading;
vm.ScaleId = p.ScaleId;
vm.WeighingData = p.WeighingData;
@@ -186,54 +188,14 @@ namespace Elwig.Services {
prd = prd.And(p => p.IsNetWeight == true);
filter.RemoveAt(i--);
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)) {
prd = prd.And(p => p.Unloading == DeliveryPart.Pumped);
prd = prd.And(p => p.IsLesewagen == true);
filter.RemoveAt(i--);
filterNames.Add("Lesewagen");
} 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--);
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())) {
filterVar.Add(e.ToUpper());
filter.RemoveAt(i--);
@@ -468,10 +430,9 @@ namespace Elwig.Services {
}
public static async Task<DeliveryPart> UpdateDeliveryPart(this DeliveryAdminViewModel vm, int? oldYear, int? oldDid, int? oldDpnr, bool dateHasChanged, bool timeHasChanged, bool timeIsDefault) {
return await Task.Run(async () => {
DeliveryPart p;
DeliveryPart p;
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
int year = oldYear ?? Utils.CurrentYear;
int did = oldDid ?? await ctx.NextDId(year);
int dpnr = oldDpnr ?? await ctx.NextDPNr(year, did);
@@ -521,8 +482,8 @@ namespace Elwig.Services {
IsNetWeight = vm.IsNetWeight,
IsHandPicked = vm.IsHandPicked,
IsLesewagen = vm.IsLesewagen,
IsGebunden = vm.IsGebunden,
Unloading = vm.Unloading,
Temperature = vm.Temperature,
Acid = vm.Acid,
Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment,
@@ -563,16 +524,17 @@ namespace Elwig.Services {
}
await ctx.SaveChangesAsync();
}
return p;
});
App.HintContextChange();
return p;
}
public static async Task<Delivery> SplitDeliveryToMember(int year, int did, int[] weights, int mgnr) {
return await Task.Run(async () => {
Delivery n;
Delivery n;
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
bool anyLeft = false;
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var lnr = await ctx.NextLNr(d.Date, d.ZwstId);
@@ -609,28 +571,23 @@ namespace Elwig.Services {
s.DPNr = dpnr++;
s.Weight = w;
ctx.Add(s);
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
Year = s.Year,
DId = s.DId,
DPNr = s.DPNr,
ModId = m.ModId,
}));
}
}
await ctx.SaveChangesAsync();
if (!anyLeft)
await ctx.Database.ExecuteSqlAsync($"DELETE FROM delivery WHERE (year, did) = ({d.Year}, {d.DId})");
}
return n;
});
App.HintContextChange();
return n;
}
public static async Task<Delivery> SplitDeliveryToLsNr(int year, int did, int[] weights, string lsnr) {
return await Task.Run(async () => {
Delivery n;
using var ctx = new AppDbContext();
Delivery n;
using (var ctx = new AppDbContext()) {
var anyLeft = false;
n = (await ctx.Deliveries.FirstAsync(d => d.LsNr == lsnr))!;
var d = (await ctx.Deliveries.FindAsync(year, did))!;
@@ -653,27 +610,21 @@ namespace Elwig.Services {
s.DPNr = dpnr++;
s.Weight = w;
ctx.Add(s);
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
Year = s.Year,
DId = s.DId,
DPNr = s.DPNr,
ModId = m.ModId,
}));
}
}
await ctx.SaveChangesAsync();
if (!anyLeft && n.LsNr != d.LsNr)
await ctx.Database.ExecuteSqlAsync($"DELETE FROM delivery WHERE (year, did) = ({d.Year}, {d.DId})");
}
return n;
});
App.HintContextChange();
return n;
}
public static async Task DepreciateDelivery(int year, int did, int[] weights) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var dpnr = await ctx.NextDPNr(year, did);
foreach (var (p, w) in d.Parts.ToList().Zip(weights)) {
@@ -694,31 +645,24 @@ namespace Elwig.Services {
n.QualId = "WEI";
n.HkId = "OEST";
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();
});
}
App.HintContextChange();
}
public static async Task GenerateDeliveryNote(int year, int did, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var d = (await ctx.Deliveries.FindAsync(year, did))!;
using var doc = new DeliveryNote(d, ctx);
await Utils.ExportDocument(doc, mode, d.LsNr, (d.Member, $"{DeliveryNote.Name} Nr. {d.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {d.LsNr}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ctx = new AppDbContext();
var d = (await ctx.Deliveries.FindAsync(year, did))!;
using var doc = new DeliveryNote(d, ctx);
await Utils.ExportDocument(doc, mode, d.LsNr, (d.Member, $"{DeliveryNote.Name} Nr. {d.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {d.LsNr}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
@@ -763,16 +707,14 @@ namespace Elwig.Services {
Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Export) {
@@ -780,53 +722,61 @@ 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",
DefaultExt = "elwig.zip",
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
Title = $"{DeliveryJournal.Name} speichern unter - Elwig",
AddExtension = false,
Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var list = await query
.Select(p => p.Delivery)
.Distinct()
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
.Include(d => d.Parts).ThenInclude(p => p.Rd)
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
.AsSplitQuery()
.ToListAsync();
var wbKgs = list
.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!)
.DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr)
.ToList();
await ElwigData.Export(d.FileName, list, wbKgs, filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
});
Mouse.OverrideCursor = null;
} else {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryJournal(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
await ElwigData.Export(d.FileName, await query
.Select(p => p.Delivery)
.Distinct()
.Include(d => d.Parts)
.ThenInclude(p => p.PartModifiers)
.AsSplitQuery()
.ToListAsync(), filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.AppStarting;
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)
.AsSplitQuery()
.ToListAsync();
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, 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;
} else {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryJournal(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
@@ -848,16 +798,14 @@ namespace Elwig.Services {
throw new ArgumentException("Invalid value for ExportSubject");
}
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await WineQualityStatisticsData.FromQuery(query, App.Client.OrderingMemberList);
using var doc = new WineQualityStatistics(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await WineQualityStatisticsData.FromQuery(query, App.Client.OrderingMemberList);
using var doc = new WineQualityStatistics(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
@@ -881,16 +829,14 @@ namespace Elwig.Services {
Title = $"Lieferstatistik pro Ort speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ods = new OdsFile(d.FileName);
var tbl = await WineLocalityStatisticsData.FromQuery(query, filterNames);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ods = new OdsFile(d.FileName);
var tbl = await WineLocalityStatisticsData.FromQuery(query, filterNames);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
@@ -929,83 +875,33 @@ namespace Elwig.Services {
Title = $"{DeliveryDepreciationList.Name} 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 DeliveryJournalData.FromQuery(query, filterNames);
tblTotal.FullName = DeliveryDepreciationList.Name;
tblTotal.Name = "Gesamt";
await ods.AddTable(tblTotal);
foreach (var branch in await ctx.Branches.OrderBy(b => b.Name).ToListAsync()) {
var tbl = await DeliveryJournalData.FromQuery(query.Where(p => p.Delivery.ZwstId == branch.ZwstId), filterNames);
tbl.FullName = DeliveryDepreciationList.Name;
tbl.Name = branch.Name;
await ods.AddTable(tbl);
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ods = new OdsFile(d.FileName);
var tblTotal = await DeliveryJournalData.FromQuery(query, filterNames);
tblTotal.FullName = DeliveryDepreciationList.Name;
tblTotal.Name = "Gesamt";
await ods.AddTable(tblTotal);
foreach (var branch in await ctx.Branches.OrderBy(b => b.Name).ToListAsync()) {
var tbl = await DeliveryJournalData.FromQuery(query.Where(p => p.Delivery.ZwstId == branch.ZwstId), filterNames);
tbl.FullName = DeliveryDepreciationList.Name;
tbl.Name = branch.Name;
await ods.AddTable(tbl);
}
});
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
} else {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryDepreciationList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
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 = Cursors.AppStarting;
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryDepreciationList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
@@ -1050,7 +946,7 @@ namespace Elwig.Services {
var weight = await deliveryParts.SumAsync(p => p.Weight);
wText = $"{weight:N0} kg";
wGrid.Add(("Menge", null, weight, null, weight));
wGrid.Add(("Gewicht", null, weight, null, weight));
if (await deliveryParts.AnyAsync()) {
var kmwMin = await deliveryParts.MinAsync(p => p.Kmw);
@@ -1151,7 +1047,7 @@ namespace Elwig.Services {
wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
int rowNum = 0;
foreach (var row in weightData) {
if (rowNum != 0 && row.Item2 == null) rowNum++;
if (rowNum == 1 || (rowNum != 0 && row.Item1 != null)) rowNum++;
AddWeightToolTipRow(wGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
@@ -1166,7 +1062,7 @@ namespace Elwig.Services {
AddToolTipCell(gGrid, "Max.", 0, 4, 1, false, false, true);
rowNum = 1;
foreach (var row in gradationData) {
if (rowNum != 1 && row.Item2 == null) rowNum++;
if (rowNum == 2 || (rowNum != 1 && row.Item1 != null)) rowNum++;
AddGradationToolTipRow(gGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
@@ -1174,11 +1070,11 @@ namespace Elwig.Services {
}
public static async Task DeleteDelivery(string lsnr) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
await ctx.Deliveries.Where(d => d.LsNr == lsnr).ExecuteDeleteAsync();
await ctx.SaveChangesAsync();
});
}
App.HintContextChange();
}
}
}
+158 -203
View File
@@ -1,18 +1,20 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System;
using Microsoft.EntityFrameworkCore;
using Elwig.Documents;
using System.Windows.Input;
using System.Windows;
using Elwig.Helpers.Billing;
using Elwig.Models.Dtos;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using Elwig.ViewModels;
using System.IO;
using System.Net.Http;
namespace Elwig.Services {
public static class MemberService {
@@ -47,11 +49,6 @@ namespace Elwig.Services {
vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}";
vm.StatusAreaCommitmentToolTip = null;
vm.Age = "-";
vm.CreatedAt = "-";
vm.ModifiedAt = "-";
vm.ModifiedAtShort = "-";
vm.ExportedAt = "-";
vm.ImportedAt = "-";
}
public static void FillInputs(this MemberAdminViewModel vm, Member m) {
@@ -140,12 +137,6 @@ namespace Elwig.Services {
vm.ContactViaPost = m.ContactViaPost;
vm.ContactViaEmail = m.ContactViaEmail;
vm.CreatedAt = $"{m.CreatedAt:dd.MM.yyyy, HH:mm:ss}";
vm.ModifiedAt = $"{m.ModifiedAt:dd.MM.yyyy, HH:mm:ss}";
vm.ModifiedAtShort = $"{m.ModifiedAt:dd.MM.yyyy, HH:mm}";
vm.ExportedAt = m.ExportedAt == null ? "-" : $"{m.ExportedAt:dd.MM.yyyy, HH:mm:ss}";
vm.ImportedAt = m.ImportedAt == null ? "-" : $"{m.ImportedAt:dd.MM.yyyy, HH:mm:ss}";
vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}";
vm.StatusDeliveriesLastSeason = "...";
vm.StatusDeliveriesLastSeasonToolTip = null;
@@ -313,22 +304,6 @@ namespace Elwig.Services {
memberQuery = memberQuery.Where(m => !m.ContactViaPost);
filter.RemoveAt(i--);
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)) {
filterMgNr.Add(int.Parse(e));
filter.RemoveAt(i--);
@@ -382,8 +357,8 @@ namespace Elwig.Services {
if (filterMgNr.Count > 0) memberQuery = memberQuery.Where(m => filterMgNr.Contains(m.MgNr));
if (filterKgNr.Count > 0) memberQuery = memberQuery.Where(m => m.DefaultKgNr != null && filterKgNr.Contains((int)m.DefaultKgNr));
if (filterZwst.Count > 0) memberQuery = memberQuery.Where(m => m.ZwstId != null && filterZwst.Contains(m.ZwstId));
if (filterAreaCom.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments(Utils.CurrentLastSeason)).Any(c => filterAreaCom.Contains(c.VtrgId)));
if (filterNotAreaCom.Count > 0) memberQuery = memberQuery.Where(m => !m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments(Utils.CurrentLastSeason)).All(c => filterNotAreaCom.Contains(c.VtrgId)));
if (filterAreaCom.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments()).Any(c => filterAreaCom.Contains(c.VtrgId)));
if (filterNotAreaCom.Count > 0) memberQuery = memberQuery.Where(m => !m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments()).All(c => filterNotAreaCom.Contains(c.VtrgId)));
if (filterLfbisNr.Count > 0) memberQuery = memberQuery.Where(m => m.LfbisNr != null && filterLfbisNr.Contains(m.LfbisNr));
if (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr));
}
@@ -392,57 +367,51 @@ namespace Elwig.Services {
}
public static async Task GenerateMemberDataSheet(Member m, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
using var doc = new MemberDataSheet(m, ctx);
await Utils.ExportDocument(doc, mode, emailData: (m, MemberDataSheet.Name, "Im Anhang finden Sie das aktuelle Stammdatenblatt"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ctx = new AppDbContext();
using var doc = new MemberDataSheet(m, ctx);
await Utils.ExportDocument(doc, mode, emailData: (m, MemberDataSheet.Name, "Im Anhang finden Sie das aktuelle Stammdatenblatt"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
public static async Task GenerateDeliveryConfirmation(Member m, int year, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
App.HintContextChange();
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
App.HintContextChange();
using var ctx = new AppDbContext();
var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, year, m);
using var doc = new DeliveryConfirmation(ctx, year, m, data);
await Utils.ExportDocument(doc, mode, emailData: (m, $"{DeliveryConfirmation.Name} {year}", $"Im Anhang finden Sie die Anlieferungsbestätigung {year}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
using var ctx = new AppDbContext();
var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, year, m);
using var doc = new DeliveryConfirmation(ctx, year, m, data);
await Utils.ExportDocument(doc, mode, emailData: (m, $"{DeliveryConfirmation.Name} {year}", $"Im Anhang finden Sie die Anlieferungsbestätigung {year}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
public static async Task GenerateCreditNote(Member m, int year, int avnr, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr);
var p = (await ctx.MemberPayments.FindAsync(year, avnr, m.MgNr))!;
var b = BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data);
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.Seasons, year, avnr);
var p = (await ctx.MemberPayments.FindAsync(year, avnr, m.MgNr))!;
var b = BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data);
using var doc = new CreditNote(ctx, p, data[m.MgNr],
b.ConsiderContractPenalties, b.ConsiderTotalPenalty, b.ConsiderAutoBusinessShares, b.ConsiderCustomModifiers,
await ctx.GetMemberUnderDelivery(year, m.MgNr));
await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {v.Name}", $"Im Anhang finden Sie die Traubengutschrift {v.Name}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
using var doc = new CreditNote(ctx, p, data[m.MgNr],
b.ConsiderContractPenalties, b.ConsiderTotalPenalty, b.ConsiderAutoBusinessShares, b.ConsiderCustomModifiers,
await ctx.GetMemberUnderDelivery(year, m.MgNr));
await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {v.Name}", $"Im Anhang finden Sie die Traubengutschrift {v.Name}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
@@ -495,146 +464,130 @@ namespace Elwig.Services {
Title = $"{MemberList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
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 = Cursors.AppStarting;
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Export) {
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",
DefaultExt = "elwig.zip",
DefaultExt = ".elwig.zip",
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
Title = $"{MemberList.Name} speichern unter - Elwig",
AddExtension = false,
Title = $"{MemberList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
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)
.Include(m => m.DefaultWbKg!.Gl)
.AsSplitQuery()
.ToListAsync();
var areaComs = await query
.SelectMany(m => m.AreaCommitments)
.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();
await ElwigData.Export(d.FileName, members, areaComs, wbKgs, filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
});
Mouse.OverrideCursor = null;
} else {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var doc = new MemberList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
var members = await query
.OrderBy(m => m.MgNr)
.Include(m => m.BillingAddress)
.Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses)
.AsSplitQuery()
.ToListAsync();
var areaComs = await query
.SelectMany(m => m.AreaCommitments)
.Include(c => c.Rd)
.ToListAsync();
await ElwigData.Export(d.FileName, members, areaComs, filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.AppStarting;
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)
.AsSplitQuery()
.ToListAsync();
var areaComs = await query
.SelectMany(m => m.AreaCommitments)
.Include(c => c.Rd)
.ToListAsync();
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, 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;
} else {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var doc = new MemberList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
public static async Task<int> UpdateMember(this MemberAdminViewModel vm, int? oldMgNr) {
var newMgNr = (int)vm.MgNr!;
var m = new Member {
MgNr = oldMgNr ?? newMgNr,
PredecessorMgNr = vm.PredecessorMgNr,
IsJuridicalPerson = vm.IsJuridicalPerson,
Prefix = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.Prefix) ? null : vm.Prefix,
GivenName = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.GivenName) ? null : vm.GivenName,
Name = vm.Name!,
Suffix = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.Suffix) ? null : vm.Suffix,
ForTheAttentionOf = !vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.ForTheAttentionOf) ? null : vm.ForTheAttentionOf,
Birthday = string.IsNullOrEmpty(vm.Birthday) ? null : string.Join("-", vm.Birthday!.Split(".").Reverse()),
IsDeceased = vm.IsDeceased,
CountryNum = 40, // Austria AT AUT
PostalDestId = vm.Ort!.Id,
Address = vm.Address!,
Iban = string.IsNullOrEmpty(vm.Iban) ? null : vm.Iban?.Replace(" ", ""),
Bic = string.IsNullOrEmpty(vm.Bic) ? null : vm.Bic,
using (var ctx = new AppDbContext()) {
var m = new Member {
MgNr = oldMgNr ?? newMgNr,
PredecessorMgNr = vm.PredecessorMgNr,
IsJuridicalPerson = vm.IsJuridicalPerson,
Prefix = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.Prefix) ? null : vm.Prefix,
GivenName = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.GivenName) ? null : vm.GivenName,
Name = vm.Name!,
Suffix = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.Suffix) ? null : vm.Suffix,
ForTheAttentionOf = !vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.ForTheAttentionOf) ? null : vm.ForTheAttentionOf,
Birthday = string.IsNullOrEmpty(vm.Birthday) ? null : string.Join("-", vm.Birthday!.Split(".").Reverse()),
IsDeceased = vm.IsDeceased,
CountryNum = 40, // Austria AT AUT
PostalDestId = vm.Ort!.Id,
Address = vm.Address!,
UstIdNr = string.IsNullOrEmpty(vm.UstIdNr) ? null : vm.UstIdNr,
LfbisNr = string.IsNullOrEmpty(vm.LfbisNr) ? null : vm.LfbisNr,
IsBuchführend = vm.IsBuchführend,
IsOrganic = vm.IsOrganic,
Iban = string.IsNullOrEmpty(vm.Iban) ? null : vm.Iban?.Replace(" ", ""),
Bic = string.IsNullOrEmpty(vm.Bic) ? null : vm.Bic,
EntryDateString = string.IsNullOrEmpty(vm.EntryDate) ? null : string.Join("-", vm.EntryDate.Split(".").Reverse()),
ExitDateString = string.IsNullOrEmpty(vm.ExitDate) ? null : string.Join("-", vm.ExitDate.Split(".").Reverse()),
BusinessShares = (int)vm.BusinessShares!,
AccountingNr = string.IsNullOrEmpty(vm.AccountingNr) ? null : vm.AccountingNr,
IsActive = vm.IsActive,
IsVollLieferant = vm.IsVollLieferant,
IsFunktionär = vm.IsFunktionär,
ZwstId = vm.Branch?.ZwstId,
DefaultKgNr = vm.DefaultKg?.KgNr,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
ContactViaPost = vm.ContactViaPost,
ContactViaEmail = vm.ContactViaEmail,
};
UstIdNr = string.IsNullOrEmpty(vm.UstIdNr) ? null : vm.UstIdNr,
LfbisNr = string.IsNullOrEmpty(vm.LfbisNr) ? null : vm.LfbisNr,
IsBuchführend = vm.IsBuchführend,
IsOrganic = vm.IsOrganic,
EntryDateString = string.IsNullOrEmpty(vm.EntryDate) ? null : string.Join("-", vm.EntryDate.Split(".").Reverse()),
ExitDateString = string.IsNullOrEmpty(vm.ExitDate) ? null : string.Join("-", vm.ExitDate.Split(".").Reverse()),
BusinessShares = (int)vm.BusinessShares!,
AccountingNr = string.IsNullOrEmpty(vm.AccountingNr) ? null : vm.AccountingNr,
IsActive = vm.IsActive,
IsVollLieferant = vm.IsVollLieferant,
IsFunktionär = vm.IsFunktionär,
ZwstId = vm.Branch?.ZwstId,
DefaultKgNr = vm.DefaultKg?.KgNr,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
ContactViaPost = vm.ContactViaPost,
ContactViaEmail = vm.ContactViaEmail,
};
return await Task.Run(async () => {
using var ctx = new AppDbContext();
if (oldMgNr != null) {
ctx.Update(m);
} else {
@@ -691,7 +644,7 @@ namespace Elwig.Services {
KgNr = c.KgNr,
GstNr = c.GstNr,
RdNr = c.RdNr,
YearFrom = vm.MaintainAreaComYearFrom ? c.YearFrom : year,
YearFrom = vm.MaintainAreaComYearTo ? c.YearFrom : year,
YearTo = c.YearTo,
}));
@@ -717,14 +670,15 @@ namespace Elwig.Services {
if (newMgNr != m.MgNr) {
await ctx.Database.ExecuteSqlAsync($"UPDATE member SET mgnr = {newMgNr} WHERE mgnr = {oldMgNr}");
}
}
return newMgNr;
});
App.HintContextChange();
return newMgNr;
}
public static async Task DeleteMember(int mgnr, bool deletePaymentData, bool deleteDeliveries, bool deleteAreaComs) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
using (var ctx = new AppDbContext()) {
var l = (await ctx.Members.FindAsync(mgnr))!;
if (deletePaymentData) {
ctx.RemoveRange(l.Credits);
@@ -737,7 +691,8 @@ namespace Elwig.Services {
}
ctx.Remove(l);
await ctx.SaveChangesAsync();
});
}
App.HintContextChange();
}
}
}
-387
View File
@@ -1,387 +0,0 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Elwig.Services {
public static class PaymentVariantService {
private static readonly JsonSerializerOptions JsonOpt = new() { WriteIndented = true };
public static void ClearInputs(this PaymentVariantsViewModel vm) {
vm.IsPaymentVariantSelected = false;
vm.EditText = "Bearbeiten";
vm.SaveIsEnabled = false;
vm.DeleteIsEnabled = false;
vm.CalculateIsEnabled = false;
vm.CommitIsEnabled = false;
vm.CommitVisibility = Visibility.Visible;
vm.RevertIsEnabled = false;
vm.RevertVisibility = Visibility.Hidden;
vm.Arrow = "\xF0AF";
vm.ExportIsEnabled = false;
vm.IsReadOnly = true;
vm.DataIsReadOnly = true;
vm.BillingData = null;
vm.Name = "";
vm.Comment = "";
vm.Date = null;
vm.TransferDate = null;
vm.WeightModifier = null;
vm.ConsiderModifiers = false;
vm.ConsiderPenalties = false;
vm.ConsiderPenalty = false;
vm.ConsiderAuto = false;
vm.ConsiderCustom = false;
vm.IsEnabled = false;
vm.Data = "";
vm.StatusModifierSum = "-";
vm.StatusTotalSum = "-";
vm.StatusVatSum = "-";
vm.StatusDeductionSum = "-";
vm.StatusPaymentSum = "-";
}
public static void FillInputs(this PaymentVariantsViewModel vm, PaymentVar v) {
var locked = !v.TestVariant;
vm.IsPaymentVariantSelected = true;
vm.SaveIsEnabled = !locked;
vm.DeleteIsEnabled = !locked;
vm.CalculateIsEnabled = !locked;
vm.CommitIsEnabled = !locked && !vm.SeasonLocked;
vm.CommitVisibility = !locked ? Visibility.Visible : Visibility.Hidden;
vm.RevertIsEnabled = locked && !vm.SeasonLocked;
vm.RevertVisibility = locked ? Visibility.Visible : Visibility.Hidden;
vm.Arrow = locked ? "\xF0B0" : "\xF0AF";
vm.EditText = locked ? "Ansehen" : "Bearbeiten";
vm.ExportIsEnabled = locked;
vm.IsReadOnly = false;
vm.DataIsReadOnly = locked;
vm.Name = v.Name;
vm.Comment = v.Comment ?? "";
vm.Date = v.Date;
vm.TransferDate = v.TransferDate;
try {
vm.BillingData = BillingData.FromJson(v.Data);
vm.ConsiderModifiers = vm.BillingData.ConsiderDelieryModifiers;
vm.ConsiderPenalties = vm.BillingData.ConsiderContractPenalties;
vm.ConsiderPenalty = vm.BillingData.ConsiderTotalPenalty;
vm.ConsiderAuto = vm.BillingData.ConsiderAutoBusinessShares;
vm.ConsiderCustom = vm.BillingData.ConsiderCustomModifiers;
if (vm.BillingData.NetWeightModifier != 0) {
vm.WeightModifier = Math.Round(vm.BillingData.NetWeightModifier * 100.0, 8);
} else if (vm.BillingData.GrossWeightModifier != 0) {
vm.WeightModifier = Math.Round(vm.BillingData.GrossWeightModifier * 100.0, 8);
} else {
vm.WeightModifier = null;
}
vm.Data = JsonSerializer.Serialize(vm.BillingData.Data, JsonOpt);
} catch {
vm.BillingData = null;
vm.ConsiderModifiers = false;
vm.ConsiderPenalties = false;
vm.ConsiderPenalty = false;
vm.ConsiderAuto = false;
vm.ConsiderCustom = false;
vm.WeightModifier = null;
vm.Data = v.Data;
}
vm.IsEnabled = !locked;
vm.StatusModifierSum = "...";
vm.StatusTotalSum = "...";
vm.StatusVatSum = "...";
vm.StatusDeductionSum = "...";
vm.StatusPaymentSum = "...";
Utils.RunBackground("Variantendaten laden", async () => {
await vm.UpdateSums(v);
});
}
private static async Task UpdateSums(this PaymentVariantsViewModel vm, PaymentVar v) {
if (App.MainDispatcher == null)
return;
var modText = "-";
var totalText = "-";
var vatText = "-";
var deductionText = "-";
var paymentText = "-";
using var ctx = new AppDbContext();
var sym = v.Season.Currency.Symbol ?? v.Season.Currency.Code;
var modSum = await ctx.PaymentDeliveryParts
.Where(p => p.Year == v.Year && p.AvNr == v.AvNr)
.SumAsync(p => p.AmountValue - p.NetAmountValue);
modText = $"{v.Season.DecFromDb(modSum):N2} {sym}";
var totalSum = await ctx.MemberPayments
.Where(p => p.Year == v.Year && p.AvNr == v.AvNr)
.SumAsync(p => p.AmountValue);
totalText = $"{v.Season.DecFromDb(totalSum):N2} {sym}";
await App.MainDispatcher.BeginInvoke(() => {
if (vm.SelectedPaymentVariant != v)
return;
vm.StatusModifierSum = modText;
vm.StatusTotalSum = totalText;
});
var credits = ctx.Credits.Where(c => c.Year == v.Year && c.AvNr == v.AvNr);
if (!credits.Any()) {
long lastTotalSum = 0;
decimal vatSum = 0;
var currentPayments = await ctx.MemberPayments
.Where(p => p.Year == v.Year && p.AvNr == v.AvNr)
.ToDictionaryAsync(p => p.MgNr);
var lastV = await ctx.PaymentVariants
.Where(l => l.Year == v.Year && !l.TestVariant)
.OrderByDescending(l => l.TransferDateString ?? l.DateString)
.ThenByDescending(l => l.AvNr)
.FirstOrDefaultAsync();
if (lastV != null) {
var lastPayments = await ctx.MemberPayments
.Where(p => p.Year == v.Year && p.AvNr == lastV.AvNr)
.ToDictionaryAsync(p => p.MgNr);
lastTotalSum = lastPayments.Sum(e => e.Value.AmountValue);
foreach (int mgnr in currentPayments.Keys) {
var c = currentPayments[mgnr];
var l = lastPayments[mgnr];
vatSum += (c.Amount - l.Amount) * (decimal)(c.Member.IsBuchführend ? v.Season.VatNormal : v.Season.VatFlatrate);
}
} else {
vatSum = currentPayments.Sum(e => e.Value.Amount * (decimal)(e.Value.Member.IsBuchführend ? v.Season.VatNormal : v.Season.VatFlatrate));
}
vatText = $"~{vatSum:N2} {sym}";
deductionText = $"- {sym}";
paymentText = $"~{v.Season.DecFromDb(totalSum - lastTotalSum) + vatSum:N2} {sym}";
} else {
// all values in the credit table are stored with precision 2!
totalText = $"{Utils.DecFromDb(await credits.SumAsync(c => c.NetAmountValue), 2):N2} {sym}";
vatText = $"{Utils.DecFromDb(await credits.SumAsync(c => c.VatAmountValue), 2):N2} {sym}";
deductionText = $"{-Utils.DecFromDb(await credits.SumAsync(c => c.ModifiersValue ?? 0), 2):N2} {sym}";
paymentText = $"{Utils.DecFromDb(await credits.SumAsync(c => c.AmountValue), 2):N2} {sym}";
}
await App.MainDispatcher.BeginInvoke(() => {
if (vm.SelectedPaymentVariant != v)
return;
vm.StatusModifierSum = modText;
vm.StatusTotalSum = totalText;
vm.StatusVatSum = vatText;
vm.StatusDeductionSum = deductionText;
vm.StatusPaymentSum = paymentText;
});
}
public static async Task GenerateSummary(PaymentVar v, ExportMode mode) {
if (mode == ExportMode.SaveList) {
var d = new SaveFileDialog() {
FileName = $"Variantendaten-{v.Name.Trim().Replace(' ', '-')}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Variantendaten {v.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == false)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
} else {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
using var doc = new PaymentVariantSummary((await ctx.PaymentVariants.FindAsync(v.Year, v.AvNr))!, data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
public static async Task GenerateEbics(int year, int avnr) {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
var withoutIban = v.Credits.Count(c => c.Member.Iban == null);
if (withoutIban > 0) {
var r = MessageBox.Show($"Achtung: Für {withoutIban:N0} Mitglieder ist kein IBAN hinterlegt.\n\nDiese werden NICHT exportiert.",
"Mitglieder ohne IBAN", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (r != MessageBoxResult.OK) return;
}
var withNegAmount = v.Credits.Count(c => c.Amount <= 0);
if (withNegAmount > 0) {
var r = MessageBox.Show($"Achtung: Es gibt {withNegAmount:N0} Traubengutschriften mit negativem Betrag.\n\nDiese werden NICHT exportiert.",
"Traubengutschriften mit negativem Betrag", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.OK);
if (r != MessageBoxResult.OK) return;
}
var d = new SaveFileDialog() {
FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}",
DefaultExt = Ebics.FileExtension,
Filter = "EBICS-Datei (*.xml)|*.xml",
Title = $"Überweisungsdaten speichern unter - Elwig",
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var e = new Ebics(v, d.FileName, App.Client.ExportEbicsVersion, (Ebics.AddressMode)App.Client.ExportEbicsAddress);
await e.ExportAsync(Transaction.FromPaymentVariant(v));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
public static async Task GenerateAccountingList(int year, int avnr, string name) {
var d = new SaveFileDialog() {
FileName = $"{App.Client.NameToken}-Buchungsliste-{year}-{name.Trim().Replace(' ', '-')}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Buchungsliste speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var tbl = await CreditNoteData.ForPaymentVariant(ctx, year, avnr);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
public static async Task<PaymentVar> CreatePaymentVariant(int year) {
return await Task.Run(async () => {
using var ctx = new AppDbContext();
var v = new PaymentVar {
Year = year,
AvNr = await ctx.NextAvNr(year),
Name = "Neue Auszahlungsvariante",
TestVariant = true,
DateString = $"{DateTime.Today:yyyy-MM-dd}",
Data = "{\"mode\": \"elwig\", \"version\": 1, \"payment\": {}, \"curves\": []}",
};
ctx.Add(v);
await ctx.SaveChangesAsync();
return v;
});
}
public static async Task<PaymentVar> Duplicate(this PaymentVar orig) {
return await Task.Run(async () => {
using var ctx = new AppDbContext();
var n = new PaymentVar {
Year = orig.Year,
AvNr = await ctx.NextAvNr(orig.Year),
Name = $"{orig.Name} (Kopie)",
TestVariant = true,
DateString = $"{DateTime.Today:yyyy-MM-dd}",
Data = orig.Data,
};
ctx.Add(n);
await ctx.SaveChangesAsync();
return n;
});
}
public static async Task<(int, int)> UpdatePaymentVariant(this PaymentVariantsViewModel vm, int? oldYear, int? oldAvNr) {
var year = oldYear ?? Utils.CurrentYear;
int avnr = 0;
await Task.Run(async () => {
var d = App.Config.Debug ? BillingData.FromJson(vm.Data ?? "{}") : vm.BillingData!;
d.ConsiderDelieryModifiers = vm.ConsiderModifiers;
d.ConsiderContractPenalties = vm.ConsiderPenalties;
d.ConsiderTotalPenalty = vm.ConsiderPenalty;
d.ConsiderAutoBusinessShares = vm.ConsiderAuto;
d.ConsiderCustomModifiers = vm.ConsiderCustom;
var modVal = vm.WeightModifier ?? 0;
d.NetWeightModifier = modVal > 0 ? modVal / 100.0 : 0;
d.GrossWeightModifier = modVal < 0 ? modVal / 100.0 : 0;
using var ctx = new AppDbContext();
var v = new PaymentVar {
Year = year,
AvNr = oldAvNr ?? await ctx.NextAvNr(year),
Name = vm.Name!,
DateString = $"{vm.Date!.Value:yyyy-MM-dd}",
TransferDate = vm.TransferDate,
TestVariant = vm.SelectedPaymentVariant?.TestVariant ?? true,
CalcTimeUnix = vm.SelectedPaymentVariant?.CalcTimeUnix,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
Data = JsonSerializer.Serialize(d.Data),
};
avnr = v.AvNr;
ctx.Update(v);
await ctx.SaveChangesAsync();
});
vm.WeightModifierChanged = false;
return (year, avnr);
}
public static async Task DeletePaymentVariant(int year, int avnr) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
ctx.Remove(v);
await ctx.SaveChangesAsync();
});
}
public static async Task Calculate(int year, int avnr) {
await Task.Run(async () => {
var b = new BillingVariant(year, avnr);
await b.Calculate();
});
}
public static async Task Commit(int year, int avnr) {
await Task.Run(async () => {
var b = new BillingVariant(year, avnr);
await b.Commit();
});
}
public static async Task Revert(int year, int avnr) {
await Task.Run(async () => {
var b = new BillingVariant(year, avnr);
await b.Revert();
});
}
}
}
-277
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);
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private string? _searchQuery = "";
public List<string> TextFilter {
get => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0) ?? []];
get => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0)];
set => SearchQuery = string.Join(' ', value.Where(e => e.Length > 0));
}
+3 -21
View File
@@ -18,7 +18,7 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private string? _searchQuery = "";
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0) ?? []];
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0)];
[ObservableProperty]
private string? _lastScaleError;
@@ -169,28 +169,10 @@ namespace Elwig.ViewModels {
set => AcidString = $"{value:0.0}";
}
[ObservableProperty]
private bool _isLesewagen;
[ObservableProperty]
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]
private string _statusMembers = "-";
[ObservableProperty]
@@ -10,7 +10,7 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private string? _searchQuery = "";
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0) ?? []];
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0)];
[ObservableProperty]
private bool _filterOnlyUpcoming;
@@ -11,7 +11,7 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private string? _searchQuery = "";
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0) ?? []];
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0)];
[ObservableProperty]
private bool _filterOnlyUpcoming;
+2 -13
View File
@@ -11,14 +11,14 @@ namespace Elwig.ViewModels {
public partial class MemberAdminViewModel : ObservableObject {
public int? TransferPredecessorAreaComs = null;
public bool MaintainAreaComYearFrom = false;
public bool MaintainAreaComYearTo = false;
public int? CancelAreaComs = null;
public ObservableCollection<KeyValuePair<string, string>> PhoneNrTypes { get; set; } = new(Utils.PhoneNrTypes);
[ObservableProperty]
private string? _searchQuery = "";
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0) ?? []];
public List<string> TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0)];
[ObservableProperty]
private bool _showOnlyActiveMembers;
@@ -153,17 +153,6 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private bool _contactViaEmail;
[ObservableProperty]
private string _createdAt = "-";
[ObservableProperty]
private string _modifiedAt = "-";
[ObservableProperty]
private string _modifiedAtShort = "-";
[ObservableProperty]
private string _exportedAt = "-";
[ObservableProperty]
private string _importedAt = "-";
public ObservableCollection<string?> EmailAddresses { get; private set; } = [null, null, null, null, null, null, null, null, null];
public partial class PhoneNr(int? type = null, string? number = null, string? comment = null) : ObservableObject {
@@ -1,95 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.Windows;
namespace Elwig.ViewModels {
public partial class PaymentVariantsViewModel : ObservableObject {
[ObservableProperty]
private PaymentVar? _selectedPaymentVariant;
[ObservableProperty]
private IEnumerable<PaymentVar> _paymentVariants = [];
public BillingData? BillingData;
public bool SeasonLocked;
public bool WeightModifierChanged;
[ObservableProperty]
private string _name = "";
[ObservableProperty]
private string _comment = "";
[ObservableProperty]
private string _dateString = "";
public DateOnly? Date {
get => DateOnly.TryParseExact(DateString, "dd.MM.yyyy", out var d) ? d : null;
set => DateString = $"{value:dd.MM.yyyy}";
}
[ObservableProperty]
private string _transferDateString = "";
public DateOnly? TransferDate {
get => DateOnly.TryParseExact(TransferDateString, "dd.MM.yyyy", out var d) ? d : null;
set => TransferDateString = $"{value:dd.MM.yyyy}";
}
[ObservableProperty]
private string _weightModifierString = "";
public double? WeightModifier {
get => double.TryParse(WeightModifierString, out var d) ? d : null;
set => WeightModifierString = $"{value}";
}
[ObservableProperty]
private string _data = "";
[ObservableProperty]
private bool _considerModifiers;
[ObservableProperty]
private bool _considerPenalties;
[ObservableProperty]
private bool _considerPenalty;
[ObservableProperty]
private bool _considerAuto;
[ObservableProperty]
private bool _considerCustom;
[ObservableProperty]
private bool _isPaymentVariantSelected;
[ObservableProperty]
private bool _isReadOnly = true;
[ObservableProperty]
private bool _dataIsReadOnly = true;
[ObservableProperty]
private bool _isEnabled = false;
[ObservableProperty]
private bool _saveIsEnabled = false;
[ObservableProperty]
private bool _deleteIsEnabled = false;
[ObservableProperty]
private bool _calculateIsEnabled = false;
[ObservableProperty]
private bool _exportIsEnabled = false;
[ObservableProperty]
private bool _commitIsEnabled = false;
[ObservableProperty]
private Visibility _commitVisibility = Visibility.Visible;
[ObservableProperty]
private bool _revertIsEnabled = false;
[ObservableProperty]
private Visibility _revertVisibility = Visibility.Hidden;
[ObservableProperty]
private string _arrow = "\xF0AF";
[ObservableProperty]
private string _editText = "Bearbeiten";
[ObservableProperty]
private string _statusModifierSum = "-";
[ObservableProperty]
private string _statusTotalSum = "-";
[ObservableProperty]
private string _statusVatSum = "-";
[ObservableProperty]
private string _statusDeductionSum = "-";
[ObservableProperty]
private string _statusPaymentSum = "-";
}
}
-32
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>
-17
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 });
}
}
}
+2 -12
View File
@@ -13,8 +13,8 @@ using System.Windows.Input;
namespace Elwig.Windows {
public abstract class AdministrationWindow : ContextWindow {
protected Control[] ExemptInputs { get; set; }
protected Control[] RequiredInputs { get; set; }
protected Control[] ExemptInputs { private get; set; }
protected Control[] RequiredInputs { private get; set; }
private bool _isEditing;
private bool _isCreating;
@@ -166,10 +166,8 @@ namespace Elwig.Windows {
Valid[input] = false;
} else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) {
ControlUtils.SetInputInvalid(input);
Valid[input] = false;
} else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) {
ControlUtils.SetInputInvalid(input);
Valid[input] = false;
} else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) {
ControlUtils.SetInputInvalid(input);
Valid[input] = false;
@@ -572,14 +570,6 @@ namespace Elwig.Windows {
InputLostFocus((TextBox)sender, Validator.CheckLfbisNr);
}
protected void OrganicAuthorityCodeInput_TextChanged(object sender, TextChangedEventArgs? evt) {
InputTextChanged((TextBox)sender, Validator.CheckOrganicAuthorityCode);
}
protected void OrganicAuthorityCodeInput_LostFocus(object sender, RoutedEventArgs? evt) {
InputLostFocus((TextBox)sender, Validator.CheckOrganicAuthorityCode);
}
protected void UpperCaseInput_TextChanged(object sender, TextChangedEventArgs? evt) {
InputTextChanged((TextBox)sender, Validator.CheckUpperCase);
}

Some files were not shown because too many files have changed in this diff Show More