Compare commits

...
10 Commits
Author SHA1 Message Date
lorenz.stechauner cadac4302d App: Add option to restore database when a DB error occurs at startup
Test / Run tests (push) Successful in 2m12s
2026-08-26 12:01:03 +02:00
lorenz.stechauner c1690bf104 DeliveryService: Fix GetFilterToday and add tests
Test / Run tests (push) Successful in 2m16s
2026-08-26 10:57:02 +02:00
lorenz.stechauner ca5b719630 Elwig: Add BackupService
Test / Run tests (push) Successful in 2m48s
2026-08-26 10:39:50 +02:00
lorenz.stechauner 163fe0fac8 Bump version to 1.1.1.2
Test / Run tests (push) Successful in 2m41s
Deploy / Build and Deploy (push) Successful in 1m52s
2026-08-25 20:36:03 +02:00
lorenz.stechauner edf3678154 Export/ElwigData: Fix detection of KGs without Großlage
Test / Run tests (push) Successful in 1m54s
2026-08-25 18:19:41 +02:00
lorenz.stechauner f450dc35b1 Weighing: Add option to opt-out of automatic datetime sync at startup 2026-08-25 18:19:31 +02:00
lorenz.stechauner 0c4611024f App: Add default weighing mode for Pamhagen
Test / Run tests (push) Successful in 1m57s
2026-08-25 16:31:33 +02:00
lorenz.stechauner 46e596ae23 Utils: Replace Utils.Today with DateTime.Today and adapt DeliveryService filters
Test / Run tests (push) Successful in 2m51s
2026-08-25 16:30:38 +02:00
lorenz.stechauner 3366cc25eb DeliveryAdminWindow: Change modifier handling for Matzen
Test / Run tests (push) Successful in 2m12s
2026-08-24 22:11:52 +02:00
lorenz.stechauner ff71e9fb7f DeliveryNote: Fix alignment of weights 2026-08-24 21:49:10 +02:00
31 changed files with 616 additions and 163 deletions
+20
View File
@@ -2,6 +2,26 @@
Changelog Changelog
========= =========
[v1.1.1.2][v1.1.1.2] (2026-08-25) {#v1.1.1.2}
---------------------------------------------
### Sonstiges {#v1.1.1.2-misc}
* In Matzen werden für Planenw./Kipper und Lesewagen die Klassen automatisch gesetzt. (3366cc25eb)
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) wird für "Nur heute" nicht mehr nur das Datum, sondern die letzten zusammenhängenden Lieferungen verwendet. (46e596ae23)
* Für Pamhagen wird als standard Wiege-Methode "brutto" verwendet. (0c4611024f)
* Für Waagen kann das automatische setzen von Datum und Uhrzeit nun per Opt-out ausgeschalten werden (Konfigurationsdatei -> `[scale.X]` -> `synctime = false`). (f450dc35b1)
### Behobene Fehler {#v1.1.1.2-bugfixes}
* Auf Lieferscheinen (`DeliveryNote`) waren die Mengenangaben nicht rechtsbündig. (ff71e9fb7f)
* Beim Importieren von Mitgliedern mit Stamm-KG ohne Großlage kam es zu einem Fehler, obwohl die Großlage automatisch hätte zugewiesen werden müssen. (edf3678154)
[v1.1.1.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.1.2
[v1.1.1.1][v1.1.1.1] (2026-08-24) {#v1.1.1.1} [v1.1.1.1][v1.1.1.1] (2026-08-24) {#v1.1.1.1}
--------------------------------------------- ---------------------------------------------
+1 -2
View File
@@ -2,8 +2,7 @@
x:Class="Elwig.App" x:Class="Elwig.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls">
Exit="Application_Exit">
<Application.Resources> <Application.Resources>
<ctrl:BoolToStringConverter x:Key="BoolToStarConverter" FalseValue="" TrueValue="*"/> <ctrl:BoolToStringConverter x:Key="BoolToStarConverter" FalseValue="" TrueValue="*"/>
<ctrl:WidthToMarginConverter x:Key="WidthToMarginConverter"/> <ctrl:WidthToMarginConverter x:Key="WidthToMarginConverter"/>
+41 -12
View File
@@ -25,9 +25,10 @@ namespace Elwig {
protected static App CurrentApp; protected static App CurrentApp;
public static int NumWindows => CurrentApp.Windows.Count; public static int NumWindows => CurrentApp.Windows.Count;
public static bool ForceShutdown { get; private set; } = false; public static bool ForceShutdown { get; set; } = false;
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) }; private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
private readonly DispatcherTimer _autoBackupTimer = new() { Interval = TimeSpan.FromMinutes(10) };
public readonly SerialPortWatcher SerialPortWatcher = new(); public readonly SerialPortWatcher SerialPortWatcher = new();
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig"); public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig");
@@ -74,7 +75,7 @@ namespace Elwig {
Config = new(Path.GetFullPath(args[1])); Config = new(Path.GetFullPath(args[1]));
} }
ContextTimer.Tick += (object? sender, EventArgs evt) => { ContextTimer.Tick += (sender, evt) => {
var ch = CurrentLastWrite; var ch = CurrentLastWrite;
if (ch > LastChanged) { if (ch > LastChanged) {
LastChanged = ch; LastChanged = ch;
@@ -92,11 +93,15 @@ namespace Elwig {
try { try {
await AppDbUpdater.CheckDb(); await AppDbUpdater.CheckDb();
} catch (FileNotFoundException exc) {
InteractionService.ShowException("Datenbank nicht gefunden", "Die Datenbank konnte nicht gefunden werden", exc);
} catch (Exception exc) { } catch (Exception exc) {
if (Config.UpdateUrl != null && Utils.HasInternetConnectivity()) { if (Config.UpdateUrl != null && Utils.HasInternetConnectivity()) {
await CheckForUpdates(); await CheckForUpdates();
} }
InteractionService.ShowException("Fehlerhafte Datenbank", "Fehlerhafte Datenbank", exc); InteractionService.ShowException("Fehlerhafte Datenbank", "Schwerwiegender Fehler in der Datenbank, unbedingt den Programmierern melden!", exc);
if (InteractionService.AskQuestion("Backup wiederherstellen", "Soll die Datenbank aus einem Backup wiederhergestellt werden?\n\nAchtung: Das kann zu Datenverlust führen!", false))
await BackupService.RestoreDatabase();
Shutdown(); Shutdown();
return; return;
} }
@@ -106,13 +111,14 @@ namespace Elwig {
Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = []; Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = [];
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
branches = ctx.FetchBranches() branches = await ctx.FetchBranches()
.ToDictionaryAsync(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr)) .ToDictionaryAsync(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
.GetAwaiter().GetResult();
try { try {
Client = new(ctx); Client = new(ctx);
} catch (Exception exc) { } catch (Exception exc) {
InteractionService.ShowException("Fehler", "Fehler beim Laden der Mandantendaten", exc); InteractionService.ShowException("Fehler", "Fehler beim Laden der Mandantendaten, unbedingt den Programmierern melden!", exc);
if (InteractionService.AskQuestion("Backup wiederherstellen", "Soll die Datenbank aus einem Backup wiederhergestellt werden?\n\nAchtung: Das kann zu Datenverlust führen!", false))
await BackupService.RestoreDatabase();
Shutdown(); Shutdown();
return; return;
} }
@@ -124,9 +130,23 @@ namespace Elwig {
return Task.CompletedTask; return Task.CompletedTask;
}); });
Utils.RunBackground("PDF Initialization", () => Pdf.Init()); Utils.RunBackground("PDF Initialization", async () => Pdf.Init());
Utils.RunBackground("JSON Schema Initialization", BillingData.Init); Utils.RunBackground("JSON Schema Initialization", BillingData.Init);
if (Config.BackupAuto) {
Utils.RunBackground("Automatisches Backup", async () => {
Directory.CreateDirectory(Config.BackupPath);
await Task.Delay(500);
await BackupService.TryDatabaseBackup();
});
_autoBackupTimer.Tick += new EventHandler((sender, evt) => {
Utils.RunBackground("Automatisches Backup", async () => {
await BackupService.TryDatabaseBackup();
});
});
_autoBackupTimer.Start();
}
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 () => {
@@ -146,7 +166,7 @@ namespace Elwig {
foreach (var s in Config.Scales) { foreach (var s in Config.Scales) {
try { try {
var scale = Scale.FromConfig(s); var scale = Scale.FromConfig(s);
if (scale is ICommandScale cmd) { if (s.SyncTime && scale is ICommandScale cmd) {
try { try {
await cmd.SetDateAndTime(DateTime.Now); await cmd.SetDateAndTime(DateTime.Now);
} catch { } } catch { }
@@ -179,7 +199,7 @@ namespace Elwig {
Config.WeighingMode = WeighingMode.Net; Config.WeighingMode = WeighingMode.Net;
} else if (Client.IsHaugsdorf || Client.IsSitzendorf) { } else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
Config.WeighingMode = WeighingMode.Box; Config.WeighingMode = WeighingMode.Box;
} else if (Client.IsBaden || Client.IsGrInzersdorf) { } else if (Client.IsBaden || Client.IsGrInzersdorf || Client.IsPamhagen) {
Config.WeighingMode = WeighingMode.Gross; Config.WeighingMode = WeighingMode.Gross;
} }
} }
@@ -190,12 +210,21 @@ namespace Elwig {
window.Show(); window.Show();
} }
private async void Application_Exit(object sender, ExitEventArgs evt) { public async Task OnClosing() {
try {
await BackupService.TryDatabaseBackup();
} catch (Exception exc) {
InteractionService.ShowException("Automatisches Backup", exc);
}
}
protected override void OnExit(ExitEventArgs evt) {
SerialPortWatcher.Dispose(); SerialPortWatcher.Dispose();
foreach (var s in EventScales) { foreach (var s in EventScales) {
s.Dispose(); s.Dispose();
} }
await Pdf.Cleanup(); Pdf.Cleanup();
base.OnExit(evt);
} }
public static void SetBranch(Branch b) { public static void SetBranch(Branch b) {
+1 -1
View File
@@ -147,7 +147,7 @@ namespace Elwig.Documents {
.AddCell(NewDeliveryMainTd(part.Quality.Name)) .AddCell(NewDeliveryMainTd(part.Quality.Name))
.AddCell(NewDeliveryMainTd($"{part.Oe:N0}", center: true)) .AddCell(NewDeliveryMainTd($"{part.Oe:N0}", center: true))
.AddCell(NewDeliveryMainTd($"{part.Kmw:N1}", center: true)) .AddCell(NewDeliveryMainTd($"{part.Kmw:N1}", center: true))
.AddCell(NewDeliveryMainTd($"{part.Weight:N0}", center: true)); .AddCell(NewDeliveryMainTd($"{part.Weight:N0}", right: true));
if (part.Cultivation != null) { if (part.Cultivation != null) {
var cult = new KernedParagraph(8); var cult = new KernedParagraph(8);
+2 -2
View File
@@ -57,7 +57,7 @@ namespace Elwig.Documents {
public Document(string title) { public Document(string title) {
Title = title; Title = title;
Author = App.Client.NameFull; Author = App.Client.NameFull;
Date = DateOnly.FromDateTime(Utils.Today); Date = DateOnly.FromDateTime(DateTime.Today);
} }
~Document() { ~Document() {
@@ -252,7 +252,7 @@ namespace Elwig.Documents {
public async Task Print(int copies = 1) { public async Task Print(int copies = 1) {
if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet"); if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
await Pdf.Print(PdfPath, copies, IsDoublePaged); Pdf.Print(PdfPath, copies, IsDoublePaged);
} }
public void Show() { public void Show() {
+1 -1
View File
@@ -9,7 +9,7 @@
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon> <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.1.1.1</Version> <Version>1.1.1.2</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages> <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>App.manifest</ApplicationManifest> <ApplicationManifest>App.manifest</ApplicationManifest>
+14 -7
View File
@@ -14,10 +14,17 @@ namespace Elwig.Helpers {
private static int VersionOffset = 0; private static int VersionOffset = 0;
public static async Task<Version> CheckDb() { public static async Task<Version> CheckDb() {
if (!File.Exists(App.Config.DatabaseFile))
throw new FileNotFoundException($"Die Datei \"{App.Config.DatabaseFile}\" exisitiert nicht");
long? applId, schemaVers; long? applId, schemaVers;
using (var cnx = await AppDbContext.ConnectAsync()) { using (var cnx = await AppDbContext.ConnectAsync()) {
await cnx.QuickCheck();
applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0; applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})"); if (applId != 0x454C5747)
throw new FileFormatException($"Invalid application_id in database (0x{applId:X08})");
schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0; schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
VersionOffset = (int)(schemaVers % 100); VersionOffset = (int)(schemaVers % 100);
@@ -76,16 +83,16 @@ namespace Elwig.Helpers {
try { try {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch("PRAGMA locking_mode = EXCLUSIVE"); await cnx.ExecuteBatch("PRAGMA locking_mode = EXCLUSIVE");
await cnx.IntegrityCheck();
await cnx.ForeignKeyCheck();
foreach (var script in toExecute) { foreach (var script in toExecute) {
await cnx.ExecuteEmbeddedScript(asm, script); await cnx.ExecuteEmbeddedScript(asm, script);
} }
var violations = await cnx.ForeignKeyCheck(); await cnx.IntegrityCheck();
if (violations.Length > 0) { await cnx.ForeignKeyCheck();
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
.Take(50)
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
}
await cnx.ExecuteBatch("VACUUM"); await cnx.ExecuteBatch("VACUUM");
await cnx.ExecuteBatch($"PRAGMA schema_version = {toVersion * 100 + VersionOffset}"); await cnx.ExecuteBatch($"PRAGMA schema_version = {toVersion * 100 + VersionOffset}");
+24 -2
View File
@@ -17,10 +17,11 @@ namespace Elwig.Helpers {
public string? Filling; public string? Filling;
public string? Limit; public string? Limit;
public bool Required; public bool Required;
public bool SyncTime;
public string? Log; public string? Log;
public string? _Log; public string? _Log;
public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, bool? required, string? log) { public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, bool? required, bool? syncTime, string? log) {
Id = id; Id = id;
Type = type; Type = type;
Model = model; Model = model;
@@ -29,6 +30,7 @@ namespace Elwig.Helpers {
Filling = filling; Filling = filling;
Limit = limit; Limit = limit;
Required = required ?? true; Required = required ?? true;
SyncTime = syncTime ?? true;
_Log = log; _Log = log;
Log = log != null ? Path.Combine(App.DataPath, log) : null; Log = log != null ? Path.Combine(App.DataPath, log) : null;
} }
@@ -37,16 +39,26 @@ namespace Elwig.Helpers {
public class Config { public class Config {
private static readonly string[] TrueValues = ["1", "true", "yes", "on"]; private static readonly string[] TrueValues = ["1", "true", "yes", "on"];
private static readonly string[] FalseValues = ["0", "false", "no", "off"];
private readonly string FileName; private readonly string FileName;
public bool Debug; public bool Debug;
public string DatabaseFile = App.DataPath + "database.sqlite3"; public string DatabaseFile = Path.Combine(App.DataPath, "database.sqlite3");
public string? DatabaseLog = null; public string? DatabaseLog = null;
public string? Branch = null; public string? Branch = null;
public WeighingMode? WeighingMode; public WeighingMode? WeighingMode;
public bool BackupAuto = true;
public string BackupPath = Path.Combine(App.DataPath, "backups");
public int BackupRetainHours = 36;
public int BackupRetainDays = 8;
public int BackupRetainWeeks = 6;
public int BackupRetainMonths = 18;
public string? UpdateUrl = null; public string? UpdateUrl = null;
public bool UpdateAuto = false; public bool UpdateAuto = false;
public string? SyncUrl = null; public string? SyncUrl = null;
public string SyncUsername = ""; public string SyncUsername = "";
public string SyncPassword = ""; public string SyncPassword = "";
@@ -82,8 +94,17 @@ namespace Elwig.Helpers {
Debug = TrueValues.Contains(config["general:debug"]?.ToLower()); Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
var weighing = config["general:weighing"]; var weighing = config["general:weighing"];
WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null; WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null;
BackupAuto = !FalseValues.Contains(config["backup:auto"]?.ToLower());
BackupPath = Path.Combine(Path.GetDirectoryName(DatabaseFile) ?? App.DataPath, config["backup:path"] ?? "backups");
BackupRetainHours = int.TryParse(config["backup:retain_hours"], out var hours) ? hours : 36;
BackupRetainDays = int.TryParse(config["backup:retain_days"], out var days) ? days : 8;
BackupRetainWeeks = int.TryParse(config["backup:retain_weeks"], out var weeks) ? weeks : 6;
BackupRetainMonths = int.TryParse(config["backup:retain_months"], out var months) ? months : 18;
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"];
SyncUsername = config["sync:username"] ?? ""; SyncUsername = config["sync:username"] ?? "";
SyncPassword = config["sync:password"] ?? ""; SyncPassword = config["sync:password"] ?? "";
@@ -105,6 +126,7 @@ namespace Elwig.Helpers {
s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"], s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"],
config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"],
config[$"scale.{s}:required"] != null ? TrueValues.Contains(config[$"scale.{s}:required"]?.ToLower()) : null, config[$"scale.{s}:required"] != null ? TrueValues.Contains(config[$"scale.{s}:required"]?.ToLower()) : null,
config[$"scale.{s}:synctime"] != null ? !FalseValues.Contains(config[$"scale.{s}:synctime"]?.ToLower()) : null,
config[$"scale.{s}:log"] config[$"scale.{s}:log"]
)); ));
} }
+18 -6
View File
@@ -18,9 +18,11 @@ namespace Elwig.Helpers.Export {
} }
public static async Task ExportSqlite(string filename, bool zipFile) { public static async Task ExportSqlite(string filename, bool zipFile) {
var tmp = filename + ".tmp";
try {
File.Delete(tmp);
if (zipFile) { if (zipFile) {
File.Delete(filename); using var zip = ZipFile.Open(tmp, ZipArchiveMode.Create);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression); var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
@@ -46,14 +48,20 @@ namespace Elwig.Helpers.Export {
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize); var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
} else { } else {
File.Copy(App.Config.DatabaseFile, filename, true); File.Copy(App.Config.DatabaseFile, tmp);
}
File.Move(tmp, filename, true);
} finally {
File.Delete(tmp);
} }
} }
public static async Task ExportSql(string filename, bool zipFile) { public static async Task ExportSql(string filename, bool zipFile) {
var tmp = filename + ".tmp";
try {
File.Delete(tmp);
if (zipFile) { if (zipFile) {
File.Delete(filename); using var zip = ZipFile.Open(tmp, ZipArchiveMode.Create);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression); var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
@@ -82,10 +90,14 @@ namespace Elwig.Helpers.Export {
await ExportSql(writer); await ExportSql(writer);
} }
} else { } else {
using var stream = File.OpenWrite(filename); using var stream = File.OpenWrite(tmp);
using var writer = new StreamWriter(stream, Utils.UTF8); using var writer = new StreamWriter(stream, Utils.UTF8);
await ExportSql(writer); await ExportSql(writer);
} }
File.Move(tmp, filename, true);
} finally {
File.Delete(tmp);
}
} }
public static async Task ExportSql(StreamWriter writer) { public static async Task ExportSql(StreamWriter writer) {
+24 -13
View File
@@ -10,7 +10,6 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
public static class ElwigData { public static class ElwigData {
@@ -36,14 +35,16 @@ namespace Elwig.Helpers.Export {
public static async Task Import(IEnumerable<string> filenames, ImportMode mode) { public static async Task Import(IEnumerable<string> filenames, ImportMode mode) {
try { try {
Dictionary<string, Branch> branches; Dictionary<string, Branch> branches;
Dictionary<int, AT_Kg> kgs;
Dictionary<int, int> currentDids; Dictionary<int, int> currentDids;
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, WbKg> currentWbKgs;
List<WbGl> currentWbGls; List<WbGl> currentWbGls;
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
branches = await ctx.FetchBranches().ToDictionaryAsync(b => b.ZwstId); branches = await ctx.FetchBranches().ToDictionaryAsync(b => b.ZwstId);
kgs = await ctx.Katastralgemeinden.ToDictionaryAsync(k => k.KgNr);
currentDids = await ctx.Deliveries currentDids = await ctx.Deliveries
.GroupBy(d => d.Year) .GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId)); .ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));
@@ -52,8 +53,8 @@ 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());
currentWbKgs = await ctx.WbKgs.ToDictionaryAsync(k => k.KgNr);
currentWbGls = await ctx.WbGls.ToListAsync(); currentWbGls = await ctx.WbGls.ToListAsync();
kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr);
} }
var data = new List<( var data = new List<(
@@ -122,6 +123,7 @@ namespace Elwig.Helpers.Export {
var obj = JsonNode.Parse(line)!.AsObject(); var obj = JsonNode.Parse(line)!.AsObject();
var (k, g) = obj.ToWbKg(currentWbGls); var (k, g) = obj.ToWbKg(currentWbGls);
r.WbKgs.Add(k); r.WbKgs.Add(k);
currentWbKgs[k.KgNr] = k;
if (g != null) { if (g != null) {
currentWbGls[g.GlNr] = g; currentWbGls[g.GlNr] = g;
r.WbGls.Add(g); r.WbGls.Add(g);
@@ -135,7 +137,7 @@ 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 (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs); var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs, currentWbKgs);
r.Members.Add(m); r.Members.Add(m);
if (b != null) r.BillingAddresses.Add(b); if (b != null) r.BillingAddresses.Add(b);
r.TelephoneNumbers.AddRange(telNrs); r.TelephoneNumbers.AddRange(telNrs);
@@ -163,7 +165,7 @@ 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 (contract, areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde); var (contract, areaCom, wbrd, timestamps) = obj.ToAreaCom(kgs, currentWbKgs, currentWbRde);
r.Contracts.Add(contract); r.Contracts.Add(contract);
r.AreaCommitments.Add(areaCom); r.AreaCommitments.Add(areaCom);
if (wbrd != null) { if (wbrd != null) {
@@ -182,7 +184,7 @@ 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 (contract, areaComs, wbrd, timestamps) = obj.ToAreaComContract(currentWbRde); var (contract, areaComs, wbrd, timestamps) = obj.ToAreaComContract(kgs, currentWbKgs, currentWbRde);
r.Contracts.Add(contract); r.Contracts.Add(contract);
r.AreaCommitments.AddRange(areaComs.Select(v => v.Item1)); r.AreaCommitments.AddRange(areaComs.Select(v => v.Item1));
if (wbrd != null) { if (wbrd != null) {
@@ -203,7 +205,7 @@ 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, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbKgs, currentWbRde);
r.Deliveries.Add(d); r.Deliveries.Add(d);
r.DeliveryParts.AddRange(parts.Select(p => p.Item1)); r.DeliveryParts.AddRange(parts.Select(p => p.Item1));
r.Modifiers.AddRange(mods); r.Modifiers.AddRange(mods);
@@ -680,11 +682,11 @@ namespace Elwig.Helpers.Export {
}; };
} }
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs) { public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs) {
var mgnr = json["mgnr"]!.AsValue().GetValue<int>(); var mgnr = json["mgnr"]!.AsValue().GetValue<int>();
var kgnr = json["default_kgnr"]?.AsValue().GetValue<int>(); var kgnr = json["default_kgnr"]?.AsValue().GetValue<int>();
if (kgnr != null && !kgs.Values.Any(k => k.WbKg?.KgNr == kgnr)) { if (kgnr != null && !currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten Herkunftshierarchie)"); throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
} }
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>();
@@ -815,10 +817,13 @@ namespace Elwig.Helpers.Export {
}; };
} }
public static (AreaComContract, List<(AreaCom, (DateTime CreatedAt, DateTime ModifiedAt)?)>, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaComContract(this JsonNode json, Dictionary<int, List<WbRd>> riede) { public static (AreaComContract, List<(AreaCom, (DateTime CreatedAt, DateTime ModifiedAt)?)>, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaComContract(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs, 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>();
var fbnr = json["fbnr"]!.AsValue()!.GetValue<int>(); var fbnr = json["fbnr"]!.AsValue()!.GetValue<int>();
if (!currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
}
WbRd? rd = null; WbRd? rd = null;
bool newRd = false; bool newRd = false;
if (ried != null) { if (ried != null) {
@@ -866,9 +871,12 @@ namespace Elwig.Helpers.Export {
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None))); DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
} }
public static (AreaComContract, AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) { public static (AreaComContract, AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs, 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>();
if (!currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
}
WbRd? rd = null; WbRd? rd = null;
bool newRd = false; bool newRd = false;
if (ried != null) { if (ried != null) {
@@ -957,7 +965,7 @@ namespace Elwig.Helpers.Export {
return obj; return obj;
} }
public static (Delivery, List<(DeliveryPart, (DateTime CreatedAt, DateTime ModifiedAt)?)>, 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, (DateTime CreatedAt, DateTime ModifiedAt)?)>, 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, WbKg> currentWbKgs, Dictionary<int, List<WbRd>> riede) {
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);
@@ -983,6 +991,9 @@ namespace Elwig.Helpers.Export {
}, [.. json["parts"]!.AsArray().Select(p => p!.AsObject()).Select<JsonObject, (DeliveryPart, (DateTime, DateTime)?)>(p => { }, [.. json["parts"]!.AsArray().Select(p => p!.AsObject()).Select<JsonObject, (DeliveryPart, (DateTime, DateTime)?)>(p => {
var kgnr = p["kgnr"]?.AsValue().GetValue<int>(); var kgnr = p["kgnr"]?.AsValue().GetValue<int>();
var ried = p["ried"]?.AsValue().GetValue<string>(); var ried = p["ried"]?.AsValue().GetValue<string>();
if (kgnr != null && !currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
}
WbRd? rd = null; WbRd? rd = null;
if (ried != null && kgnr != null) { if (ried != null && kgnr != null) {
var rde = riede.GetValueOrDefault(kgnr.Value, []); var rde = riede.GetValueOrDefault(kgnr.Value, []);
+43 -3
View File
@@ -1,6 +1,7 @@
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.IO.Hashing; using System.IO.Hashing;
@@ -134,11 +135,39 @@ namespace Elwig.Helpers {
return await cmd.ExecuteScalarAsync(); return await cmd.ExecuteScalarAsync();
} }
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(this SqliteConnection cnx) { public static async Task QuickCheck(this SqliteConnection cnx) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA quick_check";
using var reader = await cmd.ExecuteReaderAsync();
var list = new List<string>();
while (await reader.ReadAsync()) {
list.Add(reader.GetString(0));
}
if (list.Count != 1 || list[0] != "ok") {
throw new FileFormatException($"Integrity problems ({list.Count}):\n" + string.Join("\n", list.Take(50)));
}
}
public static async Task IntegrityCheck(this SqliteConnection cnx) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA integrity_check";
using var reader = await cmd.ExecuteReaderAsync();
var list = new List<string>();
while (await reader.ReadAsync()) {
list.Add(reader.GetString(0));
}
if (list.Count != 1 || list[0] != "ok") {
throw new FileFormatException($"Integrity problems ({list.Count}):\n" + string.Join("\n", list.Take(50)));
}
}
public static async Task ForeignKeyCheck(this SqliteConnection cnx) {
using var cmd = cnx.CreateCommand(); using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA foreign_key_check"; cmd.CommandText = "PRAGMA foreign_key_check";
using var reader = await cmd.ExecuteReaderAsync(); using var reader = await cmd.ExecuteReaderAsync();
var list = new List<(string, long, string, long)>(); var list = new List<(string Table, long RowId, string Parent, long FkId)>();
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
var table = reader.GetString(0); var table = reader.GetString(0);
var rowid = reader.GetInt64(1); var rowid = reader.GetInt64(1);
@@ -146,7 +175,11 @@ namespace Elwig.Helpers {
var fkid = reader.GetInt64(3); var fkid = reader.GetInt64(3);
list.Add((table, rowid, parent, fkid)); list.Add((table, rowid, parent, fkid));
} }
return [.. list];
if (list.Count > 0) {
throw new InvalidDataException($"Foreign key violations ({list.Count}):\n" + string.Join("\n", list.Take(50)
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
}
} }
public static IEnumerable<T> Join<T>(this IEnumerable<T> src, Func<T> separatorFactory) { public static IEnumerable<T> Join<T>(this IEnumerable<T> src, Func<T> separatorFactory) {
@@ -171,5 +204,12 @@ namespace Elwig.Helpers {
grid.Children.Add(tb); grid.Children.Add(tb);
} }
public static (int Year, int Week) GetYearAndWeek(this DateTime date) {
return (ISOWeek.GetYear(date), ISOWeek.GetWeekOfYear(date));
}
public static (int Year, int Tertial) GetYearAndTertial(this DateTime date) {
return (date.Month <= 7 ? date.Year - 1 : date.Year, date.Month == 12 || date.Month <= 3 ? 2 : date.Month <= 7 ? 3 : 1);
}
} }
} }
+3 -7
View File
@@ -1,20 +1,17 @@
using Elwig.Services; using Elwig.Services;
using Elwig.Windows; using Elwig.Windows;
using System; using System;
using System.Threading.Tasks;
namespace Elwig.Helpers.Printing { namespace Elwig.Helpers.Printing {
public static class Pdf { public static class Pdf {
public static Task Init(Action? evtHandler = null) { public static void Init(Action? evtHandler = null) {
PdfiumNative.FPDF_InitLibrary(); PdfiumNative.FPDF_InitLibrary();
evtHandler?.Invoke(); evtHandler?.Invoke();
return Task.CompletedTask;
} }
public static Task Cleanup() { public static void Cleanup() {
PdfiumNative.FPDF_DestroyLibrary(); PdfiumNative.FPDF_DestroyLibrary();
return Task.CompletedTask;
} }
public static void Show(TempFile file, string title) { public static void Show(TempFile file, string title) {
@@ -31,14 +28,13 @@ namespace Elwig.Helpers.Printing {
}); });
} }
public static Task Print(string path, int copies = 1, bool doublePaged = false) { public static void Print(string path, int copies = 1, bool doublePaged = false) {
try { try {
var printer = new PdfPrinter(); var printer = new PdfPrinter();
printer.Print(path, copies, doublePaged); printer.Print(path, copies, doublePaged);
} catch (Exception exc) { } catch (Exception exc) {
InteractionService.ShowException("Fehler beim Drucken", "Beim Drucken ist ein Fehler aufgetreten", exc); InteractionService.ShowException("Fehler beim Drucken", "Beim Drucken ist ein Fehler aufgetreten", exc);
} }
return Task.CompletedTask;
} }
} }
} }
-1
View File
@@ -45,7 +45,6 @@ namespace Elwig.Helpers {
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);
public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 6 ? 1 : 0); public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 6 ? 1 : 0);
public static int FollowingSeason => DateTime.Now.Year + (DateTime.Now.Month >= 11 ? 1 : 0); public static int FollowingSeason => DateTime.Now.Year + (DateTime.Now.Month >= 11 ? 1 : 0);
public static DateTime Today => (DateTime.Now.Hour >= 3) ? DateTime.Today : DateTime.Today.AddDays(-1);
[GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)] [GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)]
private static partial Regex GeneratedSerialRegex(); private static partial Regex GeneratedSerialRegex();
+133
View File
@@ -0,0 +1,133 @@
using Elwig.Helpers;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Elwig.Services {
public static class BackupService {
public static bool IsAnyBackupUpToDate {
get {
var lastDbWrite = File.GetLastWriteTime(App.Config.DatabaseFile);
lastDbWrite += new TimeSpan(0, 0, -1);
return Directory.GetFiles(App.Config.BackupPath, "????-??-??_??-??-??.*.zip")
.Any(n => DateTime.ParseExact(Path.GetFileName(n)[..19], "yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.None) >= lastDbWrite);
}
}
public static async Task TryDatabaseBackup() {
if (IsAnyBackupUpToDate) return;
await BackupDatabase();
PruneBackups();
}
public static async Task BackupDatabase() {
await Database.ExportSql(Path.Combine(App.Config.BackupPath, $"{File.GetLastWriteTime(App.Config.DatabaseFile):yyyy-MM-dd_HH-mm-ss}.sql.zip"), true);
}
public static void PruneBackups() {
var retain = new HashSet<string>();
var backups = Directory.GetFiles(App.Config.BackupPath, "????-??-??_??-??-??.*.zip")
.Select(n => (FileName: n, Timestamp: DateTime.ParseExact(Path.GetFileName(n)[..19], "yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.None)))
.OrderByDescending(t => t.Timestamp).ToArray();
DateTime now;
// retain hourly backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainHours; i++, now = now.AddHours(-1)) {
foreach (var b in backups) {
if (b.Timestamp.Date == now.Date && b.Timestamp.Hour == now.Hour) {
retain.Add(b.FileName);
break;
}
}
}
// retain daily backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainDays; i++, now = now.AddDays(-1)) {
foreach (var b in backups) {
if (b.Timestamp.Date == now.Date) {
retain.Add(b.FileName);
break;
}
}
}
// retain weekly backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainWeeks; i++, now = now.AddDays(-7)) {
foreach (var b in backups) {
if (b.Timestamp.GetYearAndWeek() == now.GetYearAndWeek()) {
retain.Add(b.FileName);
break;
}
}
}
// retain monthly backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainMonths; i++, now = now.AddMonths(-1)) {
foreach (var b in backups) {
if (b.Timestamp.Year == now.Year && b.Timestamp.Month == now.Month) {
retain.Add(b.FileName);
break;
}
}
}
// retain tertial backups
now = new DateTime(DateTime.Now.Year + 1, 3, 1);
for (int i = 0; i < 600; i++, now = now.AddMonths(-4)) {
foreach (var b in backups) {
if (b.Timestamp.GetYearAndTertial() == now.GetYearAndTertial()) {
retain.Add(b.FileName);
break;
}
}
}
// delete non-retained files
foreach (var b in backups) {
if (retain.Contains(b.FileName)) continue;
try {
File.Delete(b.FileName);
} catch { }
}
// delete temp files
foreach (var f in Directory.GetFiles(App.Config.BackupPath, "*.tmp")) {
try {
File.Delete(f);
} catch { }
}
}
public static async Task RestoreDatabase() {
try {
var d = new OpenFileDialog() {
Title = "Datenbank wiederherstellen - Elwig",
DefaultDirectory = App.Config.BackupPath,
InitialDirectory = App.Config.BackupPath,
DefaultExt = "sql.zip",
Filter = "SQLite-Datenbank (*.sqlite3, *.sqlite3.zip, *.sql, *.sql.zip)|*.sqlite3;*.sqlite3.zip;*.sql;*.sql.zip",
};
if (d.ShowDialog() == true) {
if (!InteractionService.AskContinue("Datenbank wiederherstellen", "Soll die Datenbank wirklich unwiederruflich durch die wiederhergestellte Version ersetzt werden?"))
return;
Mouse.OverrideCursor = Cursors.Wait;
await App.ReplaceDatabase(d.FileName);
}
} catch (Exception exc) {
InteractionService.ShowException(exc);
}
Mouse.OverrideCursor = null;
}
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ namespace Elwig.Services {
deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == s.Year && a.DsNr == s.DsNr); deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == s.Year && a.DsNr == s.DsNr);
filterNames.Add($"{s.Date:dd.MM.yyyy} {s.Branch.Name} {s.Description}"); filterNames.Add($"{s.Date:dd.MM.yyyy} {s.Branch.Name} {s.Description}");
} else { } else {
deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == vm.FilterSeason && (!vm.FilterOnlyUpcoming || a.Schedule.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0)); deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == vm.FilterSeason && (!vm.FilterOnlyUpcoming || a.Schedule.DateString.CompareTo(DateTime.Today.ToString("yyyy-MM-dd")) >= 0));
filterNames.Add($"{vm.FilterSeason}"); filterNames.Add($"{vm.FilterSeason}");
} }
+2 -2
View File
@@ -49,8 +49,8 @@ namespace Elwig.Services {
filterNames.Add($"{vm.FilterSeason}"); filterNames.Add($"{vm.FilterSeason}");
} }
if (vm.FilterOnlyUpcoming) { if (vm.FilterOnlyUpcoming) {
deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0); deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.DateString.CompareTo(DateTime.Today.ToString("yyyy-MM-dd")) >= 0);
filterNames.Add($"ab {Utils.Today:dd.MM.yyyy}"); filterNames.Add($"ab {DateTime.Today:dd.MM.yyyy}");
} }
var filterVar = new List<string>(); var filterVar = new List<string>();
+48 -12
View File
@@ -72,6 +72,45 @@ namespace Elwig.Services {
vm.ManualWeighingReason = p.WeighingReason; vm.ManualWeighingReason = p.WeighingReason;
} }
public static (DateTime From, DateTime To)? GetFilterToday(DateTime today, IEnumerable<DateTime> timestamps) {
var filtered = timestamps.Where(t => t >= today.AddDays(-1) && t < today.AddDays(1)).Order().ToArray();
if (filtered.Length == 0) return null;
var latest = filtered.Last();
var since = filtered.Cast<DateTime>().Reverse().Aggregate(latest, (s, c) => s.Subtract(c).TotalHours <= 6 ? c : s);
latest += new TimeSpan(0, 1, -latest.Minute, -latest.Second, -latest.Millisecond, -latest.Microsecond);
since += new TimeSpan(0, 0, -since.Minute, -since.Second, -since.Millisecond, -since.Microsecond);
if (since.Subtract(today) <= new TimeSpan(-6, 0, 0) && latest.Subtract(today) < new TimeSpan(0, 0, 0))
return null;
return (since, latest);
}
private static async Task<(Expression<Func<Delivery, bool>>, string)> GetFilterToday(AppDbContext ctx) {
var today = DateTime.Today;
var timestamps = (await ctx.Deliveries
.Where(d => d.DateString.CompareTo(today.AddDays(-1).ToString("yyyy-MM-dd")) >= 0)
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString)
.Select(d => d.DateString + " " + d.TimeString)
.ToListAsync())
.Select(s => DateTime.TryParseExact(s, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? (DateTime?)dt : null)
.Where(t => t.HasValue)
.Cast<DateTime>()
.ToArray();
var res = GetFilterToday(today, timestamps);
if (!res.HasValue) {
return (_ => false, today.ToString("dd.MM.yyyy"));
} else {
var from = res.Value.From;
var to = res.Value.To;
return (d => (d.DateString == from.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo(from.ToString("HH:mm:ss")) >= 0)) ||
(d.DateString.CompareTo(from.ToString("yyyy-MM-dd")) > 0),
from.Date == to.Date ? from.ToString("dd.MM.yyyy") : $"{from:dd.MM.yyyy \\a\\b HH:mm} / {to:dd.MM.yyyy \\b\\i\\s HH:mm}");
}
}
public static async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, Predicate<DeliveryPart>, List<string>)> GetFilters(this DeliveryAdminViewModel vm, AppDbContext ctx) { public static async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, Predicate<DeliveryPart>, List<string>)> GetFilters(this DeliveryAdminViewModel vm, AppDbContext ctx) {
List<string> filterNames = []; List<string> filterNames = [];
IQueryable<Delivery> deliveryQuery = ctx.Deliveries; IQueryable<Delivery> deliveryQuery = ctx.Deliveries;
@@ -84,10 +123,9 @@ namespace Elwig.Services {
filterNames.Add(vm.FilterMember.AdministrativeName); filterNames.Add(vm.FilterMember.AdministrativeName);
} }
if (vm.FilterTodayOnly) { if (vm.FilterTodayOnly) {
deliveryQuery = deliveryQuery var (pred, name) = await GetFilterToday(ctx);
.Where(d => (d.DateString == Utils.Today.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") > 0)) || deliveryQuery = deliveryQuery.Where(pred);
(d.DateString == Utils.Today.AddDays(1).ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") <= 0))); filterNames.Add(name);
filterNames.Add(Utils.Today.ToString("dd.MM.yyyy"));
} else if (!vm.FilterAllSeasons) { } else if (!vm.FilterAllSeasons) {
deliveryQuery = deliveryQuery.Where(d => d.Year == vm.FilterSeason); deliveryQuery = deliveryQuery.Where(d => d.Year == vm.FilterSeason);
filterNames.Add($"{vm.FilterSeason}"); filterNames.Add($"{vm.FilterSeason}");
@@ -730,10 +768,9 @@ namespace Elwig.Services {
query = q; query = q;
filterNames.AddRange(f); filterNames.AddRange(f);
} else if (subject == ExportSubject.FromToday) { } else if (subject == ExportSubject.FromToday) {
var date = $"{Utils.Today:yyyy-MM-dd}"; var (pred, name) = await GetFilterToday(ctx);
query = ctx.DeliveryParts query = ctx.Deliveries.Where(pred).SelectMany(d => d.Parts);
.Where(p => p.Delivery.DateString == date); filterNames.Add(name);
filterNames.Add($"{Utils.Today:dd.MM.yyyy}");
} else if (subject == ExportSubject.FromSeasonAndBranch) { } else if (subject == ExportSubject.FromSeasonAndBranch) {
query = ctx.DeliveryParts query = ctx.DeliveryParts
.Where(p => p.Year == Utils.CurrentLastSeason && p.Delivery.ZwstId == App.ZwstId); .Where(p => p.Year == Utils.CurrentLastSeason && p.Delivery.ZwstId == App.ZwstId);
@@ -805,10 +842,9 @@ namespace Elwig.Services {
query = q; query = q;
filterNames.AddRange(f); filterNames.AddRange(f);
} else if (subject == ExportSubject.FromToday) { } else if (subject == ExportSubject.FromToday) {
var date = $"{Utils.Today:yyyy-MM-dd}"; var (pred, name) = await GetFilterToday(ctx);
query = ctx.DeliveryParts query = ctx.Deliveries.Where(pred).SelectMany(d => d.Parts);
.Where(p => p.Delivery.DateString == date); filterNames.Add(name);
filterNames.Add($"{Utils.Today:dd.MM.yyyy}");
} else { } else {
throw new ArgumentException("Invalid value for ExportSubject"); throw new ArgumentException("Invalid value for ExportSubject");
} }
@@ -3,6 +3,7 @@ using Elwig.Models;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Elwig.ViewModels; using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,7 +12,7 @@ namespace Elwig.Services {
public static async Task InitInputs(this MemberBusinessSharesViewModel vm) { public static async Task InitInputs(this MemberBusinessSharesViewModel vm) {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
vm.DateNoticeString = $"{Utils.Today:dd.MM.yyyy}"; vm.DateNoticeString = $"{DateTime.Today:dd.MM.yyyy}";
vm.ValuePerShare = (await ctx.Seasons.OrderBy(s => s.Year).LastOrDefaultAsync())?.BusinessShareValue; vm.ValuePerShare = (await ctx.Seasons.OrderBy(s => s.Year).LastOrDefaultAsync())?.BusinessShareValue;
} }
+3 -3
View File
@@ -139,7 +139,7 @@ namespace Elwig.Windows {
if (await ctx.FetchSeasons(Utils.CurrentYear).SingleOrDefaultAsync() == null) { if (await ctx.FetchSeasons(Utils.CurrentYear).SingleOrDefaultAsync() == null) {
InteractionService.ShowWarning("Saison noch nicht erstellt", InteractionService.ShowWarning("Saison noch nicht erstellt",
"Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.\n\n" + "Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.\n\n" +
"(Stammdaten -> Saisons -> Neu anlegen...)"); "(Stammdaten \u2192 Saisons \u2192 Neu anlegen...)");
} }
} }
} }
@@ -685,7 +685,7 @@ namespace Elwig.Windows {
private async void TodayOnlyInput_Changed(object sender, RoutedEventArgs evt) { private async void TodayOnlyInput_Changed(object sender, RoutedEventArgs evt) {
if (!HasContextLoaded) return; if (!HasContextLoaded) return;
if (TodayOnlyInput.IsChecked == true && AllSeasonsInput.IsChecked == false) { if (TodayOnlyInput.IsChecked == true && AllSeasonsInput.IsChecked == false) {
ViewModel.FilterSeason = Utils.Today.Year; ViewModel.FilterSeason = DateTime.Today.Year;
ViewModel.FilterTodayOnly = true; ViewModel.FilterTodayOnly = true;
} }
await RefreshList(); await RefreshList();
@@ -1379,7 +1379,7 @@ namespace Elwig.Windows {
var kl = mod.Where(m => m.Name.StartsWith("Klasse ")).Select(m => m.ModId).LastOrDefault("_")[0]; var kl = mod.Where(m => m.Name.StartsWith("Klasse ")).Select(m => m.ModId).LastOrDefault("_")[0];
if (ViewModel.IsUnloadingPumped && (kl == 'A' || kl == '_')) { if (ViewModel.IsUnloadingPumped && (kl == 'A' || kl == '_')) {
kl = 'B'; kl = 'B';
} else if (ViewModel.IsUnloadingDumper && kl == '_') { } else if (ViewModel.IsUnloadingDumper && (kl == 'B' || kl == '_')) {
kl = 'A'; kl = 'A';
} else { } else {
kl = '_'; kl = '_';
@@ -92,7 +92,7 @@ namespace Elwig.Windows {
list.ForEach(v => v.Schedule.AnnouncedWeightOverride = v.AnnouncedWeight); list.ForEach(v => v.Schedule.AnnouncedWeightOverride = v.AnnouncedWeight);
var deliverySchedules = list.Select(v => v.Schedule).ToList(); 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(DateTime.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);
ControlUtils.RenewItemsSource(DeliveryScheduleInput, deliverySchedules, DeliveryScheduleInput_SelectionChanged); ControlUtils.RenewItemsSource(DeliveryScheduleInput, deliverySchedules, DeliveryScheduleInput_SelectionChanged);
} }
+3 -3
View File
@@ -108,7 +108,7 @@ namespace Elwig.Windows {
public MailWindow(int? year = null) { public MailWindow(int? year = null) {
InitializeComponent(); InitializeComponent();
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
Year = year ?? ctx.Seasons.OrderByDescending(s => s.Year).FirstOrDefault()?.Year ?? Utils.Today.Year; Year = year ?? ctx.Seasons.OrderByDescending(s => s.Year).FirstOrDefault()?.Year ?? DateTime.Today.Year;
Title = $"Rundschreiben - Lese {Year} - Elwig"; Title = $"Rundschreiben - Lese {Year} - Elwig";
} }
@@ -150,7 +150,7 @@ namespace Elwig.Windows {
PostalSender1.Text = App.Client.Sender1; PostalSender1.Text = App.Client.Sender1;
PostalSender2.Text = App.Client.Sender2; PostalSender2.Text = App.Client.Sender2;
PostalLocation.Text = App.BranchLocation; PostalLocation.Text = App.BranchLocation;
PostalDate.Text = $"{Utils.Today:dd.MM.yyyy}"; PostalDate.Text = $"{DateTime.Today:dd.MM.yyyy}";
EmailSubjectInput.Text = App.Client.TextEmailSubject ?? "Rundschreiben"; EmailSubjectInput.Text = App.Client.TextEmailSubject ?? "Rundschreiben";
EmailBodyInput.Text = App.Client.TextEmailBody ?? "Sehr geehrtes Mitglied,\n\nim Anhang finden Sie das aktuelle Rundschreiben.\n\nIhre Winzergenossenschaft\n"; EmailBodyInput.Text = App.Client.TextEmailBody ?? "Sehr geehrtes Mitglied,\n\nim Anhang finden Sie das aktuelle Rundschreiben.\n\nIhre Winzergenossenschaft\n";
} }
@@ -438,7 +438,7 @@ namespace Elwig.Windows {
private void Date_LostFocus(object sender, RoutedEventArgs evt) { private void Date_LostFocus(object sender, RoutedEventArgs evt) {
var res = Validator.CheckDate((TextBox)sender, true); var res = Validator.CheckDate((TextBox)sender, true);
if (!res.IsValid) ((TextBox)sender).Text = $"{Utils.Today:dd.MM.yyyy}"; if (!res.IsValid) ((TextBox)sender).Text = $"{DateTime.Today:dd.MM.yyyy}";
} }
private async Task UpdateRecipients(AppDbContext ctx) { private async Task UpdateRecipients(AppDbContext ctx) {
+15 -25
View File
@@ -50,23 +50,28 @@ namespace Elwig.Windows {
_syncTimer.Start(); _syncTimer.Start();
} }
private void Window_Closing(object sender, CancelEventArgs evt) { private async void Window_Closing(object sender, CancelEventArgs evt) {
evt.Cancel = !App.ForceShutdown;
if (App.NumWindows > 1 && !App.ForceShutdown) { if (App.NumWindows > 1 && !App.ForceShutdown) {
foreach (var w in App.Current.Windows.Cast<Window>().Where(w => ((w as AdministrationWindow)?.IsEditing ?? false) || ((w as AdministrationWindow)?.IsCreating ?? false))) { foreach (var w in Application.Current.Windows.Cast<Window>().Where(w => ((w as AdministrationWindow)?.IsEditing ?? false) || ((w as AdministrationWindow)?.IsCreating ?? false))) {
try { try {
w.Close(); w.Close();
} catch { } } catch { }
} }
Thread.Sleep(100); Thread.Sleep(100);
if (App.NumWindows > 1 && !App.Current.Windows.Cast<Window>().Any(w => ((w as AdministrationWindow)?.IsEditing ?? false) || ((w as AdministrationWindow)?.IsCreating ?? false))) { if (App.NumWindows > 1 && !Application.Current.Windows.Cast<Window>().Any(w => ((w as AdministrationWindow)?.IsEditing ?? false) || ((w as AdministrationWindow)?.IsCreating ?? false))) {
if (!InteractionService.AskConfirmation("Elwig beenden", "Es sind noch weitere Fenster geöffnet.\nSollen alle Fenster geschlossen werden?")) { if (!InteractionService.AskConfirmation("Elwig beenden", "Es sind noch weitere Fenster geöffnet.\nSollen alle Fenster geschlossen werden?")) {
evt.Cancel = true; return;
} else { }
App.ForceShutdown = true;
}
}
Mouse.OverrideCursor = Cursors.AppStarting;
await (Application.Current as App)!.OnClosing();
Application.Current.Shutdown(); Application.Current.Shutdown();
} }
}
}
}
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) { private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
var w = new AboutWindow(); var w = new AboutWindow();
@@ -162,7 +167,7 @@ namespace Elwig.Windows {
private async void Menu_Database_Backup_Click(object sender, RoutedEventArgs evt) { private async void Menu_Database_Backup_Click(object sender, RoutedEventArgs evt) {
try { try {
var filename = InteractionService.SaveFile("Datenbank-Sicherung", $"database_{Utils.Today:yyyy-MM-dd}", "sql.zip"); var filename = InteractionService.SaveFile("Datenbank-Sicherung", $"database_{DateTime.Today:yyyy-MM-dd}", "sql.zip");
if (filename != null) { if (filename != null) {
if (!filename.EndsWith(".sql.zip")) filename += ".sql.zip"; if (!filename.EndsWith(".sql.zip")) filename += ".sql.zip";
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
@@ -177,22 +182,7 @@ namespace Elwig.Windows {
} }
private async void Menu_Database_Restore_Click(object sender, RoutedEventArgs evt) { private async void Menu_Database_Restore_Click(object sender, RoutedEventArgs evt) {
try { await BackupService.RestoreDatabase();
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) {
if (!InteractionService.AskContinue("Datenbank wiederherstellen", "Soll die Datenbank wirklich unwiederruflich durch die wiederhergestellte Version ersetzt werden?"))
return;
Mouse.OverrideCursor = Cursors.Wait;
await App.ReplaceDatabase(d.FileName);
}
} catch (Exception exc) {
InteractionService.ShowException(exc);
}
Mouse.OverrideCursor = null;
} }
private async void SyncButton_Click(object sender, RoutedEventArgs evt) { private async void SyncButton_Click(object sender, RoutedEventArgs evt) {
@@ -198,7 +198,7 @@ namespace Elwig.Windows {
int mgnr; int mgnr;
try { try {
mgnr = await ViewModel.UpdateMemberHistory(ViewModel.SelectedHistoryEntry?.HistNr, IsEditing || ViewModel.DateNotice >= DateOnly.FromDateTime(Utils.Today).AddDays(-14)); mgnr = await ViewModel.UpdateMemberHistory(ViewModel.SelectedHistoryEntry?.HistNr, IsEditing || ViewModel.DateNotice >= DateOnly.FromDateTime(DateTime.Today).AddDays(-14));
App.HintContextChange(); App.HintContextChange();
} catch (Exception exc) { } catch (Exception exc) {
InteractionService.ShowDbException("Geschäftsanteilbewegung aktualisieren", exc); InteractionService.ShowDbException("Geschäftsanteilbewegung aktualisieren", exc);
+1
View File
@@ -13,5 +13,6 @@ Data and configuration folder
C:\ProgramData\Elwig\ C:\ProgramData\Elwig\
- config.ini : main configuration file - config.ini : main configuration file
- database.sqlite3 : stores all data - database.sqlite3 : stores all data
- backups\ : (automatic) backups of the database
- imported.txt : list of all imported *.elwig.zip files to not automatically import them again - imported.txt : list of all imported *.elwig.zip files to not automatically import them again
- mails\ : sent/outgoing email log - mails\ : sent/outgoing email log
+11
View File
@@ -3,6 +3,7 @@
; Only needed, if more than one branch is stored in database ; Only needed, if more than one branch is stored in database
;branch = Zweigstelle ;branch = Zweigstelle
;debug = true ;debug = true
;weighing = gross ; gross/net/box
[database] [database]
; Relative or absolute path to database file ; Relative or absolute path to database file
@@ -10,6 +11,14 @@ file = database.sqlite3
; Enables database logging ; Enables database logging
;log = db.log ;log = db.log
[backup]
;auto = false
;path = backups
;retain_hours = 36
;retain_days = 8
;retain_weeks = 6
;retain_months = 18
[update] [update]
url = https://elwig.at/files/elwig/latest url = https://elwig.at/files/elwig/latest
auto = true auto = true
@@ -38,6 +47,8 @@ auto = true
;limit = 3500 ;limit = 3500
; Enables scale logging ; Enables scale logging
;log = waage.log ;log = waage.log
; Opt-out of auto time synchronization at startup
;synctime = false
;[scale.B] ;[scale.B]
;type = Avery-Async ;type = Avery-Async
+2 -2
View File
@@ -13,7 +13,7 @@ About
**Product:** Elwig **Product:** Elwig
**Description:** Electronic Management for Vintners' Cooperatives **Description:** Electronic Management for Vintners' Cooperatives
**Type:** ERP system **Type:** ERP system
**Version:** 1.1.1.1 ([Changelog](./CHANGELOG.md)) **Version:** 1.1.1.2 ([Changelog](./CHANGELOG.md))
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE) **License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/ **Website:** https://elwig.at/
**Source code:** https://git.necronda.net/winzer/elwig **Source code:** https://git.necronda.net/winzer/elwig
@@ -33,7 +33,7 @@ Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
**Produkt:** Elwig **Produkt:** Elwig
**Beschreibung:** Elektronische Winzergenossenschaftsverwaltung **Beschreibung:** Elektronische Winzergenossenschaftsverwaltung
**Typ:** Warenwirtschaftssystem (ERP-System) **Typ:** Warenwirtschaftssystem (ERP-System)
**Version:** 1.1.1.1 ([Änderungsprotokoll](./CHANGELOG.md)) **Version:** 1.1.1.2 ([Änderungsprotokoll](./CHANGELOG.md))
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE) **Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/ **Website:** https://elwig.at/
**Quellcode:** https://git.necronda.net/winzer/elwig **Quellcode:** https://git.necronda.net/winzer/elwig
+1 -1
View File
@@ -53,7 +53,7 @@ namespace Tests.E2ETests {
Session.App.FindElement(By.Name("Rundschreiben")).Click(); Session.App.FindElement(By.Name("Rundschreiben")).Click();
Thread.Sleep(Utils.WINDOW_OPEN_SLEEP); Thread.Sleep(Utils.WINDOW_OPEN_SLEEP);
var window = Session.CreateWindowDriver("MailWindow"); var window = Session.CreateWindowDriver("MailWindow");
Assert.That(window.Title, Is.EqualTo($"Rundschreiben - Lese {Elwig.Helpers.Utils.Today.Year} - Elwig")); Assert.That(window.Title, Is.EqualTo($"Rundschreiben - Lese {DateTime.Today.Year} - Elwig"));
window.Close(); window.Close();
}); });
} }
@@ -16,7 +16,7 @@ namespace Tests.UnitTests.DocumentTests {
""")); """));
Assert.That(text, Contains.Substring("0123463")); // Betriebsnummer Assert.That(text, Contains.Substring("0123463")); // Betriebsnummer
Assert.That(text, Contains.Substring("pauschaliert")); Assert.That(text, Contains.Substring("pauschaliert"));
Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}")); Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Today:dd.MM.yyyy}"));
Assert.That(text, Contains.Substring("Traubengutschrift Max Mustermann Probevariante")); Assert.That(text, Contains.Substring("Traubengutschrift Max Mustermann Probevariante"));
Assert.That(text, Contains.Substring("AT81 1234 5678 9012 3457")); Assert.That(text, Contains.Substring("AT81 1234 5678 9012 3457"));
Assert.That(text, Contains.Substring(""" Assert.That(text, Contains.Substring("""
@@ -21,7 +21,7 @@ namespace Tests.UnitTests.DocumentTests {
""")); """));
Assert.That(text, Contains.Substring("0123463")); // Betriebsnummer Assert.That(text, Contains.Substring("0123463")); // Betriebsnummer
Assert.That(text, Contains.Substring("pauschaliert")); Assert.That(text, Contains.Substring("pauschaliert"));
Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}")); Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Today:dd.MM.yyyy}"));
Assert.That(text, Contains.Substring("Anlieferungsbestätigung 2020")); Assert.That(text, Contains.Substring("Anlieferungsbestätigung 2020"));
Assert.That(text, Contains.Substring(""" Assert.That(text, Contains.Substring("""
20201001X001 1 Grüner Veltliner QUW 73 15,0 ungeb.: 3 219 3 219 20201001X001 1 Grüner Veltliner QUW 73 15,0 ungeb.: 3 219 3 219
+4 -4
View File
@@ -24,13 +24,13 @@ namespace Tests.UnitTests.DocumentTests {
} }
[OneTimeSetUp] [OneTimeSetUp]
public async Task SetupPrinting() { public void SetupPrinting() {
await Pdf.Init(); Pdf.Init();
} }
[OneTimeTearDown] [OneTimeTearDown]
public async Task TeardownPrinting() { public void TeardownPrinting() {
await Pdf.Cleanup(); Pdf.Cleanup();
} }
} }
} }
@@ -842,5 +842,151 @@ namespace Tests.UnitTests.ServiceTests {
Assert.That(await ctx.Deliveries.FindAsync(2022, 2), Is.Null); Assert.That(await ctx.Deliveries.FindAsync(2022, 2), Is.Null);
} }
} }
private static readonly DateTime[] DeliveryDates = [
new(2020, 10, 1, 17, 45, 0),
new(2020, 10, 1, 18, 30, 0),
new(2020, 10, 2, 8, 34, 0),
new(2020, 10, 2, 9, 45, 0),
new(2020, 10, 2, 10, 19, 0),
new(2020, 10, 2, 11, 22, 0),
new(2020, 10, 2, 13, 39, 0),
new(2020, 10, 2, 14, 2, 0),
new(2020, 10, 2, 15, 55, 0),
new(2020, 10, 2, 16, 28, 0),
new(2020, 10, 2, 17, 13, 0),
new(2020, 10, 2, 18, 10, 0),
new(2020, 10, 3, 8, 13, 0),
new(2020, 10, 3, 9, 39, 0),
new(2020, 10, 3, 10, 20, 0),
new(2020, 10, 3, 11, 46, 0),
new(2020, 10, 3, 13, 56, 0),
new(2020, 10, 3, 14, 4, 0),
new(2020, 10, 3, 15, 33, 0),
new(2020, 10, 3, 16, 23, 0),
new(2020, 10, 3, 17, 18, 0),
new(2020, 10, 3, 18, 19, 0),
new(2020, 10, 3, 19, 10, 0),
new(2020, 10, 3, 20, 25, 0),
new(2020, 10, 4, 5, 57, 0),
new(2020, 10, 4, 6, 13, 0),
new(2020, 10, 4, 7, 42, 0),
new(2020, 10, 4, 8, 34, 0),
new(2020, 10, 4, 9, 45, 0),
new(2020, 10, 4, 10, 19, 0),
new(2020, 10, 4, 11, 22, 0),
new(2020, 10, 4, 13, 39, 0),
new(2020, 10, 4, 14, 2, 0),
new(2020, 10, 4, 15, 55, 0),
new(2020, 10, 4, 16, 28, 0),
new(2020, 10, 4, 17, 13, 0),
new(2020, 10, 4, 18, 10, 0),
new(2020, 10, 5, 5, 57, 0),
new(2020, 10, 5, 6, 13, 0),
new(2020, 10, 5, 7, 42, 0),
new(2020, 10, 5, 8, 34, 0),
new(2020, 10, 5, 9, 45, 0),
new(2020, 10, 5, 10, 19, 0),
new(2020, 10, 5, 11, 22, 0),
new(2020, 10, 5, 13, 39, 0),
new(2020, 10, 5, 14, 2, 0),
new(2020, 10, 5, 15, 55, 0),
new(2020, 10, 5, 16, 28, 0),
new(2020, 10, 5, 17, 13, 0),
new(2020, 10, 5, 18, 10, 0),
new(2020, 10, 5, 19, 10, 0),
new(2020, 10, 5, 20, 25, 0),
new(2020, 10, 6, 16, 23, 0),
new(2020, 10, 6, 22, 24, 0),
new(2020, 10, 6, 23, 45, 0),
new(2020, 10, 7, 0, 32, 0),
new(2020, 10, 7, 1, 12, 0),
new(2020, 10, 7, 2, 16, 0),
new(2020, 10, 7, 3, 24, 0),
new(2020, 10, 7, 4, 9, 0),
new(2020, 10, 7, 5, 57, 0),
new(2020, 10, 7, 6, 41, 0),
new(2020, 10, 7, 7, 10, 0),
new(2020, 10, 7, 17, 23, 0),
new(2020, 10, 7, 18, 16, 0),
new(2020, 10, 7, 19, 39, 0),
new(2020, 10, 7, 20, 31, 0),
new(2020, 10, 7, 21, 23, 0),
new(2020, 10, 7, 22, 24, 0),
new(2020, 10, 7, 23, 45, 0),
new(2020, 10, 8, 0, 32, 0),
new(2020, 10, 8, 1, 12, 0),
new(2020, 10, 8, 2, 16, 0),
new(2020, 10, 8, 3, 24, 0),
new(2020, 10, 8, 4, 9, 0),
new(2020, 10, 8, 5, 57, 0),
new(2020, 10, 8, 6, 41, 0),
new(2020, 10, 8, 7, 10, 0),
new(2020, 10, 9, 7, 53, 0),
new(2020, 10, 9, 12, 36, 0),
new(2020, 10, 9, 16, 26, 0),
new(2020, 10, 9, 20, 19, 0),
];
[Test]
public void Test_GetFilterToday_01_Empty() {
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 10), DeliveryDates),
Is.Null);
}
[Test]
public void Test_GetFilterToday_02_Normal() {
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 2), DeliveryDates),
Is.EqualTo((new DateTime(2020, 10, 2, 8, 0, 0), new DateTime(2020, 10, 2, 19, 0, 0))));
}
[Test]
public void Test_GetFilterToday_03_Overtime() {
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 3), DeliveryDates),
Is.EqualTo((new DateTime(2020, 10, 3, 8, 0, 0), new DateTime(2020, 10, 3, 21, 0, 0))));
}
[Test]
public void Test_GetFilterToday_04_EarlyStart() {
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 4), DeliveryDates),
Is.EqualTo((new DateTime(2020, 10, 4, 5, 0, 0), new DateTime(2020, 10, 4, 19, 0, 0))));
}
[Test]
public void Test_GetFilterToday_05_EarlyStartAndOvertime() {
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 5), DeliveryDates),
Is.EqualTo((new DateTime(2020, 10, 5, 5, 0, 0), new DateTime(2020, 10, 5, 21, 0, 0))));
}
[Test]
public void Test_GetFilterToday_06_NightShift() {
using (Assert.EnterMultipleScope()) {
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 6), DeliveryDates),
Is.EqualTo((new DateTime(2020, 10, 6, 22, 0, 0), new DateTime(2020, 10, 7, 0, 0, 0))));
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 7), DeliveryDates.Where(d => d <= new DateTime(2020, 10, 7, 12, 0, 0))),
Is.EqualTo((new DateTime(2020, 10, 6, 22, 0, 0), new DateTime(2020, 10, 7, 8, 0, 0))));
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 7), DeliveryDates.Where(d => d <= new DateTime(2020, 10, 7, 12, 0, 0)).Append(new(2020, 10, 7, 15, 10, 0))),
Is.EqualTo((new DateTime(2020, 10, 7, 15, 0, 0), new DateTime(2020, 10, 7, 16, 0, 0))));
}
}
[Test]
public void Test_GetFilterToday_07_EarlyStartNightShift() {
using (Assert.EnterMultipleScope()) {
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 7), DeliveryDates),
Is.EqualTo((new DateTime(2020, 10, 7, 17, 0, 0), new DateTime(2020, 10, 8, 0, 0, 0))));
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 8), DeliveryDates),
Is.EqualTo((new DateTime(2020, 10, 7, 17, 0, 0), new DateTime(2020, 10, 8, 8, 0, 0))));
Assert.That(DeliveryService.GetFilterToday(new(2020, 10, 8), DeliveryDates.Append(new(2020, 10, 8, 15, 10, 0))),
Is.EqualTo((new DateTime(2020, 10, 8, 15, 0, 0), new DateTime(2020, 10, 8, 16, 0, 0))));
}
}
} }
} }