Compare commits

..

1 Commits

Author SHA1 Message Date
0739299694 Bump version to 1.0.0.0
All checks were successful
Test / Run tests (push) Successful in 2m2s
2025-07-30 00:00:01 +02:00
35 changed files with 277 additions and 1236 deletions

View File

@@ -1,7 +1,7 @@
name: Deploy name: Deploy
on: on:
push: push:
tags: ["v[0-9]+.[0-9]+.[0-9]+.[0-9]+"] tags: ["v([0-9]+.)?[0-9]+.[0-9]+.[0-9]+"]
jobs: jobs:
deploy: deploy:
name: Build and Deploy name: Build and Deploy

View File

@@ -2,206 +2,22 @@
Changelog Changelog
========= =========
[v1.0.1.3][v1.0.1.3] (2025-10-13) {#v1.0.1.3} [v1.0.0.0][v1.0.0.0] (2025-XX-YY) {#v1.0.0.0}
---------------------------------------------
### Neue Funktionen {#v1.0.1.3-features}
* In der Liste des Lieferungen-Fenster (`DeliveryAdminWindow`) werden
* statt ausschließlich der Sorte auch Attribut und Bewirtschaftungsart angezeigt. (a0d4f19f30)
* Kommentare der Lieferungen (und Teillieferungen) angezeigt. (548aeb2ce9)
### Sonstiges {#v1.0.1.3-misc}
* Verzögerung der Überprüfung auf automatische Updates auf einige Sekunden verlängert. (67ba342c28)
* Verbesserung der Ladezeiten im Anmeldungen-Fenster (`DeliveryAncmtAdminWindow`). (7edd888aa2)
[v1.0.1.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.3
[v1.0.1.2][v1.0.1.2] (2025-09-25) {#v1.0.1.2}
---------------------------------------------
### Behobene Fehler {#v1.0.1.2-bugfixes}
* Beim automatischen Importieren/Synchronisieren wird bei einem Fehlerfall der Benutzer verständigt, aber der Vorgang nicht abgebrochen. (9d02f18bac)
### Sonstiges {#v1.0.1.2-misc}
* Beim Sichern der Datenbank werden Meta-Informationen in der ZIP-Datei gespeichert. (c8a95422af)
[v1.0.1.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.2
[v1.0.1.1][v1.0.1.1] (2025-09-21) {#v1.0.1.1}
---------------------------------------------
### Sonstiges {#v1.0.1.1-misc}
* Eingabe von Sorten und Qualitätsstufen im Übernahme-Fenster (`DeliveryAdminWindows`) verbessert. (e2de7a1f1c, b27b89f599)
[v1.0.1.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.1
[v1.0.1.0][v1.0.1.0] (2025-09-18) {#v1.0.1.0}
---------------------------------------------
### Neue Funktionen {#v1.0.1.0-features}
* Neue Weinsorten gemäß Kürzelliste der Bundeskellereiinspektion hinzugefügt. (a0dcaf7b4f)
### Sonstiges {#v1.0.1.0-misc}
* HTTP-Anfragen haben jetzt das Feld `User-Agent` gesetzt. (844fc5217a)
* Auto-Update-Funktion wird auch beim Erlangen von Netzwerkverbindung ausgeführt. (8bc053053c)
[v1.0.1.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.0
[v1.0.0.6][v1.0.0.6] (2025-09-17) {#v1.0.0.6}
---------------------------------------------
### Behobene Fehler {#v1.0.0.6-bugfixes}
* Die automatische Erkennung des Wiege-Modus hat im WKW nicht funktioniert. (463769b549)
### Sonstiges {#v1.0.0.6-misc}
* Tippfehler im Über-Fenster (`AboutWindow`) behoben. (2c383d0c55)
* `SaveFileDialog` mit Multi-File-Extensions verbessert. (9e02b15ff1)
[v1.0.0.6]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.6
[v1.0.0.5][v1.0.0.5] (2025-09-15) {#v1.0.0.5}
---------------------------------------------
### Neue Funktionen {#v1.0.0.5-features}
* Die Datenbank kann im Haupt-Fenster (`MainWindow`) gesichert und wiederhergestellt werden. (f02598760f)
### Sonstiges {#v1.0.0.5-misc}
* In der WGM ist eine Auswahl eines Zu/-Abschlags im Übernahme-Fenster (`DeliveryAdminWindow`) nun erforderlich. (a9b5317e79)
* In der Konfigurationsdatei kann im `[general]` Block `weighing = gross`, `weighing = net`, oder `weighing = box` angegeben werden. (d7012ebfa1)
* Über-Fenser (`AboutWindow`) hinzugefügt. (3c9b3c2db1)
* Abhängigkeiten aktualisiert. (44dcc5e19f, 98f8907817)
[v1.0.0.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.5
[v1.0.0.4][v1.0.0.4] (2025-09-01) {#v1.0.0.4}
---------------------------------------------
### Behobene Fehler {#v1.0.0.4-bugfixes}
* Absturz beim Verschicken von einzelnen E-Mails außerhalb des Rundschreiben-Fensters (`MailWindow`) behoben. (07f9a0f522)
### Sonstiges {#v1.0.0.4-misc}
* Ladezeit des Fehler-Protokoll-Fensters (`LogWindow`) verbessert. (4653a4f129)
* Im Übernahme-Fenster (`DeliveryAdminWindow`) ist es nun möglich die Qualitätsstufe zu verändern. (104798d4f2)
[v1.0.0.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.4
[v1.0.0.3][v1.0.0.3] (2025-08-11) {#v1.0.0.3}
---------------------------------------------
### Neue Funktionen {#v1.0.0.3-features}
* Im Haupt-Fenster (`MainWindow`) ist es nun möglich die gesamte Datenbank zu hochzuladen bzw. herunterzuladen. ([#69][i69])
### Sonstiges {#v1.0.0.3-misc}
* Die Intigrität von Elwig-Export-Dateien (`.elwig.zip`) und anderen `.zip` Dateien wir nun überprüft. (d3157e4d48)
* Die Herkunftshierarchie wird nun auch automatisch synchronisiert (benötigt für Stamm-KG bei Mitglied, Ried/KG bei Flächenbindung, und Herkunft bei Lieferung). ([#70][i70], b6c03892b1)
* Abhängigkeiten aktualisiert. (1f165055c1, 4e2eca295d)
[v1.0.0.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.3
[i69]: https://git.necronda.net/winzer/elwig/issues/69
[i70]: https://git.necronda.net/winzer/elwig/issues/70
[v1.0.0.2][v1.0.0.2] (2025-08-05) {#v1.0.0.2}
---------------------------------------------
### Sonstiges {#v1.0.0.2-misc}
* Explizit native SQLite-Bibliothek hinzugefügt. (77c3f388e7)
[v1.0.0.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.2
[v1.0.0.1][v1.0.0.1] (2025-08-05) {#v1.0.0.1}
---------------------------------------------
### Sonstiges {#v1.0.0.1-misc}
* Abhängigkeiten aktualisiert. (466c8a322c)
* Angepasste Fehlermeldung, wenn Importieren fehlschlägt. (ab7c7404e2)
[v1.0.0.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.1
[v1.0.0.0][v1.0.0.0] (2025-07-30) {#v1.0.0.0}
--------------------------------------------- ---------------------------------------------
### Neue Funktionen {#v1.0.0.0-features} ### Neue Funktionen {#v1.0.0.0-features}
* Es wird nun gespeichert, wann Mitglieder/Flächenbindungen/Lieferungen importiert bzw. exporiert wurden. (4234c7f994) * Something.
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) wird der Auszahlungsbetrag für nicht-festgesetzte Varianten nun überschlagsmäßig angezeigt. (913129f155)
* Wenn eine Variante im Auszahlungsvariante-Fenster (`ChartWindow`) gespeichert wird, wird sie nun automatisch auch berechnet. (c6c8fd9b68)
* Überall wo Weinsorten vorkommen werden diese jetzt rot oder grün eingefärbt angezeigt. ([#61][i61])
* Das Flächenbindungs-Fenster (`AreaComAdminWindow`) wurde überarbeitet. (53d82604a1, 267aa3d47c, [#64][i64])
* Auf allen Dokumenten wird der Bio-Kontrollstellen-Code in der Fußzeile angeführt (sofern dieser in den Stammdaten gesetzt wurde). (fad1e28c06)
### Behobene Fehler {#v1.0.0.0-bugfixes} ### Behobene Fehler {#v1.0.0.0-bugfixes}
* Bei Traubengutschriften (`CreditNote`) wurde der Rebelzuschlag immer angeführt, auch wenn dieser in der zugrundeliegenden Berechnung nicht berücksichtigt wurde. (336aef5c70) * Something.
* In den Variantendaten einer Auszahlungsvariante (`PaymentVariantSummary`) wurde neben den Spalten _gebunden_ und _ungebunden_ noch _attributlos gebunden_ hinzugefügt. Ohne diese neue Spalte wären die Werte der anderen beiden falsch. ([#58][i58])
* Das erste Laden des Ausgangs-Protokoll-Fensters (`MailLogWindow`) hat nicht funktioniert. ([#65][i65])
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) und im Mitglieder-Fenster (`MemberAdminWindow`) wird der Tool-Tip für Gewicht/Gradation mit korrektem Layout angezeigt. (e9f389b885)
* Bei Traubengutschriften (`CreditNote`) werden längere Freitexte vollständig angezeigt statt abgeschnitten. ([#62][i62])
### Sonstiges {#v1.0.0.0-misc} ### Sonstiges {#v1.0.0.0-misc}
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) wird angeführt, dass "Zu- und Abschläge bei Lieferungen" auch den Rebelzuschlag umfassen. (0dff3986b7) * Something.
* Im gesamten Program wird an passenden Stellen statt "Gewicht" nun "Menge" verwendet. (0f3ce39f35)
* Verbesserungen der Code-Qualität und der Leistung. ([#59][i59], b580e1bf79, 623f55f5b0, 3493ff6df1, 267797b55d, 41811925be, [#57][i57], 38d0ff969d, e6746f76b1, 7e9a27c75d)
* Im Mitglieder-Fenster (`MemberAdminWindow`) werden die Flächenbindungen jetzt für die momentane Saison angezeigt statt für das aktuelle Jahr. (953532cae4)
* Abhängigkeiten aktualisiert. (4b27ebf81b, 7153bfab6f, 9b48242f0e)
* GPLv3 Lizenz hinzugefügt. (93e920f61a)
[v1.0.0.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.0 [v1.0.0.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.0
[i57]: https://git.necronda.net/winzer/elwig/issues/57
[i58]: https://git.necronda.net/winzer/elwig/issues/58
[i59]: https://git.necronda.net/winzer/elwig/issues/59
[i61]: https://git.necronda.net/winzer/elwig/issues/61
[i62]: https://git.necronda.net/winzer/elwig/issues/62
[i64]: https://git.necronda.net/winzer/elwig/issues/64
[i65]: https://git.necronda.net/winzer/elwig/issues/65

View File

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

View File

@@ -1,22 +1,20 @@
using Elwig.Dialogs;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Helpers.Printing;
using Elwig.Helpers.Weighing;
using Elwig.Models.Entities;
using Elwig.Windows;
using System; using System;
using System.Collections.Generic;
using System.Data; using System.Data;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.IO;
using Elwig.Helpers;
using Elwig.Helpers.Weighing;
using System.Collections.Generic;
using System.Windows.Threading; using System.Windows.Threading;
using System.Reflection;
using Elwig.Helpers.Printing;
using Elwig.Windows;
using Elwig.Dialogs;
using System.Threading.Tasks;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using System.Text;
namespace Elwig { namespace Elwig {
public partial class App : Application { public partial class App : Application {
@@ -128,11 +126,10 @@ namespace Elwig {
if (Config.UpdateAuto && Config.UpdateUrl != null) { if (Config.UpdateAuto && Config.UpdateUrl != null) {
if (Utils.HasInternetConnectivity()) { if (Utils.HasInternetConnectivity()) {
Utils.RunBackground("Auto Updater", async () => { Utils.RunBackground("Auto Updater", async () => {
await Task.Delay(1000); await Task.Delay(500);
await CheckForUpdates(); await CheckForUpdates();
}); });
} }
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
_autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer); _autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
_autoUpdateTimer.Start(); _autoUpdateTimer.Start();
} }
@@ -164,16 +161,6 @@ namespace Elwig {
Shutdown(); Shutdown();
} }
if (Config.WeighingMode == null) {
if (Client.IsMatzen || Client.IsWolkersdorf) {
Config.WeighingMode = WeighingMode.Net;
} else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
Config.WeighingMode = WeighingMode.Box;
} else if (Client.IsBaden || Client.IsGrInzersdorf) {
Config.WeighingMode = WeighingMode.Gross;
}
}
base.OnStartup(evt); base.OnStartup(evt);
var window = new MainWindow(); var window = new MainWindow();
@@ -230,16 +217,6 @@ namespace Elwig {
} }
} }
private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs evt) {
if (!evt.IsAvailable) return;
if (Utils.HasInternetConnectivity()) {
Utils.RunBackground("Auto Updater", async () => {
await Task.Delay(2000);
await CheckForUpdates();
});
}
}
public static async Task CheckForUpdates(bool showAlert = false) { public static async Task CheckForUpdates(bool showAlert = false) {
if (Config.UpdateUrl == null) return; if (Config.UpdateUrl == null) return;
var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl); var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
@@ -262,19 +239,6 @@ namespace Elwig {
} }
} }
public static async Task ReplaceDatabase(string filename) {
try {
await Task.Run(async () => {
await Database.Import(filename);
});
MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
ForceShutdown = true;
Current.Shutdown();
} catch (Exception exc) {
MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static T FocusWindow<T>(Func<T> constructor, Predicate<T>? selector = null) where T : Window { private static T FocusWindow<T>(Func<T> constructor, Predicate<T>? selector = null) where T : Window {
foreach (Window w in CurrentApp.Windows) { foreach (Window w in CurrentApp.Windows) {
if (w is T t && (selector == null || selector(t))) { if (w is T t && (selector == null || selector(t))) {

View File

@@ -7,7 +7,7 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon> <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.0.1.3</Version> <Version>1.0.0.0</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages> <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
@@ -25,19 +25,17 @@
<PackageReference Include="LinqKit" Version="1.3.8" /> <PackageReference Include="LinqKit" Version="1.3.8" />
<PackageReference Include="MailKit" Version="4.13.0" /> <PackageReference Include="MailKit" Version="4.13.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.9" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.7" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3485.44" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3351.48" />
<PackageReference Include="NJsonSchema" Version="11.4.0" /> <PackageReference Include="NJsonSchema" Version="11.3.2" />
<PackageReference Include="PdfiumViewer" Version="2.13.0" /> <PackageReference Include="PdfiumViewer" Version="2.13.0" />
<PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" /> <PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
<PackageReference Include="RazorLight" Version="2.3.1" /> <PackageReference Include="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.56" /> <PackageReference Include="ScottPlot.WPF" Version="5.0.55" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" /> <PackageReference Include="System.IO.Ports" Version="9.0.7" />
<PackageReference Include="System.IO.Hashing" Version="9.0.9" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageReference Include="System.IO.Ports" Version="9.0.9" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.9" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -248,6 +248,13 @@ namespace Elwig.Helpers {
return c + 1; return c + 1;
} }
public async Task<WineQualLevel> GetWineQualityLevel(double kmw) {
return await WineQualityLevels
.Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
.OrderBy(q => q.MinKmw)
.LastAsync();
}
public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) { public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) {
foreach (var m in Modifiers.Where(m => m.Year == part.Year)) { foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
var mod = new DeliveryPartModifier { var mod = new DeliveryPartModifier {

View File

@@ -9,7 +9,7 @@ namespace Elwig.Helpers {
public static class AppDbUpdater { public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat! // Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 33; public static readonly int RequiredSchemaVersion = 32;
private static int VersionOffset = 0; private static int VersionOffset = 0;

View File

@@ -19,6 +19,14 @@ namespace Elwig.Helpers {
public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S"; public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
public bool IsGrInzersdorf => IsWeinland; public bool IsGrInzersdorf => IsWeinland;
public bool HasNetWeighing(string? zwstId) => IsMatzen || (IsWinzerkeller && zwstId == "W");
public bool HasNetWeighing(Branch? b) => HasNetWeighing(b?.ZwstId);
public bool HasNetWeighing() => HasNetWeighing(App.ZwstId);
public bool HasBoxWeighing(string? zwstId) => IsWinzerkeller && (zwstId != "W");
public bool HasBoxWeighing(Branch? b) => HasBoxWeighing(b?.ZwstId);
public bool HasBoxWeighing() => HasBoxWeighing(App.ZwstId);
public string NameToken; public string NameToken;
public string NameShort; public string NameShort;
public string Name; public string Name;

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@@ -17,6 +18,8 @@ namespace Elwig.Helpers.Export {
public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt"); public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
private static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
public static async Task<string[]> GetImportedFiles() { public static async Task<string[]> GetImportedFiles() {
try { try {
return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8); return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
@@ -38,7 +41,6 @@ namespace Elwig.Helpers.Export {
Dictionary<string, int> currentLsNrs; Dictionary<string, int> currentLsNrs;
Dictionary<int, List<WbRd>> currentWbRde; Dictionary<int, List<WbRd>> currentWbRde;
Dictionary<int, AT_Kg> kgs; Dictionary<int, AT_Kg> kgs;
List<WbGl> currentWbGls;
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId); branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId);
@@ -50,7 +52,6 @@ namespace Elwig.Helpers.Export {
currentWbRde = await ctx.WbRde currentWbRde = await ctx.WbRde
.GroupBy(r => r.KgNr) .GroupBy(r => r.KgNr)
.ToDictionaryAsync(g => g.Key, g => g.ToList()); .ToDictionaryAsync(g => g.Key, g => g.ToList());
currentWbGls = await ctx.WbGls.ToListAsync();
kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr); kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr);
} }
@@ -61,8 +62,6 @@ namespace Elwig.Helpers.Export {
List<MemberEmailAddr> EmailAddresses, List<MemberEmailAddr> EmailAddresses,
List<AreaCom> AreaCommitments, List<AreaCom> AreaCommitments,
List<WbRd> Riede, List<WbRd> Riede,
List<WbKg> WbKgs,
List<WbGl> WbGls,
List<Delivery> Deliveries, List<Delivery> Deliveries,
List<DeliveryPart> DeliveryParts, List<DeliveryPart> DeliveryParts,
List<DeliveryPartModifier> Modifiers, List<DeliveryPartModifier> Modifiers,
@@ -74,22 +73,13 @@ namespace Elwig.Helpers.Export {
int? DeliveryNum, string? DeliveryFilters)>(); int? DeliveryNum, string? DeliveryFilters)>();
foreach (var filename in filenames) { foreach (var filename in filenames) {
try {
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
["member"] = [],
["area_commitment"] = [],
["delivery"] = [],
})));
var r = data[^1];
// TODO read encrypted files // TODO read encrypted files
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read); using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
await zip.CheckIntegrity();
var version = zip.GetEntry("version"); var version = zip.GetEntry("version");
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) { using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
if (await reader.ReadToEndAsync() != "elwig:1") if (await reader.ReadToEndAsync() != "elwig:1")
throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})"); throw new FileFormatException($"Ungültige Export-Datei ({filename})");
} }
var metaJson = zip.GetEntry("meta.json"); var metaJson = zip.GetEntry("meta.json");
@@ -106,20 +96,12 @@ namespace Elwig.Helpers.Export {
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null, areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null)); deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
var wbKgsJson = zip.GetEntry("wb_kgs.json"); data.Add(new([], [], [], [], [], [], [], new([], [], new() {
if (wbKgsJson != null) { ["member"] = [],
using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8); ["area_commitment"] = [],
string? line; ["delivery"] = [],
while ((line = await reader.ReadLineAsync()) != null) { })));
var obj = JsonNode.Parse(line)!.AsObject(); var r = data[^1];
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"); var membersJson = zip.GetEntry("members.json");
if (membersJson != null) { if (membersJson != null) {
@@ -143,9 +125,10 @@ namespace Elwig.Helpers.Export {
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 (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde); var (areaCom, wbrd, timestamps) = obj.ToAreaCom(kgs, currentWbRde);
r.AreaCommitments.Add(areaCom); r.AreaCommitments.Add(areaCom);
if (wbrd != null) { if (wbrd != null) {
currentWbRde[wbrd.KgNr].Add(wbrd);
r.Riede.Add(wbrd); r.Riede.Add(wbrd);
} }
if (timestamps.HasValue) if (timestamps.HasValue)
@@ -159,52 +142,26 @@ namespace Elwig.Helpers.Export {
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 (d, parts, mods, timestamps) = obj.ToDelivery(currentLsNrs, currentDids);
r.Deliveries.Add(d); r.Deliveries.Add(d);
r.DeliveryParts.AddRange(parts); r.DeliveryParts.AddRange(parts);
r.Modifiers.AddRange(mods); r.Modifiers.AddRange(mods);
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["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
} }
} }
} catch (Exception exc) when (
exc is InvalidDataException ||
exc is FileFormatException ||
exc is FileNotFoundException ||
exc is IOException) {
data.RemoveAt(data.Count - 1);
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
await AddImportedFiles(Path.GetFileName(filename));
} catch (Exception exc) {
data.RemoveAt(data.Count - 1);
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
if (r == MessageBoxResult.Yes) {
await AddImportedFiles(Path.GetFileName(filename));
}
}
} }
var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>(); var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int Imported, int NotImported, string Filters)>(); var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int Imported, int NotImported, string Filters)>();
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>(); var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) { foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
var branch = branches[meta.ZwstId]; var branch = branches[meta.ZwstId];
var device = meta.Device; var device = meta.Device;
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var kgnrs = wbKgs.Select(k => k.KgNr).ToList();
var duplicateKgNrs = await ctx.WbKgs
.Where(k => kgnrs.Contains(k.KgNr))
.Select(k => k.KgNr)
.ToListAsync();
var mgnrs = members.Select(m => m.MgNr).ToList(); var mgnrs = members.Select(m => m.MgNr).ToList();
var duplicateMgNrs = await ctx.Members var duplicateMgNrs = await ctx.Members
.Where(m => mgnrs.Contains(m.MgNr)) .Where(m => mgnrs.Contains(m.MgNr))
@@ -258,12 +215,6 @@ namespace Elwig.Helpers.Export {
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count); importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
} }
if (importDuplicateMembers || importNewMembers || importDuplicateDeliveries || importNewDeliveries) {
ctx.AddRange(wbGls);
ctx.UpdateRange(wbKgs.Where(k => duplicateKgNrs.Contains(k.KgNr)));
ctx.AddRange(wbKgs.Where(k => !duplicateKgNrs.Contains(k.KgNr)));
}
if (importDuplicateMembers) { if (importDuplicateMembers) {
ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr))); ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr))); ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr)));
@@ -383,9 +334,9 @@ namespace Elwig.Helpers.Export {
"Importieren erfolgreich", "Importieren erfolgreich",
MessageBoxButton.OK, MessageBoxImage.Information); MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception exc) { } catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\nEvtl. muss die Datenbank manuell auf dieses Gerät kopieren werden.\n\n" + exc.Message; var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(str, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} }
GC.Collect(); GC.Collect();
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
@@ -400,30 +351,26 @@ namespace Elwig.Helpers.Export {
) == MessageBoxResult.Yes; ) == MessageBoxResult.Yes;
} }
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) { public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<string> filters) {
return new ElwigExport { return new ElwigExport {
Members = (members, filters), Members = (members, filters)
WbKgs = (wbKgs, ["von exportierten Mitgliedern"]),
}.Export(filename); }.Export(filename);
} }
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) { public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<string> filters) {
return new ElwigExport { return new ElwigExport {
Members = (members, filters), Members = (members, filters),
AreaComs = (areaComs, ["von exportierten Mitgliedern"]), AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]),
}.Export(filename); }.Export(filename);
} }
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) { public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
return new ElwigExport { return new ElwigExport {
Deliveries = (deliveries, filters), Deliveries = (deliveries, filters)
WbKgs = (wbKgs, ["von exportierten Lieferungen"]),
}.Export(filename); }.Export(filename);
} }
public class ElwigExport { public class ElwigExport {
public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; } public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
public (IEnumerable<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; } public (IEnumerable<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; } public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
@@ -444,12 +391,6 @@ namespace Elwig.Helpers.Export {
["zwstid"] = App.ZwstId, ["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName, ["device"] = Environment.MachineName,
}; };
if (WbKgs != null) {
obj["wb_kgs"] = new JsonObject {
["count"] = WbKgs.Value.WbKgs.Count(),
["filters"] = new JsonArray(WbKgs.Value.Filters.Select(f => (JsonNode)f).ToArray()),
};
}
if (Members != null) if (Members != null)
obj["members"] = new JsonObject { obj["members"] = new JsonObject {
["count"] = Members.Value.Members.Count(), ["count"] = Members.Value.Members.Count(),
@@ -466,69 +407,34 @@ namespace Elwig.Helpers.Export {
["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count), ["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()), ["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()),
}; };
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts)); await writer.WriteAsync(obj.ToJsonString(JsonOpts));
} }
// TODO encrypt files // TODO encrypt files
if (WbKgs != null) {
var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var k in WbKgs.Value.WbKgs) {
await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.JsonOpts));
}
}
if (Members != null) { if (Members != null) {
var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var m in Members.Value.Members) { foreach (var m in Members.Value.Members) {
await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(m.ToJson().ToJsonString(JsonOpts));
} }
} }
if (AreaComs != null) { if (AreaComs != null) {
var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var c in AreaComs.Value.AreaComs) { foreach (var c in AreaComs.Value.AreaComs) {
await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(c.ToJson().ToJsonString(JsonOpts));
} }
} }
if (Deliveries != null) { if (Deliveries != null) {
var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var d in Deliveries.Value.Deliveries) { foreach (var d in Deliveries.Value.Deliveries) {
await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts));
} }
} }
} }
} }
public static JsonObject ToJson(this WbKg k) {
return new JsonObject {
["kgnr"] = k.KgNr,
["großlage"] = k.Gl?.Name,
};
}
public static (WbKg, WbGl?) ToWbKg(this JsonNode json, List<WbGl> gls) {
var grosslage = json["großlage"]?.AsValue().GetValue<string>();
WbGl? gl = null;
bool newGl = false;
if (grosslage != null) {
gl = gls.FirstOrDefault(g => g.Name == grosslage);
if (gl == null) {
newGl = true;
gl = new WbGl {
GlNr = (gls.Count == 0 ? 1 : gls.Max(g => g.GlNr)) + 1,
Name = grosslage,
};
gls[gl.GlNr] = gl;
}
}
return (new WbKg {
KgNr = json["kgnr"]!.AsValue().GetValue<int>(),
GlNr = gl?.GlNr,
}, newGl ? gl : null);
}
public static JsonObject ToJson(this Member m) { public static JsonObject ToJson(this Member m) {
return new JsonObject { return new JsonObject {
["mgnr"] = m.MgNr, ["mgnr"] = m.MgNr,
@@ -674,23 +580,22 @@ namespace Elwig.Helpers.Export {
}; };
} }
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) { public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
var kgnr = json["kgnr"]!.AsValue().GetValue<int>(); var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
var ried = json["ried"]?.AsValue().GetValue<string>(); var ried = json["ried"]?.AsValue().GetValue<string>();
WbRd? rd = null; WbRd? rd = null;
bool newRd = false; bool newRd = false;
if (ried != null) { if (ried != null) {
var rde = riede.GetValueOrDefault(kgnr, []); var rde = riede[kgnr] ?? throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten → Herkunftshierarchie)");
rd = rde.FirstOrDefault(r => r.Name == ried); rd = rde.FirstOrDefault(r => r.Name == ried);
if (rd == null) { if (rd == null) {
newRd = true; newRd = true;
rd = new WbRd { rd = new WbRd {
KgNr = kgnr, KgNr = kgnr,
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1, RdNr = (rde.Count == 0 ? 0 : rde.Max(r => r.RdNr)) + 1,
Name = ried, Name = ried,
}; };
rde.Add(rd); rde.Add(rd);
riede[rd.KgNr] = rde;
} }
} }
var createdAt = json["created_at"]?.AsValue().GetValue<string>(); var createdAt = json["created_at"]?.AsValue().GetValue<string>();
@@ -703,7 +608,7 @@ namespace Elwig.Helpers.Export {
Area = json["area"]!.AsValue().GetValue<int>(), Area = json["area"]!.AsValue().GetValue<int>(),
KgNr = kgnr, KgNr = kgnr,
GstNr = json["gstnr"]?.AsValue().GetValue<string>() ?? "-", GstNr = json["gstnr"]?.AsValue().GetValue<string>() ?? "-",
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(), RdNr = rd?.RdNr,
YearFrom = json["year_from"]?.AsValue().GetValue<int>(), YearFrom = json["year_from"]?.AsValue().GetValue<int>(),
YearTo = json["year_to"]?.AsValue().GetValue<int>(), YearTo = json["year_to"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(), Comment = json["comment"]?.AsValue().GetValue<string>(),
@@ -734,7 +639,7 @@ namespace Elwig.Helpers.Export {
["qualid"] = p.QualId, ["qualid"] = p.QualId,
["hkid"] = p.HkId, ["hkid"] = p.HkId,
["kgnr"] = p.KgNr, ["kgnr"] = p.KgNr,
["ried"] = p.Rd?.Name, ["rdnr"] = p.RdNr,
["net_weight"] = p.IsNetWeight, ["net_weight"] = p.IsNetWeight,
["manual_weighing"] = p.IsManualWeighing, ["manual_weighing"] = p.IsManualWeighing,
["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()), ["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
@@ -759,7 +664,7 @@ namespace Elwig.Helpers.Export {
}; };
} }
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>, List<WbRd>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) { public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids) {
var year = json["year"]!.AsValue().GetValue<int>(); var year = json["year"]!.AsValue().GetValue<int>();
var lsnr = json["lsnr"]!.AsValue().GetValue<string>(); var lsnr = json["lsnr"]!.AsValue().GetValue<string>();
var did = currentLsNrs.GetValueOrDefault(lsnr, -1); var did = currentLsNrs.GetValueOrDefault(lsnr, -1);
@@ -770,7 +675,6 @@ namespace Elwig.Helpers.Export {
currentLsNrs[lsnr] = did; currentLsNrs[lsnr] = did;
var createdAt = json["created_at"]?.AsValue().GetValue<string>(); var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>(); var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
var wbRde = new List<WbRd>();
return (new Delivery { return (new Delivery {
Year = year, Year = year,
DId = did, DId = did,
@@ -782,25 +686,7 @@ namespace Elwig.Helpers.Export {
MgNr = json["mgnr"]!.AsValue().GetValue<int>(), MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(), Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now, ImportedAt = DateTime.Now,
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => { }, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
var kgnr = p["kgnr"]!.AsValue().GetValue<int>();
var ried = p["ried"]?.AsValue().GetValue<string>();
WbRd? rd = null;
if (ried != null) {
var rde = riede.GetValueOrDefault(kgnr, []);
rd = rde.FirstOrDefault(r => r.Name == ried);
if (rd == null) {
rd = new WbRd {
KgNr = kgnr,
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
Name = ried,
};
rde.Add(rd);
riede[rd.KgNr] = rde;
wbRde.Add(rd);
}
}
return new DeliveryPart {
Year = year, Year = year,
DId = did, DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(), DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
@@ -812,7 +698,7 @@ namespace Elwig.Helpers.Export {
QualId = p["qualid"]!.AsValue().GetValue<string>(), QualId = p["qualid"]!.AsValue().GetValue<string>(),
HkId = p["hkid"]!.AsValue().GetValue<string>(), HkId = p["hkid"]!.AsValue().GetValue<string>(),
KgNr = p["kgnr"]?.AsValue().GetValue<int>(), KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
RdNr = rd?.RdNr ?? p["rdnr"]?.AsValue().GetValue<int>(), RdNr = p["rdnr"]?.AsValue().GetValue<int>(),
IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(), IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(), IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
Comment = p["comment"]?.AsValue().GetValue<string>(), Comment = p["comment"]?.AsValue().GetValue<string>(),
@@ -823,16 +709,14 @@ namespace Elwig.Helpers.Export {
Temperature = p["temperature"]?.AsValue().GetValue<double>(), Temperature = p["temperature"]?.AsValue().GetValue<double>(),
Acid = p["acid"]?.AsValue().GetValue<double>(), Acid = p["acid"]?.AsValue().GetValue<double>(),
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(), ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts), WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(), WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
};
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier { }).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
Year = year, Year = year,
DId = did, DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(), DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
ModId = m!.AsValue().GetValue<string>(), ModId = m!.AsValue().GetValue<string>(),
})).ToList(), })).ToList(),
wbRde,
createdAt == null || modifiedAt == null ? null : createdAt == null || modifiedAt == null ? null :
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None), (DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None))); DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));

View File

@@ -1,7 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression;
using System.IO.Hashing;
using System.Net.Http; using System.Net.Http;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@@ -97,16 +95,5 @@ namespace Elwig.Helpers {
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
progress.Report(100.0); progress.Report(100.0);
} }
public static async Task CheckIntegrity(this ZipArchive zip) {
var crc = new Crc32();
foreach (var entry in zip.Entries) {
crc.Reset();
using var stream = entry.Open();
await crc.AppendAsync(stream);
if (crc.GetCurrentHashAsUInt32() != entry.Crc32)
throw new InvalidDataException($"CRC-32 mismatch in '{entry.FullName}'");
}
}
} }
} }

View File

@@ -1,42 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.IO.Ports;
using System.Net.Sockets;
using Elwig.Dialogs; using Elwig.Dialogs;
using Elwig.Documents; using System.Text;
using Elwig.Helpers.Billing; using System.Numerics;
using Elwig.Models;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using LinqKit; using Elwig.Helpers.Billing;
using System.Runtime.InteropServices;
using System.Net.Http;
using System.Text.Json.Nodes;
using System.IO;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using MailKit.Security; using MailKit.Security;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using MimeKit;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Net.Sockets;
using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Collections;
using System.Text; using Elwig.Documents;
using System.Text.Json; using MimeKit;
using System.Text.Json.Nodes; using System.Windows.Input;
using System.Text.RegularExpressions; using LinqKit;
using System.Linq.Expressions;
using Elwig.Models;
using Microsoft.Win32;
using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup; using System.Windows.Markup;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public static partial class Utils { public static partial class Utils {
public static readonly Encoding UTF8 = new UTF8Encoding(false, true); public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
public static int CurrentYear => DateTime.Now.Year; public static int CurrentYear => DateTime.Now.Year;
public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0); public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
@@ -431,8 +430,6 @@ namespace Elwig.Helpers {
var client = new HttpClient() { var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
}; };
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"Elwig/{App.Version} ({App.Client.NameToken}, {App.BranchName}, {Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Clear();
if (accept != null) if (accept != null)
client.DefaultRequestHeaders.Accept.Add(new(accept)); client.DefaultRequestHeaders.Accept.Add(new(accept));
@@ -460,7 +457,7 @@ namespace Elwig.Helpers {
url = "https://sync.elwig.at/" + url[25..]; url = "https://sync.elwig.at/" + url[25..];
if (!url.EndsWith('/')) url += "/"; if (!url.EndsWith('/')) url += "/";
using var client = GetHttpClient(username, password, accept: "application/json"); using var client = GetHttpClient(username, password, accept: "application/json");
using var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read)); var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
content.Headers.ContentType = new("application/zip"); content.Headers.ContentType = new("application/zip");
using var res = await client.PutAsync(url + Path.GetFileName(zip), content); using var res = await client.PutAsync(url + Path.GetFileName(zip), content);
res.EnsureSuccessStatusCode(); res.EnsureSuccessStatusCode();
@@ -501,7 +498,10 @@ namespace Elwig.Helpers {
public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) { public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
if (App.Config.Smtp == null) if (App.Config.Smtp == null)
return false; return false;
return await Task.Run(async () => {
Mouse.OverrideCursor = Cursors.Wait;
var success = await Task.Run(async () => {
SmtpClient? client = null; SmtpClient? client = null;
try { try {
client = await GetSmtpClient(); client = await GetSmtpClient();
@@ -529,15 +529,18 @@ namespace Elwig.Helpers {
} }
return true; return true;
}); });
Mouse.OverrideCursor = null;
return success;
} }
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) { public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member, string, string)? emailData = null) {
if (mode == ExportMode.Print && !App.Config.Debug) { if (mode == ExportMode.Print && !App.Config.Debug) {
await doc.Generate(); await doc.Generate();
await doc.Print(); await doc.Print();
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) { } else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
await doc.Generate(); await doc.Generate();
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]); var success = await SendEmail(e.Item1, e.Item2, e.Item3, [doc]);
if (success) if (success)
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt", MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt",
MessageBoxButton.OK, MessageBoxImage.Information); MessageBoxButton.OK, MessageBoxImage.Information);
@@ -564,7 +567,9 @@ namespace Elwig.Helpers {
Log = "Application", Log = "Application",
Source = ".NET Runtime", Source = ".NET Runtime",
}; };
return [.. log.Entries.OfType<EventLogEntry>().Where(e => e.InstanceId == 1026).Where(e => e.Message.StartsWith("Application: Elwig.exe"))]; return log.Entries.Cast<EventLogEntry>()
.Where(e => e.Message.StartsWith("Application: Elwig.exe"))
.ToList();
} }
public static int GetEntityIdetifierForPk(params object?[] primaryKey) { public static int GetEntityIdetifierForPk(params object?[] primaryKey) {

View File

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

View File

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

View File

@@ -6,10 +6,10 @@ namespace Elwig.Models.Entities {
[Table("wb_gl"), PrimaryKey("GlNr")] [Table("wb_gl"), PrimaryKey("GlNr")]
public class WbGl { public class WbGl {
[Column("glnr")] [Column("glnr")]
public int GlNr { get; set; } public int GlNr { get; private set; }
[Column("name")] [Column("name")]
public string Name { get; set; } = null!; public string Name { get; private set; } = null!;
[InverseProperty(nameof(WbKg.Gl))] [InverseProperty(nameof(WbKg.Gl))]
public virtual ICollection<WbKg> Kgs { get; private set; } = null!; public virtual ICollection<WbKg> Kgs { get; private set; } = null!;

View File

@@ -15,7 +15,7 @@ namespace Elwig.Models.Entities {
public virtual AT_Kg AtKg { get; private set; } = null!; public virtual AT_Kg AtKg { get; private set; } = null!;
[ForeignKey("GlNr")] [ForeignKey("GlNr")]
public virtual WbGl? Gl { get; private set; } = null!; public virtual WbGl Gl { get; private set; } = null!;
[InverseProperty(nameof(WbRd.Kg))] [InverseProperty(nameof(WbRd.Kg))]
public virtual ICollection<WbRd> Rds { get; private set; } = null!; public virtual ICollection<WbRd> Rds { get; private set; } = null!;

View File

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

View File

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

View File

@@ -721,30 +721,19 @@ 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 {
var list = await query await ElwigData.Export(d.FileName, await query
.Select(p => p.Delivery) .Select(p => p.Delivery)
.Distinct() .Distinct()
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers) .Include(d => d.Parts)
.Include(d => d.Parts).ThenInclude(p => p.Rd) .ThenInclude(p => p.PartModifiers)
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
.AsSplitQuery() .AsSplitQuery()
.ToListAsync(); .ToListAsync(), filterNames);
var wbKgs = list
.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!)
.DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr)
.ToList();
await ElwigData.Export(d.FileName, list, wbKgs, filterNames);
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} }
@@ -760,23 +749,15 @@ namespace Elwig.Services {
var list = await query var list = await query
.Select(p => p.Delivery) .Select(p => p.Delivery)
.Distinct() .Distinct()
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers) .Include(d => d.Parts)
.Include(d => d.Parts).ThenInclude(p => p.Rd) .ThenInclude(p => p.PartModifiers)
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
.AsSplitQuery() .AsSplitQuery()
.ToListAsync(); .ToListAsync();
var wbKgs = list
.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!)
.DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr)
.ToList();
if (list.Count == 0) { if (list.Count == 0) {
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen", MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} else { } else {
await ElwigData.Export(path, list, wbKgs, filterNames); await ElwigData.Export(path, list, filterNames);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen", MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information); MessageBoxButton.OK, MessageBoxImage.Information);

View File

@@ -496,13 +496,11 @@ namespace Elwig.Services {
} else if (mode == ExportMode.Export) { } else if (mode == ExportMode.Export) {
var d = new SaveFileDialog() { var d = new SaveFileDialog() {
FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip", FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
DefaultExt = "elwig.zip", DefaultExt = ".elwig.zip",
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip", Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
Title = $"{MemberList.Name} speichern unter - Elwig", Title = $"{MemberList.Name} speichern unter - Elwig"
AddExtension = false,
}; };
if (d.ShowDialog() == true) { if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
try { try {
@@ -511,22 +509,13 @@ namespace Elwig.Services {
.Include(m => m.BillingAddress) .Include(m => m.BillingAddress)
.Include(m => m.TelephoneNumbers) .Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses) .Include(m => m.EmailAddresses)
.Include(m => m.DefaultWbKg!.Gl)
.AsSplitQuery() .AsSplitQuery()
.ToListAsync(); .ToListAsync();
var areaComs = await query var areaComs = await query
.SelectMany(m => m.AreaCommitments) .SelectMany(m => m.AreaCommitments)
.Include(c => c.Rd) .Include(c => c.Rd)
.Include(c => c.Kg.Gl)
.ToListAsync(); .ToListAsync();
var wbKgs = members await ElwigData.Export(d.FileName, members, areaComs, filterNames);
.Where(m => m.DefaultWbKg != null)
.Select(m => m.DefaultWbKg!)
.Union(areaComs.Select(c => c.Kg))
.Distinct()
.OrderBy(k => k.KgNr)
.ToList();
await ElwigData.Export(d.FileName, members, areaComs, wbKgs, filterNames);
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} }
@@ -544,27 +533,17 @@ namespace Elwig.Services {
.Include(m => m.BillingAddress) .Include(m => m.BillingAddress)
.Include(m => m.TelephoneNumbers) .Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses) .Include(m => m.EmailAddresses)
.Include(m => m.DefaultWbKg!.Gl)
.AsSplitQuery() .AsSplitQuery()
.ToListAsync(); .ToListAsync();
var areaComs = await query var areaComs = await query
.SelectMany(m => m.AreaCommitments) .SelectMany(m => m.AreaCommitments)
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
.Include(c => c.Rd) .Include(c => c.Rd)
.Include(c => c.Kg.Gl)
.ToListAsync(); .ToListAsync();
var wbKgs = members
.Where(m => m.DefaultWbKg != null)
.Select(m => m.DefaultWbKg!)
.Union(areaComs.Select(c => c.Kg))
.Distinct()
.OrderBy(k => k.KgNr)
.ToList();
if (members.Count == 0) { if (members.Count == 0) {
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen", MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} else { } else {
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames); await ElwigData.Export(path, members, areaComs, filterNames);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen", MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information); MessageBoxButton.OK, MessageBoxImage.Information);

View File

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

View File

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

View File

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

View File

@@ -265,18 +265,13 @@
</Style> </Style>
</DataGridTextColumn.CellStyle> </DataGridTextColumn.CellStyle>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="Sorte" Binding="{Binding FilteredVaributeString}" Width="60"> <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
<DataGridTextColumn.CellStyle> <DataGridTextColumn.CellStyle>
<Style> <Style>
<Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/> <Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/>
<Setter Property="TextBlock.TextAlignment" Value="Center"/> <Setter Property="TextBlock.TextAlignment" Value="Center"/>
</Style> </Style>
</DataGridTextColumn.CellStyle> </DataGridTextColumn.CellStyle>
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75"> <DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
<DataGridTextColumn.CellStyle> <DataGridTextColumn.CellStyle>
@@ -295,7 +290,6 @@
<DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/> <DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/>
<DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/> <DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/>
<DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/> <DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/>
<DataGridTextColumn Header="Kommentar" Binding="{Binding CommentsString}" Width="150"/>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>

View File

@@ -8,7 +8,6 @@ using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@@ -35,8 +34,6 @@ namespace Elwig.Windows {
private readonly Button[] WeighingButtons; private readonly Button[] WeighingButtons;
private List<WineQualLevel> WineQualityLevels = [];
public DeliveryAdminWindow(bool receipt = false) { public DeliveryAdminWindow(bool receipt = false) {
InitializeComponent(); InitializeComponent();
CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput)); CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
@@ -92,9 +89,6 @@ namespace Elwig.Windows {
foreach (var s in App.EventScales) { foreach (var s in App.EventScales) {
s.WeighingEvent += Scale_Weighing; s.WeighingEvent += Scale_Weighing;
} }
if (App.Client.IsMatzen) {
RequiredInputs = [.. RequiredInputs, ModifiersInput];
}
} else { } else {
WeighingManualButton.Visibility = Visibility.Hidden; WeighingManualButton.Visibility = Visibility.Hidden;
WeighingAButton.Visibility = Visibility.Hidden; WeighingAButton.Visibility = Visibility.Hidden;
@@ -284,7 +278,6 @@ namespace Elwig.Windows {
DateInput.IsReadOnly = false; DateInput.IsReadOnly = false;
TimeInput.IsReadOnly = false; TimeInput.IsReadOnly = false;
BranchInput.IsEnabled = true; BranchInput.IsEnabled = true;
GerebeltGewogenInput.IsEnabled = true;
if (IsCreating) ViewModel.Time = ""; if (IsCreating) ViewModel.Time = "";
OnSecondPassed(null, null); OnSecondPassed(null, null);
} }
@@ -294,7 +287,6 @@ namespace Elwig.Windows {
DateInput.IsReadOnly = true; DateInput.IsReadOnly = true;
TimeInput.IsReadOnly = true; TimeInput.IsReadOnly = true;
BranchInput.IsEnabled = false; BranchInput.IsEnabled = false;
GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode != WeighingMode.Net;
OnSecondPassed(null, null); OnSecondPassed(null, null);
} }
@@ -309,7 +301,7 @@ namespace Elwig.Windows {
} }
private void InitialDefaultInputs() { private void InitialDefaultInputs() {
if (App.Config.WeighingMode == WeighingMode.Net) { if (App.Client.HasNetWeighing(ViewModel.Branch)) {
GerebeltGewogenInput.IsEnabled = false; GerebeltGewogenInput.IsEnabled = false;
SetDefaultValue(GerebeltGewogenInput, true); SetDefaultValue(GerebeltGewogenInput, true);
} else { } else {
@@ -318,7 +310,7 @@ namespace Elwig.Windows {
UnsetDefaultValue(GerebeltGewogenInput); UnsetDefaultValue(GerebeltGewogenInput);
} }
if (App.Config.WeighingMode == WeighingMode.Box) { if (App.Client.HasBoxWeighing(ViewModel.Branch)) {
LesewagenInput.IsEnabled = false; LesewagenInput.IsEnabled = false;
SetDefaultValue(LesewagenInput, false); SetDefaultValue(LesewagenInput, false);
} else { } else {
@@ -326,7 +318,7 @@ namespace Elwig.Windows {
UnsetDefaultValue(LesewagenInput); UnsetDefaultValue(LesewagenInput);
} }
if (App.Config.WeighingMode != WeighingMode.Net) { if (!App.Client.HasNetWeighing(ViewModel.Branch)) {
HandPickedInput.IsThreeState = false; HandPickedInput.IsThreeState = false;
UnsetDefaultValue(HandPickedInput); UnsetDefaultValue(HandPickedInput);
} else { } else {
@@ -352,13 +344,13 @@ namespace Elwig.Windows {
ClearOriginalValues(); ClearOriginalValues();
ClearDefaultValues(); ClearDefaultValues();
ViewModel.IsNetWeight = App.Config.WeighingMode == WeighingMode.Net; ViewModel.IsNetWeight = App.Client.HasNetWeighing(ViewModel.Branch);
ViewModel.IsLesewagen = false; ViewModel.IsLesewagen = false;
ViewModel.IsHandPicked = App.Config.WeighingMode != WeighingMode.Net ? true : null; ViewModel.IsHandPicked = !App.Client.HasNetWeighing(ViewModel.Branch) ? true : null;
ViewModel.IsGebunden = null; ViewModel.IsGebunden = null;
InitialDefaultInputs(); InitialDefaultInputs();
//WineQualityLevelInput.IsEnabled = false; // disable wine quality level input in Übernahme WineQualityLevelInput.IsEnabled = false;
ValidateRequiredInputs(); ValidateRequiredInputs();
} }
@@ -486,8 +478,7 @@ namespace Elwig.Windows {
var cultList = await ctx.WineCultivations.OrderBy(a => a.Name).Cast<object>().ToListAsync(); var cultList = await ctx.WineCultivations.OrderBy(a => a.Name).Cast<object>().ToListAsync();
cultList.Insert(0, new NullItem("")); cultList.Insert(0, new NullItem(""));
ControlUtils.RenewItemsSource(CultivationInput, cultList, null, ControlUtils.RenewSourceDefault.First); ControlUtils.RenewItemsSource(CultivationInput, cultList, null, ControlUtils.RenewSourceDefault.First);
WineQualityLevels = await ctx.WineQualityLevels.ToListAsync(); ControlUtils.RenewItemsSource(WineQualityLevelInput, await ctx.WineQualityLevels.ToListAsync());
ControlUtils.RenewItemsSource(WineQualityLevelInput, WineQualityLevels);
ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers ControlUtils.RenewItemsSource(ModifiersInput, await ctx.Modifiers
.Where(m => m.Year == y && (!IsCreating || m.IsActive)) .Where(m => m.Year == y && (!IsCreating || m.IsActive))
.OrderBy(m => m.Ordering) .OrderBy(m => m.Ordering)
@@ -1148,7 +1139,6 @@ namespace Elwig.Windows {
DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked; DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked; TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked; BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked;
GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode == WeighingMode.Net || Menu_Settings_EnableFreeEditing.IsChecked;
} }
private void DisableWeighingButtons() { private void DisableWeighingButtons() {
@@ -1211,7 +1201,6 @@ namespace Elwig.Windows {
AttributeInput.SelectedIndex = 0; AttributeInput.SelectedIndex = 0;
CultivationInput.SelectedIndex = 0; CultivationInput.SelectedIndex = 0;
} }
UpdateWineQualityLevels();
} }
private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) { private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) {
@@ -1225,39 +1214,19 @@ namespace Elwig.Windows {
private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) { private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
if (WineVarietyInput.SelectedItem is WineVar s) if (WineVarietyInput.SelectedItem is WineVar s)
ViewModel.SortId = s.SortId; ViewModel.SortId = s.SortId;
UpdateWineQualityLevels();
if (!ViewModel.WineVar?.IsQuw ?? false) {
App.MainDispatcher.BeginInvoke(() => {
MessageBox.Show("Die eingegebene Sorte darf nicht als Qualitätswein\nübernommen werden!", "Kein Qualitätswein",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
}
}
private WineQualLevel GetWineQualityLevel(double kmw, string? maxQualId = null) {
return WineQualityLevels
.Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
.Where(q => maxQualId == null || q.QualId == "WEI" || q.QualId == maxQualId)
.OrderBy(q => q.MinKmw)
.Last();
} }
private void UpdateWineQualityLevels() { private void UpdateWineQualityLevels() {
using var ctx = new AppDbContext();
if (!GetInputValid(GradationKmwInput)) { if (!GetInputValid(GradationKmwInput)) {
UnsetDefaultValue(WineQualityLevelInput); UnsetDefaultValue(WineQualityLevelInput);
ComboBox_SelectionChanged(WineQualityLevelInput, null); ComboBox_SelectionChanged(WineQualityLevelInput, null);
WineQualityLevelInput.ItemsSource = WineQualityLevels; WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.QualId == "WEI").ToList();
WineQualityLevelInput.SelectedItem = null;
return; return;
} }
var kmw = (double)ViewModel.GradationKmw!; var kmw = (double)ViewModel.GradationKmw!;
var max = ViewModel.WineVar?.MaxQualId; WineQualityLevelInput.ItemsSource = ctx.WineQualityLevels.Where(q => q.MinKmw == null || q.MinKmw <= kmw).ToList();
var quw = ViewModel.WineVar?.IsQuw ?? true; var qual = ctx.GetWineQualityLevel(kmw).GetAwaiter().GetResult();
WineQualityLevelInput.ItemsSource = WineQualityLevels
.Where(q => q.MinKmw == null || q.MinKmw <= kmw)
.Where(q => quw || q.QualId == "WEI" || q.QualId == max)
.ToList();
var qual = GetWineQualityLevel(kmw, !quw ? max : null);
SetDefaultValue(WineQualityLevelInput, qual); SetDefaultValue(WineQualityLevelInput, qual);
if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) { if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) {
ControlUtils.SelectItem(WineQualityLevelInput, qual); ControlUtils.SelectItem(WineQualityLevelInput, qual);
@@ -1397,7 +1366,8 @@ namespace Elwig.Windows {
AbgewertetInput.IsChecked = false; AbgewertetInput.IsChecked = false;
return; return;
} }
var defQual = GetWineQualityLevel(ViewModel.GradationKmw!.Value, !(ViewModel.WineVar?.IsQuw ?? true) ? ViewModel.WineVar?.MaxQualId : null); using var ctx = new AppDbContext();
var defQual = ctx.GetWineQualityLevel(double.Parse(GradationKmwInput.Text)).GetAwaiter().GetResult();
AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual; AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
} }
@@ -1419,17 +1389,17 @@ namespace Elwig.Windows {
} }
private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) { private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) { if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked; HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
} }
if (!ViewModel.IsReceipt || App.Config.WeighingMode == WeighingMode.Net) { if (!ViewModel.IsReceipt || App.Client.HasNetWeighing(ViewModel.Branch)) {
GerebeltGewogenInput.IsChecked ??= false; GerebeltGewogenInput.IsChecked ??= false;
} }
CheckBox_Changed(sender, evt); CheckBox_Changed(sender, evt);
} }
private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) { private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) { if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked; GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
} }
CheckBox_Changed(sender, evt); CheckBox_Changed(sender, evt);

View File

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

View File

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

View File

@@ -31,17 +31,6 @@
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator/> <Separator/>
<MenuItem Header="Datenbank sichern..." Click="Menu_Database_Backup_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEA35;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Datenbank wiederherstellen..." Click="Menu_Database_Restore_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE777;"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click"> <MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE756;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE756;"/>
@@ -52,17 +41,6 @@
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xED25;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xED25;"/>
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator/>
<MenuItem x:Name="Menu_Database_Upload" Header="Datenbank hochladen..." Click="Menu_Database_Upload_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE898;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Database_Download" Header="Datenbank herunterladen..." Click="Menu_Database_Download_Click" IsEnabled="False">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE896;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="Waage"> <MenuItem Header="Waage">
<MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click"> <MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click">
@@ -72,7 +50,7 @@
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem x:Name="HelpMenu" Header="Hilfe"> <MenuItem x:Name="HelpMenu" Header="Hilfe">
<MenuItem Header="Über" Click="Menu_Help_About_Click"> <MenuItem Header="Über">
<MenuItem.Icon> <MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/> <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/>
</MenuItem.Icon> </MenuItem.Icon>

View File

@@ -31,8 +31,6 @@ namespace Elwig.Windows {
Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null; Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null;
DownloadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden; DownloadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
UploadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden; UploadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
Menu_Database_Upload.IsEnabled = App.Config.SyncUrl != null;
Menu_Database_Download.IsEnabled = App.Config.SyncUrl != null;
} }
private void Window_Loaded(object sender, RoutedEventArgs evt) { private void Window_Loaded(object sender, RoutedEventArgs evt) {
@@ -59,11 +57,6 @@ namespace Elwig.Windows {
} }
} }
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
var w = new AboutWindow();
w.Show();
}
private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) { private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) {
await App.CheckForUpdates(true); await App.CheckForUpdates(true);
} }
@@ -151,50 +144,6 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
private async void Menu_Database_Backup_Click(object sender, RoutedEventArgs evt) {
try {
var d = new SaveFileDialog() {
Title = "Datenbank sichern - Elwig",
FileName = $"database_{Utils.Today:yyyy-MM-dd}.sql.zip",
DefaultExt = "sql.zip",
Filter = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip",
AddExtension = false,
};
if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".sql.zip")) d.FileName += ".sql.zip";
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await Database.ExportSql(d.FileName, true);
});
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private async void Menu_Database_Restore_Click(object sender, RoutedEventArgs evt) {
try {
var d = new OpenFileDialog() {
Title = "Datenbank wiederherstellen - Elwig",
DefaultExt = "sql.zip",
Filter = "SQLite-Datenbank (*.sqlite3, *.sqlite3.zip, *.sql, *.sql.zip)|*.sqlite3;*.sqlite3.zip;*.sql;*.sql.zip",
};
if (d.ShowDialog() == true) {
var res = MessageBox.Show("Soll die Datenbank wirklich unwiederruflich durch die wiederhergestellte Version ersetzt werden?", "Datenbank wiederherstellen",
MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (res != MessageBoxResult.OK)
return;
Mouse.OverrideCursor = Cursors.Wait;
await App.ReplaceDatabase(d.FileName);
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private async void DownloadButton_Click(object sender, RoutedEventArgs evt) { private async void DownloadButton_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null) if (App.Config.SyncUrl == null)
return; return;
@@ -207,7 +156,7 @@ namespace Elwig.Windows {
Name = f!["name"]!.AsValue().GetValue<string>(), Name = f!["name"]!.AsValue().GetValue<string>(),
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null, Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(), ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(), Device = f!["meta"]?["device"]!.AsValue().GetValue<string>(),
Url = f!["url"]!.AsValue().GetValue<string>(), Url = f!["url"]!.AsValue().GetValue<string>(),
Size = f!["size"]!.AsValue().GetValue<long>(), Size = f!["size"]!.AsValue().GetValue<long>(),
}) })
@@ -229,11 +178,11 @@ namespace Elwig.Windows {
} }
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches); await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
} catch (HttpRequestException exc) { } catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) { } catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} }
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
@@ -251,25 +200,16 @@ namespace Elwig.Windows {
.Where(d => d.Year == Utils.CurrentLastSeason && d.ZwstId == App.ZwstId) .Where(d => d.Year == Utils.CurrentLastSeason && d.ZwstId == App.ZwstId)
.Include(d => d.Parts) .Include(d => d.Parts)
.ThenInclude(p => p.PartModifiers) .ThenInclude(p => p.PartModifiers)
.Include(d => d.Parts)
.ThenInclude(p => p.Kg)
.ThenInclude(k => k!.Gl)
.OrderBy(d => d.DateString) .OrderBy(d => d.DateString)
.ThenBy(d => d.TimeString) .ThenBy(d => d.TimeString)
.ThenBy(d => d.LsNr) .ThenBy(d => d.LsNr)
.AsSplitQuery() .AsSplitQuery()
.ToListAsync(); .ToListAsync();
var wbKgs = deliveries
.SelectMany(d => d.Parts)
.Where(p => p.Kg != null)
.Select(p => p.Kg!)
.DistinctBy(k => k.KgNr)
.ToList();
if (deliveries.Count == 0) { if (deliveries.Count == 0) {
MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen", MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Error); MessageBoxButton.OK, MessageBoxImage.Error);
} else { } else {
await ElwigData.Export(path, deliveries, wbKgs, [$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]); await ElwigData.Export(path, deliveries, [$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen", MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Information); MessageBoxButton.OK, MessageBoxImage.Information);
@@ -285,89 +225,6 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
private async void Menu_Database_Download_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
var files = data
.Select(f => new {
Name = f!["name"]!.AsValue().GetValue<string>(),
Timestamp = f!["modified"] != null && DateTime.TryParseExact(f!["modified"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
Url = f!["url"]!.AsValue().GetValue<string>(),
Size = f!["size"]!.AsValue().GetValue<long>(),
})
.Where(f => f.Name.StartsWith("database.") && f.Name.EndsWith(".zip"))
.OrderBy(f => f.Size)
.ToList();
if (files.Count == 0) {
MessageBox.Show("Die Datenbank wurde noch nicht vom Hauptgerät hochgeladen!", "Datenbank herunterladen",
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
var file = files[0];
var res = MessageBox.Show($"Es wurde eine komprimierte Datenbank (ca. {file.Size / 1024 / 1024} MB) vom {file.Timestamp:dd.MM.yyyy, HH:mm} gefunden.\n\nWollen Sie wirklich die aktuelle Datenbank unwiederruflich\nlöschen und durch die gefundene ersetzen?\n\nDas kann zu Datenverlust führen!", "Datenbank herunterladen",
MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (res != MessageBoxResult.OK)
return;
var filename = Path.Combine(App.TempPath, file.Name);
using (var client = Utils.GetHttpClient(App.Config.SyncUsername, App.Config.SyncPassword)) {
using var stream = new FileStream(filename, FileMode.Create);
await client.DownloadAsync(file.Url, stream);
}
res = MessageBox.Show("Die Datenbank wurde erfolgreich heruntergeladen!\n\nSoll die Datenbank wirklich unwiederruflich ersetzt werden?\n\nWenn Sie unsicher sind sprechen Sie sich mit dem Benutzer des Hauptgerätes ab!", "Datenbank herunterladen",
MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (res != MessageBoxResult.OK)
return;
await App.MainDispatcher.BeginInvoke(async () => {
await App.ReplaceDatabase(filename);
});
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Datenbank herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Datenbank herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Datenbank herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
private async void Menu_Database_Upload_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
var res = MessageBox.Show("Sind Sie wirklich sicher, dass Sie die Datenbank dieses\nGerätes hochladen möchten? Das sollte nur vom Hauptgerät aus passieren!", "Datenbank hochladen",
MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (res != MessageBoxResult.OK)
return;
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var path = Path.Combine(App.TempPath, "database.sql.zip");
await Database.ExportSql(path, true);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen der gesamten Datenbank erfolgreich!", "Datenbank hochladen",
MessageBoxButton.OK, MessageBoxImage.Information);
} catch (HttpRequestException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Datenbank hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Datenbank hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Datenbank hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
private void MemberAdminButton_Click(object sender, RoutedEventArgs evt) { private void MemberAdminButton_Click(object sender, RoutedEventArgs evt) {
var w = new MemberAdminWindow(); var w = new MemberAdminWindow();
w.Show(); w.Show();

View File

@@ -38,7 +38,7 @@ namespace Elwig.Windows {
IList<DbColumn> header; IList<DbColumn> header;
using (var cnx = await AppDbContext.ConnectAsync()) { using (var cnx = await AppDbContext.ConnectAsync()) {
using var cmd = cnx.CreateCommand(); var cmd = cnx.CreateCommand();
cmd.CommandText = sqlQuery; cmd.CommandText = sqlQuery;
using var reader = await cmd.ExecuteReaderAsync(); using var reader = await cmd.ExecuteReaderAsync();
header = await reader.GetColumnSchemaAsync(); header = await reader.GetColumnSchemaAsync();

View File

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

View File

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

View File

@@ -22,9 +22,9 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Appium.WebDriver" Version="4.4.5" /> <PackageReference Include="Appium.WebDriver" Version="4.4.5" />
<PackageReference Include="NReco.PdfRenderer" Version="1.6.0" /> <PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
<PackageReference Include="NUnit" Version="4.4.0" /> <PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" /> <PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.10.0"> <PackageReference Include="NUnit.Analyzers" Version="4.9.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

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