Compare commits
	
		
			25 Commits
		
	
	
		
			98f8907817
			...
			v1.0.1.4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7300b30cf5 | |||
| 428cd6ddc2 | |||
| 2de8af878b | |||
| 34d95eab9d | |||
| 548aeb2ce9 | |||
| 7edd888aa2 | |||
| a0d4f19f30 | |||
| 67ba342c28 | |||
| 1b69fcb16a | |||
| c8a95422af | |||
| 9d02f18bac | |||
| f9ee2cb120 | |||
| b27b89f599 | |||
| bfbd0a6a22 | |||
| e2de7a1f1c | |||
| 3f769eb7d7 | |||
| 8bc053053c | |||
| a0dcaf7b4f | |||
| 844fc5217a | |||
| 542afa5892 | |||
| 9e02b15ff1 | |||
| 463769b549 | |||
| 2c383d0c55 | |||
| 5ba74c8368 | |||
| 3c9b3c2db1 | 
							
								
								
									
										116
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -2,6 +2,122 @@
 | 
				
			|||||||
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}
 | 
					[v1.0.0.4][v1.0.0.4] (2025-09-01) {#v1.0.0.4}
 | 
				
			||||||
---------------------------------------------
 | 
					---------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@
 | 
				
			|||||||
        </DataTemplate>
 | 
					        </DataTemplate>
 | 
				
			||||||
        <DataTemplate x:Key="WineVarietyTemplateExpanded">
 | 
					        <DataTemplate x:Key="WineVarietyTemplateExpanded">
 | 
				
			||||||
            <StackPanel Orientation="Horizontal">
 | 
					            <StackPanel Orientation="Horizontal">
 | 
				
			||||||
                <TextBlock Text="{Binding SortId}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/>
 | 
					                <TextBlock Text="{Binding SortIdFormat}" 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,6 +11,7 @@ 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;
 | 
				
			||||||
@@ -115,16 +116,6 @@ namespace Elwig {
 | 
				
			|||||||
                BranchNum = branches.Count;
 | 
					                BranchNum = branches.Count;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (Config.WeighingMode == null) {
 | 
					 | 
				
			||||||
                if (App.Client.IsMatzen || App.Client.IsWolkersdorf) {
 | 
					 | 
				
			||||||
                    Config.WeighingMode = WeighingMode.Net;
 | 
					 | 
				
			||||||
                } else if (App.Client.IsHaugsdorf || App.Client.IsSitzendorf) {
 | 
					 | 
				
			||||||
                    Config.WeighingMode = WeighingMode.Box;
 | 
					 | 
				
			||||||
                } else if (App.Client.IsBaden || App.Client.IsGrInzersdorf) {
 | 
					 | 
				
			||||||
                    Config.WeighingMode = WeighingMode.Gross;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Utils.RunBackground("Temp File Cleanup", () => {
 | 
					            Utils.RunBackground("Temp File Cleanup", () => {
 | 
				
			||||||
                Utils.CleanupTempFiles();
 | 
					                Utils.CleanupTempFiles();
 | 
				
			||||||
                return Task.CompletedTask;
 | 
					                return Task.CompletedTask;
 | 
				
			||||||
@@ -137,10 +128,11 @@ 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(500);
 | 
					                        await Task.Delay(1000);
 | 
				
			||||||
                        await CheckForUpdates();
 | 
					                        await CheckForUpdates();
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
 | 
				
			||||||
                _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
 | 
					                _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
 | 
				
			||||||
                _autoUpdateTimer.Start();
 | 
					                _autoUpdateTimer.Start();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -172,6 +164,16 @@ 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();
 | 
				
			||||||
@@ -228,6 +230,16 @@ 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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.0.4</Version>
 | 
					    <Version>1.0.1.4</Version>
 | 
				
			||||||
    <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
 | 
					    <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
 | 
				
			||||||
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
					    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
				
			||||||
    <ApplicationManifest>app.manifest</ApplicationManifest>
 | 
					    <ApplicationManifest>app.manifest</ApplicationManifest>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -248,13 +248,6 @@ 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 = 32;
 | 
					        public static readonly int RequiredSchemaVersion = 33;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static int VersionOffset = 0;
 | 
					        private static int VersionOffset = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,13 +20,19 @@ 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? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
 | 
					        public async Task Calculate(bool strictPrices = true, 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);
 | 
				
			||||||
            await CalculatePrices(cnx);
 | 
					            KeyNotFoundException? exception = null;
 | 
				
			||||||
 | 
					            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);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -34,6 +40,8 @@ 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() {
 | 
				
			||||||
@@ -142,7 +150,8 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                """);
 | 
					                """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task CalculatePrices(SqliteConnection cnx) {
 | 
					        protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) {
 | 
				
			||||||
 | 
					            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 = $"""
 | 
				
			||||||
@@ -172,15 +181,25 @@ 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);
 | 
				
			||||||
                var price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
 | 
					                decimal price = 0;
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
 | 
				
			||||||
 | 
					                } catch (KeyNotFoundException e) {
 | 
				
			||||||
 | 
					                    invalid.Add(e.Message.Split('\'')[1]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                var 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) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,16 +2,48 @@ using System;
 | 
				
			|||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.IO.Compression;
 | 
					using System.IO.Compression;
 | 
				
			||||||
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Export {
 | 
					namespace Elwig.Helpers.Export {
 | 
				
			||||||
    public static class Database {
 | 
					    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) {
 | 
					        public static async Task ExportSqlite(string filename, bool zipFile) {
 | 
				
			||||||
            if (zipFile) {
 | 
					            if (zipFile) {
 | 
				
			||||||
                File.Delete(filename);
 | 
					                File.Delete(filename);
 | 
				
			||||||
                using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
 | 
					                using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
 | 
				
			||||||
                await zip.CheckIntegrity();
 | 
					
 | 
				
			||||||
 | 
					                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);
 | 
					                var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                File.Copy(App.Config.DatabaseFile, filename, true);
 | 
					                File.Copy(App.Config.DatabaseFile, filename, true);
 | 
				
			||||||
@@ -22,10 +54,33 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
            if (zipFile) {
 | 
					            if (zipFile) {
 | 
				
			||||||
                File.Delete(filename);
 | 
					                File.Delete(filename);
 | 
				
			||||||
                using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
 | 
					                using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
 | 
				
			||||||
                var entry = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
 | 
					
 | 
				
			||||||
                using var stream = entry.Open();
 | 
					                var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
 | 
				
			||||||
                using var writer = new StreamWriter(stream, Utils.UTF8);
 | 
					                using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
 | 
				
			||||||
                await ExportSql(writer);
 | 
					                    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 {
 | 
					            } else {
 | 
				
			||||||
                using var stream = File.OpenWrite(filename);
 | 
					                using var stream = File.OpenWrite(filename);
 | 
				
			||||||
                using var writer = new StreamWriter(stream, Utils.UTF8);
 | 
					                using var writer = new StreamWriter(stream, Utils.UTF8);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,6 @@ 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;
 | 
				
			||||||
@@ -18,8 +17,6 @@ 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);
 | 
				
			||||||
@@ -77,97 +74,117 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                    int? DeliveryNum, string? DeliveryFilters)>();
 | 
					                    int? DeliveryNum, string? DeliveryFilters)>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                foreach (var filename in filenames) {
 | 
					                foreach (var filename in filenames) {
 | 
				
			||||||
                    // TODO read encrypted files
 | 
					                    try {
 | 
				
			||||||
                    using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
 | 
					                        data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
 | 
				
			||||||
                    await zip.CheckIntegrity();
 | 
					                            ["member"] = [],
 | 
				
			||||||
 | 
					                            ["area_commitment"] = [],
 | 
				
			||||||
 | 
					                            ["delivery"] = [],
 | 
				
			||||||
 | 
					                        })));
 | 
				
			||||||
 | 
					                        var r = data[^1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var version = zip.GetEntry("version");
 | 
					                        // TODO read encrypted files
 | 
				
			||||||
                    using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
 | 
					                        using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
 | 
				
			||||||
                        if (await reader.ReadToEndAsync() != "elwig:1")
 | 
					                        await zip.CheckIntegrity();
 | 
				
			||||||
                            throw new FileFormatException($"Ungültige Export-Datei ({filename})");
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var metaJson = zip.GetEntry("meta.json");
 | 
					                        var version = zip.GetEntry("version");
 | 
				
			||||||
                    var meta = await JsonNode.ParseAsync(metaJson!.Open());
 | 
					                        using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
 | 
				
			||||||
                    var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
 | 
					                            if (await reader.ReadToEndAsync() != "elwig:1")
 | 
				
			||||||
                    var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
					                                throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})");
 | 
				
			||||||
                    var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
 | 
					                        }
 | 
				
			||||||
                    var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
					 | 
				
			||||||
                    var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
 | 
					 | 
				
			||||||
                    var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
					 | 
				
			||||||
                    metaData.Add((Path.GetFileName(filename),
 | 
					 | 
				
			||||||
                        meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
 | 
					 | 
				
			||||||
                        memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
 | 
					 | 
				
			||||||
                        areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
 | 
					 | 
				
			||||||
                        deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
 | 
					                        var metaJson = zip.GetEntry("meta.json");
 | 
				
			||||||
                        ["member"] = [],
 | 
					                        var meta = await JsonNode.ParseAsync(metaJson!.Open());
 | 
				
			||||||
                        ["area_commitment"] = [],
 | 
					                        var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
 | 
				
			||||||
                        ["delivery"] = [],
 | 
					                        var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
				
			||||||
                    })));
 | 
					                        var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
 | 
				
			||||||
                    var r = data[^1];
 | 
					                        var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
				
			||||||
 | 
					                        var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
 | 
				
			||||||
 | 
					                        var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
				
			||||||
 | 
					                        metaData.Add((Path.GetFileName(filename),
 | 
				
			||||||
 | 
					                            meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
 | 
				
			||||||
 | 
					                            memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
 | 
				
			||||||
 | 
					                            areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
 | 
				
			||||||
 | 
					                            deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var wbKgsJson = zip.GetEntry("wb_kgs.json");
 | 
					                        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);
 | 
				
			||||||
                        string? line;
 | 
					                            string? line;
 | 
				
			||||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
					                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
				
			||||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
					                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
				
			||||||
                            var (k, g) = obj.ToWbKg(currentWbGls);
 | 
					                                var (k, g) = obj.ToWbKg(currentWbGls);
 | 
				
			||||||
                            r.WbKgs.Add(k);
 | 
					                                r.WbKgs.Add(k);
 | 
				
			||||||
                            if (g != null) {
 | 
					                                if (g != null) {
 | 
				
			||||||
                                currentWbGls[g.GlNr] = g;
 | 
					                                    currentWbGls[g.GlNr] = g;
 | 
				
			||||||
                                r.WbGls.Add(g);
 | 
					                                    r.WbGls.Add(g);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var membersJson = zip.GetEntry("members.json");
 | 
					                        var membersJson = zip.GetEntry("members.json");
 | 
				
			||||||
                    if (membersJson != null) {
 | 
					                        if (membersJson != null) {
 | 
				
			||||||
                        using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
 | 
					                            using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
 | 
				
			||||||
                        string? line;
 | 
					                            string? line;
 | 
				
			||||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
					                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
				
			||||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
					                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
				
			||||||
                            var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
 | 
					                                var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
 | 
				
			||||||
                            r.Members.Add(m);
 | 
					                                r.Members.Add(m);
 | 
				
			||||||
                            if (b != null) r.BillingAddresses.Add(b);
 | 
					                                if (b != null) r.BillingAddresses.Add(b);
 | 
				
			||||||
                            r.TelephoneNumbers.AddRange(telNrs);
 | 
					                                r.TelephoneNumbers.AddRange(telNrs);
 | 
				
			||||||
                            r.EmailAddresses.AddRange(emailAddrs);
 | 
					                                r.EmailAddresses.AddRange(emailAddrs);
 | 
				
			||||||
                            if (timestamps.HasValue)
 | 
					                                if (timestamps.HasValue)
 | 
				
			||||||
                                r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
					                                    r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var areaComsJson = zip.GetEntry("area_commitments.json");
 | 
					 | 
				
			||||||
                    if (areaComsJson != null) {
 | 
					 | 
				
			||||||
                        using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
 | 
					 | 
				
			||||||
                        string? line;
 | 
					 | 
				
			||||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
					 | 
				
			||||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
					 | 
				
			||||||
                            var (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
 | 
					 | 
				
			||||||
                            r.AreaCommitments.Add(areaCom);
 | 
					 | 
				
			||||||
                            if (wbrd != null) {
 | 
					 | 
				
			||||||
                                r.Riede.Add(wbrd);
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            if (timestamps.HasValue)
 | 
					 | 
				
			||||||
                                r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var deliveriesJson = zip.GetEntry("deliveries.json");
 | 
					                        var areaComsJson = zip.GetEntry("area_commitments.json");
 | 
				
			||||||
                    if (deliveriesJson != null) {
 | 
					                        if (areaComsJson != null) {
 | 
				
			||||||
                        using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
 | 
					                            using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
 | 
				
			||||||
                        string? line;
 | 
					                            string? line;
 | 
				
			||||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
					                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
				
			||||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
					                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
				
			||||||
                            var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
 | 
					                                var (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
 | 
				
			||||||
                            r.Deliveries.Add(d);
 | 
					                                r.AreaCommitments.Add(areaCom);
 | 
				
			||||||
                            r.DeliveryParts.AddRange(parts);
 | 
					                                if (wbrd != null) {
 | 
				
			||||||
                            r.Modifiers.AddRange(mods);
 | 
					                                    r.Riede.Add(wbrd);
 | 
				
			||||||
                            r.Riede.AddRange(rde);
 | 
					                                }
 | 
				
			||||||
                            if (timestamps.HasValue)
 | 
					                                if (timestamps.HasValue)
 | 
				
			||||||
                                r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
					                                    r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        var deliveriesJson = zip.GetEntry("deliveries.json");
 | 
				
			||||||
 | 
					                        if (deliveriesJson != null) {
 | 
				
			||||||
 | 
					                            using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
 | 
				
			||||||
 | 
					                            string? line;
 | 
				
			||||||
 | 
					                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
				
			||||||
 | 
					                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
				
			||||||
 | 
					                                var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
 | 
				
			||||||
 | 
					                                r.Deliveries.Add(d);
 | 
				
			||||||
 | 
					                                r.DeliveryParts.AddRange(parts);
 | 
				
			||||||
 | 
					                                r.Modifiers.AddRange(mods);
 | 
				
			||||||
 | 
					                                r.Riede.AddRange(rde);
 | 
				
			||||||
 | 
					                                if (timestamps.HasValue)
 | 
				
			||||||
 | 
					                                    r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } catch (Exception exc) when (
 | 
				
			||||||
 | 
					                            exc is InvalidDataException ||
 | 
				
			||||||
 | 
					                            exc is FileFormatException ||
 | 
				
			||||||
 | 
					                            exc is FileNotFoundException ||
 | 
				
			||||||
 | 
					                            exc is IOException) {
 | 
				
			||||||
 | 
					                        data.RemoveAt(data.Count - 1);
 | 
				
			||||||
 | 
					                        var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message;
 | 
				
			||||||
 | 
					                        if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
				
			||||||
 | 
					                        MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
 | 
					                        await AddImportedFiles(Path.GetFileName(filename));
 | 
				
			||||||
 | 
					                    } catch (Exception exc) {
 | 
				
			||||||
 | 
					                        data.RemoveAt(data.Count - 1);
 | 
				
			||||||
 | 
					                        var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message;
 | 
				
			||||||
 | 
					                        if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
				
			||||||
 | 
					                        var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
 | 
				
			||||||
 | 
					                        if (r == MessageBoxResult.Yes) {
 | 
				
			||||||
 | 
					                            await AddImportedFiles(Path.GetFileName(filename));
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -449,7 +466,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(JsonOpts));
 | 
					                    await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // TODO encrypt files
 | 
					                // TODO encrypt files
 | 
				
			||||||
@@ -457,28 +474,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(JsonOpts));
 | 
					                        await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.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(JsonOpts));
 | 
					                        await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.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(JsonOpts));
 | 
					                        await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.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(JsonOpts));
 | 
					                        await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -806,7 +823,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(JsonOpts),
 | 
					                    WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.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,41 +1,42 @@
 | 
				
			|||||||
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 System.Text;
 | 
					using Elwig.Documents;
 | 
				
			||||||
using System.Numerics;
 | 
					 | 
				
			||||||
using Elwig.Models.Entities;
 | 
					 | 
				
			||||||
using Elwig.Helpers.Billing;
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using System.Runtime.InteropServices;
 | 
					using Elwig.Models;
 | 
				
			||||||
using System.Net.Http;
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
using System.Text.Json.Nodes;
 | 
					using LinqKit;
 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using MailKit.Net.Smtp;
 | 
					using MailKit.Net.Smtp;
 | 
				
			||||||
using MailKit.Security;
 | 
					using MailKit.Security;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using System.Reflection;
 | 
					 | 
				
			||||||
using System.Collections;
 | 
					 | 
				
			||||||
using Elwig.Documents;
 | 
					 | 
				
			||||||
using MimeKit;
 | 
					 | 
				
			||||||
using System.Windows.Input;
 | 
					 | 
				
			||||||
using LinqKit;
 | 
					 | 
				
			||||||
using System.Linq.Expressions;
 | 
					 | 
				
			||||||
using Elwig.Models;
 | 
					 | 
				
			||||||
using Microsoft.Win32;
 | 
					using Microsoft.Win32;
 | 
				
			||||||
 | 
					using MimeKit;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
using System.Globalization;
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using System.IO.Ports;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Linq.Expressions;
 | 
				
			||||||
 | 
					using System.Net.Http;
 | 
				
			||||||
 | 
					using System.Net.Sockets;
 | 
				
			||||||
 | 
					using System.Numerics;
 | 
				
			||||||
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using System.Text.Json;
 | 
				
			||||||
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
using System.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);
 | 
				
			||||||
@@ -430,6 +431,8 @@ 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));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
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;
 | 
				
			||||||
@@ -62,6 +63,11 @@ 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]
 | 
				
			||||||
@@ -108,16 +114,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<string> SortIds => Parts
 | 
					        public IEnumerable<RawVaribute> Vaributes => Parts
 | 
				
			||||||
            .GroupBy(p => p.SortId)
 | 
					            .GroupBy(p => (p.SortId, p.AttrId, p.CultId))
 | 
				
			||||||
            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
					            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
				
			||||||
            .Select(g => g.Key);
 | 
					            .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
 | 
				
			||||||
        public IEnumerable<string> FilteredSortIds => FilteredParts
 | 
					        public IEnumerable<RawVaribute> FilteredVaributes => FilteredParts
 | 
				
			||||||
            .GroupBy(p => p.SortId)
 | 
					            .GroupBy(p => (p.SortId, p.AttrId, p.CultId))
 | 
				
			||||||
            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
					            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
				
			||||||
            .Select(g => g.Key);
 | 
					            .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
 | 
				
			||||||
        public string SortIdString => string.Join(", ", SortIds);
 | 
					        public string VaributeString => string.Join(", ", Vaributes);
 | 
				
			||||||
        public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
 | 
					        public string FilteredVaributeString => string.Join(", ", FilteredVaributes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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,7 +42,9 @@ 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 => Announcements.Sum(a => a.Weight);
 | 
					        public int AnnouncedWeight => AnnouncedWeightOverride ?? 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,16 +11,24 @@ 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() { }
 | 
				
			||||||
@@ -28,6 +36,7 @@ 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() {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								Elwig/Resources/Sql/32-33.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Elwig/Resources/Sql/32-33.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					-- 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,9 +721,11 @@ 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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -496,11 +496,13 @@ 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 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								Elwig/Windows/AboutWindow.xaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Elwig/Windows/AboutWindow.xaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					<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>
 | 
				
			||||||
							
								
								
									
										17
									
								
								Elwig/Windows/AboutWindow.xaml.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Elwig/Windows/AboutWindow.xaml.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					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 });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -664,8 +664,10 @@ 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();
 | 
					                    await b.Calculate(false);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					            } 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,13 +265,18 @@
 | 
				
			|||||||
                            </Style>
 | 
					                            </Style>
 | 
				
			||||||
                        </DataGridTextColumn.CellStyle>
 | 
					                        </DataGridTextColumn.CellStyle>
 | 
				
			||||||
                    </DataGridTextColumn>
 | 
					                    </DataGridTextColumn>
 | 
				
			||||||
                    <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
 | 
					                    <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredVaributeString}" Width="60">
 | 
				
			||||||
                        <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>
 | 
				
			||||||
@@ -290,6 +295,7 @@
 | 
				
			|||||||
                    <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,6 +8,7 @@ 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;
 | 
				
			||||||
@@ -34,6 +35,8 @@ 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));
 | 
				
			||||||
@@ -483,7 +486,8 @@ 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);
 | 
				
			||||||
            ControlUtils.RenewItemsSource(WineQualityLevelInput, await ctx.WineQualityLevels.ToListAsync());
 | 
					            WineQualityLevels = 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)
 | 
				
			||||||
@@ -1207,6 +1211,7 @@ 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) {
 | 
				
			||||||
@@ -1220,19 +1225,39 @@ 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 = ctx.WineQualityLevels.Where(q => q.QualId == "WEI").ToList();
 | 
					                WineQualityLevelInput.ItemsSource = WineQualityLevels;
 | 
				
			||||||
 | 
					                WineQualityLevelInput.SelectedItem = null;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            var kmw = (double)ViewModel.GradationKmw!;
 | 
					            var kmw = (double)ViewModel.GradationKmw!;
 | 
				
			||||||
            WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.MinKmw == null || q.MinKmw <= kmw).ToList();
 | 
					            var max = ViewModel.WineVar?.MaxQualId;
 | 
				
			||||||
            var qual = ctx.GetWineQualityLevel(kmw).GetAwaiter().GetResult();
 | 
					            var quw = ViewModel.WineVar?.IsQuw ?? true;
 | 
				
			||||||
 | 
					            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);
 | 
				
			||||||
@@ -1372,8 +1397,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                AbgewertetInput.IsChecked = false;
 | 
					                AbgewertetInput.IsChecked = false;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            using var ctx = new AppDbContext();
 | 
					            var defQual = GetWineQualityLevel(ViewModel.GradationKmw!.Value, !(ViewModel.WineVar?.IsQuw ?? true) ? ViewModel.WineVar?.MaxQualId : null);
 | 
				
			||||||
            var defQual = ctx.GetWineQualityLevel(double.Parse(GradationKmwInput.Text)).GetAwaiter().GetResult();
 | 
					 | 
				
			||||||
            AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
 | 
					            AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,14 +83,19 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async Task RefreshDeliveryScheduleList() {
 | 
					        private async Task RefreshDeliveryScheduleList() {
 | 
				
			||||||
            using var ctx = new AppDbContext();
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
            var deliverySchedules = await ctx.DeliverySchedules
 | 
					            var list = 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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 == 2 ? PostalNoEmailCount : 0;
 | 
					            double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 1 ? 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,7 +72,7 @@
 | 
				
			|||||||
                </MenuItem>
 | 
					                </MenuItem>
 | 
				
			||||||
            </MenuItem>
 | 
					            </MenuItem>
 | 
				
			||||||
            <MenuItem x:Name="HelpMenu" Header="Hilfe">
 | 
					            <MenuItem x:Name="HelpMenu" Header="Hilfe">
 | 
				
			||||||
                <MenuItem Header="Über">
 | 
					                <MenuItem Header="Über" Click="Menu_Help_About_Click">
 | 
				
			||||||
                    <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,6 +59,11 @@ 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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -153,8 +158,10 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                    FileName = $"database_{Utils.Today:yyyy-MM-dd}.sql.zip",
 | 
					                    FileName = $"database_{Utils.Today:yyyy-MM-dd}.sql.zip",
 | 
				
			||||||
                    DefaultExt = "sql.zip",
 | 
					                    DefaultExt = "sql.zip",
 | 
				
			||||||
                    Filter = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip",
 | 
					                    Filter = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip",
 | 
				
			||||||
 | 
					                    AddExtension = false,
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                if (d.ShowDialog() == true) {
 | 
					                if (d.ShowDialog() == true) {
 | 
				
			||||||
 | 
					                    if (!d.FileName.EndsWith(".sql.zip")) d.FileName += ".sql.zip";
 | 
				
			||||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
					                    Mouse.OverrideCursor = Cursors.Wait;
 | 
				
			||||||
                    await Task.Run(async () => {
 | 
					                    await Task.Run(async () => {
 | 
				
			||||||
                        await Database.ExportSql(d.FileName, true);
 | 
					                        await Database.ExportSql(d.FileName, true);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							@@ -5,3 +5,43 @@ 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/)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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(false, false, false);
 | 
					            await b.Calculate(true, 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, false, false);
 | 
					            await b.Calculate(true, 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=32" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
 | 
					curl --fail -s -L "https://elwig.at/files/create.sql?v=33" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user