Compare commits
	
		
			23 Commits
		
	
	
		
			463769b549
			...
			v1.0.1.5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e9722c790c | |||
| af98c32026 | |||
| 7300b30cf5 | |||
| 428cd6ddc2 | |||
| 2de8af878b | |||
| 34d95eab9d | |||
| 548aeb2ce9 | |||
| 7edd888aa2 | |||
| a0d4f19f30 | |||
| 67ba342c28 | |||
| 1b69fcb16a | |||
| c8a95422af | |||
| 9d02f18bac | |||
| f9ee2cb120 | |||
| b27b89f599 | |||
| bfbd0a6a22 | |||
| e2de7a1f1c | |||
| 3f769eb7d7 | |||
| 8bc053053c | |||
| a0dcaf7b4f | |||
| 844fc5217a | |||
| 542afa5892 | |||
| 9e02b15ff1 | 
							
								
								
									
										109
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -2,6 +2,115 @@
 | 
			
		||||
Changelog
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
[v1.0.1.5][v1.0.1.5] (2025-10-29) {#v1.0.1.5}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
### Behobene Fehler {#v1.0.1.5-bugfixes}
 | 
			
		||||
 | 
			
		||||
* Im Rundschreiben-Fenster (`MailWindow`) kam es zu einem Absturz, wenn man das Fenster über den "Anlieferungsbestätigung"-Knopf im Leseabschluss-Abschnitt geöffnet hat. (af98c32026)
 | 
			
		||||
 | 
			
		||||
[v1.0.1.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[v1.0.1.4][v1.0.1.4] (2025-10-28) {#v1.0.1.4}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
### Behobene Fehler {#v1.0.1.4-bugfixes}
 | 
			
		||||
 | 
			
		||||
* Im Rundschreiben-Fenster (`MailWindow`) kam es zu einem Absturz, wenn man die Zustelloptionen "Post zusenden an Mitglieder, die keine E-Mail erhalten würden" und "E-Mail zusenden an niemanden" kombiniert hat. (2de8af878b)
 | 
			
		||||
 | 
			
		||||
### Sonstiges {#v1.0.1.4-misc}
 | 
			
		||||
 | 
			
		||||
* Im Auszahlungsvariante-Fenster (`ChartWindow`) gibt es keine Fehlermeldung mehr wenn nicht für alle Sorten ein Preis definiert ist, nur noch eine Warnung. (428cd6ddc2)
 | 
			
		||||
 | 
			
		||||
[v1.0.1.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.4
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[v1.0.1.3][v1.0.1.3] (2025-10-13) {#v1.0.1.3}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
### Neue Funktionen {#v1.0.1.3-features}
 | 
			
		||||
 | 
			
		||||
* In der Liste des Lieferungen-Fenster (`DeliveryAdminWindow`) werden
 | 
			
		||||
	* statt ausschließlich der Sorte auch Attribut und Bewirtschaftungsart angezeigt. (a0d4f19f30)
 | 
			
		||||
	* Kommentare der Lieferungen (und Teillieferungen) angezeigt. (548aeb2ce9)
 | 
			
		||||
 | 
			
		||||
### Sonstiges {#v1.0.1.3-misc}
 | 
			
		||||
 | 
			
		||||
* Verzögerung der Überprüfung auf automatische Updates auf einige Sekunden verlängert. (67ba342c28)
 | 
			
		||||
* Verbesserung der Ladezeiten im Anmeldungen-Fenster (`DeliveryAncmtAdminWindow`). (7edd888aa2)
 | 
			
		||||
 | 
			
		||||
[v1.0.1.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[v1.0.1.2][v1.0.1.2] (2025-09-25) {#v1.0.1.2}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
### Behobene Fehler {#v1.0.1.2-bugfixes}
 | 
			
		||||
 | 
			
		||||
* Beim automatischen Importieren/Synchronisieren wird bei einem Fehlerfall der Benutzer verständigt, aber der Vorgang nicht abgebrochen. (9d02f18bac)
 | 
			
		||||
 | 
			
		||||
### Sonstiges {#v1.0.1.2-misc}
 | 
			
		||||
 | 
			
		||||
* Beim Sichern der Datenbank werden Meta-Informationen in der ZIP-Datei gespeichert. (c8a95422af)
 | 
			
		||||
 | 
			
		||||
[v1.0.1.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[v1.0.1.1][v1.0.1.1] (2025-09-21) {#v1.0.1.1}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
### Sonstiges {#v1.0.1.1-misc}
 | 
			
		||||
 | 
			
		||||
* Eingabe von Sorten und Qualitätsstufen im Übernahme-Fenster (`DeliveryAdminWindows`) verbessert. (e2de7a1f1c, b27b89f599)
 | 
			
		||||
 | 
			
		||||
[v1.0.1.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[v1.0.1.0][v1.0.1.0] (2025-09-18) {#v1.0.1.0}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
### Neue Funktionen {#v1.0.1.0-features}
 | 
			
		||||
 | 
			
		||||
* Neue Weinsorten gemäß Kürzelliste der Bundeskellereiinspektion hinzugefügt. (a0dcaf7b4f)
 | 
			
		||||
 | 
			
		||||
### Sonstiges {#v1.0.1.0-misc}
 | 
			
		||||
 | 
			
		||||
* HTTP-Anfragen haben jetzt das Feld `User-Agent` gesetzt. (844fc5217a)
 | 
			
		||||
* Auto-Update-Funktion wird auch beim Erlangen von Netzwerkverbindung ausgeführt. (8bc053053c)
 | 
			
		||||
 | 
			
		||||
[v1.0.1.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[v1.0.0.6][v1.0.0.6] (2025-09-17) {#v1.0.0.6}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
### Behobene Fehler {#v1.0.0.6-bugfixes}
 | 
			
		||||
 | 
			
		||||
* Die automatische Erkennung des Wiege-Modus hat im WKW nicht funktioniert. (463769b549)
 | 
			
		||||
 | 
			
		||||
### Sonstiges {#v1.0.0.6-misc}
 | 
			
		||||
 | 
			
		||||
* Tippfehler im Über-Fenster (`AboutWindow`) behoben. (2c383d0c55)
 | 
			
		||||
* `SaveFileDialog` mit Multi-File-Extensions verbessert. (9e02b15ff1)
 | 
			
		||||
 | 
			
		||||
[v1.0.0.6]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.6
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[v1.0.0.5][v1.0.0.5] (2025-09-15) {#v1.0.0.5}
 | 
			
		||||
---------------------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
        </DataTemplate>
 | 
			
		||||
        <DataTemplate x:Key="WineVarietyTemplateExpanded">
 | 
			
		||||
            <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 CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
 | 
			
		||||
            </StackPanel>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.NetworkInformation;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
@@ -127,10 +128,11 @@ namespace Elwig {
 | 
			
		||||
            if (Config.UpdateAuto && Config.UpdateUrl != null) {
 | 
			
		||||
                if (Utils.HasInternetConnectivity()) {
 | 
			
		||||
                    Utils.RunBackground("Auto Updater", async () => {
 | 
			
		||||
                        await Task.Delay(500);
 | 
			
		||||
                        await Task.Delay(1000);
 | 
			
		||||
                        await CheckForUpdates();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
 | 
			
		||||
                _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
 | 
			
		||||
                _autoUpdateTimer.Start();
 | 
			
		||||
            }
 | 
			
		||||
@@ -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) {
 | 
			
		||||
            if (Config.UpdateUrl == null) return;
 | 
			
		||||
            var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
    <UseWPF>true</UseWPF>
 | 
			
		||||
    <PreserveCompilationContext>true</PreserveCompilationContext>
 | 
			
		||||
    <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
 | 
			
		||||
    <Version>1.0.0.5</Version>
 | 
			
		||||
    <Version>1.0.1.5</Version>
 | 
			
		||||
    <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
 | 
			
		||||
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
 | 
			
		||||
    <ApplicationManifest>app.manifest</ApplicationManifest>
 | 
			
		||||
 
 | 
			
		||||
@@ -248,13 +248,6 @@ namespace Elwig.Helpers {
 | 
			
		||||
            return c + 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<WineQualLevel> GetWineQualityLevel(double kmw) {
 | 
			
		||||
            return await WineQualityLevels
 | 
			
		||||
                .Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
 | 
			
		||||
                .OrderBy(q => q.MinKmw)
 | 
			
		||||
                .LastAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) {
 | 
			
		||||
            foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
 | 
			
		||||
                var mod = new DeliveryPartModifier {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ namespace Elwig.Helpers {
 | 
			
		||||
    public static class AppDbUpdater {
 | 
			
		||||
 | 
			
		||||
        // 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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,13 +20,19 @@ namespace Elwig.Helpers.Billing {
 | 
			
		||||
            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 tx = await cnx.BeginTransactionAsync();
 | 
			
		||||
            await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
 | 
			
		||||
            await DeleteInDb(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) {
 | 
			
		||||
                await CalculateDeliveryModifiers(cnx);
 | 
			
		||||
            }
 | 
			
		||||
@@ -34,6 +40,8 @@ namespace Elwig.Helpers.Billing {
 | 
			
		||||
                await CalculateMemberModifiers(cnx);
 | 
			
		||||
            }
 | 
			
		||||
            await tx.CommitAsync();
 | 
			
		||||
            if (exception != null)
 | 
			
		||||
                throw exception;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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)>();
 | 
			
		||||
            using (var cmd = cnx.CreateCommand()) {
 | 
			
		||||
                cmd.CommandText = $"""
 | 
			
		||||
@@ -172,15 +181,25 @@ namespace Elwig.Helpers.Billing {
 | 
			
		||||
                var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
 | 
			
		||||
                var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
 | 
			
		||||
                var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
 | 
			
		||||
                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);
 | 
			
		||||
                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, $"""
 | 
			
		||||
                INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
 | 
			
		||||
                VALUES {string.Join(",\n       ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))};
 | 
			
		||||
                """);
 | 
			
		||||
            if (msg != null)
 | 
			
		||||
                throw new KeyNotFoundException(msg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,16 +2,48 @@ using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.IO.Compression;
 | 
			
		||||
using System.Text.Json.Nodes;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Elwig.Helpers.Export {
 | 
			
		||||
    public static class Database {
 | 
			
		||||
 | 
			
		||||
        private static async Task<(long? ApplicationId, string? UserVersion, long? SchemaVersion, long FileSize)> GetMeta() {
 | 
			
		||||
            long size = new FileInfo(App.Config.DatabaseFile).Length;
 | 
			
		||||
            using var cnx = await AppDbContext.ConnectAsync();
 | 
			
		||||
            var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id");
 | 
			
		||||
            var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version");
 | 
			
		||||
            var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version");
 | 
			
		||||
            return (applId, userVers != null ? $"{userVers >> 24}.{(userVers >> 16) & 0xFF}.{(userVers >> 8) & 0xFF}.{userVers & 0xFF}" : null, schemaVers, size);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static async Task ExportSqlite(string filename, bool zipFile) {
 | 
			
		||||
            if (zipFile) {
 | 
			
		||||
                File.Delete(filename);
 | 
			
		||||
                using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
 | 
			
		||||
                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);
 | 
			
		||||
            } else {
 | 
			
		||||
                File.Copy(App.Config.DatabaseFile, filename, true);
 | 
			
		||||
@@ -22,10 +54,33 @@ namespace Elwig.Helpers.Export {
 | 
			
		||||
            if (zipFile) {
 | 
			
		||||
                File.Delete(filename);
 | 
			
		||||
                using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
 | 
			
		||||
                var entry = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
 | 
			
		||||
                using var stream = entry.Open();
 | 
			
		||||
                using var writer = new StreamWriter(stream, Utils.UTF8);
 | 
			
		||||
                await ExportSql(writer);
 | 
			
		||||
 | 
			
		||||
                var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
 | 
			
		||||
                using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
 | 
			
		||||
                    await writer.WriteAsync("elwig:1");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var (applId, userVers, schemaVers, size) = await GetMeta();
 | 
			
		||||
                var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
 | 
			
		||||
                using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
 | 
			
		||||
                    var obj = new JsonObject {
 | 
			
		||||
                        ["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
 | 
			
		||||
                        ["zwstid"] = App.ZwstId,
 | 
			
		||||
                        ["device"] = Environment.MachineName,
 | 
			
		||||
                        ["database"] = new JsonObject {
 | 
			
		||||
                            ["application_id"] = applId,
 | 
			
		||||
                            ["user_version"] = userVers,
 | 
			
		||||
                            ["schema_version"] = schemaVers,
 | 
			
		||||
                            ["file_size"] = size,
 | 
			
		||||
                        },
 | 
			
		||||
                    };
 | 
			
		||||
                    await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
 | 
			
		||||
                using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) {
 | 
			
		||||
                    await ExportSql(writer);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                using var stream = File.OpenWrite(filename);
 | 
			
		||||
                using var writer = new StreamWriter(stream, Utils.UTF8);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ using System.Globalization;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.IO.Compression;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Nodes;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Windows;
 | 
			
		||||
@@ -18,8 +17,6 @@ namespace Elwig.Helpers.Export {
 | 
			
		||||
 | 
			
		||||
        public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
 | 
			
		||||
 | 
			
		||||
        private static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
 | 
			
		||||
 | 
			
		||||
        public static async Task<string[]> GetImportedFiles() {
 | 
			
		||||
            try {
 | 
			
		||||
                return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
 | 
			
		||||
@@ -77,97 +74,117 @@ namespace Elwig.Helpers.Export {
 | 
			
		||||
                    int? DeliveryNum, string? DeliveryFilters)>();
 | 
			
		||||
 | 
			
		||||
                foreach (var filename in filenames) {
 | 
			
		||||
                    // TODO read encrypted files
 | 
			
		||||
                    using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
 | 
			
		||||
                    await zip.CheckIntegrity();
 | 
			
		||||
                    try {
 | 
			
		||||
                        data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
 | 
			
		||||
                            ["member"] = [],
 | 
			
		||||
                            ["area_commitment"] = [],
 | 
			
		||||
                            ["delivery"] = [],
 | 
			
		||||
                        })));
 | 
			
		||||
                        var r = data[^1];
 | 
			
		||||
 | 
			
		||||
                    var version = zip.GetEntry("version");
 | 
			
		||||
                    using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
 | 
			
		||||
                        if (await reader.ReadToEndAsync() != "elwig:1")
 | 
			
		||||
                            throw new FileFormatException($"Ungültige Export-Datei ({filename})");
 | 
			
		||||
                    }
 | 
			
		||||
                        // TODO read encrypted files
 | 
			
		||||
                        using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
 | 
			
		||||
                        await zip.CheckIntegrity();
 | 
			
		||||
 | 
			
		||||
                    var metaJson = zip.GetEntry("meta.json");
 | 
			
		||||
                    var meta = await JsonNode.ParseAsync(metaJson!.Open());
 | 
			
		||||
                    var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
 | 
			
		||||
                    var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
			
		||||
                    var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
 | 
			
		||||
                    var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
			
		||||
                    var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
 | 
			
		||||
                    var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
			
		||||
                    metaData.Add((Path.GetFileName(filename),
 | 
			
		||||
                        meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
 | 
			
		||||
                        memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
 | 
			
		||||
                        areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
 | 
			
		||||
                        deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
 | 
			
		||||
                        var version = zip.GetEntry("version");
 | 
			
		||||
                        using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
 | 
			
		||||
                            if (await reader.ReadToEndAsync() != "elwig:1")
 | 
			
		||||
                                throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})");
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
 | 
			
		||||
                        ["member"] = [],
 | 
			
		||||
                        ["area_commitment"] = [],
 | 
			
		||||
                        ["delivery"] = [],
 | 
			
		||||
                    })));
 | 
			
		||||
                    var r = data[^1];
 | 
			
		||||
                        var metaJson = zip.GetEntry("meta.json");
 | 
			
		||||
                        var meta = await JsonNode.ParseAsync(metaJson!.Open());
 | 
			
		||||
                        var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
 | 
			
		||||
                        var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
			
		||||
                        var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
 | 
			
		||||
                        var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
			
		||||
                        var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
 | 
			
		||||
                        var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
 | 
			
		||||
                        metaData.Add((Path.GetFileName(filename),
 | 
			
		||||
                            meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
 | 
			
		||||
                            memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
 | 
			
		||||
                            areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
 | 
			
		||||
                            deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
 | 
			
		||||
 | 
			
		||||
                    var wbKgsJson = zip.GetEntry("wb_kgs.json");
 | 
			
		||||
                    if (wbKgsJson != null) {
 | 
			
		||||
                        using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
 | 
			
		||||
                        string? line;
 | 
			
		||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                            var (k, g) = obj.ToWbKg(currentWbGls);
 | 
			
		||||
                            r.WbKgs.Add(k);
 | 
			
		||||
                            if (g != null) {
 | 
			
		||||
                                currentWbGls[g.GlNr] = g;
 | 
			
		||||
                                r.WbGls.Add(g);
 | 
			
		||||
                        var wbKgsJson = zip.GetEntry("wb_kgs.json");
 | 
			
		||||
                        if (wbKgsJson != null) {
 | 
			
		||||
                            using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
 | 
			
		||||
                            string? line;
 | 
			
		||||
                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                                var (k, g) = obj.ToWbKg(currentWbGls);
 | 
			
		||||
                                r.WbKgs.Add(k);
 | 
			
		||||
                                if (g != null) {
 | 
			
		||||
                                    currentWbGls[g.GlNr] = g;
 | 
			
		||||
                                    r.WbGls.Add(g);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var membersJson = zip.GetEntry("members.json");
 | 
			
		||||
                    if (membersJson != null) {
 | 
			
		||||
                        using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
 | 
			
		||||
                        string? line;
 | 
			
		||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                            var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
 | 
			
		||||
                            r.Members.Add(m);
 | 
			
		||||
                            if (b != null) r.BillingAddresses.Add(b);
 | 
			
		||||
                            r.TelephoneNumbers.AddRange(telNrs);
 | 
			
		||||
                            r.EmailAddresses.AddRange(emailAddrs);
 | 
			
		||||
                            if (timestamps.HasValue)
 | 
			
		||||
                                r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var areaComsJson = zip.GetEntry("area_commitments.json");
 | 
			
		||||
                    if (areaComsJson != null) {
 | 
			
		||||
                        using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
 | 
			
		||||
                        string? line;
 | 
			
		||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                            var (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
 | 
			
		||||
                            r.AreaCommitments.Add(areaCom);
 | 
			
		||||
                            if (wbrd != null) {
 | 
			
		||||
                                r.Riede.Add(wbrd);
 | 
			
		||||
                        var membersJson = zip.GetEntry("members.json");
 | 
			
		||||
                        if (membersJson != null) {
 | 
			
		||||
                            using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
 | 
			
		||||
                            string? line;
 | 
			
		||||
                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                                var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
 | 
			
		||||
                                r.Members.Add(m);
 | 
			
		||||
                                if (b != null) r.BillingAddresses.Add(b);
 | 
			
		||||
                                r.TelephoneNumbers.AddRange(telNrs);
 | 
			
		||||
                                r.EmailAddresses.AddRange(emailAddrs);
 | 
			
		||||
                                if (timestamps.HasValue)
 | 
			
		||||
                                    r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
			
		||||
                            }
 | 
			
		||||
                            if (timestamps.HasValue)
 | 
			
		||||
                                r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var deliveriesJson = zip.GetEntry("deliveries.json");
 | 
			
		||||
                    if (deliveriesJson != null) {
 | 
			
		||||
                        using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
 | 
			
		||||
                        string? line;
 | 
			
		||||
                        while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                            var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                            var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
 | 
			
		||||
                            r.Deliveries.Add(d);
 | 
			
		||||
                            r.DeliveryParts.AddRange(parts);
 | 
			
		||||
                            r.Modifiers.AddRange(mods);
 | 
			
		||||
                            r.Riede.AddRange(rde);
 | 
			
		||||
                            if (timestamps.HasValue)
 | 
			
		||||
                                r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
			
		||||
                        var areaComsJson = zip.GetEntry("area_commitments.json");
 | 
			
		||||
                        if (areaComsJson != null) {
 | 
			
		||||
                            using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
 | 
			
		||||
                            string? line;
 | 
			
		||||
                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                                var (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
 | 
			
		||||
                                r.AreaCommitments.Add(areaCom);
 | 
			
		||||
                                if (wbrd != null) {
 | 
			
		||||
                                    r.Riede.Add(wbrd);
 | 
			
		||||
                                }
 | 
			
		||||
                                if (timestamps.HasValue)
 | 
			
		||||
                                    r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var deliveriesJson = zip.GetEntry("deliveries.json");
 | 
			
		||||
                        if (deliveriesJson != null) {
 | 
			
		||||
                            using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
 | 
			
		||||
                            string? line;
 | 
			
		||||
                            while ((line = await reader.ReadLineAsync()) != null) {
 | 
			
		||||
                                var obj = JsonNode.Parse(line)!.AsObject();
 | 
			
		||||
                                var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
 | 
			
		||||
                                r.Deliveries.Add(d);
 | 
			
		||||
                                r.DeliveryParts.AddRange(parts);
 | 
			
		||||
                                r.Modifiers.AddRange(mods);
 | 
			
		||||
                                r.Riede.AddRange(rde);
 | 
			
		||||
                                if (timestamps.HasValue)
 | 
			
		||||
                                    r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch (Exception exc) when (
 | 
			
		||||
                            exc is InvalidDataException ||
 | 
			
		||||
                            exc is FileFormatException ||
 | 
			
		||||
                            exc is FileNotFoundException ||
 | 
			
		||||
                            exc is IOException) {
 | 
			
		||||
                        data.RemoveAt(data.Count - 1);
 | 
			
		||||
                        var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message;
 | 
			
		||||
                        if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
			
		||||
                        MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
			
		||||
                        await AddImportedFiles(Path.GetFileName(filename));
 | 
			
		||||
                    } catch (Exception exc) {
 | 
			
		||||
                        data.RemoveAt(data.Count - 1);
 | 
			
		||||
                        var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message;
 | 
			
		||||
                        if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
			
		||||
                        var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
 | 
			
		||||
                        if (r == MessageBoxResult.Yes) {
 | 
			
		||||
                            await AddImportedFiles(Path.GetFileName(filename));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -449,7 +466,7 @@ namespace Elwig.Helpers.Export {
 | 
			
		||||
                            ["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
 | 
			
		||||
                            ["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()),
 | 
			
		||||
                        };
 | 
			
		||||
                    await writer.WriteAsync(obj.ToJsonString(JsonOpts));
 | 
			
		||||
                    await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // TODO encrypt files
 | 
			
		||||
@@ -457,28 +474,28 @@ namespace Elwig.Helpers.Export {
 | 
			
		||||
                    var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize);
 | 
			
		||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
			
		||||
                    foreach (var k in WbKgs.Value.WbKgs) {
 | 
			
		||||
                        await writer.WriteLineAsync(k.ToJson().ToJsonString(JsonOpts));
 | 
			
		||||
                        await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.JsonOpts));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (Members != null) {
 | 
			
		||||
                    var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
 | 
			
		||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
			
		||||
                    foreach (var m in Members.Value.Members) {
 | 
			
		||||
                        await writer.WriteLineAsync(m.ToJson().ToJsonString(JsonOpts));
 | 
			
		||||
                        await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (AreaComs != null) {
 | 
			
		||||
                    var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
 | 
			
		||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
			
		||||
                    foreach (var c in AreaComs.Value.AreaComs) {
 | 
			
		||||
                        await writer.WriteLineAsync(c.ToJson().ToJsonString(JsonOpts));
 | 
			
		||||
                        await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (Deliveries != null) {
 | 
			
		||||
                    var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
 | 
			
		||||
                    using var writer = new StreamWriter(json.Open(), Utils.UTF8);
 | 
			
		||||
                    foreach (var d in Deliveries.Value.Deliveries) {
 | 
			
		||||
                        await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts));
 | 
			
		||||
                        await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -806,7 +823,7 @@ namespace Elwig.Helpers.Export {
 | 
			
		||||
                    Temperature = p["temperature"]?.AsValue().GetValue<double>(),
 | 
			
		||||
                    Acid = p["acid"]?.AsValue().GetValue<double>(),
 | 
			
		||||
                    ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
 | 
			
		||||
                    WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
 | 
			
		||||
                    WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts),
 | 
			
		||||
                    WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
 | 
			
		||||
                };
 | 
			
		||||
            }).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 System.Text;
 | 
			
		||||
using System.Numerics;
 | 
			
		||||
using Elwig.Models.Entities;
 | 
			
		||||
using Elwig.Documents;
 | 
			
		||||
using Elwig.Helpers.Billing;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Text.Json.Nodes;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using Elwig.Models;
 | 
			
		||||
using Elwig.Models.Entities;
 | 
			
		||||
using LinqKit;
 | 
			
		||||
using MailKit.Net.Smtp;
 | 
			
		||||
using MailKit.Security;
 | 
			
		||||
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 MimeKit;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.IO.Ports;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Net.Sockets;
 | 
			
		||||
using System.Numerics;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Nodes;
 | 
			
		||||
using System.Text.RegularExpressions;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Windows;
 | 
			
		||||
using System.Windows.Markup;
 | 
			
		||||
 | 
			
		||||
namespace Elwig.Helpers {
 | 
			
		||||
    public static partial class Utils {
 | 
			
		||||
 | 
			
		||||
        public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
 | 
			
		||||
        public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
 | 
			
		||||
 | 
			
		||||
        public static int CurrentYear => DateTime.Now.Year;
 | 
			
		||||
        public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
 | 
			
		||||
@@ -430,6 +431,8 @@ namespace Elwig.Helpers {
 | 
			
		||||
            var client = new HttpClient() {
 | 
			
		||||
                Timeout = TimeSpan.FromSeconds(5),
 | 
			
		||||
            };
 | 
			
		||||
            client.DefaultRequestHeaders.UserAgent.Clear();
 | 
			
		||||
            client.DefaultRequestHeaders.UserAgent.ParseAdd($"Elwig/{App.Version} ({App.Client.NameToken}, {App.BranchName}, {Environment.MachineName}, {Environment.OSVersion})");
 | 
			
		||||
            client.DefaultRequestHeaders.Accept.Clear();
 | 
			
		||||
            if (accept != null)
 | 
			
		||||
                client.DefaultRequestHeaders.Accept.Add(new(accept));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
using Elwig.Helpers;
 | 
			
		||||
using Elwig.Helpers.Billing;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
@@ -62,6 +63,11 @@ namespace Elwig.Models.Entities {
 | 
			
		||||
        [Column("comment")]
 | 
			
		||||
        public string? Comment { get; set; }
 | 
			
		||||
 | 
			
		||||
        [NotMapped]
 | 
			
		||||
        public string[] Comments => [.. Parts.Select(p => p.Comment).Prepend(Comment).Where(c => c != null).Cast<string>()];
 | 
			
		||||
        [NotMapped]
 | 
			
		||||
        public string CommentsString => string.Join(" / ", Comments);
 | 
			
		||||
 | 
			
		||||
        [Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
 | 
			
		||||
        public long CTime { get; set; }
 | 
			
		||||
        [NotMapped]
 | 
			
		||||
@@ -108,16 +114,16 @@ namespace Elwig.Models.Entities {
 | 
			
		||||
        public int Weight => Parts.Select(p => p.Weight).Sum();
 | 
			
		||||
        public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<string> SortIds => Parts
 | 
			
		||||
            .GroupBy(p => p.SortId)
 | 
			
		||||
        public IEnumerable<RawVaribute> Vaributes => Parts
 | 
			
		||||
            .GroupBy(p => (p.SortId, p.AttrId, p.CultId))
 | 
			
		||||
            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
			
		||||
            .Select(g => g.Key);
 | 
			
		||||
        public IEnumerable<string> FilteredSortIds => FilteredParts
 | 
			
		||||
            .GroupBy(p => p.SortId)
 | 
			
		||||
            .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
 | 
			
		||||
        public IEnumerable<RawVaribute> FilteredVaributes => FilteredParts
 | 
			
		||||
            .GroupBy(p => (p.SortId, p.AttrId, p.CultId))
 | 
			
		||||
            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
			
		||||
            .Select(g => g.Key);
 | 
			
		||||
        public string SortIdString => string.Join(", ", SortIds);
 | 
			
		||||
        public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
 | 
			
		||||
            .Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
 | 
			
		||||
        public string VaributeString => string.Join(", ", Vaributes);
 | 
			
		||||
        public string FilteredVaributeString => string.Join(", ", FilteredVaributes);
 | 
			
		||||
 | 
			
		||||
        public Brush? Color => Parts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
 | 
			
		||||
        public Brush? FilteredColor => FilteredParts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,9 @@ namespace Elwig.Models.Entities {
 | 
			
		||||
        [Column("max_weight")]
 | 
			
		||||
        public int? MaxWeight { get; set; }
 | 
			
		||||
        [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]
 | 
			
		||||
        public double? Percent => (double)AnnouncedWeight / MaxWeight * 100;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,16 +11,24 @@ namespace Elwig.Models.Entities {
 | 
			
		||||
        [Column("type")]
 | 
			
		||||
        public string Type { get; private set; } = null!;
 | 
			
		||||
 | 
			
		||||
        [Column("max_qualid")]
 | 
			
		||||
        public string MaxQualId { get; private set; } = null!;
 | 
			
		||||
 | 
			
		||||
        [ForeignKey("MaxQualId")]
 | 
			
		||||
        public virtual WineQualLevel MaxQualityLevel { get; private set; } = null!;
 | 
			
		||||
 | 
			
		||||
        [Column("name")]
 | 
			
		||||
        public string Name { get; private set; } = null!;
 | 
			
		||||
 | 
			
		||||
        [Column("comment")]
 | 
			
		||||
        public string? Comment { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public string SortIdFormat => IsQuw ? SortId : $"({SortId})";
 | 
			
		||||
        public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
 | 
			
		||||
 | 
			
		||||
        public bool IsRed => Type == "R";
 | 
			
		||||
        public bool IsWhite => Type == "W";
 | 
			
		||||
        public bool IsQuw => MaxQualId == "QUW";
 | 
			
		||||
        public Brush? Color => IsWhite ? Brushes.DarkGreen : IsRed ? Brushes.DarkRed : null;
 | 
			
		||||
 | 
			
		||||
        public WineVar() { }
 | 
			
		||||
@@ -28,6 +36,7 @@ namespace Elwig.Models.Entities {
 | 
			
		||||
        public WineVar(string sortId, string name) {
 | 
			
		||||
            SortId = sortId;
 | 
			
		||||
            Name = name;
 | 
			
		||||
            MaxQualId = "QUW";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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",
 | 
			
		||||
                    DefaultExt = "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.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
 | 
			
		||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
			
		||||
                    await Task.Run(async () => {
 | 
			
		||||
                        try {
 | 
			
		||||
 
 | 
			
		||||
@@ -496,11 +496,13 @@ namespace Elwig.Services {
 | 
			
		||||
            } else if (mode == ExportMode.Export) {
 | 
			
		||||
                var d = new SaveFileDialog() {
 | 
			
		||||
                    FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
 | 
			
		||||
                    DefaultExt = ".elwig.zip",
 | 
			
		||||
                    DefaultExt = "elwig.zip",
 | 
			
		||||
                    Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
 | 
			
		||||
                    Title = $"{MemberList.Name} speichern unter - Elwig"
 | 
			
		||||
                    Title = $"{MemberList.Name} speichern unter - Elwig",
 | 
			
		||||
                    AddExtension = false,
 | 
			
		||||
                };
 | 
			
		||||
                if (d.ShowDialog() == true) {
 | 
			
		||||
                    if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
 | 
			
		||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
			
		||||
                    await Task.Run(async () => {
 | 
			
		||||
                        try {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
        <TextBlock Margin="20,10" FontSize="12">
 | 
			
		||||
            <Bold>Produkt:</Bold> Elwig<LineBreak/>
 | 
			
		||||
            <Bold>Beschreibung:</Bold> Elektronische Winzergenossenschaftsverwaltung<LineBreak/>
 | 
			
		||||
            <Bold>Typ:</Bold> Warenwirschaftssystem<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/>
 | 
			
		||||
 
 | 
			
		||||
@@ -664,8 +664,10 @@ namespace Elwig.Windows {
 | 
			
		||||
            try {
 | 
			
		||||
                await Task.Run(async () => {
 | 
			
		||||
                    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) {
 | 
			
		||||
                MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -265,13 +265,18 @@
 | 
			
		||||
                            </Style>
 | 
			
		||||
                        </DataGridTextColumn.CellStyle>
 | 
			
		||||
                    </DataGridTextColumn>
 | 
			
		||||
                    <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
 | 
			
		||||
                    <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredVaributeString}" Width="60">
 | 
			
		||||
                        <DataGridTextColumn.CellStyle>
 | 
			
		||||
                            <Style>
 | 
			
		||||
                                <Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/>
 | 
			
		||||
                                <Setter Property="TextBlock.TextAlignment" Value="Center"/>
 | 
			
		||||
                            </Style>
 | 
			
		||||
                        </DataGridTextColumn.CellStyle>
 | 
			
		||||
                        <DataGridTextColumn.ElementStyle>
 | 
			
		||||
                            <Style TargetType="TextBlock">
 | 
			
		||||
                                <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
 | 
			
		||||
                            </Style>
 | 
			
		||||
                        </DataGridTextColumn.ElementStyle>
 | 
			
		||||
                    </DataGridTextColumn>
 | 
			
		||||
                    <DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
 | 
			
		||||
                        <DataGridTextColumn.CellStyle>
 | 
			
		||||
@@ -290,6 +295,7 @@
 | 
			
		||||
                    <DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/>
 | 
			
		||||
                    <DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/>
 | 
			
		||||
                    <DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/>
 | 
			
		||||
                    <DataGridTextColumn Header="Kommentar" Binding="{Binding CommentsString}" Width="150"/>
 | 
			
		||||
                </DataGrid.Columns>
 | 
			
		||||
            </DataGrid>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ using Elwig.ViewModels;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.Win32;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Windows;
 | 
			
		||||
@@ -34,6 +35,8 @@ namespace Elwig.Windows {
 | 
			
		||||
 | 
			
		||||
        private readonly Button[] WeighingButtons;
 | 
			
		||||
 | 
			
		||||
        private List<WineQualLevel> WineQualityLevels = [];
 | 
			
		||||
 | 
			
		||||
        public DeliveryAdminWindow(bool receipt = false) {
 | 
			
		||||
            InitializeComponent();
 | 
			
		||||
            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();
 | 
			
		||||
            cultList.Insert(0, new NullItem(""));
 | 
			
		||||
            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
 | 
			
		||||
                .Where(m => m.Year == y && (!IsCreating || m.IsActive))
 | 
			
		||||
                .OrderBy(m => m.Ordering)
 | 
			
		||||
@@ -1207,6 +1211,7 @@ namespace Elwig.Windows {
 | 
			
		||||
                AttributeInput.SelectedIndex = 0;
 | 
			
		||||
                CultivationInput.SelectedIndex = 0;
 | 
			
		||||
            }
 | 
			
		||||
            UpdateWineQualityLevels();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) {
 | 
			
		||||
@@ -1220,19 +1225,39 @@ namespace Elwig.Windows {
 | 
			
		||||
        private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
 | 
			
		||||
            if (WineVarietyInput.SelectedItem is WineVar s)
 | 
			
		||||
                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() {
 | 
			
		||||
            using var ctx = new AppDbContext();
 | 
			
		||||
            if (!GetInputValid(GradationKmwInput)) {
 | 
			
		||||
                UnsetDefaultValue(WineQualityLevelInput);
 | 
			
		||||
                ComboBox_SelectionChanged(WineQualityLevelInput, null);
 | 
			
		||||
                WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.QualId == "WEI").ToList();
 | 
			
		||||
                WineQualityLevelInput.ItemsSource = WineQualityLevels;
 | 
			
		||||
                WineQualityLevelInput.SelectedItem = null;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            var kmw = (double)ViewModel.GradationKmw!;
 | 
			
		||||
            WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.MinKmw == null || q.MinKmw <= kmw).ToList();
 | 
			
		||||
            var qual = ctx.GetWineQualityLevel(kmw).GetAwaiter().GetResult();
 | 
			
		||||
            var max = ViewModel.WineVar?.MaxQualId;
 | 
			
		||||
            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);
 | 
			
		||||
            if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) {
 | 
			
		||||
                ControlUtils.SelectItem(WineQualityLevelInput, qual);
 | 
			
		||||
@@ -1372,8 +1397,7 @@ namespace Elwig.Windows {
 | 
			
		||||
                AbgewertetInput.IsChecked = false;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            using var ctx = new AppDbContext();
 | 
			
		||||
            var defQual = ctx.GetWineQualityLevel(double.Parse(GradationKmwInput.Text)).GetAwaiter().GetResult();
 | 
			
		||||
            var defQual = GetWineQualityLevel(ViewModel.GradationKmw!.Value, !(ViewModel.WineVar?.IsQuw ?? true) ? ViewModel.WineVar?.MaxQualId : null);
 | 
			
		||||
            AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -83,14 +83,19 @@ namespace Elwig.Windows {
 | 
			
		||||
 | 
			
		||||
        private async Task RefreshDeliveryScheduleList() {
 | 
			
		||||
            using var ctx = new AppDbContext();
 | 
			
		||||
            var deliverySchedules = await ctx.DeliverySchedules
 | 
			
		||||
            var list = await ctx.DeliverySchedules
 | 
			
		||||
                .Where(s => s.Year == ViewModel.FilterSeason)
 | 
			
		||||
                .Include(s => s.Branch)
 | 
			
		||||
                .Include(s => s.Announcements)
 | 
			
		||||
                .OrderBy(s => s.DateString)
 | 
			
		||||
                .ThenBy(s => s.Branch.Name)
 | 
			
		||||
                .ThenBy(s => s.Description)
 | 
			
		||||
                .Select(s => new {
 | 
			
		||||
                    Schedule = s,
 | 
			
		||||
                    AnnouncedWeight = s.Announcements.Sum(a => a.Weight)
 | 
			
		||||
                })
 | 
			
		||||
                .ToListAsync();
 | 
			
		||||
            list.ForEach(v => v.Schedule.AnnouncedWeightOverride = v.AnnouncedWeight);
 | 
			
		||||
            var deliverySchedules = list.Select(v => v.Schedule).ToList();
 | 
			
		||||
            ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules
 | 
			
		||||
                .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);
 | 
			
		||||
 
 | 
			
		||||
@@ -738,7 +738,7 @@ namespace Elwig.Windows {
 | 
			
		||||
                PostalNoEmailInput.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 totalNum = printNum + emailNum;
 | 
			
		||||
 | 
			
		||||
@@ -954,7 +954,7 @@ namespace Elwig.Windows {
 | 
			
		||||
            AvaiableDocumentsList.SelectedIndex = 1;
 | 
			
		||||
            if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation))
 | 
			
		||||
                return;
 | 
			
		||||
            SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, (Year, DocumentNonDeliverersInput.IsChecked == true)));
 | 
			
		||||
            SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, Year));
 | 
			
		||||
            SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1;
 | 
			
		||||
            RecipientsDeliveryMembersInput.IsChecked = true;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -158,8 +158,10 @@ namespace Elwig.Windows {
 | 
			
		||||
                    FileName = $"database_{Utils.Today:yyyy-MM-dd}.sql.zip",
 | 
			
		||||
                    DefaultExt = "sql.zip",
 | 
			
		||||
                    Filter = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip",
 | 
			
		||||
                    AddExtension = false,
 | 
			
		||||
                };
 | 
			
		||||
                if (d.ShowDialog() == true) {
 | 
			
		||||
                    if (!d.FileName.EndsWith(".sql.zip")) d.FileName += ".sql.zip";
 | 
			
		||||
                    Mouse.OverrideCursor = Cursors.Wait;
 | 
			
		||||
                    await Task.Run(async () => {
 | 
			
		||||
                        await Database.ExportSql(d.FileName, true);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							@@ -5,3 +5,43 @@ Elwig
 | 
			
		||||
**El**ektronische **Wi**nzer**g**enossenschaftsverwaltung (Electronic Management for Vintners' Cooperatives).
 | 
			
		||||
 | 
			
		||||
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.5 ([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.5 ([Ä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));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            await b.Calculate(false, false, false);
 | 
			
		||||
            await b.Calculate(true, false, false, false);
 | 
			
		||||
            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
			
		||||
            Assert.Multiple(() => {
 | 
			
		||||
                Assert.That(prices, Has.Count.EqualTo(7));
 | 
			
		||||
@@ -234,7 +234,7 @@ namespace Tests.UnitTests.HelperTests {
 | 
			
		||||
                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);
 | 
			
		||||
            Assert.Multiple(() => {
 | 
			
		||||
                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