Compare commits
	
		
			3 Commits
		
	
	
		
			v1.0.1.4
			...
			10cb23db9e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 10cb23db9e | |||
| a417d0f2a2 | |||
| b184d5661b | 
							
								
								
									
										157
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -2,159 +2,6 @@
 | 
				
			|||||||
Changelog
 | 
					Changelog
 | 
				
			||||||
=========
 | 
					=========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[v1.0.1.4][v1.0.1.4] (2025-10-28) {#v1.0.1.4}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Behobene Fehler {#v1.0.1.4-bugfixes}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Im Rundschreiben-Fenster (`MailWindow`) kam es zu einem Absturz, wenn man die Zustelloptionen "Post zusenden an Mitglieder, die keine E-Mail erhalten würden" und "E-Mail zusenden an niemanden" kombiniert hat. (2de8af878b)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.1.4-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Im Auszahlungsvariante-Fenster (`ChartWindow`) gibt es keine Fehlermeldung mehr wenn nicht für alle Sorten ein Preis definiert ist, nur noch eine Warnung. (428cd6ddc2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.3][v1.0.1.3] (2025-10-13) {#v1.0.1.3}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Neue Funktionen {#v1.0.1.3-features}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* In der Liste des Lieferungen-Fenster (`DeliveryAdminWindow`) werden
 | 
					 | 
				
			||||||
	* statt ausschließlich der Sorte auch Attribut und Bewirtschaftungsart angezeigt. (a0d4f19f30)
 | 
					 | 
				
			||||||
	* Kommentare der Lieferungen (und Teillieferungen) angezeigt. (548aeb2ce9)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.1.3-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Verzögerung der Überprüfung auf automatische Updates auf einige Sekunden verlängert. (67ba342c28)
 | 
					 | 
				
			||||||
* Verbesserung der Ladezeiten im Anmeldungen-Fenster (`DeliveryAncmtAdminWindow`). (7edd888aa2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.2][v1.0.1.2] (2025-09-25) {#v1.0.1.2}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Behobene Fehler {#v1.0.1.2-bugfixes}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Beim automatischen Importieren/Synchronisieren wird bei einem Fehlerfall der Benutzer verständigt, aber der Vorgang nicht abgebrochen. (9d02f18bac)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.1.2-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Beim Sichern der Datenbank werden Meta-Informationen in der ZIP-Datei gespeichert. (c8a95422af)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.1][v1.0.1.1] (2025-09-21) {#v1.0.1.1}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.1.1-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Eingabe von Sorten und Qualitätsstufen im Übernahme-Fenster (`DeliveryAdminWindows`) verbessert. (e2de7a1f1c, b27b89f599)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.0][v1.0.1.0] (2025-09-18) {#v1.0.1.0}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Neue Funktionen {#v1.0.1.0-features}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Neue Weinsorten gemäß Kürzelliste der Bundeskellereiinspektion hinzugefügt. (a0dcaf7b4f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.1.0-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* HTTP-Anfragen haben jetzt das Feld `User-Agent` gesetzt. (844fc5217a)
 | 
					 | 
				
			||||||
* Auto-Update-Funktion wird auch beim Erlangen von Netzwerkverbindung ausgeführt. (8bc053053c)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.1.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.6][v1.0.0.6] (2025-09-17) {#v1.0.0.6}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Behobene Fehler {#v1.0.0.6-bugfixes}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Die automatische Erkennung des Wiege-Modus hat im WKW nicht funktioniert. (463769b549)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.0.6-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Tippfehler im Über-Fenster (`AboutWindow`) behoben. (2c383d0c55)
 | 
					 | 
				
			||||||
* `SaveFileDialog` mit Multi-File-Extensions verbessert. (9e02b15ff1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.6]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.6
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.5][v1.0.0.5] (2025-09-15) {#v1.0.0.5}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Neue Funktionen {#v1.0.0.5-features}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Die Datenbank kann im Haupt-Fenster (`MainWindow`) gesichert und wiederhergestellt werden. (f02598760f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.0.5-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* In der WGM ist eine Auswahl eines Zu/-Abschlags im Übernahme-Fenster (`DeliveryAdminWindow`) nun erforderlich. (a9b5317e79)
 | 
					 | 
				
			||||||
* In der Konfigurationsdatei kann im `[general]` Block `weighing = gross`, `weighing = net`, oder `weighing = box` angegeben werden. (d7012ebfa1)
 | 
					 | 
				
			||||||
* Über-Fenser (`AboutWindow`) hinzugefügt. (3c9b3c2db1)
 | 
					 | 
				
			||||||
* Abhängigkeiten aktualisiert. (44dcc5e19f, 98f8907817)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.4][v1.0.0.4] (2025-09-01) {#v1.0.0.4}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Behobene Fehler {#v1.0.0.4-bugfixes}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Absturz beim Verschicken von einzelnen E-Mails außerhalb des Rundschreiben-Fensters (`MailWindow`) behoben. (07f9a0f522)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.0.4-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Ladezeit des Fehler-Protokoll-Fensters (`LogWindow`) verbessert. (4653a4f129)
 | 
					 | 
				
			||||||
* Im Übernahme-Fenster (`DeliveryAdminWindow`) ist es nun möglich die Qualitätsstufe zu verändern. (104798d4f2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.3][v1.0.0.3] (2025-08-11) {#v1.0.0.3}
 | 
					 | 
				
			||||||
---------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Neue Funktionen {#v1.0.0.3-features}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Im Haupt-Fenster (`MainWindow`) ist es nun möglich die gesamte Datenbank zu hochzuladen bzw. herunterzuladen. ([#69][i69])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sonstiges {#v1.0.0.3-misc}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Die Intigrität von Elwig-Export-Dateien (`.elwig.zip`) und anderen `.zip` Dateien wir nun überprüft. (d3157e4d48)
 | 
					 | 
				
			||||||
* Die Herkunftshierarchie wird nun auch automatisch synchronisiert (benötigt für Stamm-KG bei Mitglied, Ried/KG bei Flächenbindung, und Herkunft bei Lieferung). ([#70][i70], b6c03892b1)
 | 
					 | 
				
			||||||
* Abhängigkeiten aktualisiert. (1f165055c1, 4e2eca295d)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.3
 | 
					 | 
				
			||||||
[i69]: https://git.necronda.net/winzer/elwig/issues/69
 | 
					 | 
				
			||||||
[i70]: https://git.necronda.net/winzer/elwig/issues/70
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[v1.0.0.2][v1.0.0.2] (2025-08-05) {#v1.0.0.2}
 | 
					[v1.0.0.2][v1.0.0.2] (2025-08-05) {#v1.0.0.2}
 | 
				
			||||||
---------------------------------------------
 | 
					---------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -162,8 +9,6 @@ Changelog
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* Explizit native SQLite-Bibliothek hinzugefügt. (77c3f388e7)
 | 
					* Explizit native SQLite-Bibliothek hinzugefügt. (77c3f388e7)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[v1.0.0.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -175,8 +20,6 @@ Changelog
 | 
				
			|||||||
* Abhängigkeiten aktualisiert. (466c8a322c)
 | 
					* Abhängigkeiten aktualisiert. (466c8a322c)
 | 
				
			||||||
* Angepasste Fehlermeldung, wenn Importieren fehlschlägt. (ab7c7404e2)
 | 
					* Angepasste Fehlermeldung, wenn Importieren fehlschlägt. (ab7c7404e2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[v1.0.0.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@
 | 
				
			|||||||
        </DataTemplate>
 | 
					        </DataTemplate>
 | 
				
			||||||
        <DataTemplate x:Key="WineVarietyTemplateExpanded">
 | 
					        <DataTemplate x:Key="WineVarietyTemplateExpanded">
 | 
				
			||||||
            <StackPanel Orientation="Horizontal">
 | 
					            <StackPanel Orientation="Horizontal">
 | 
				
			||||||
                <TextBlock Text="{Binding SortIdFormat}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/>
 | 
					                <TextBlock Text="{Binding SortId}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/>
 | 
				
			||||||
                <TextBlock Text="{Binding Name}" Foreground="{Binding Color}"/>
 | 
					                <TextBlock Text="{Binding Name}" Foreground="{Binding Color}"/>
 | 
				
			||||||
                <TextBlock Text="{Binding CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
 | 
					                <TextBlock Text="{Binding CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
 | 
				
			||||||
            </StackPanel>
 | 
					            </StackPanel>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,6 @@ using System.Collections.Generic;
 | 
				
			|||||||
using System.Data;
 | 
					using System.Data;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Net.NetworkInformation;
 | 
					 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
@@ -128,11 +127,10 @@ namespace Elwig {
 | 
				
			|||||||
            if (Config.UpdateAuto && Config.UpdateUrl != null) {
 | 
					            if (Config.UpdateAuto && Config.UpdateUrl != null) {
 | 
				
			||||||
                if (Utils.HasInternetConnectivity()) {
 | 
					                if (Utils.HasInternetConnectivity()) {
 | 
				
			||||||
                    Utils.RunBackground("Auto Updater", async () => {
 | 
					                    Utils.RunBackground("Auto Updater", async () => {
 | 
				
			||||||
                        await Task.Delay(1000);
 | 
					                        await Task.Delay(500);
 | 
				
			||||||
                        await CheckForUpdates();
 | 
					                        await CheckForUpdates();
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
 | 
					 | 
				
			||||||
                _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
 | 
					                _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
 | 
				
			||||||
                _autoUpdateTimer.Start();
 | 
					                _autoUpdateTimer.Start();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -164,16 +162,6 @@ namespace Elwig {
 | 
				
			|||||||
                Shutdown();
 | 
					                Shutdown();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (Config.WeighingMode == null) {
 | 
					 | 
				
			||||||
                if (Client.IsMatzen || Client.IsWolkersdorf) {
 | 
					 | 
				
			||||||
                    Config.WeighingMode = WeighingMode.Net;
 | 
					 | 
				
			||||||
                } else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
 | 
					 | 
				
			||||||
                    Config.WeighingMode = WeighingMode.Box;
 | 
					 | 
				
			||||||
                } else if (Client.IsBaden || Client.IsGrInzersdorf) {
 | 
					 | 
				
			||||||
                    Config.WeighingMode = WeighingMode.Gross;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            base.OnStartup(evt);
 | 
					            base.OnStartup(evt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var window = new MainWindow();
 | 
					            var window = new MainWindow();
 | 
				
			||||||
@@ -230,16 +218,6 @@ namespace Elwig {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs evt) {
 | 
					 | 
				
			||||||
            if (!evt.IsAvailable) return;
 | 
					 | 
				
			||||||
            if (Utils.HasInternetConnectivity()) {
 | 
					 | 
				
			||||||
                Utils.RunBackground("Auto Updater", async () => {
 | 
					 | 
				
			||||||
                    await Task.Delay(2000);
 | 
					 | 
				
			||||||
                    await CheckForUpdates();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public static async Task CheckForUpdates(bool showAlert = false) {
 | 
					        public static async Task CheckForUpdates(bool showAlert = false) {
 | 
				
			||||||
            if (Config.UpdateUrl == null) return;
 | 
					            if (Config.UpdateUrl == null) return;
 | 
				
			||||||
            var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
 | 
					            var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
 | 
				
			||||||
@@ -264,9 +242,7 @@ namespace Elwig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public static async Task ReplaceDatabase(string filename) {
 | 
					        public static async Task ReplaceDatabase(string filename) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                await Task.Run(async () => {
 | 
					                await ElwigData.ImportDatabase(filename);
 | 
				
			||||||
                    await Database.Import(filename);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
 | 
					                MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
 | 
				
			||||||
                ForceShutdown = true;
 | 
					                ForceShutdown = true;
 | 
				
			||||||
                Current.Shutdown();
 | 
					                Current.Shutdown();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
    <UseWPF>true</UseWPF>
 | 
					    <UseWPF>true</UseWPF>
 | 
				
			||||||
    <PreserveCompilationContext>true</PreserveCompilationContext>
 | 
					    <PreserveCompilationContext>true</PreserveCompilationContext>
 | 
				
			||||||
    <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
 | 
					    <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
 | 
				
			||||||
    <Version>1.0.1.4</Version>
 | 
					    <Version>1.0.0.2</Version>
 | 
				
			||||||
    <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
 | 
					    <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
 | 
				
			||||||
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
					    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
				
			||||||
    <ApplicationManifest>app.manifest</ApplicationManifest>
 | 
					    <ApplicationManifest>app.manifest</ApplicationManifest>
 | 
				
			||||||
@@ -25,19 +25,19 @@
 | 
				
			|||||||
    <PackageReference Include="LinqKit" Version="1.3.8" />
 | 
					    <PackageReference Include="LinqKit" Version="1.3.8" />
 | 
				
			||||||
    <PackageReference Include="MailKit" Version="4.13.0" />
 | 
					    <PackageReference Include="MailKit" Version="4.13.0" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.9" />
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.8" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.9" />
 | 
					    <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.8" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3485.44" />
 | 
					    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3405.78" />
 | 
				
			||||||
    <PackageReference Include="NJsonSchema" Version="11.4.0" />
 | 
					    <PackageReference Include="NJsonSchema" Version="11.4.0" />
 | 
				
			||||||
    <PackageReference Include="PdfiumViewer" Version="2.13.0" />
 | 
					    <PackageReference Include="PdfiumViewer" Version="2.13.0" />
 | 
				
			||||||
    <PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
 | 
					    <PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
 | 
				
			||||||
    <PackageReference Include="RazorLight" Version="2.3.1" />
 | 
					    <PackageReference Include="RazorLight" Version="2.3.1" />
 | 
				
			||||||
    <PackageReference Include="ScottPlot.WPF" Version="5.0.56" />
 | 
					    <PackageReference Include="ScottPlot.WPF" Version="5.0.55" />
 | 
				
			||||||
    <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
 | 
					    <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.1" />
 | 
				
			||||||
    <PackageReference Include="System.IO.Hashing" Version="9.0.9" />
 | 
					    <PackageReference Include="System.IO.Hashing" Version="9.0.8" />
 | 
				
			||||||
    <PackageReference Include="System.IO.Ports" Version="9.0.9" />
 | 
					    <PackageReference Include="System.IO.Ports" Version="9.0.8" />
 | 
				
			||||||
    <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.9" />
 | 
					    <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -248,6 +248,13 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
            return c + 1;
 | 
					            return c + 1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public async Task<WineQualLevel> GetWineQualityLevel(double kmw) {
 | 
				
			||||||
 | 
					            return await WineQualityLevels
 | 
				
			||||||
 | 
					                .Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
 | 
				
			||||||
 | 
					                .OrderBy(q => q.MinKmw)
 | 
				
			||||||
 | 
					                .LastAsync();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) {
 | 
					        public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) {
 | 
				
			||||||
            foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
 | 
					            foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
 | 
				
			||||||
                var mod = new DeliveryPartModifier {
 | 
					                var mod = new DeliveryPartModifier {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
    public static class AppDbUpdater {
 | 
					    public static class AppDbUpdater {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Don't forget to update value in Tests/fetch-resources.bat!
 | 
					        // Don't forget to update value in Tests/fetch-resources.bat!
 | 
				
			||||||
        public static readonly int RequiredSchemaVersion = 33;
 | 
					        public static readonly int RequiredSchemaVersion = 32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static int VersionOffset = 0;
 | 
					        private static int VersionOffset = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,19 +20,13 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
            Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(ctx, Year, onlyDelivered: false));
 | 
					            Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(ctx, Year, onlyDelivered: false));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task Calculate(bool strictPrices = true, bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
 | 
					        public async Task Calculate(bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
 | 
				
			||||||
            using var cnx = await AppDbContext.ConnectAsync();
 | 
					            using var cnx = await AppDbContext.ConnectAsync();
 | 
				
			||||||
            using var tx = await cnx.BeginTransactionAsync();
 | 
					            using var tx = await cnx.BeginTransactionAsync();
 | 
				
			||||||
            await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
 | 
					            await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
 | 
				
			||||||
            await DeleteInDb(cnx);
 | 
					            await DeleteInDb(cnx);
 | 
				
			||||||
            await SetCalcTime(cnx);
 | 
					            await SetCalcTime(cnx);
 | 
				
			||||||
            KeyNotFoundException? exception = null;
 | 
					            await CalculatePrices(cnx);
 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                await CalculatePrices(cnx, strictPrices);
 | 
					 | 
				
			||||||
            } catch (KeyNotFoundException e) {
 | 
					 | 
				
			||||||
                if (strictPrices) throw;
 | 
					 | 
				
			||||||
                exception = e;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (Data.ConsiderDelieryModifiers) {
 | 
					            if (Data.ConsiderDelieryModifiers) {
 | 
				
			||||||
                await CalculateDeliveryModifiers(cnx);
 | 
					                await CalculateDeliveryModifiers(cnx);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -40,8 +34,6 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                await CalculateMemberModifiers(cnx);
 | 
					                await CalculateMemberModifiers(cnx);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await tx.CommitAsync();
 | 
					            await tx.CommitAsync();
 | 
				
			||||||
            if (exception != null)
 | 
					 | 
				
			||||||
                throw exception;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task Commit() {
 | 
					        public async Task Commit() {
 | 
				
			||||||
@@ -150,8 +142,7 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                """);
 | 
					                """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) {
 | 
					        protected async Task CalculatePrices(SqliteConnection cnx) {
 | 
				
			||||||
            var invalid = new HashSet<string>();
 | 
					 | 
				
			||||||
            var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>();
 | 
					            var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>();
 | 
				
			||||||
            using (var cmd = cnx.CreateCommand()) {
 | 
					            using (var cmd = cnx.CreateCommand()) {
 | 
				
			||||||
                cmd.CommandText = $"""
 | 
					                cmd.CommandText = $"""
 | 
				
			||||||
@@ -181,25 +172,15 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
 | 
					                var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
 | 
				
			||||||
                var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
 | 
					                var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
 | 
				
			||||||
                var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
 | 
					                var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
 | 
				
			||||||
                decimal price = 0;
 | 
					                var price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
 | 
					 | 
				
			||||||
                } catch (KeyNotFoundException e) {
 | 
					 | 
				
			||||||
                    invalid.Add(e.Message.Split('\'')[1]);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                var priceL = PaymentVariant.Season.DecToDb(price);
 | 
					                var priceL = PaymentVariant.Season.DecToDb(price);
 | 
				
			||||||
                inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
 | 
					                inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var msg = invalid.Count == 0 ? null : "Für folgende Sorten wurde noch keine Preiskurve festgelegt: " + string.Join(", ", invalid);
 | 
					 | 
				
			||||||
            if (msg != null && strict)
 | 
					 | 
				
			||||||
                throw new KeyNotFoundException(msg);
 | 
					 | 
				
			||||||
            await AppDbContext.ExecuteBatch(cnx, $"""
 | 
					            await AppDbContext.ExecuteBatch(cnx, $"""
 | 
				
			||||||
                INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
 | 
					                INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
 | 
				
			||||||
                VALUES {string.Join(",\n       ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))};
 | 
					                VALUES {string.Join(",\n       ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))};
 | 
				
			||||||
                """);
 | 
					                """);
 | 
				
			||||||
            if (msg != null)
 | 
					 | 
				
			||||||
                throw new KeyNotFoundException(msg);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
 | 
					        protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,14 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
 | 
					        public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
 | 
				
			||||||
        public bool IsGrInzersdorf => IsWeinland;
 | 
					        public bool IsGrInzersdorf => IsWeinland;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public bool HasNetWeighing(string? zwstId) => IsMatzen || (IsWinzerkeller && zwstId == "W");
 | 
				
			||||||
 | 
					        public bool HasNetWeighing(Branch? b) => HasNetWeighing(b?.ZwstId);
 | 
				
			||||||
 | 
					        public bool HasNetWeighing() => HasNetWeighing(App.ZwstId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public bool HasBoxWeighing(string? zwstId) => IsWinzerkeller && (zwstId != "W");
 | 
				
			||||||
 | 
					        public bool HasBoxWeighing(Branch? b) => HasBoxWeighing(b?.ZwstId);
 | 
				
			||||||
 | 
					        public bool HasBoxWeighing() => HasBoxWeighing(App.ZwstId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string NameToken;
 | 
					        public string NameToken;
 | 
				
			||||||
        public string NameShort;
 | 
					        public string NameShort;
 | 
				
			||||||
        public string Name;
 | 
					        public string Name;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
@@ -6,8 +5,6 @@ using Microsoft.Extensions.Configuration;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers {
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public enum WeighingMode { Gross, Net, Box }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public record struct ScaleConfig {
 | 
					    public record struct ScaleConfig {
 | 
				
			||||||
        public string Id;
 | 
					        public string Id;
 | 
				
			||||||
        public string? Type;
 | 
					        public string? Type;
 | 
				
			||||||
@@ -44,7 +41,6 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public string DatabaseFile = App.DataPath + "database.sqlite3";
 | 
					        public string DatabaseFile = App.DataPath + "database.sqlite3";
 | 
				
			||||||
        public string? DatabaseLog = null;
 | 
					        public string? DatabaseLog = null;
 | 
				
			||||||
        public string? Branch = null;
 | 
					        public string? Branch = null;
 | 
				
			||||||
        public WeighingMode? WeighingMode;
 | 
					 | 
				
			||||||
        public string? UpdateUrl = null;
 | 
					        public string? UpdateUrl = null;
 | 
				
			||||||
        public bool UpdateAuto = false;
 | 
					        public bool UpdateAuto = false;
 | 
				
			||||||
        public string? SyncUrl = null;
 | 
					        public string? SyncUrl = null;
 | 
				
			||||||
@@ -78,8 +74,6 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
            DatabaseLog = log != null ? Path.Combine(Path.GetDirectoryName(FileName) ?? App.DataPath, log) : null;
 | 
					            DatabaseLog = log != null ? Path.Combine(Path.GetDirectoryName(FileName) ?? App.DataPath, log) : null;
 | 
				
			||||||
            Branch = config["general:branch"];
 | 
					            Branch = config["general:branch"];
 | 
				
			||||||
            Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
 | 
					            Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
 | 
				
			||||||
            var weighing = config["general:weighing"];
 | 
					 | 
				
			||||||
            WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null;
 | 
					 | 
				
			||||||
            UpdateUrl = config["update:url"];
 | 
					            UpdateUrl = config["update:url"];
 | 
				
			||||||
            UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
 | 
					            UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
 | 
				
			||||||
            SyncUrl = config["sync:url"];
 | 
					            SyncUrl = config["sync:url"];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id");
 | 
					 | 
				
			||||||
            var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version");
 | 
					 | 
				
			||||||
            var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "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 AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
 | 
					 | 
				
			||||||
            var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
 | 
					 | 
				
			||||||
            var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "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 AppDbContext.ExecuteBatch(cnx, "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 AppDbContext.ExecuteBatch(cnx, await reader.ReadToEndAsync());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                await ImportSqlite(newName);
 | 
					 | 
				
			||||||
            } finally {
 | 
					 | 
				
			||||||
                if (File.Exists(newName)) File.Delete(newName);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -6,6 +6,7 @@ using System.Globalization;
 | 
				
			|||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.IO.Compression;
 | 
					using System.IO.Compression;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text.Json;
 | 
				
			||||||
using System.Text.Json.Nodes;
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using System.Windows;
 | 
					using System.Windows;
 | 
				
			||||||
@@ -17,6 +18,8 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
 | 
					        public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<string[]> GetImportedFiles() {
 | 
					        public static async Task<string[]> GetImportedFiles() {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
 | 
					                return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
 | 
				
			||||||
@@ -74,14 +77,6 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                    int? DeliveryNum, string? DeliveryFilters)>();
 | 
					                    int? DeliveryNum, string? DeliveryFilters)>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                foreach (var filename in filenames) {
 | 
					                foreach (var filename in filenames) {
 | 
				
			||||||
                    try {
 | 
					 | 
				
			||||||
                        data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
 | 
					 | 
				
			||||||
                            ["member"] = [],
 | 
					 | 
				
			||||||
                            ["area_commitment"] = [],
 | 
					 | 
				
			||||||
                            ["delivery"] = [],
 | 
					 | 
				
			||||||
                        })));
 | 
					 | 
				
			||||||
                        var r = data[^1];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // TODO read encrypted files
 | 
					                    // TODO read encrypted files
 | 
				
			||||||
                    using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
 | 
					                    using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
 | 
				
			||||||
                    await zip.CheckIntegrity();
 | 
					                    await zip.CheckIntegrity();
 | 
				
			||||||
@@ -89,7 +84,7 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                    var version = zip.GetEntry("version");
 | 
					                    var version = zip.GetEntry("version");
 | 
				
			||||||
                    using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
 | 
					                    using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
 | 
				
			||||||
                        if (await reader.ReadToEndAsync() != "elwig:1")
 | 
					                        if (await reader.ReadToEndAsync() != "elwig:1")
 | 
				
			||||||
                                throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})");
 | 
					                            throw new FileFormatException($"Ungültige Export-Datei ({filename})");
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var metaJson = zip.GetEntry("meta.json");
 | 
					                    var metaJson = zip.GetEntry("meta.json");
 | 
				
			||||||
@@ -106,6 +101,13 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                        areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
 | 
					                        areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
 | 
				
			||||||
                        deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
 | 
					                        deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
 | 
				
			||||||
 | 
					                        ["member"] = [],
 | 
				
			||||||
 | 
					                        ["area_commitment"] = [],
 | 
				
			||||||
 | 
					                        ["delivery"] = [],
 | 
				
			||||||
 | 
					                    })));
 | 
				
			||||||
 | 
					                    var r = data[^1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var wbKgsJson = zip.GetEntry("wb_kgs.json");
 | 
					                    var wbKgsJson = zip.GetEntry("wb_kgs.json");
 | 
				
			||||||
                    if (wbKgsJson != null) {
 | 
					                    if (wbKgsJson != null) {
 | 
				
			||||||
                        using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
 | 
					                        using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
 | 
				
			||||||
@@ -168,25 +170,6 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                                r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
					                                r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    } catch (Exception exc) when (
 | 
					 | 
				
			||||||
                            exc is InvalidDataException ||
 | 
					 | 
				
			||||||
                            exc is FileFormatException ||
 | 
					 | 
				
			||||||
                            exc is FileNotFoundException ||
 | 
					 | 
				
			||||||
                            exc is IOException) {
 | 
					 | 
				
			||||||
                        data.RemoveAt(data.Count - 1);
 | 
					 | 
				
			||||||
                        var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message;
 | 
					 | 
				
			||||||
                        if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
					 | 
				
			||||||
                        MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					 | 
				
			||||||
                        await AddImportedFiles(Path.GetFileName(filename));
 | 
					 | 
				
			||||||
                    } catch (Exception exc) {
 | 
					 | 
				
			||||||
                        data.RemoveAt(data.Count - 1);
 | 
					 | 
				
			||||||
                        var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message;
 | 
					 | 
				
			||||||
                        if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
					 | 
				
			||||||
                        var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
 | 
					 | 
				
			||||||
                        if (r == MessageBoxResult.Yes) {
 | 
					 | 
				
			||||||
                            await AddImportedFiles(Path.GetFileName(filename));
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
 | 
					                var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
 | 
				
			||||||
@@ -422,6 +405,29 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
            }.Export(filename);
 | 
					            }.Export(filename);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static async Task ImportDatabase(string filename) {
 | 
				
			||||||
 | 
					            var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3");
 | 
				
			||||||
 | 
					            var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                using (var zip = ZipFile.Open(filename, ZipArchiveMode.Read)) {
 | 
				
			||||||
 | 
					                    await zip.CheckIntegrity();
 | 
				
			||||||
 | 
					                    var db = zip.GetEntry("database.sqlite3")!;
 | 
				
			||||||
 | 
					                    db.ExtractToFile(newName, true);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                File.Move(App.Config.DatabaseFile, oldName, true);
 | 
				
			||||||
 | 
					                File.Move(newName, App.Config.DatabaseFile, false);
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                if (File.Exists(newName))
 | 
				
			||||||
 | 
					                    File.Delete(newName);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static void ExportDatabase(string filename) {
 | 
				
			||||||
 | 
					            File.Delete(filename);
 | 
				
			||||||
 | 
					            using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
 | 
				
			||||||
 | 
					            var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public class ElwigExport {
 | 
					        public class ElwigExport {
 | 
				
			||||||
            public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
 | 
					            public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
 | 
				
			||||||
            public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
 | 
					            public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
 | 
				
			||||||
@@ -466,7 +472,7 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                            ["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
 | 
					                            ["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
 | 
				
			||||||
                            ["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()),
 | 
					                            ["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()),
 | 
				
			||||||
                        };
 | 
					                        };
 | 
				
			||||||
                    await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
 | 
					                    await writer.WriteAsync(obj.ToJsonString(JsonOpts));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // TODO encrypt files
 | 
					                // TODO encrypt files
 | 
				
			||||||
@@ -474,28 +480,28 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                    var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize);
 | 
					                    var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize);
 | 
				
			||||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
					                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
				
			||||||
                    foreach (var k in WbKgs.Value.WbKgs) {
 | 
					                    foreach (var k in WbKgs.Value.WbKgs) {
 | 
				
			||||||
                        await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.JsonOpts));
 | 
					                        await writer.WriteLineAsync(k.ToJson().ToJsonString(JsonOpts));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (Members != null) {
 | 
					                if (Members != null) {
 | 
				
			||||||
                    var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
 | 
					                    var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
 | 
				
			||||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
					                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
				
			||||||
                    foreach (var m in Members.Value.Members) {
 | 
					                    foreach (var m in Members.Value.Members) {
 | 
				
			||||||
                        await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts));
 | 
					                        await writer.WriteLineAsync(m.ToJson().ToJsonString(JsonOpts));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (AreaComs != null) {
 | 
					                if (AreaComs != null) {
 | 
				
			||||||
                    var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
 | 
					                    var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
 | 
				
			||||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
					                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
				
			||||||
                    foreach (var c in AreaComs.Value.AreaComs) {
 | 
					                    foreach (var c in AreaComs.Value.AreaComs) {
 | 
				
			||||||
                        await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts));
 | 
					                        await writer.WriteLineAsync(c.ToJson().ToJsonString(JsonOpts));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (Deliveries != null) {
 | 
					                if (Deliveries != null) {
 | 
				
			||||||
                    var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
 | 
					                    var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
 | 
				
			||||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
					                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
				
			||||||
                    foreach (var d in Deliveries.Value.Deliveries) {
 | 
					                    foreach (var d in Deliveries.Value.Deliveries) {
 | 
				
			||||||
                        await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts));
 | 
					                        await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -823,7 +829,7 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                    Temperature = p["temperature"]?.AsValue().GetValue<double>(),
 | 
					                    Temperature = p["temperature"]?.AsValue().GetValue<double>(),
 | 
				
			||||||
                    Acid = p["acid"]?.AsValue().GetValue<double>(),
 | 
					                    Acid = p["acid"]?.AsValue().GetValue<double>(),
 | 
				
			||||||
                    ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
 | 
					                    ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
 | 
				
			||||||
                    WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts),
 | 
					                    WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
 | 
				
			||||||
                    WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
 | 
					                    WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            }).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
 | 
					            }).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,42 +1,41 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using System.Windows;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					using System.IO.Ports;
 | 
				
			||||||
 | 
					using System.Net.Sockets;
 | 
				
			||||||
using Elwig.Dialogs;
 | 
					using Elwig.Dialogs;
 | 
				
			||||||
using Elwig.Documents;
 | 
					using System.Text;
 | 
				
			||||||
using Elwig.Helpers.Billing;
 | 
					using System.Numerics;
 | 
				
			||||||
using Elwig.Models;
 | 
					 | 
				
			||||||
using Elwig.Models.Entities;
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
using LinqKit;
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					using System.Net.Http;
 | 
				
			||||||
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
using MailKit.Net.Smtp;
 | 
					using MailKit.Net.Smtp;
 | 
				
			||||||
using MailKit.Security;
 | 
					using MailKit.Security;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using Microsoft.Win32;
 | 
					 | 
				
			||||||
using MimeKit;
 | 
					 | 
				
			||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Diagnostics;
 | 
					 | 
				
			||||||
using System.Globalization;
 | 
					 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using System.IO.Ports;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using System.Linq.Expressions;
 | 
					 | 
				
			||||||
using System.Net.Http;
 | 
					 | 
				
			||||||
using System.Net.Sockets;
 | 
					 | 
				
			||||||
using System.Numerics;
 | 
					 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
using System.Runtime.InteropServices;
 | 
					using System.Collections;
 | 
				
			||||||
using System.Text;
 | 
					using Elwig.Documents;
 | 
				
			||||||
using System.Text.Json;
 | 
					using MimeKit;
 | 
				
			||||||
using System.Text.Json.Nodes;
 | 
					using System.Windows.Input;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using LinqKit;
 | 
				
			||||||
 | 
					using System.Linq.Expressions;
 | 
				
			||||||
 | 
					using Elwig.Models;
 | 
				
			||||||
 | 
					using Microsoft.Win32;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using System.Windows;
 | 
					 | 
				
			||||||
using System.Windows.Markup;
 | 
					using System.Windows.Markup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers {
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
    public static partial class Utils {
 | 
					    public static partial class Utils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
 | 
					        public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
 | 
				
			||||||
        public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static int CurrentYear => DateTime.Now.Year;
 | 
					        public static int CurrentYear => DateTime.Now.Year;
 | 
				
			||||||
        public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
 | 
					        public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
 | 
				
			||||||
@@ -431,8 +430,6 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
            var client = new HttpClient() {
 | 
					            var client = new HttpClient() {
 | 
				
			||||||
                Timeout = TimeSpan.FromSeconds(5),
 | 
					                Timeout = TimeSpan.FromSeconds(5),
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            client.DefaultRequestHeaders.UserAgent.Clear();
 | 
					 | 
				
			||||||
            client.DefaultRequestHeaders.UserAgent.ParseAdd($"Elwig/{App.Version} ({App.Client.NameToken}, {App.BranchName}, {Environment.MachineName}, {Environment.OSVersion})");
 | 
					 | 
				
			||||||
            client.DefaultRequestHeaders.Accept.Clear();
 | 
					            client.DefaultRequestHeaders.Accept.Clear();
 | 
				
			||||||
            if (accept != null)
 | 
					            if (accept != null)
 | 
				
			||||||
                client.DefaultRequestHeaders.Accept.Add(new(accept));
 | 
					                client.DefaultRequestHeaders.Accept.Add(new(accept));
 | 
				
			||||||
@@ -501,7 +498,10 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
 | 
					        public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
 | 
				
			||||||
            if (App.Config.Smtp == null)
 | 
					            if (App.Config.Smtp == null)
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            return await Task.Run(async () => {
 | 
					
 | 
				
			||||||
 | 
					            Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var success = await Task.Run(async () => {
 | 
				
			||||||
                SmtpClient? client = null;
 | 
					                SmtpClient? client = null;
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    client = await GetSmtpClient();
 | 
					                    client = await GetSmtpClient();
 | 
				
			||||||
@@ -529,15 +529,18 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
 | 
					            return success;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) {
 | 
					        public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member, string, string)? emailData = null) {
 | 
				
			||||||
            if (mode == ExportMode.Print && !App.Config.Debug) {
 | 
					            if (mode == ExportMode.Print && !App.Config.Debug) {
 | 
				
			||||||
                await doc.Generate();
 | 
					                await doc.Generate();
 | 
				
			||||||
                await doc.Print();
 | 
					                await doc.Print();
 | 
				
			||||||
            } else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
 | 
					            } else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
 | 
				
			||||||
                await doc.Generate();
 | 
					                await doc.Generate();
 | 
				
			||||||
                var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
 | 
					                var success = await SendEmail(e.Item1, e.Item2, e.Item3, [doc]);
 | 
				
			||||||
                if (success)
 | 
					                if (success)
 | 
				
			||||||
                    MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt",
 | 
					                    MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt",
 | 
				
			||||||
                        MessageBoxButton.OK, MessageBoxImage.Information);
 | 
					                        MessageBoxButton.OK, MessageBoxImage.Information);
 | 
				
			||||||
@@ -564,7 +567,9 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
                Log = "Application",
 | 
					                Log = "Application",
 | 
				
			||||||
                Source = ".NET Runtime",
 | 
					                Source = ".NET Runtime",
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            return [.. log.Entries.OfType<EventLogEntry>().Where(e => e.InstanceId == 1026).Where(e => e.Message.StartsWith("Application: Elwig.exe"))];
 | 
					            return log.Entries.Cast<EventLogEntry>()
 | 
				
			||||||
 | 
					                .Where(e => e.Message.StartsWith("Application: Elwig.exe"))
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
 | 
					        public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Elwig.Helpers.Billing;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
@@ -63,11 +62,6 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        [Column("comment")]
 | 
					        [Column("comment")]
 | 
				
			||||||
        public string? Comment { get; set; }
 | 
					        public string? Comment { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [NotMapped]
 | 
					 | 
				
			||||||
        public string[] Comments => [.. Parts.Select(p => p.Comment).Prepend(Comment).Where(c => c != null).Cast<string>()];
 | 
					 | 
				
			||||||
        [NotMapped]
 | 
					 | 
				
			||||||
        public string CommentsString => string.Join(" / ", Comments);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
 | 
					        [Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
 | 
				
			||||||
        public long CTime { get; set; }
 | 
					        public long CTime { get; set; }
 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
@@ -114,16 +108,16 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        public int Weight => Parts.Select(p => p.Weight).Sum();
 | 
					        public int Weight => Parts.Select(p => p.Weight).Sum();
 | 
				
			||||||
        public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
 | 
					        public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public IEnumerable<RawVaribute> Vaributes => Parts
 | 
					        public IEnumerable<string> SortIds => Parts
 | 
				
			||||||
            .GroupBy(p => (p.SortId, p.AttrId, p.CultId))
 | 
					            .GroupBy(p => p.SortId)
 | 
				
			||||||
            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
					            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
				
			||||||
            .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
 | 
					            .Select(g => g.Key);
 | 
				
			||||||
        public IEnumerable<RawVaribute> FilteredVaributes => FilteredParts
 | 
					        public IEnumerable<string> FilteredSortIds => FilteredParts
 | 
				
			||||||
            .GroupBy(p => (p.SortId, p.AttrId, p.CultId))
 | 
					            .GroupBy(p => p.SortId)
 | 
				
			||||||
            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
					            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
				
			||||||
            .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
 | 
					            .Select(g => g.Key);
 | 
				
			||||||
        public string VaributeString => string.Join(", ", Vaributes);
 | 
					        public string SortIdString => string.Join(", ", SortIds);
 | 
				
			||||||
        public string FilteredVaributeString => string.Join(", ", FilteredVaributes);
 | 
					        public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Brush? Color => Parts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
 | 
					        public Brush? Color => Parts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
 | 
				
			||||||
        public Brush? FilteredColor => FilteredParts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
 | 
					        public Brush? FilteredColor => FilteredParts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,9 +42,7 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        [Column("max_weight")]
 | 
					        [Column("max_weight")]
 | 
				
			||||||
        public int? MaxWeight { get; set; }
 | 
					        public int? MaxWeight { get; set; }
 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public int AnnouncedWeight => AnnouncedWeightOverride ?? Announcements.Sum(a => a.Weight);
 | 
					        public int AnnouncedWeight => Announcements.Sum(a => a.Weight);
 | 
				
			||||||
        [NotMapped]
 | 
					 | 
				
			||||||
        public int? AnnouncedWeightOverride { get; set; }
 | 
					 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public double? Percent => (double)AnnouncedWeight / MaxWeight * 100;
 | 
					        public double? Percent => (double)AnnouncedWeight / MaxWeight * 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,24 +11,16 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        [Column("type")]
 | 
					        [Column("type")]
 | 
				
			||||||
        public string Type { get; private set; } = null!;
 | 
					        public string Type { get; private set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Column("max_qualid")]
 | 
					 | 
				
			||||||
        public string MaxQualId { get; private set; } = null!;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [ForeignKey("MaxQualId")]
 | 
					 | 
				
			||||||
        public virtual WineQualLevel MaxQualityLevel { get; private set; } = null!;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [Column("name")]
 | 
					        [Column("name")]
 | 
				
			||||||
        public string Name { get; private set; } = null!;
 | 
					        public string Name { get; private set; } = null!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Column("comment")]
 | 
					        [Column("comment")]
 | 
				
			||||||
        public string? Comment { get; private set; }
 | 
					        public string? Comment { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string SortIdFormat => IsQuw ? SortId : $"({SortId})";
 | 
					 | 
				
			||||||
        public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
 | 
					        public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public bool IsRed => Type == "R";
 | 
					        public bool IsRed => Type == "R";
 | 
				
			||||||
        public bool IsWhite => Type == "W";
 | 
					        public bool IsWhite => Type == "W";
 | 
				
			||||||
        public bool IsQuw => MaxQualId == "QUW";
 | 
					 | 
				
			||||||
        public Brush? Color => IsWhite ? Brushes.DarkGreen : IsRed ? Brushes.DarkRed : null;
 | 
					        public Brush? Color => IsWhite ? Brushes.DarkGreen : IsRed ? Brushes.DarkRed : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public WineVar() { }
 | 
					        public WineVar() { }
 | 
				
			||||||
@@ -36,7 +28,6 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        public WineVar(string sortId, string name) {
 | 
					        public WineVar(string sortId, string name) {
 | 
				
			||||||
            SortId = sortId;
 | 
					            SortId = sortId;
 | 
				
			||||||
            Name = name;
 | 
					            Name = name;
 | 
				
			||||||
            MaxQualId = "QUW";
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public override string ToString() {
 | 
					        public override string ToString() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
					 | 
				
			||||||
@@ -721,11 +721,9 @@ namespace Elwig.Services {
 | 
				
			|||||||
                    FileName = subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}.elwig.zip" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
 | 
					                    FileName = subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}.elwig.zip" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
 | 
				
			||||||
                    DefaultExt = "elwig.zip",
 | 
					                    DefaultExt = "elwig.zip",
 | 
				
			||||||
                    Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
 | 
					                    Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
 | 
				
			||||||
                    Title = $"{DeliveryJournal.Name} speichern unter - Elwig",
 | 
					                    Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
 | 
				
			||||||
                    AddExtension = false,
 | 
					 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                if (d.ShowDialog() == true) {
 | 
					                if (d.ShowDialog() == true) {
 | 
				
			||||||
                    if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
 | 
					 | 
				
			||||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
					                    Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                    await Task.Run(async () => {
 | 
					                    await Task.Run(async () => {
 | 
				
			||||||
                        try {
 | 
					                        try {
 | 
				
			||||||
@@ -733,7 +731,6 @@ namespace Elwig.Services {
 | 
				
			|||||||
                                .Select(p => p.Delivery)
 | 
					                                .Select(p => p.Delivery)
 | 
				
			||||||
                                .Distinct()
 | 
					                                .Distinct()
 | 
				
			||||||
                                .Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
 | 
					                                .Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
 | 
				
			||||||
                                .Include(d => d.Parts).ThenInclude(p => p.Rd)
 | 
					 | 
				
			||||||
                                .Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
 | 
					                                .Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
 | 
				
			||||||
                                .AsSplitQuery()
 | 
					                                .AsSplitQuery()
 | 
				
			||||||
                                .ToListAsync();
 | 
					                                .ToListAsync();
 | 
				
			||||||
@@ -761,7 +758,6 @@ namespace Elwig.Services {
 | 
				
			|||||||
                            .Select(p => p.Delivery)
 | 
					                            .Select(p => p.Delivery)
 | 
				
			||||||
                            .Distinct()
 | 
					                            .Distinct()
 | 
				
			||||||
                            .Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
 | 
					                            .Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
 | 
				
			||||||
                            .Include(d => d.Parts).ThenInclude(p => p.Rd)
 | 
					 | 
				
			||||||
                            .Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
 | 
					                            .Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
 | 
				
			||||||
                            .AsSplitQuery()
 | 
					                            .AsSplitQuery()
 | 
				
			||||||
                            .ToListAsync();
 | 
					                            .ToListAsync();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -496,13 +496,11 @@ namespace Elwig.Services {
 | 
				
			|||||||
            } else if (mode == ExportMode.Export) {
 | 
					            } else if (mode == ExportMode.Export) {
 | 
				
			||||||
                var d = new SaveFileDialog() {
 | 
					                var d = new SaveFileDialog() {
 | 
				
			||||||
                    FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
 | 
					                    FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
 | 
				
			||||||
                    DefaultExt = "elwig.zip",
 | 
					                    DefaultExt = ".elwig.zip",
 | 
				
			||||||
                    Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
 | 
					                    Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
 | 
				
			||||||
                    Title = $"{MemberList.Name} speichern unter - Elwig",
 | 
					                    Title = $"{MemberList.Name} speichern unter - Elwig"
 | 
				
			||||||
                    AddExtension = false,
 | 
					 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                if (d.ShowDialog() == true) {
 | 
					                if (d.ShowDialog() == true) {
 | 
				
			||||||
                    if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
 | 
					 | 
				
			||||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
					                    Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                    await Task.Run(async () => {
 | 
					                    await Task.Run(async () => {
 | 
				
			||||||
                        try {
 | 
					                        try {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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> 2022–2025<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>
 | 
					 | 
				
			||||||
@@ -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 });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -13,8 +13,8 @@ using System.Windows.Input;
 | 
				
			|||||||
namespace Elwig.Windows {
 | 
					namespace Elwig.Windows {
 | 
				
			||||||
    public abstract class AdministrationWindow : ContextWindow {
 | 
					    public abstract class AdministrationWindow : ContextWindow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected Control[] ExemptInputs { get; set; }
 | 
					        protected Control[] ExemptInputs { private get; set; }
 | 
				
			||||||
        protected Control[] RequiredInputs { get; set; }
 | 
					        protected Control[] RequiredInputs { private get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool _isEditing;
 | 
					        private bool _isEditing;
 | 
				
			||||||
        private bool _isCreating;
 | 
					        private bool _isCreating;
 | 
				
			||||||
@@ -166,10 +166,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                    Valid[input] = false;
 | 
					                    Valid[input] = false;
 | 
				
			||||||
                } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) {
 | 
					                } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) {
 | 
				
			||||||
                    ControlUtils.SetInputInvalid(input);
 | 
					                    ControlUtils.SetInputInvalid(input);
 | 
				
			||||||
                    Valid[input] = false;
 | 
					 | 
				
			||||||
                } else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) {
 | 
					                } else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) {
 | 
				
			||||||
                    ControlUtils.SetInputInvalid(input);
 | 
					                    ControlUtils.SetInputInvalid(input);
 | 
				
			||||||
                    Valid[input] = false;
 | 
					 | 
				
			||||||
                } else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) {
 | 
					                } else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) {
 | 
				
			||||||
                    ControlUtils.SetInputInvalid(input);
 | 
					                    ControlUtils.SetInputInvalid(input);
 | 
				
			||||||
                    Valid[input] = false;
 | 
					                    Valid[input] = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -664,10 +664,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            try {
 | 
					            try {
 | 
				
			||||||
                await Task.Run(async () => {
 | 
					                await Task.Run(async () => {
 | 
				
			||||||
                    var b = new BillingVariant(PaymentVar.Year, PaymentVar.AvNr);
 | 
					                    var b = new BillingVariant(PaymentVar.Year, PaymentVar.AvNr);
 | 
				
			||||||
                    await b.Calculate(false);
 | 
					                    await b.Calculate();
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            } catch (KeyNotFoundException exc) {
 | 
					 | 
				
			||||||
                MessageBox.Show(exc.Message, "Noch nicht alle Preise festgelegt", MessageBoxButton.OK, MessageBoxImage.Information);
 | 
					 | 
				
			||||||
            } catch (Exception exc) {
 | 
					            } catch (Exception exc) {
 | 
				
			||||||
                MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					                MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -265,18 +265,13 @@
 | 
				
			|||||||
                            </Style>
 | 
					                            </Style>
 | 
				
			||||||
                        </DataGridTextColumn.CellStyle>
 | 
					                        </DataGridTextColumn.CellStyle>
 | 
				
			||||||
                    </DataGridTextColumn>
 | 
					                    </DataGridTextColumn>
 | 
				
			||||||
                    <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredVaributeString}" Width="60">
 | 
					                    <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
 | 
				
			||||||
                        <DataGridTextColumn.CellStyle>
 | 
					                        <DataGridTextColumn.CellStyle>
 | 
				
			||||||
                            <Style>
 | 
					                            <Style>
 | 
				
			||||||
                                <Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/>
 | 
					                                <Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/>
 | 
				
			||||||
                                <Setter Property="TextBlock.TextAlignment" Value="Center"/>
 | 
					                                <Setter Property="TextBlock.TextAlignment" Value="Center"/>
 | 
				
			||||||
                            </Style>
 | 
					                            </Style>
 | 
				
			||||||
                        </DataGridTextColumn.CellStyle>
 | 
					                        </DataGridTextColumn.CellStyle>
 | 
				
			||||||
                        <DataGridTextColumn.ElementStyle>
 | 
					 | 
				
			||||||
                            <Style TargetType="TextBlock">
 | 
					 | 
				
			||||||
                                <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
 | 
					 | 
				
			||||||
                            </Style>
 | 
					 | 
				
			||||||
                        </DataGridTextColumn.ElementStyle>
 | 
					 | 
				
			||||||
                    </DataGridTextColumn>
 | 
					                    </DataGridTextColumn>
 | 
				
			||||||
                    <DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
 | 
					                    <DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
 | 
				
			||||||
                        <DataGridTextColumn.CellStyle>
 | 
					                        <DataGridTextColumn.CellStyle>
 | 
				
			||||||
@@ -295,7 +290,6 @@
 | 
				
			|||||||
                    <DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/>
 | 
					                    <DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/>
 | 
				
			||||||
                    <DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/>
 | 
					                    <DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/>
 | 
				
			||||||
                    <DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/>
 | 
					                    <DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/>
 | 
				
			||||||
                    <DataGridTextColumn Header="Kommentar" Binding="{Binding CommentsString}" Width="150"/>
 | 
					 | 
				
			||||||
                </DataGrid.Columns>
 | 
					                </DataGrid.Columns>
 | 
				
			||||||
            </DataGrid>
 | 
					            </DataGrid>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,6 @@ using Elwig.ViewModels;
 | 
				
			|||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using Microsoft.Win32;
 | 
					using Microsoft.Win32;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using System.Windows;
 | 
					using System.Windows;
 | 
				
			||||||
@@ -35,8 +34,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private readonly Button[] WeighingButtons;
 | 
					        private readonly Button[] WeighingButtons;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private List<WineQualLevel> WineQualityLevels = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public DeliveryAdminWindow(bool receipt = false) {
 | 
					        public DeliveryAdminWindow(bool receipt = false) {
 | 
				
			||||||
            InitializeComponent();
 | 
					            InitializeComponent();
 | 
				
			||||||
            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
 | 
					            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
 | 
				
			||||||
@@ -92,9 +89,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                foreach (var s in App.EventScales) {
 | 
					                foreach (var s in App.EventScales) {
 | 
				
			||||||
                    s.WeighingEvent += Scale_Weighing;
 | 
					                    s.WeighingEvent += Scale_Weighing;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (App.Client.IsMatzen) {
 | 
					 | 
				
			||||||
                    RequiredInputs = [.. RequiredInputs, ModifiersInput];
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                WeighingManualButton.Visibility = Visibility.Hidden;
 | 
					                WeighingManualButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
                WeighingAButton.Visibility = Visibility.Hidden;
 | 
					                WeighingAButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
@@ -284,7 +278,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                DateInput.IsReadOnly = false;
 | 
					                DateInput.IsReadOnly = false;
 | 
				
			||||||
                TimeInput.IsReadOnly = false;
 | 
					                TimeInput.IsReadOnly = false;
 | 
				
			||||||
                BranchInput.IsEnabled = true;
 | 
					                BranchInput.IsEnabled = true;
 | 
				
			||||||
                GerebeltGewogenInput.IsEnabled = true;
 | 
					 | 
				
			||||||
                if (IsCreating) ViewModel.Time = "";
 | 
					                if (IsCreating) ViewModel.Time = "";
 | 
				
			||||||
                OnSecondPassed(null, null);
 | 
					                OnSecondPassed(null, null);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -294,7 +287,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            DateInput.IsReadOnly = true;
 | 
					            DateInput.IsReadOnly = true;
 | 
				
			||||||
            TimeInput.IsReadOnly = true;
 | 
					            TimeInput.IsReadOnly = true;
 | 
				
			||||||
            BranchInput.IsEnabled = false;
 | 
					            BranchInput.IsEnabled = false;
 | 
				
			||||||
            GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode != WeighingMode.Net;
 | 
					 | 
				
			||||||
            OnSecondPassed(null, null);
 | 
					            OnSecondPassed(null, null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -309,7 +301,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void InitialDefaultInputs() {
 | 
					        private void InitialDefaultInputs() {
 | 
				
			||||||
            if (App.Config.WeighingMode == WeighingMode.Net) {
 | 
					            if (App.Client.HasNetWeighing(ViewModel.Branch)) {
 | 
				
			||||||
                GerebeltGewogenInput.IsEnabled = false;
 | 
					                GerebeltGewogenInput.IsEnabled = false;
 | 
				
			||||||
                SetDefaultValue(GerebeltGewogenInput, true);
 | 
					                SetDefaultValue(GerebeltGewogenInput, true);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@@ -318,7 +310,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                UnsetDefaultValue(GerebeltGewogenInput);
 | 
					                UnsetDefaultValue(GerebeltGewogenInput);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (App.Config.WeighingMode == WeighingMode.Box) {
 | 
					            if (App.Client.HasBoxWeighing(ViewModel.Branch)) {
 | 
				
			||||||
                LesewagenInput.IsEnabled = false;
 | 
					                LesewagenInput.IsEnabled = false;
 | 
				
			||||||
                SetDefaultValue(LesewagenInput, false);
 | 
					                SetDefaultValue(LesewagenInput, false);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@@ -326,7 +318,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                UnsetDefaultValue(LesewagenInput);
 | 
					                UnsetDefaultValue(LesewagenInput);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (App.Config.WeighingMode != WeighingMode.Net) {
 | 
					            if (!App.Client.HasNetWeighing(ViewModel.Branch)) {
 | 
				
			||||||
                HandPickedInput.IsThreeState = false;
 | 
					                HandPickedInput.IsThreeState = false;
 | 
				
			||||||
                UnsetDefaultValue(HandPickedInput);
 | 
					                UnsetDefaultValue(HandPickedInput);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@@ -352,13 +344,13 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            ClearOriginalValues();
 | 
					            ClearOriginalValues();
 | 
				
			||||||
            ClearDefaultValues();
 | 
					            ClearDefaultValues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ViewModel.IsNetWeight = App.Config.WeighingMode == WeighingMode.Net;
 | 
					            ViewModel.IsNetWeight = App.Client.HasNetWeighing(ViewModel.Branch);
 | 
				
			||||||
            ViewModel.IsLesewagen = false;
 | 
					            ViewModel.IsLesewagen = false;
 | 
				
			||||||
            ViewModel.IsHandPicked = App.Config.WeighingMode != WeighingMode.Net ? true : null;
 | 
					            ViewModel.IsHandPicked = !App.Client.HasNetWeighing(ViewModel.Branch) ? true : null;
 | 
				
			||||||
            ViewModel.IsGebunden = null;
 | 
					            ViewModel.IsGebunden = null;
 | 
				
			||||||
            InitialDefaultInputs();
 | 
					            InitialDefaultInputs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            //WineQualityLevelInput.IsEnabled = false;  // disable wine quality level input in Übernahme
 | 
					            WineQualityLevelInput.IsEnabled = false;
 | 
				
			||||||
            ValidateRequiredInputs();
 | 
					            ValidateRequiredInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -486,8 +478,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            var cultList = await ctx.WineCultivations.OrderBy(a => a.Name).Cast<object>().ToListAsync();
 | 
					            var cultList = await ctx.WineCultivations.OrderBy(a => a.Name).Cast<object>().ToListAsync();
 | 
				
			||||||
            cultList.Insert(0, new NullItem(""));
 | 
					            cultList.Insert(0, new NullItem(""));
 | 
				
			||||||
            ControlUtils.RenewItemsSource(CultivationInput, cultList, null, ControlUtils.RenewSourceDefault.First);
 | 
					            ControlUtils.RenewItemsSource(CultivationInput, cultList, null, ControlUtils.RenewSourceDefault.First);
 | 
				
			||||||
            WineQualityLevels = await ctx.WineQualityLevels.ToListAsync();
 | 
					            ControlUtils.RenewItemsSource(WineQualityLevelInput, await ctx.WineQualityLevels.ToListAsync());
 | 
				
			||||||
            ControlUtils.RenewItemsSource(WineQualityLevelInput, WineQualityLevels);
 | 
					 | 
				
			||||||
            ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers
 | 
					            ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers
 | 
				
			||||||
                .Where(m => m.Year == y && (!IsCreating || m.IsActive))
 | 
					                .Where(m => m.Year == y && (!IsCreating || m.IsActive))
 | 
				
			||||||
                .OrderBy(m => m.Ordering)
 | 
					                .OrderBy(m => m.Ordering)
 | 
				
			||||||
@@ -1148,7 +1139,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
 | 
					            DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
 | 
				
			||||||
            TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
 | 
					            TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
 | 
				
			||||||
            BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked;
 | 
					            BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked;
 | 
				
			||||||
            GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode == WeighingMode.Net || Menu_Settings_EnableFreeEditing.IsChecked;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void DisableWeighingButtons() {
 | 
					        private void DisableWeighingButtons() {
 | 
				
			||||||
@@ -1211,7 +1201,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                AttributeInput.SelectedIndex = 0;
 | 
					                AttributeInput.SelectedIndex = 0;
 | 
				
			||||||
                CultivationInput.SelectedIndex = 0;
 | 
					                CultivationInput.SelectedIndex = 0;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            UpdateWineQualityLevels();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) {
 | 
					        private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) {
 | 
				
			||||||
@@ -1225,39 +1214,19 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
 | 
					        private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
 | 
				
			||||||
            if (WineVarietyInput.SelectedItem is WineVar s)
 | 
					            if (WineVarietyInput.SelectedItem is WineVar s)
 | 
				
			||||||
                ViewModel.SortId = s.SortId;
 | 
					                ViewModel.SortId = s.SortId;
 | 
				
			||||||
            UpdateWineQualityLevels();
 | 
					 | 
				
			||||||
            if (!ViewModel.WineVar?.IsQuw ?? false) {
 | 
					 | 
				
			||||||
                App.MainDispatcher.BeginInvoke(() => {
 | 
					 | 
				
			||||||
                    MessageBox.Show("Die eingegebene Sorte darf nicht als Qualitätswein\nübernommen werden!", "Kein Qualitätswein",
 | 
					 | 
				
			||||||
                        MessageBoxButton.OK, MessageBoxImage.Warning);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private WineQualLevel GetWineQualityLevel(double kmw, string? maxQualId = null) {
 | 
					 | 
				
			||||||
            return WineQualityLevels
 | 
					 | 
				
			||||||
                .Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
 | 
					 | 
				
			||||||
                .Where(q => maxQualId == null || q.QualId == "WEI" || q.QualId == maxQualId)
 | 
					 | 
				
			||||||
                .OrderBy(q => q.MinKmw)
 | 
					 | 
				
			||||||
                .Last();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void UpdateWineQualityLevels() {
 | 
					        private void UpdateWineQualityLevels() {
 | 
				
			||||||
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
            if (!GetInputValid(GradationKmwInput)) {
 | 
					            if (!GetInputValid(GradationKmwInput)) {
 | 
				
			||||||
                UnsetDefaultValue(WineQualityLevelInput);
 | 
					                UnsetDefaultValue(WineQualityLevelInput);
 | 
				
			||||||
                ComboBox_SelectionChanged(WineQualityLevelInput, null);
 | 
					                ComboBox_SelectionChanged(WineQualityLevelInput, null);
 | 
				
			||||||
                WineQualityLevelInput.ItemsSource = WineQualityLevels;
 | 
					                WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.QualId == "WEI").ToList();
 | 
				
			||||||
                WineQualityLevelInput.SelectedItem = null;
 | 
					 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            var kmw = (double)ViewModel.GradationKmw!;
 | 
					            var kmw = (double)ViewModel.GradationKmw!;
 | 
				
			||||||
            var max = ViewModel.WineVar?.MaxQualId;
 | 
					            WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.MinKmw == null || q.MinKmw <= kmw).ToList();
 | 
				
			||||||
            var quw = ViewModel.WineVar?.IsQuw ?? true;
 | 
					            var qual = ctx.GetWineQualityLevel(kmw).GetAwaiter().GetResult();
 | 
				
			||||||
            WineQualityLevelInput.ItemsSource = WineQualityLevels
 | 
					 | 
				
			||||||
                .Where(q => q.MinKmw == null || q.MinKmw <= kmw)
 | 
					 | 
				
			||||||
                .Where(q => quw || q.QualId == "WEI" || q.QualId == max)
 | 
					 | 
				
			||||||
                .ToList();
 | 
					 | 
				
			||||||
            var qual = GetWineQualityLevel(kmw, !quw ? max : null);
 | 
					 | 
				
			||||||
            SetDefaultValue(WineQualityLevelInput, qual);
 | 
					            SetDefaultValue(WineQualityLevelInput, qual);
 | 
				
			||||||
            if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) {
 | 
					            if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) {
 | 
				
			||||||
                ControlUtils.SelectItem(WineQualityLevelInput, qual);
 | 
					                ControlUtils.SelectItem(WineQualityLevelInput, qual);
 | 
				
			||||||
@@ -1397,7 +1366,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                AbgewertetInput.IsChecked = false;
 | 
					                AbgewertetInput.IsChecked = false;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            var defQual = GetWineQualityLevel(ViewModel.GradationKmw!.Value, !(ViewModel.WineVar?.IsQuw ?? true) ? ViewModel.WineVar?.MaxQualId : null);
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
 | 
					            var defQual = ctx.GetWineQualityLevel(double.Parse(GradationKmwInput.Text)).GetAwaiter().GetResult();
 | 
				
			||||||
            AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
 | 
					            AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1419,17 +1389,17 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
 | 
					        private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) {
 | 
					            if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
 | 
				
			||||||
                HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
 | 
					                HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!ViewModel.IsReceipt || App.Config.WeighingMode == WeighingMode.Net) {
 | 
					            if (!ViewModel.IsReceipt || App.Client.HasNetWeighing(ViewModel.Branch)) {
 | 
				
			||||||
                GerebeltGewogenInput.IsChecked ??= false;
 | 
					                GerebeltGewogenInput.IsChecked ??= false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            CheckBox_Changed(sender, evt);
 | 
					            CheckBox_Changed(sender, evt);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
 | 
					        private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) {
 | 
					            if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
 | 
				
			||||||
                GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
 | 
					                GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            CheckBox_Changed(sender, evt);
 | 
					            CheckBox_Changed(sender, evt);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,19 +83,14 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async Task RefreshDeliveryScheduleList() {
 | 
					        private async Task RefreshDeliveryScheduleList() {
 | 
				
			||||||
            using var ctx = new AppDbContext();
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
            var list = await ctx.DeliverySchedules
 | 
					            var deliverySchedules = await ctx.DeliverySchedules
 | 
				
			||||||
                .Where(s => s.Year == ViewModel.FilterSeason)
 | 
					                .Where(s => s.Year == ViewModel.FilterSeason)
 | 
				
			||||||
                .Include(s => s.Branch)
 | 
					                .Include(s => s.Branch)
 | 
				
			||||||
 | 
					                .Include(s => s.Announcements)
 | 
				
			||||||
                .OrderBy(s => s.DateString)
 | 
					                .OrderBy(s => s.DateString)
 | 
				
			||||||
                .ThenBy(s => s.Branch.Name)
 | 
					                .ThenBy(s => s.Branch.Name)
 | 
				
			||||||
                .ThenBy(s => s.Description)
 | 
					                .ThenBy(s => s.Description)
 | 
				
			||||||
                .Select(s => new {
 | 
					 | 
				
			||||||
                    Schedule = s,
 | 
					 | 
				
			||||||
                    AnnouncedWeight = s.Announcements.Sum(a => a.Weight)
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .ToListAsync();
 | 
					                .ToListAsync();
 | 
				
			||||||
            list.ForEach(v => v.Schedule.AnnouncedWeightOverride = v.AnnouncedWeight);
 | 
					 | 
				
			||||||
            var deliverySchedules = list.Select(v => v.Schedule).ToList();
 | 
					 | 
				
			||||||
            ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules
 | 
					            ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules
 | 
				
			||||||
                .Where(s => !ViewModel.FilterOnlyUpcoming || s.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0)
 | 
					                .Where(s => !ViewModel.FilterOnlyUpcoming || s.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0)
 | 
				
			||||||
                .ToList(), DeliveryScheduleList_SelectionChanged, ViewModel.FilterFromAllSchedules ? ControlUtils.RenewSourceDefault.None : ControlUtils.RenewSourceDefault.First);
 | 
					                .ToList(), DeliveryScheduleList_SelectionChanged, ViewModel.FilterFromAllSchedules ? ControlUtils.RenewSourceDefault.None : ControlUtils.RenewSourceDefault.First);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using System.Diagnostics;
 | 
					using System.Diagnostics;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using System.Windows;
 | 
					using System.Windows;
 | 
				
			||||||
using System.Windows.Input;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Windows {
 | 
					namespace Elwig.Windows {
 | 
				
			||||||
    public partial class LogWindow : Window {
 | 
					    public partial class LogWindow : Window {
 | 
				
			||||||
@@ -13,10 +11,9 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            WindowState = WindowState.Maximized;
 | 
					            WindowState = WindowState.Maximized;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void Window_Loaded(object sender, RoutedEventArgs evt) {
 | 
					        private void Window_Loaded(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            Mouse.OverrideCursor = Cursors.Wait;
 | 
					            var log = Utils.GetLogEntries();
 | 
				
			||||||
            await Task.Run(async () => {
 | 
					            EventList.ItemsSource = log
 | 
				
			||||||
                var list = Utils.GetLogEntries()
 | 
					 | 
				
			||||||
                .Select(e => new {
 | 
					                .Select(e => new {
 | 
				
			||||||
                    Event = e,
 | 
					                    Event = e,
 | 
				
			||||||
                    Lines = e.Message.Split('\n').ToArray(),
 | 
					                    Lines = e.Message.Split('\n').ToArray(),
 | 
				
			||||||
@@ -35,12 +32,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                })
 | 
					                })
 | 
				
			||||||
                .OrderByDescending(e => e.Event.TimeGenerated)
 | 
					                .OrderByDescending(e => e.Event.TimeGenerated)
 | 
				
			||||||
                .ToList();
 | 
					                .ToList();
 | 
				
			||||||
                await App.MainDispatcher.BeginInvoke(() => {
 | 
					 | 
				
			||||||
                    EventList.ItemsSource = list;
 | 
					 | 
				
			||||||
            EventList.SelectedIndex = 0;
 | 
					            EventList.SelectedIndex = 0;
 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) {
 | 
					        private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -738,7 +738,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                PostalNoEmailInput.IsChecked == true ? 1 : 0;
 | 
					                PostalNoEmailInput.IsChecked == true ? 1 : 0;
 | 
				
			||||||
            var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0;
 | 
					            var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 1 ? PostalNoEmailCount : 0;
 | 
					            double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 2 ? PostalNoEmailCount : 0;
 | 
				
			||||||
            double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0;
 | 
					            double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0;
 | 
				
			||||||
            double totalNum = printNum + emailNum;
 | 
					            double totalNum = printNum + emailNum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,17 +31,6 @@
 | 
				
			|||||||
                    </MenuItem.Icon>
 | 
					                    </MenuItem.Icon>
 | 
				
			||||||
                </MenuItem>
 | 
					                </MenuItem>
 | 
				
			||||||
                <Separator/>
 | 
					                <Separator/>
 | 
				
			||||||
                <MenuItem Header="Datenbank sichern..." Click="Menu_Database_Backup_Click">
 | 
					 | 
				
			||||||
                    <MenuItem.Icon>
 | 
					 | 
				
			||||||
                        <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
 | 
					 | 
				
			||||||
                    </MenuItem.Icon>
 | 
					 | 
				
			||||||
                </MenuItem>
 | 
					 | 
				
			||||||
                <MenuItem Header="Datenbank wiederherstellen..." Click="Menu_Database_Restore_Click">
 | 
					 | 
				
			||||||
                    <MenuItem.Icon>
 | 
					 | 
				
			||||||
                        <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
 | 
					 | 
				
			||||||
                    </MenuItem.Icon>
 | 
					 | 
				
			||||||
                </MenuItem>
 | 
					 | 
				
			||||||
                <Separator/>
 | 
					 | 
				
			||||||
                <MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click">
 | 
					                <MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click">
 | 
				
			||||||
                    <MenuItem.Icon>
 | 
					                    <MenuItem.Icon>
 | 
				
			||||||
                        <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
 | 
					                        <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
 | 
				
			||||||
@@ -72,7 +61,7 @@
 | 
				
			|||||||
                </MenuItem>
 | 
					                </MenuItem>
 | 
				
			||||||
            </MenuItem>
 | 
					            </MenuItem>
 | 
				
			||||||
            <MenuItem x:Name="HelpMenu" Header="Hilfe">
 | 
					            <MenuItem x:Name="HelpMenu" Header="Hilfe">
 | 
				
			||||||
                <MenuItem Header="Über" Click="Menu_Help_About_Click">
 | 
					                <MenuItem Header="Über">
 | 
				
			||||||
                    <MenuItem.Icon>
 | 
					                    <MenuItem.Icon>
 | 
				
			||||||
                        <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
 | 
					                        <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
 | 
				
			||||||
                    </MenuItem.Icon>
 | 
					                    </MenuItem.Icon>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,11 +59,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
 | 
					 | 
				
			||||||
            var w = new AboutWindow();
 | 
					 | 
				
			||||||
            w.Show();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            await App.CheckForUpdates(true);
 | 
					            await App.CheckForUpdates(true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -151,50 +146,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            Mouse.OverrideCursor = null;
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void Menu_Database_Backup_Click(object sender, RoutedEventArgs evt) {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                var d = new SaveFileDialog() {
 | 
					 | 
				
			||||||
                    Title = "Datenbank sichern - Elwig",
 | 
					 | 
				
			||||||
                    FileName = $"database_{Utils.Today:yyyy-MM-dd}.sql.zip",
 | 
					 | 
				
			||||||
                    DefaultExt = "sql.zip",
 | 
					 | 
				
			||||||
                    Filter = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip",
 | 
					 | 
				
			||||||
                    AddExtension = false,
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                if (d.ShowDialog() == true) {
 | 
					 | 
				
			||||||
                    if (!d.FileName.EndsWith(".sql.zip")) d.FileName += ".sql.zip";
 | 
					 | 
				
			||||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
					 | 
				
			||||||
                    await Task.Run(async () => {
 | 
					 | 
				
			||||||
                        await Database.ExportSql(d.FileName, true);
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } catch (Exception exc) {
 | 
					 | 
				
			||||||
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private async void Menu_Database_Restore_Click(object sender, RoutedEventArgs evt) {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                var d = new OpenFileDialog() {
 | 
					 | 
				
			||||||
                    Title = "Datenbank wiederherstellen - Elwig",
 | 
					 | 
				
			||||||
                    DefaultExt = "sql.zip",
 | 
					 | 
				
			||||||
                    Filter = "SQLite-Datenbank (*.sqlite3, *.sqlite3.zip, *.sql, *.sql.zip)|*.sqlite3;*.sqlite3.zip;*.sql;*.sql.zip",
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                if (d.ShowDialog() == true) {
 | 
					 | 
				
			||||||
                    var res = MessageBox.Show("Soll die Datenbank wirklich unwiederruflich durch die wiederhergestellte Version ersetzt werden?", "Datenbank wiederherstellen",
 | 
					 | 
				
			||||||
                        MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
 | 
					 | 
				
			||||||
                    if (res != MessageBoxResult.OK)
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
					 | 
				
			||||||
                    await App.ReplaceDatabase(d.FileName);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } catch (Exception exc) {
 | 
					 | 
				
			||||||
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private async void DownloadButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void DownloadButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (App.Config.SyncUrl == null)
 | 
					            if (App.Config.SyncUrl == null)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@@ -292,23 +243,21 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            await Task.Run(async () => {
 | 
					            await Task.Run(async () => {
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
 | 
					                    var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
 | 
				
			||||||
                    var files = data
 | 
					                    var file = data
 | 
				
			||||||
                        .Select(f => new {
 | 
					                        .Select(f => new {
 | 
				
			||||||
                            Name = f!["name"]!.AsValue().GetValue<string>(),
 | 
					                            Name = f!["name"]!.AsValue().GetValue<string>(),
 | 
				
			||||||
                            Timestamp = f!["modified"] != null && DateTime.TryParseExact(f!["modified"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
 | 
					                            Timestamp = f!["modified"] != null && DateTime.TryParseExact(f!["modified"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
 | 
				
			||||||
                            Url = f!["url"]!.AsValue().GetValue<string>(),
 | 
					                            Url = f!["url"]!.AsValue().GetValue<string>(),
 | 
				
			||||||
                            Size = f!["size"]!.AsValue().GetValue<long>(),
 | 
					                            Size = f!["size"]!.AsValue().GetValue<long>(),
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                        .Where(f => f.Name.StartsWith("database.") && f.Name.EndsWith(".zip"))
 | 
					                        .Where(f => f.Name == "database.sqlite3.zip")
 | 
				
			||||||
                        .OrderBy(f => f.Size)
 | 
					                        .FirstOrDefault();
 | 
				
			||||||
                        .ToList();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (files.Count == 0) {
 | 
					                    if (file == null) {
 | 
				
			||||||
                        MessageBox.Show("Die Datenbank wurde noch nicht vom Hauptgerät hochgeladen!", "Datenbank herunterladen",
 | 
					                        MessageBox.Show("Die Datenbank wurde noch nicht vom Hauptgerät hochgeladen!", "Datenbank herunterladen",
 | 
				
			||||||
                            MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					                            MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
                        return;
 | 
					                        return;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    var file = files[0];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var res = MessageBox.Show($"Es wurde eine komprimierte Datenbank (ca. {file.Size / 1024 / 1024} MB) vom {file.Timestamp:dd.MM.yyyy, HH:mm} gefunden.\n\nWollen Sie wirklich die aktuelle Datenbank unwiederruflich\nlöschen und durch die gefundene ersetzen?\n\nDas kann zu Datenverlust führen!", "Datenbank herunterladen",
 | 
					                    var res = MessageBox.Show($"Es wurde eine komprimierte Datenbank (ca. {file.Size / 1024 / 1024} MB) vom {file.Timestamp:dd.MM.yyyy, HH:mm} gefunden.\n\nWollen Sie wirklich die aktuelle Datenbank unwiederruflich\nlöschen und durch die gefundene ersetzen?\n\nDas kann zu Datenverlust führen!", "Datenbank herunterladen",
 | 
				
			||||||
                        MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
 | 
					                        MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
 | 
				
			||||||
@@ -352,8 +301,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            Mouse.OverrideCursor = Cursors.Wait;
 | 
					            Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
            await Task.Run(async () => {
 | 
					            await Task.Run(async () => {
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    var path = Path.Combine(App.TempPath, "database.sql.zip");
 | 
					                    var path = Path.Combine(App.TempPath, "database.sqlite3.zip");
 | 
				
			||||||
                    await Database.ExportSql(path, true);
 | 
					                    ElwigData.ExportDatabase(path);
 | 
				
			||||||
                    await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
 | 
					                    await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
 | 
				
			||||||
                    MessageBox.Show($"Hochladen der gesamten Datenbank erfolgreich!", "Datenbank hochladen",
 | 
					                    MessageBox.Show($"Hochladen der gesamten Datenbank erfolgreich!", "Datenbank hochladen",
 | 
				
			||||||
                        MessageBoxButton.OK, MessageBoxImage.Information);
 | 
					                        MessageBoxButton.OK, MessageBoxImage.Information);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            IList<DbColumn> header;
 | 
					            IList<DbColumn> header;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            using (var cnx = await AppDbContext.ConnectAsync()) {
 | 
					            using (var cnx = await AppDbContext.ConnectAsync()) {
 | 
				
			||||||
                using var cmd = cnx.CreateCommand();
 | 
					                var cmd = cnx.CreateCommand();
 | 
				
			||||||
                cmd.CommandText = sqlQuery;
 | 
					                cmd.CommandText = sqlQuery;
 | 
				
			||||||
                using var reader = await cmd.ExecuteReaderAsync();
 | 
					                using var reader = await cmd.ExecuteReaderAsync();
 | 
				
			||||||
                header = await reader.GetColumnSchemaAsync();
 | 
					                header = await reader.GetColumnSchemaAsync();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							@@ -5,43 +5,3 @@ Elwig
 | 
				
			|||||||
**El**ektronische **Wi**nzer**g**enossenschaftsverwaltung (Electronic Management for Vintners' Cooperatives).
 | 
					**El**ektronische **Wi**nzer**g**enossenschaftsverwaltung (Electronic Management for Vintners' Cooperatives).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Further information may be found on [the website](https://elwig.at).
 | 
					Further information may be found on [the website](https://elwig.at).
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
About
 | 
					 | 
				
			||||||
=====
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Product:** Elwig  
 | 
					 | 
				
			||||||
**Description:** Electronic Management for Vintners' Cooperatives  
 | 
					 | 
				
			||||||
**Type:** ERP system  
 | 
					 | 
				
			||||||
**Version:** 1.0.1.4 ([Changelog](./CHANGELOG.md))  
 | 
					 | 
				
			||||||
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)  
 | 
					 | 
				
			||||||
**Website:** https://elwig.at/  
 | 
					 | 
				
			||||||
**Source code:** https://git.necronda.net/winzer/elwig  
 | 
					 | 
				
			||||||
**Developement period:** 2022–2025
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Technology Stack:**  
 | 
					 | 
				
			||||||
Language: C#  
 | 
					 | 
				
			||||||
Framework: Windows Presentation Framework (WPF)  
 | 
					 | 
				
			||||||
Database: [SQLite](https://sqlite.org/)  
 | 
					 | 
				
			||||||
PDF creation: [WeasyPrint](https://weasyprint.org/), [RazorLight](https://github.com/toddams/RazorLight), [PdfiumViewer](https://github.com/pvginkel/PdfiumViewer)  
 | 
					 | 
				
			||||||
Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Über
 | 
					 | 
				
			||||||
====
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Produkt:** Elwig  
 | 
					 | 
				
			||||||
**Beschreibung:** Elektronische Winzergenossenschaftsverwaltung  
 | 
					 | 
				
			||||||
**Typ:** Warenwirtschaftssystem (ERP-System)  
 | 
					 | 
				
			||||||
**Version:** 1.0.1.2 ([Änderungsprotokoll](./CHANGELOG.md))  
 | 
					 | 
				
			||||||
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)  
 | 
					 | 
				
			||||||
**Website:** https://elwig.at/  
 | 
					 | 
				
			||||||
**Quellcode:** https://git.necronda.net/winzer/elwig  
 | 
					 | 
				
			||||||
**Entwicklungszeitraum:** 2022–2025
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Verwendete Technologien:**  
 | 
					 | 
				
			||||||
Programmiersprache: C#  
 | 
					 | 
				
			||||||
Framework: Windows Presentation Framework (WPF)  
 | 
					 | 
				
			||||||
Datenbank: [SQLite](https://sqlite.org/)  
 | 
					 | 
				
			||||||
PDF-Erstellung: [WeasyPrint](https://weasyprint.org/), [RazorLight](https://github.com/toddams/RazorLight), [PdfiumViewer](https://github.com/pvginkel/PdfiumViewer)  
 | 
					 | 
				
			||||||
Paketierung: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@
 | 
				
			|||||||
  </Target>
 | 
					  </Target>
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <ProjectReference Include="..\Installer\Installer.wixproj" />
 | 
					    <ProjectReference Include="..\Installer\Installer.wixproj" />
 | 
				
			||||||
    <PackageReference Include="WixToolset.Bal.wixext" Version="6.0.2" />
 | 
					    <PackageReference Include="WixToolset.Bal.wixext" Version="6.0.1" />
 | 
				
			||||||
    <PackageReference Include="WixToolset.Util.wixext" Version="6.0.2" />
 | 
					    <PackageReference Include="WixToolset.Util.wixext" Version="6.0.1" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
@@ -188,7 +188,7 @@ namespace Tests.UnitTests.HelperTests {
 | 
				
			|||||||
                Assert.That(payment["GV"],  Is.EqualTo(10_000));
 | 
					                Assert.That(payment["GV"],  Is.EqualTo(10_000));
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await b.Calculate(true, false, false, false);
 | 
					            await b.Calculate(false, false, false);
 | 
				
			||||||
            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
					            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                Assert.That(prices, Has.Count.EqualTo(7));
 | 
					                Assert.That(prices, Has.Count.EqualTo(7));
 | 
				
			||||||
@@ -234,7 +234,7 @@ namespace Tests.UnitTests.HelperTests {
 | 
				
			|||||||
                Assert.That(payment["GV"],  Is.EqualTo(8_000));
 | 
					                Assert.That(payment["GV"],  Is.EqualTo(8_000));
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await b.Calculate(true, true, false, false);
 | 
					            await b.Calculate(true, false, false);
 | 
				
			||||||
            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
					            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                Assert.That(prices, Has.Count.EqualTo(6));
 | 
					                Assert.That(prices, Has.Count.EqualTo(6));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
curl --fail -s -L "https://elwig.at/files/create.sql?v=33" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
 | 
					curl --fail -s -L "https://elwig.at/files/create.sql?v=32" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user