Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
5ba74c8368 | |||
3c9b3c2db1 | |||
98f8907817 | |||
44dcc5e19f | |||
d7012ebfa1 | |||
a9b5317e79 | |||
f02598760f | |||
4b8cd2a0d7 | |||
104798d4f2 | |||
4653a4f129 | |||
07f9a0f522 | |||
7e21d9e2e4 | |||
4e2eca295d | |||
1f165055c1 | |||
7b953fa73e | |||
d3157e4d48 | |||
73fe4531cc | |||
b6c03892b1 | |||
91950ad9fd | |||
77c3f388e7 | |||
6f3e1a8905 | |||
466c8a322c | |||
ab7c7404e2 |
80
CHANGELOG.md
80
CHANGELOG.md
@@ -2,6 +2,86 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
[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}
|
||||
---------------------------------------------
|
||||
|
@@ -1,20 +1,21 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.IO;
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Helpers.Weighing;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Threading;
|
||||
using System.Reflection;
|
||||
using Elwig.Helpers.Printing;
|
||||
using Elwig.Windows;
|
||||
using Elwig.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Helpers.Billing;
|
||||
using Elwig.Helpers.Export;
|
||||
using Elwig.Helpers.Printing;
|
||||
using Elwig.Helpers.Weighing;
|
||||
using Elwig.Models.Entities;
|
||||
using Elwig.Windows;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Elwig {
|
||||
public partial class App : Application {
|
||||
@@ -114,6 +115,16 @@ namespace Elwig {
|
||||
BranchNum = branches.Count;
|
||||
}
|
||||
|
||||
if (Config.WeighingMode == null) {
|
||||
if (App.Client.IsMatzen || App.Client.IsWolkersdorf) {
|
||||
Config.WeighingMode = WeighingMode.Net;
|
||||
} else if (App.Client.IsHaugsdorf || App.Client.IsSitzendorf) {
|
||||
Config.WeighingMode = WeighingMode.Box;
|
||||
} else if (App.Client.IsBaden || App.Client.IsGrInzersdorf) {
|
||||
Config.WeighingMode = WeighingMode.Gross;
|
||||
}
|
||||
}
|
||||
|
||||
Utils.RunBackground("Temp File Cleanup", () => {
|
||||
Utils.CleanupTempFiles();
|
||||
return Task.CompletedTask;
|
||||
@@ -239,6 +250,19 @@ namespace Elwig {
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ReplaceDatabase(string filename) {
|
||||
try {
|
||||
await Task.Run(async () => {
|
||||
await Database.Import(filename);
|
||||
});
|
||||
MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
ForceShutdown = true;
|
||||
Current.Shutdown();
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static T FocusWindow<T>(Func<T> constructor, Predicate<T>? selector = null) where T : Window {
|
||||
foreach (Window w in CurrentApp.Windows) {
|
||||
if (w is T t && (selector == null || selector(t))) {
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
|
||||
<Version>1.0.0.0</Version>
|
||||
<Version>1.0.0.5</Version>
|
||||
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
@@ -25,17 +25,19 @@
|
||||
<PackageReference Include="LinqKit" Version="1.3.8" />
|
||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3351.48" />
|
||||
<PackageReference Include="NJsonSchema" Version="11.3.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3485.44" />
|
||||
<PackageReference Include="NJsonSchema" Version="11.4.0" />
|
||||
<PackageReference Include="PdfiumViewer" Version="2.13.0" />
|
||||
<PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
|
||||
<PackageReference Include="RazorLight" Version="2.3.1" />
|
||||
<PackageReference Include="ScottPlot.WPF" Version="5.0.55" />
|
||||
<PackageReference Include="System.IO.Ports" Version="9.0.7" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.7" />
|
||||
<PackageReference Include="ScottPlot.WPF" Version="5.0.56" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="9.0.9" />
|
||||
<PackageReference Include="System.IO.Ports" Version="9.0.9" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -19,14 +19,6 @@ namespace Elwig.Helpers {
|
||||
public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
|
||||
public bool IsGrInzersdorf => IsWeinland;
|
||||
|
||||
public bool HasNetWeighing(string? zwstId) => IsMatzen || (IsWinzerkeller && zwstId == "W");
|
||||
public bool HasNetWeighing(Branch? b) => HasNetWeighing(b?.ZwstId);
|
||||
public bool HasNetWeighing() => HasNetWeighing(App.ZwstId);
|
||||
|
||||
public bool HasBoxWeighing(string? zwstId) => IsWinzerkeller && (zwstId != "W");
|
||||
public bool HasBoxWeighing(Branch? b) => HasBoxWeighing(b?.ZwstId);
|
||||
public bool HasBoxWeighing() => HasBoxWeighing(App.ZwstId);
|
||||
|
||||
public string NameToken;
|
||||
public string NameShort;
|
||||
public string Name;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -5,6 +6,8 @@ using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
|
||||
public enum WeighingMode { Gross, Net, Box }
|
||||
|
||||
public record struct ScaleConfig {
|
||||
public string Id;
|
||||
public string? Type;
|
||||
@@ -41,6 +44,7 @@ namespace Elwig.Helpers {
|
||||
public string DatabaseFile = App.DataPath + "database.sqlite3";
|
||||
public string? DatabaseLog = null;
|
||||
public string? Branch = null;
|
||||
public WeighingMode? WeighingMode;
|
||||
public string? UpdateUrl = null;
|
||||
public bool UpdateAuto = false;
|
||||
public string? SyncUrl = null;
|
||||
@@ -74,6 +78,8 @@ namespace Elwig.Helpers {
|
||||
DatabaseLog = log != null ? Path.Combine(Path.GetDirectoryName(FileName) ?? App.DataPath, log) : null;
|
||||
Branch = config["general:branch"];
|
||||
Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
|
||||
var weighing = config["general:weighing"];
|
||||
WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null;
|
||||
UpdateUrl = config["update:url"];
|
||||
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
|
||||
SyncUrl = config["sync:url"];
|
||||
|
188
Elwig/Helpers/Export/Database.cs
Normal file
188
Elwig/Helpers/Export/Database.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public static class Database {
|
||||
|
||||
public static async Task ExportSqlite(string filename, bool zipFile) {
|
||||
if (zipFile) {
|
||||
File.Delete(filename);
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
|
||||
await zip.CheckIntegrity();
|
||||
var 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 entry = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
|
||||
using var stream = entry.Open();
|
||||
using var writer = new StreamWriter(stream, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -41,6 +41,7 @@ namespace Elwig.Helpers.Export {
|
||||
Dictionary<string, int> currentLsNrs;
|
||||
Dictionary<int, List<WbRd>> currentWbRde;
|
||||
Dictionary<int, AT_Kg> kgs;
|
||||
List<WbGl> currentWbGls;
|
||||
|
||||
using (var ctx = new AppDbContext()) {
|
||||
branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId);
|
||||
@@ -52,6 +53,7 @@ namespace Elwig.Helpers.Export {
|
||||
currentWbRde = await ctx.WbRde
|
||||
.GroupBy(r => r.KgNr)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.ToList());
|
||||
currentWbGls = await ctx.WbGls.ToListAsync();
|
||||
kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr);
|
||||
}
|
||||
|
||||
@@ -62,6 +64,8 @@ namespace Elwig.Helpers.Export {
|
||||
List<MemberEmailAddr> EmailAddresses,
|
||||
List<AreaCom> AreaCommitments,
|
||||
List<WbRd> Riede,
|
||||
List<WbKg> WbKgs,
|
||||
List<WbGl> WbGls,
|
||||
List<Delivery> Deliveries,
|
||||
List<DeliveryPart> DeliveryParts,
|
||||
List<DeliveryPartModifier> Modifiers,
|
||||
@@ -75,6 +79,7 @@ namespace Elwig.Helpers.Export {
|
||||
foreach (var filename in filenames) {
|
||||
// TODO read encrypted files
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
|
||||
await zip.CheckIntegrity();
|
||||
|
||||
var version = zip.GetEntry("version");
|
||||
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
|
||||
@@ -96,13 +101,28 @@ namespace Elwig.Helpers.Export {
|
||||
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
|
||||
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
|
||||
|
||||
data.Add(new([], [], [], [], [], [], [], new([], [], new() {
|
||||
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
|
||||
["member"] = [],
|
||||
["area_commitment"] = [],
|
||||
["delivery"] = [],
|
||||
})));
|
||||
var r = data[^1];
|
||||
|
||||
var wbKgsJson = zip.GetEntry("wb_kgs.json");
|
||||
if (wbKgsJson != null) {
|
||||
using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (k, g) = obj.ToWbKg(currentWbGls);
|
||||
r.WbKgs.Add(k);
|
||||
if (g != null) {
|
||||
currentWbGls[g.GlNr] = g;
|
||||
r.WbGls.Add(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var membersJson = zip.GetEntry("members.json");
|
||||
if (membersJson != null) {
|
||||
using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
|
||||
@@ -125,10 +145,9 @@ namespace Elwig.Helpers.Export {
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (areaCom, wbrd, timestamps) = obj.ToAreaCom(kgs, currentWbRde);
|
||||
var (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
|
||||
r.AreaCommitments.Add(areaCom);
|
||||
if (wbrd != null) {
|
||||
currentWbRde[wbrd.KgNr].Add(wbrd);
|
||||
r.Riede.Add(wbrd);
|
||||
}
|
||||
if (timestamps.HasValue)
|
||||
@@ -142,10 +161,11 @@ namespace Elwig.Helpers.Export {
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (d, parts, mods, timestamps) = obj.ToDelivery(currentLsNrs, currentDids);
|
||||
var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
|
||||
r.Deliveries.Add(d);
|
||||
r.DeliveryParts.AddRange(parts);
|
||||
r.Modifiers.AddRange(mods);
|
||||
r.Riede.AddRange(rde);
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
}
|
||||
@@ -156,12 +176,18 @@ namespace Elwig.Helpers.Export {
|
||||
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int Imported, int NotImported, string Filters)>();
|
||||
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
|
||||
|
||||
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
|
||||
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
|
||||
var branch = branches[meta.ZwstId];
|
||||
var device = meta.Device;
|
||||
|
||||
using var ctx = new AppDbContext();
|
||||
|
||||
var kgnrs = wbKgs.Select(k => k.KgNr).ToList();
|
||||
var duplicateKgNrs = await ctx.WbKgs
|
||||
.Where(k => kgnrs.Contains(k.KgNr))
|
||||
.Select(k => k.KgNr)
|
||||
.ToListAsync();
|
||||
|
||||
var mgnrs = members.Select(m => m.MgNr).ToList();
|
||||
var duplicateMgNrs = await ctx.Members
|
||||
.Where(m => mgnrs.Contains(m.MgNr))
|
||||
@@ -215,6 +241,12 @@ namespace Elwig.Helpers.Export {
|
||||
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
|
||||
}
|
||||
|
||||
if (importDuplicateMembers || importNewMembers || importDuplicateDeliveries || importNewDeliveries) {
|
||||
ctx.AddRange(wbGls);
|
||||
ctx.UpdateRange(wbKgs.Where(k => duplicateKgNrs.Contains(k.KgNr)));
|
||||
ctx.AddRange(wbKgs.Where(k => !duplicateKgNrs.Contains(k.KgNr)));
|
||||
}
|
||||
|
||||
if (importDuplicateMembers) {
|
||||
ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
|
||||
ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr)));
|
||||
@@ -334,9 +366,9 @@ namespace Elwig.Helpers.Export {
|
||||
"Importieren erfolgreich",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} catch (Exception exc) {
|
||||
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
|
||||
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;
|
||||
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
|
||||
MessageBox.Show(str, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
@@ -351,26 +383,30 @@ namespace Elwig.Helpers.Export {
|
||||
) == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
return new ElwigExport {
|
||||
Members = (members, filters)
|
||||
Members = (members, filters),
|
||||
WbKgs = (wbKgs, ["von exportierten Mitgliedern"]),
|
||||
}.Export(filename);
|
||||
}
|
||||
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
return new ElwigExport {
|
||||
Members = (members, filters),
|
||||
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
|
||||
WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]),
|
||||
}.Export(filename);
|
||||
}
|
||||
|
||||
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
return new ElwigExport {
|
||||
Deliveries = (deliveries, filters)
|
||||
Deliveries = (deliveries, filters),
|
||||
WbKgs = (wbKgs, ["von exportierten Lieferungen"]),
|
||||
}.Export(filename);
|
||||
}
|
||||
|
||||
public class ElwigExport {
|
||||
public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
|
||||
public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
|
||||
public (IEnumerable<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
|
||||
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
|
||||
@@ -391,6 +427,12 @@ namespace Elwig.Helpers.Export {
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
};
|
||||
if (WbKgs != null) {
|
||||
obj["wb_kgs"] = new JsonObject {
|
||||
["count"] = WbKgs.Value.WbKgs.Count(),
|
||||
["filters"] = new JsonArray(WbKgs.Value.Filters.Select(f => (JsonNode)f).ToArray()),
|
||||
};
|
||||
}
|
||||
if (Members != null)
|
||||
obj["members"] = new JsonObject {
|
||||
["count"] = Members.Value.Members.Count(),
|
||||
@@ -411,6 +453,13 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
|
||||
// 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(JsonOpts));
|
||||
}
|
||||
}
|
||||
if (Members != null) {
|
||||
var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
|
||||
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
|
||||
@@ -435,6 +484,34 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonObject ToJson(this WbKg k) {
|
||||
return new JsonObject {
|
||||
["kgnr"] = k.KgNr,
|
||||
["großlage"] = k.Gl?.Name,
|
||||
};
|
||||
}
|
||||
|
||||
public static (WbKg, WbGl?) ToWbKg(this JsonNode json, List<WbGl> gls) {
|
||||
var grosslage = json["großlage"]?.AsValue().GetValue<string>();
|
||||
WbGl? gl = null;
|
||||
bool newGl = false;
|
||||
if (grosslage != null) {
|
||||
gl = gls.FirstOrDefault(g => g.Name == grosslage);
|
||||
if (gl == null) {
|
||||
newGl = true;
|
||||
gl = new WbGl {
|
||||
GlNr = (gls.Count == 0 ? 1 : gls.Max(g => g.GlNr)) + 1,
|
||||
Name = grosslage,
|
||||
};
|
||||
gls[gl.GlNr] = gl;
|
||||
}
|
||||
}
|
||||
return (new WbKg {
|
||||
KgNr = json["kgnr"]!.AsValue().GetValue<int>(),
|
||||
GlNr = gl?.GlNr,
|
||||
}, newGl ? gl : null);
|
||||
}
|
||||
|
||||
public static JsonObject ToJson(this Member m) {
|
||||
return new JsonObject {
|
||||
["mgnr"] = m.MgNr,
|
||||
@@ -580,22 +657,23 @@ namespace Elwig.Helpers.Export {
|
||||
};
|
||||
}
|
||||
|
||||
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
|
||||
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) {
|
||||
var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
|
||||
var ried = json["ried"]?.AsValue().GetValue<string>();
|
||||
WbRd? rd = null;
|
||||
bool newRd = false;
|
||||
if (ried != null) {
|
||||
var rde = riede[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)");
|
||||
var rde = riede.GetValueOrDefault(kgnr, []);
|
||||
rd = rde.FirstOrDefault(r => r.Name == ried);
|
||||
if (rd == null) {
|
||||
newRd = true;
|
||||
rd = new WbRd {
|
||||
KgNr = kgnr,
|
||||
RdNr = (rde.Count == 0 ? 0 : rde.Max(r => r.RdNr)) + 1,
|
||||
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
|
||||
Name = ried,
|
||||
};
|
||||
rde.Add(rd);
|
||||
riede[rd.KgNr] = rde;
|
||||
}
|
||||
}
|
||||
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
|
||||
@@ -608,7 +686,7 @@ namespace Elwig.Helpers.Export {
|
||||
Area = json["area"]!.AsValue().GetValue<int>(),
|
||||
KgNr = kgnr,
|
||||
GstNr = json["gstnr"]?.AsValue().GetValue<string>() ?? "-",
|
||||
RdNr = rd?.RdNr,
|
||||
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(),
|
||||
YearFrom = json["year_from"]?.AsValue().GetValue<int>(),
|
||||
YearTo = json["year_to"]?.AsValue().GetValue<int>(),
|
||||
Comment = json["comment"]?.AsValue().GetValue<string>(),
|
||||
@@ -639,7 +717,7 @@ namespace Elwig.Helpers.Export {
|
||||
["qualid"] = p.QualId,
|
||||
["hkid"] = p.HkId,
|
||||
["kgnr"] = p.KgNr,
|
||||
["rdnr"] = p.RdNr,
|
||||
["ried"] = p.Rd?.Name,
|
||||
["net_weight"] = p.IsNetWeight,
|
||||
["manual_weighing"] = p.IsManualWeighing,
|
||||
["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
|
||||
@@ -664,7 +742,7 @@ namespace Elwig.Helpers.Export {
|
||||
};
|
||||
}
|
||||
|
||||
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids) {
|
||||
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) {
|
||||
var year = json["year"]!.AsValue().GetValue<int>();
|
||||
var lsnr = json["lsnr"]!.AsValue().GetValue<string>();
|
||||
var did = currentLsNrs.GetValueOrDefault(lsnr, -1);
|
||||
@@ -675,6 +753,7 @@ namespace Elwig.Helpers.Export {
|
||||
currentLsNrs[lsnr] = did;
|
||||
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
|
||||
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
|
||||
var wbRde = new List<WbRd>();
|
||||
return (new Delivery {
|
||||
Year = year,
|
||||
DId = did,
|
||||
@@ -686,7 +765,25 @@ namespace Elwig.Helpers.Export {
|
||||
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
|
||||
Comment = json["comment"]?.AsValue().GetValue<string>(),
|
||||
ImportedAt = DateTime.Now,
|
||||
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
|
||||
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => {
|
||||
var kgnr = p["kgnr"]!.AsValue().GetValue<int>();
|
||||
var ried = p["ried"]?.AsValue().GetValue<string>();
|
||||
WbRd? rd = null;
|
||||
if (ried != null) {
|
||||
var rde = riede.GetValueOrDefault(kgnr, []);
|
||||
rd = rde.FirstOrDefault(r => r.Name == ried);
|
||||
if (rd == null) {
|
||||
rd = new WbRd {
|
||||
KgNr = kgnr,
|
||||
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
|
||||
Name = ried,
|
||||
};
|
||||
rde.Add(rd);
|
||||
riede[rd.KgNr] = rde;
|
||||
wbRde.Add(rd);
|
||||
}
|
||||
}
|
||||
return new DeliveryPart {
|
||||
Year = year,
|
||||
DId = did,
|
||||
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
|
||||
@@ -698,7 +795,7 @@ namespace Elwig.Helpers.Export {
|
||||
QualId = p["qualid"]!.AsValue().GetValue<string>(),
|
||||
HkId = p["hkid"]!.AsValue().GetValue<string>(),
|
||||
KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
|
||||
RdNr = p["rdnr"]?.AsValue().GetValue<int>(),
|
||||
RdNr = rd?.RdNr ?? p["rdnr"]?.AsValue().GetValue<int>(),
|
||||
IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
|
||||
IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
|
||||
Comment = p["comment"]?.AsValue().GetValue<string>(),
|
||||
@@ -711,12 +808,14 @@ namespace Elwig.Helpers.Export {
|
||||
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
|
||||
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
|
||||
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
|
||||
};
|
||||
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
|
||||
Year = year,
|
||||
DId = did,
|
||||
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
|
||||
ModId = m!.AsValue().GetValue<string>(),
|
||||
})).ToList(),
|
||||
wbRde,
|
||||
createdAt == null || modifiedAt == null ? null :
|
||||
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
|
||||
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
|
||||
|
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Hashing;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
@@ -95,5 +97,16 @@ namespace Elwig.Helpers {
|
||||
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
|
||||
progress.Report(100.0);
|
||||
}
|
||||
|
||||
public static async Task CheckIntegrity(this ZipArchive zip) {
|
||||
var crc = new Crc32();
|
||||
foreach (var entry in zip.Entries) {
|
||||
crc.Reset();
|
||||
using var stream = entry.Open();
|
||||
await crc.AppendAsync(stream);
|
||||
if (crc.GetCurrentHashAsUInt32() != entry.Crc32)
|
||||
throw new InvalidDataException($"CRC-32 mismatch in '{entry.FullName}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -457,7 +457,7 @@ namespace Elwig.Helpers {
|
||||
url = "https://sync.elwig.at/" + url[25..];
|
||||
if (!url.EndsWith('/')) url += "/";
|
||||
using var client = GetHttpClient(username, password, accept: "application/json");
|
||||
var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
|
||||
using var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
|
||||
content.Headers.ContentType = new("application/zip");
|
||||
using var res = await client.PutAsync(url + Path.GetFileName(zip), content);
|
||||
res.EnsureSuccessStatusCode();
|
||||
@@ -498,10 +498,7 @@ namespace Elwig.Helpers {
|
||||
public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
|
||||
if (App.Config.Smtp == null)
|
||||
return false;
|
||||
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
var success = await Task.Run(async () => {
|
||||
return await Task.Run(async () => {
|
||||
SmtpClient? client = null;
|
||||
try {
|
||||
client = await GetSmtpClient();
|
||||
@@ -529,18 +526,15 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Mouse.OverrideCursor = null;
|
||||
return success;
|
||||
}
|
||||
|
||||
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member, string, string)? emailData = null) {
|
||||
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) {
|
||||
if (mode == ExportMode.Print && !App.Config.Debug) {
|
||||
await doc.Generate();
|
||||
await doc.Print();
|
||||
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
|
||||
await doc.Generate();
|
||||
var success = await SendEmail(e.Item1, e.Item2, e.Item3, [doc]);
|
||||
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
|
||||
if (success)
|
||||
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
@@ -567,9 +561,7 @@ namespace Elwig.Helpers {
|
||||
Log = "Application",
|
||||
Source = ".NET Runtime",
|
||||
};
|
||||
return log.Entries.Cast<EventLogEntry>()
|
||||
.Where(e => e.Message.StartsWith("Application: Elwig.exe"))
|
||||
.ToList();
|
||||
return [.. log.Entries.OfType<EventLogEntry>().Where(e => e.InstanceId == 1026).Where(e => e.Message.StartsWith("Application: Elwig.exe"))];
|
||||
}
|
||||
|
||||
public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
|
||||
|
@@ -6,10 +6,10 @@ namespace Elwig.Models.Entities {
|
||||
[Table("wb_gl"), PrimaryKey("GlNr")]
|
||||
public class WbGl {
|
||||
[Column("glnr")]
|
||||
public int GlNr { get; private set; }
|
||||
public int GlNr { get; set; }
|
||||
|
||||
[Column("name")]
|
||||
public string Name { get; private set; } = null!;
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[InverseProperty(nameof(WbKg.Gl))]
|
||||
public virtual ICollection<WbKg> Kgs { get; private set; } = null!;
|
||||
|
@@ -15,7 +15,7 @@ namespace Elwig.Models.Entities {
|
||||
public virtual AT_Kg AtKg { get; private set; } = null!;
|
||||
|
||||
[ForeignKey("GlNr")]
|
||||
public virtual WbGl Gl { get; private set; } = null!;
|
||||
public virtual WbGl? Gl { get; private set; } = null!;
|
||||
|
||||
[InverseProperty(nameof(WbRd.Kg))]
|
||||
public virtual ICollection<WbRd> Rds { get; private set; } = null!;
|
||||
|
@@ -727,13 +727,22 @@ namespace Elwig.Services {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
await ElwigData.Export(d.FileName, await query
|
||||
var list = await query
|
||||
.Select(p => p.Delivery)
|
||||
.Distinct()
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(), filterNames);
|
||||
.ToListAsync();
|
||||
var wbKgs = list
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
await ElwigData.Export(d.FileName, list, wbKgs, filterNames);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
@@ -749,15 +758,23 @@ namespace Elwig.Services {
|
||||
var list = await query
|
||||
.Select(p => p.Delivery)
|
||||
.Distinct()
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = list
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (list.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
await ElwigData.Export(path, list, filterNames);
|
||||
await ElwigData.Export(path, list, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
@@ -509,13 +509,22 @@ namespace Elwig.Services {
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var areaComs = await query
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.ToListAsync();
|
||||
await ElwigData.Export(d.FileName, members, areaComs, filterNames);
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Distinct()
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
await ElwigData.Export(d.FileName, members, areaComs, wbKgs, filterNames);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
@@ -533,17 +542,27 @@ namespace Elwig.Services {
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var areaComs = await query
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.ToListAsync();
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Distinct()
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (members.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
await ElwigData.Export(path, members, areaComs, filterNames);
|
||||
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
32
Elwig/Windows/AboutWindow.xaml
Normal file
32
Elwig/Windows/AboutWindow.xaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<Window x:Class="Elwig.Windows.AboutWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Elwig.Windows"
|
||||
Title="Über - Elwig" Height="340" Width="480" ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<TextBlock Margin="20,10" FontSize="12">
|
||||
<Bold>Produkt:</Bold> Elwig<LineBreak/>
|
||||
<Bold>Beschreibung:</Bold> Elektronische Winzergenossenschaftsveraltung<LineBreak/>
|
||||
<Bold>Typ:</Bold> Warenwirschaftssystem<LineBreak/>
|
||||
<Bold>Version:</Bold> <Run x:Name="Version">0.0.0.0</Run> (<Hyperlink NavigateUri="https://elwig.at/changelog" RequestNavigate="Hyperlink_RequestNavigate">Änderungsprotokoll</Hyperlink>)<LineBreak/>
|
||||
<Bold>Lizenz:</Bold> <Hyperlink NavigateUri="https://www.gnu.org/licenses/gpl-3.0.html" RequestNavigate="Hyperlink_RequestNavigate">GNU General Public License 3.0 (GPLv3)</Hyperlink><LineBreak/>
|
||||
<Bold>Website:</Bold> <Hyperlink NavigateUri="https://elwig.at/" RequestNavigate="Hyperlink_RequestNavigate">https://elwig.at/</Hyperlink><LineBreak/>
|
||||
<Bold>Entwickler:</Bold> Lorenz Stechauner, Thomas Hilscher<LineBreak/>
|
||||
<Bold>Kontakt:</Bold> <Hyperlink NavigateUri="mailto:lorenz.stechauner@necronda.net" RequestNavigate="Hyperlink_RequestNavigate">lorenz.stechauner@necronda.net</Hyperlink>, <Hyperlink NavigateUri="mailto:thomas.hilscher@gmail.com" RequestNavigate="Hyperlink_RequestNavigate">thomas.hilscher@gmail.com</Hyperlink><LineBreak/>
|
||||
<Bold>Quellcode:</Bold> <Hyperlink NavigateUri="https://git.necronda.net/winzer/elwig" RequestNavigate="Hyperlink_RequestNavigate">https://git.necronda.net/winzer/elwig</Hyperlink><LineBreak/>
|
||||
<Bold>Entwicklungszeitraum:</Bold> 2022–2025<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Bold>Verwendete Technologien:</Bold><LineBreak/>
|
||||
Programmiersprache: C#<LineBreak/>
|
||||
Framework: Windows Presentation Framework (WPF)<LineBreak/>
|
||||
Datenbank: <Hyperlink NavigateUri="https://sqlite.org/" RequestNavigate="Hyperlink_RequestNavigate">SQLite</Hyperlink><LineBreak/>
|
||||
PDF-Erstellung: <Hyperlink NavigateUri="https://weasyprint.org/" RequestNavigate="Hyperlink_RequestNavigate">WeasyPrint</Hyperlink>, <Hyperlink NavigateUri="https://github.com/toddams/RazorLight" RequestNavigate="Hyperlink_RequestNavigate">RazorLight</Hyperlink>, <Hyperlink NavigateUri="https://github.com/pvginkel/PdfiumViewer" RequestNavigate="Hyperlink_RequestNavigate">PdfiumViewer</Hyperlink><LineBreak/>
|
||||
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<Image Source="\Resources\Images\Elwig.png" RenderOptions.BitmapScalingMode="HighQuality" Height="96"
|
||||
HorizontalAlignment="Right" Margin="10" VerticalAlignment="Top"/>
|
||||
</Grid>
|
||||
</Window>
|
17
Elwig/Windows/AboutWindow.xaml.cs
Normal file
17
Elwig/Windows/AboutWindow.xaml.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace Elwig.Windows {
|
||||
public partial class AboutWindow : Window {
|
||||
|
||||
public AboutWindow() {
|
||||
InitializeComponent();
|
||||
Version.Text = App.Version.ToString();
|
||||
}
|
||||
|
||||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) {
|
||||
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,8 +13,8 @@ using System.Windows.Input;
|
||||
namespace Elwig.Windows {
|
||||
public abstract class AdministrationWindow : ContextWindow {
|
||||
|
||||
protected Control[] ExemptInputs { private get; set; }
|
||||
protected Control[] RequiredInputs { private get; set; }
|
||||
protected Control[] ExemptInputs { get; set; }
|
||||
protected Control[] RequiredInputs { get; set; }
|
||||
|
||||
private bool _isEditing;
|
||||
private bool _isCreating;
|
||||
@@ -166,8 +166,10 @@ namespace Elwig.Windows {
|
||||
Valid[input] = false;
|
||||
} else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) {
|
||||
ControlUtils.SetInputInvalid(input);
|
||||
Valid[input] = false;
|
||||
} else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) {
|
||||
ControlUtils.SetInputInvalid(input);
|
||||
Valid[input] = false;
|
||||
} else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) {
|
||||
ControlUtils.SetInputInvalid(input);
|
||||
Valid[input] = false;
|
||||
|
@@ -89,6 +89,9 @@ namespace Elwig.Windows {
|
||||
foreach (var s in App.EventScales) {
|
||||
s.WeighingEvent += Scale_Weighing;
|
||||
}
|
||||
if (App.Client.IsMatzen) {
|
||||
RequiredInputs = [.. RequiredInputs, ModifiersInput];
|
||||
}
|
||||
} else {
|
||||
WeighingManualButton.Visibility = Visibility.Hidden;
|
||||
WeighingAButton.Visibility = Visibility.Hidden;
|
||||
@@ -278,6 +281,7 @@ namespace Elwig.Windows {
|
||||
DateInput.IsReadOnly = false;
|
||||
TimeInput.IsReadOnly = false;
|
||||
BranchInput.IsEnabled = true;
|
||||
GerebeltGewogenInput.IsEnabled = true;
|
||||
if (IsCreating) ViewModel.Time = "";
|
||||
OnSecondPassed(null, null);
|
||||
}
|
||||
@@ -287,6 +291,7 @@ namespace Elwig.Windows {
|
||||
DateInput.IsReadOnly = true;
|
||||
TimeInput.IsReadOnly = true;
|
||||
BranchInput.IsEnabled = false;
|
||||
GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode != WeighingMode.Net;
|
||||
OnSecondPassed(null, null);
|
||||
}
|
||||
|
||||
@@ -301,7 +306,7 @@ namespace Elwig.Windows {
|
||||
}
|
||||
|
||||
private void InitialDefaultInputs() {
|
||||
if (App.Client.HasNetWeighing(ViewModel.Branch)) {
|
||||
if (App.Config.WeighingMode == WeighingMode.Net) {
|
||||
GerebeltGewogenInput.IsEnabled = false;
|
||||
SetDefaultValue(GerebeltGewogenInput, true);
|
||||
} else {
|
||||
@@ -310,7 +315,7 @@ namespace Elwig.Windows {
|
||||
UnsetDefaultValue(GerebeltGewogenInput);
|
||||
}
|
||||
|
||||
if (App.Client.HasBoxWeighing(ViewModel.Branch)) {
|
||||
if (App.Config.WeighingMode == WeighingMode.Box) {
|
||||
LesewagenInput.IsEnabled = false;
|
||||
SetDefaultValue(LesewagenInput, false);
|
||||
} else {
|
||||
@@ -318,7 +323,7 @@ namespace Elwig.Windows {
|
||||
UnsetDefaultValue(LesewagenInput);
|
||||
}
|
||||
|
||||
if (!App.Client.HasNetWeighing(ViewModel.Branch)) {
|
||||
if (App.Config.WeighingMode != WeighingMode.Net) {
|
||||
HandPickedInput.IsThreeState = false;
|
||||
UnsetDefaultValue(HandPickedInput);
|
||||
} else {
|
||||
@@ -344,13 +349,13 @@ namespace Elwig.Windows {
|
||||
ClearOriginalValues();
|
||||
ClearDefaultValues();
|
||||
|
||||
ViewModel.IsNetWeight = App.Client.HasNetWeighing(ViewModel.Branch);
|
||||
ViewModel.IsNetWeight = App.Config.WeighingMode == WeighingMode.Net;
|
||||
ViewModel.IsLesewagen = false;
|
||||
ViewModel.IsHandPicked = !App.Client.HasNetWeighing(ViewModel.Branch) ? true : null;
|
||||
ViewModel.IsHandPicked = App.Config.WeighingMode != WeighingMode.Net ? true : null;
|
||||
ViewModel.IsGebunden = null;
|
||||
InitialDefaultInputs();
|
||||
|
||||
WineQualityLevelInput.IsEnabled = false;
|
||||
//WineQualityLevelInput.IsEnabled = false; // disable wine quality level input in Übernahme
|
||||
ValidateRequiredInputs();
|
||||
}
|
||||
|
||||
@@ -1139,6 +1144,7 @@ namespace Elwig.Windows {
|
||||
DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
|
||||
TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
|
||||
BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked;
|
||||
GerebeltGewogenInput.IsEnabled = App.Config.WeighingMode == WeighingMode.Net || Menu_Settings_EnableFreeEditing.IsChecked;
|
||||
}
|
||||
|
||||
private void DisableWeighingButtons() {
|
||||
@@ -1389,17 +1395,17 @@ namespace Elwig.Windows {
|
||||
}
|
||||
|
||||
private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
|
||||
if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
|
||||
if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) {
|
||||
HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
|
||||
}
|
||||
if (!ViewModel.IsReceipt || App.Client.HasNetWeighing(ViewModel.Branch)) {
|
||||
if (!ViewModel.IsReceipt || App.Config.WeighingMode == WeighingMode.Net) {
|
||||
GerebeltGewogenInput.IsChecked ??= false;
|
||||
}
|
||||
CheckBox_Changed(sender, evt);
|
||||
}
|
||||
|
||||
private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
|
||||
if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
|
||||
if ((IsEditing || IsCreating) && App.Config.WeighingMode != WeighingMode.Net) {
|
||||
GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
|
||||
}
|
||||
CheckBox_Changed(sender, evt);
|
||||
|
@@ -1,7 +1,9 @@
|
||||
using Elwig.Helpers;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Elwig.Windows {
|
||||
public partial class LogWindow : Window {
|
||||
@@ -11,9 +13,10 @@ namespace Elwig.Windows {
|
||||
WindowState = WindowState.Maximized;
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs evt) {
|
||||
var log = Utils.GetLogEntries();
|
||||
EventList.ItemsSource = log
|
||||
private async void Window_Loaded(object sender, RoutedEventArgs evt) {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
var list = Utils.GetLogEntries()
|
||||
.Select(e => new {
|
||||
Event = e,
|
||||
Lines = e.Message.Split('\n').ToArray(),
|
||||
@@ -32,7 +35,12 @@ namespace Elwig.Windows {
|
||||
})
|
||||
.OrderByDescending(e => e.Event.TimeGenerated)
|
||||
.ToList();
|
||||
await App.MainDispatcher.BeginInvoke(() => {
|
||||
EventList.ItemsSource = list;
|
||||
EventList.SelectedIndex = 0;
|
||||
});
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) {
|
||||
|
@@ -31,6 +31,17 @@
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator/>
|
||||
<MenuItem Header="Datenbank sichern..." Click="Menu_Database_Backup_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Datenbank wiederherstellen..." Click="Menu_Database_Restore_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator/>
|
||||
<MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
@@ -41,6 +52,17 @@
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</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=""/>
|
||||
</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=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Waage">
|
||||
<MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click">
|
||||
@@ -50,7 +72,7 @@
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="HelpMenu" Header="Hilfe">
|
||||
<MenuItem Header="Über">
|
||||
<MenuItem Header="Über" Click="Menu_Help_About_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
|
@@ -31,6 +31,8 @@ namespace Elwig.Windows {
|
||||
Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null;
|
||||
DownloadButton.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) {
|
||||
@@ -57,6 +59,11 @@ namespace Elwig.Windows {
|
||||
}
|
||||
}
|
||||
|
||||
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
|
||||
var w = new AboutWindow();
|
||||
w.Show();
|
||||
}
|
||||
|
||||
private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) {
|
||||
await App.CheckForUpdates(true);
|
||||
}
|
||||
@@ -144,6 +151,48 @@ namespace Elwig.Windows {
|
||||
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",
|
||||
};
|
||||
if (d.ShowDialog() == true) {
|
||||
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) {
|
||||
if (App.Config.SyncUrl == null)
|
||||
return;
|
||||
@@ -156,7 +205,7 @@ namespace Elwig.Windows {
|
||||
Name = f!["name"]!.AsValue().GetValue<string>(),
|
||||
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
|
||||
ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
|
||||
Device = f!["meta"]?["device"]!.AsValue().GetValue<string>(),
|
||||
Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
|
||||
Url = f!["url"]!.AsValue().GetValue<string>(),
|
||||
Size = f!["size"]!.AsValue().GetValue<long>(),
|
||||
})
|
||||
@@ -178,11 +227,11 @@ namespace Elwig.Windows {
|
||||
}
|
||||
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
@@ -200,16 +249,25 @@ namespace Elwig.Windows {
|
||||
.Where(d => d.Year == Utils.CurrentLastSeason && d.ZwstId == App.ZwstId)
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.Kg)
|
||||
.ThenInclude(k => k!.Gl)
|
||||
.OrderBy(d => d.DateString)
|
||||
.ThenBy(d => d.TimeString)
|
||||
.ThenBy(d => d.LsNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = deliveries
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (deliveries.Count == 0) {
|
||||
MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
await ElwigData.Export(path, deliveries, [$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]);
|
||||
await ElwigData.Export(path, deliveries, wbKgs, [$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]);
|
||||
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
@@ -225,6 +283,89 @@ namespace Elwig.Windows {
|
||||
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) {
|
||||
var w = new MemberAdminWindow();
|
||||
w.Show();
|
||||
|
@@ -38,7 +38,7 @@ namespace Elwig.Windows {
|
||||
IList<DbColumn> header;
|
||||
|
||||
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||
var cmd = cnx.CreateCommand();
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sqlQuery;
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
header = await reader.GetColumnSchemaAsync();
|
||||
|
@@ -13,7 +13,7 @@
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Installer\Installer.wixproj" />
|
||||
<PackageReference Include="WixToolset.Bal.wixext" Version="6.0.1" />
|
||||
<PackageReference Include="WixToolset.Util.wixext" Version="6.0.1" />
|
||||
<PackageReference Include="WixToolset.Bal.wixext" Version="6.0.2" />
|
||||
<PackageReference Include="WixToolset.Util.wixext" Version="6.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -22,9 +22,9 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="Appium.WebDriver" Version="4.4.5" />
|
||||
<PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.3.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.9.2">
|
||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.10.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
Reference in New Issue
Block a user