Compare commits

..
32 Commits
Author SHA1 Message Date
lorenz.stechauner 0a93167ea7 Bump version to 1.1.2.1
Test / Run tests (push) Successful in 1m53s
Deploy / Build and Deploy (push) Successful in 1m51s
2026-08-27 11:33:48 +02:00
lorenz.stechauner 848a6d1ce0 MailWindow: Add Date header to email
Test / Run tests (push) Successful in 1m55s
2026-08-27 11:28:27 +02:00
lorenz.stechauner f0745eae4e Elwig: Fix LINQ queries containing DateTime.Today
Test / Run tests (push) Successful in 2m18s
2026-08-27 11:20:22 +02:00
lorenz.stechauner 8471ac9319 Bump version to 1.1.2.0
Test / Run tests (push) Successful in 2m34s
Deploy / Build and Deploy (push) Successful in 2m11s
2026-08-26 23:05:34 +02:00
lorenz.stechauner 7add96dfd5 Elwig: Update dependencies
Test / Run tests (push) Successful in 2m18s
2026-08-26 16:28:29 +02:00
lorenz.stechauner 2360402124 Tests: Update dependencies 2026-08-26 16:28:14 +02:00
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
lorenz.stechauner 7d5d7d9307 Bump version to 1.1.1.1
Test / Run tests (push) Successful in 2m20s
Deploy / Build and Deploy (push) Successful in 2m0s
2026-08-24 00:51:00 +02:00
lorenz.stechauner b468bbce31 MainWindow: Add individual commitments to Area Commitment export
Test / Run tests (push) Successful in 2m37s
2026-08-24 00:39:30 +02:00
lorenz.stechauner a79631c586 Bump version to 1.1.1.0
Test / Run tests (push) Successful in 3m10s
Deploy / Build and Deploy (push) Successful in 1m52s
2026-08-18 23:58:31 +02:00
lorenz.stechauner 20b6e11b7f Export/Bki: Honor vollablieferer flag for member
Test / Run tests (push) Successful in 2m44s
2026-08-18 16:26:29 +02:00
lorenz.stechauner 77e812f5e8 Bump version 1.1.0.3
Deploy / Build and Deploy (push) Successful in 2m1s
2026-08-17 23:53:32 +02:00
lorenz.stechauner 58bfb4f56d Tests: Update dependencies
Test / Run tests (push) Successful in 2m4s
2026-08-17 23:38:46 +02:00
lorenz.stechauner e6e140dc08 Elwig: Update dependencies 2026-08-17 23:38:43 +02:00
lorenz.stechauner eaddf0c3cb SyncService: Add distinction main/non-main device
Test / Run tests (push) Successful in 2m26s
2026-08-17 23:29:31 +02:00
lorenz.stechauner fc5ad82686 Printing: Print all copies as one single print job 2026-08-17 17:50:33 +02:00
lorenz.stechauner b76367f73a Bump version to 1.1.0.2
Test / Run tests (push) Successful in 1m56s
Deploy / Build and Deploy (push) Successful in 1m51s
2026-08-11 18:27:22 +02:00
lorenz.stechauner 76ea3513ac MainWindow: Delay email sending
Test / Run tests (push) Successful in 2m4s
2026-08-11 18:25:16 +02:00
lorenz.stechauner 07b197958d Printing: Try to duplex print with manual dialog
Test / Run tests (push) Successful in 2m12s
2026-08-11 18:09:51 +02:00
lorenz.stechauner 470c6e09e5 Printing: Change fit-to-printable-are to scale 1:1
Test / Run tests (push) Successful in 2m40s
2026-08-11 17:25:49 +02:00
lorenz.stechauner 20b19775b1 Printing: Use –FPDF_RenderPage instead of FPDF_RenderPageBitmap to allow vector graphics
Test / Run tests (push) Successful in 2m38s
2026-08-10 18:35:49 +02:00
lorenz.stechauner f13a836ebd Printing: Fix slight translocation to the bottom right
Test / Run tests (push) Successful in 3m2s
2026-08-10 13:10:10 +02:00
lorenz.stechauner 2bf926c84c Bump version to 1.1.0.1
Test / Run tests (push) Successful in 2m15s
Deploy / Build and Deploy (push) Successful in 1m59s
2026-08-05 17:17:27 +02:00
49 changed files with 1349 additions and 374 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ name: Test
on: on:
push: push:
branches: ["**"] branches: ["**"]
paths: ["Elwig/**", "Tests/**", "Installer/Files/*.exe", ".gitea/workflows/test.yaml"] paths: ["Elwig/**", "Tests/**", ".gitea/workflows/test.yaml"]
jobs: jobs:
test: test:
name: Run tests name: Run tests
+137 -1
View File
@@ -2,6 +2,142 @@
Changelog Changelog
========= =========
[v1.1.2.1][v1.1.2.1] (2026-08-27) {#v1.1.2.1}
---------------------------------------------
### Behobene Fehler {#v1.1.2.1-bugfixes}
* Im Anmeldungen-Fenster (`DeliveryAncmtAdminWindow`) und im Leseplanung-Fenster (`DeliveryScheduleAdminWindow`) kam es seit [v1.1.1.2](#v1.1.1.2) (2026-08-25) zu einem Absturz. (f0745eae4e)
### Sonstiges {#v1.1.2.1-misc}
* Bei E-Mails wird ab jetzt auch Datum und Uhrzeit gesetzt. (848a6d1ce0)
[v1.1.2.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.2.1
[v1.1.2.0][v1.1.2.0] (2026-08-26) {#v1.1.2.0}
---------------------------------------------
### Neue Funktionen {#v1.1.2.0-features}
* Eine Automatische Backup-/Sicherungs-Funktion für die Datenbank wurde hinzugefügt. (ca5b719630, cadac4302d)
### Behobene Fehler {#v1.1.2.0-bugfixes}
* Wenn am heutigen Tag noch keine Lieferung übernommen wurde, wurden im Lieferungen-/Übernahme-Fenster (`DeliveryAdminWindow`) mit dem Filter "Nur heute" auch Lieferungen des vorhergehenden Tages angezeigt. (c1690bf104)
### Sonstiges {#v1.1.2.0-misc}
* Abhängigkeiten aktualisiert. (2360402124, 7add96dfd5)
[v1.1.2.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.2.0
[v1.1.1.2][v1.1.1.2] (2026-08-25) {#v1.1.1.2}
---------------------------------------------
### 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)
### 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)
[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}
---------------------------------------------
### Sonstiges {#v1.1.1.1-misc}
* Beim Export der Flächenbindungen im Haupt-Fenster (`MainWindow`) werden neben der zusammengefassten gebundenen Fläche nun auch die jeweiligen Flächenindungen exportiert. (b468bbce31)
[v1.1.1.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.1.1
[v1.1.1.0][v1.1.1.0] (2026-08-19) {#v1.1.1.0}
---------------------------------------------
### Sonstiges {#v1.1.1.0-misc}
* Bei der BKI-Traubentransportscheinliste werden Volllieferanten/Vollablieferer nun auch als solche markiert. (20b6e11b7f)
[v1.1.1.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.1.0
[v1.1.0.3][v1.1.0.3] (2026-08-17) {#v1.1.0.3}
---------------------------------------------
### Sonstiges {#v1.1.0.3-misc}
* Beim Drucken mehrerer Kopien einer PDF-Datei wird nun nur mehr ein einzelner Druck-Auftrag verwendet. (fc5ad82686)
* Beim Synchronisieren können Geräte als Hauptgerät festgelegt werden. (eaddf0c3cb)
* Abhängigkeiten aktualisiert. (e6e140dc08, 58bfb4f56d)
[v1.1.0.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.0.3
[v1.1.0.2][v1.1.0.2] (2026-08-11) {#v1.1.0.2}
---------------------------------------------
### Neue Funktionen {#v1.1.0.2-features}
* Es ist nun möglich zwischen dem Senden von E-Mail einige Sekunden zu warten (Konfigurationsdatei -> `[smtp]` -> `wait = 0.5`). (76ea3513ac)
### Behobene Fehler {#v1.1.0.2-bugfixes}
* Beim Drucken von Dokumenten waren die Seitenränder nicht korrekt. (f13a836ebd, 20b19775b1, 470c6e09e5, 07b197958d)
[v1.1.0.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.0.2
[v1.1.0.1][v1.1.0.1] (2026-08-05) {#v1.1.0.1}
---------------------------------------------
### Neue Funktionen {#v1.1.0.1-features}
* Auf Lieferscheinen sind nun Waage und Terminal angegeben. (309f118739)
* Beim Starten von Elwig wird Datum/Uhrzeit auf allen angeschlossenen Waagen gesetzt. (2a3999f300)
### Behobene Fehler {#v1.1.0.1-bugfixes}
* Im Stammdaten-Fenster (`BaseDataWindow`) war es nach einem Fehler beim Bearbeiten bis zum Schließen des Fensters nicht mehr möglich zu speichern. ([#82][i82])
### Sonstiges {#v1.1.0.1-misc}
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`)...
* wurde auf 3 Knöpfen das Symbol "in neuem Fenster öffnen" hinzugefügt. (88616b2c0b)
* wird beim "Speichern" die Variante automatisch auch berechnet um Fehler/Missverständnisse zu vermeiden. ([#83][i83])
* Abhängigkeiten aktualisiert. (b445928c4d, 668030fbff, a8cc3c9cb9)
[v1.1.0.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.1.0.1
[i82]: https://git.necronda.net/winzer/elwig/issues/82
[i83]: https://git.necronda.net/winzer/elwig/issues/83
[v1.1.0.0][v1.1.0.0] (2026-07-09) {#v1.1.0.0} [v1.1.0.0][v1.1.0.0] (2026-07-09) {#v1.1.0.0}
--------------------------------------------- ---------------------------------------------
@@ -290,7 +426,7 @@ Changelog
[v1.0.3.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.0 [v1.0.3.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.0
[i50]: https://git.necronda.net/winzer/elwig/issues/50 [i50]: https://git.necronda.net/winzer/elwig/issues/50
[i66]: https://git.necronda.net/winzer/elwig/issues/60 [i66]: https://git.necronda.net/winzer/elwig/issues/66
[i71]: https://git.necronda.net/winzer/elwig/issues/71 [i71]: https://git.necronda.net/winzer/elwig/issues/71
[i73]: https://git.necronda.net/winzer/elwig/issues/73 [i73]: https://git.necronda.net/winzer/elwig/issues/73
+1 -8
View File
@@ -16,14 +16,7 @@
<dependency> <dependency>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly> </dependentAssembly>
</dependency> </dependency>
</assembly> </assembly>
+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() {
+8 -8
View File
@@ -9,10 +9,10 @@
<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.0.0</Version> <Version>1.1.2.1</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages> <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>App.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -23,21 +23,21 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="bblanchon.PDFium.Win32" Version="153.0.7988" /> <PackageReference Include="bblanchon.PDFium.Win32" Version="154.0.8021" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="itext" Version="9.7.0" /> <PackageReference Include="itext" Version="9.7.0" />
<PackageReference Include="itext.bouncy-castle-adapter" Version="9.7.0" /> <PackageReference Include="itext.bouncy-castle-adapter" Version="9.7.0" />
<PackageReference Include="LinqKit" Version="1.3.11" /> <PackageReference Include="LinqKit" Version="1.3.11" />
<PackageReference Include="MailKit" Version="4.17.0" /> <PackageReference Include="MailKit" Version="4.17.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.10" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.11" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.4129.50" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.4129.50" />
<PackageReference Include="NJsonSchema" Version="11.6.1" /> <PackageReference Include="NJsonSchema" Version="11.6.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.1.59" /> <PackageReference Include="ScottPlot.WPF" Version="5.1.59" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
<PackageReference Include="System.IO.Hashing" Version="10.0.10" /> <PackageReference Include="System.IO.Hashing" Version="10.0.11" />
<PackageReference Include="System.IO.Ports" Version="10.0.10" /> <PackageReference Include="System.IO.Ports" Version="10.0.11" />
<PackageReference Include="System.Management" Version="10.0.10" /> <PackageReference Include="System.Management" Version="10.0.11" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+2 -1
View File
@@ -69,7 +69,8 @@ namespace Elwig.Helpers {
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; } public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; } public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
public DbSet<MemberDeliveryPerVarietyRowSingle> MemberDeliveryPerVariantRows { get; private set; } public DbSet<MemberDeliveryPerVarietyRowSingle> MemberDeliveryPerVariantRows { get; private set; }
public DbSet<MemberAreaComsRowSingle> MemberAreaComsRows { get; private set; } public DbSet<MemberAreaComTotalsRowSingle> MemberAreaComTotalsRows { get; private set; }
public DbSet<MemberAreaComsRow> MemberAreaComsRows { get; private set; }
public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; } public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; } public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; } public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; }
+15 -8
View File
@@ -9,15 +9,22 @@ namespace Elwig.Helpers {
public static class AppDbUpdater { public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat! // Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 40; public static readonly int RequiredSchemaVersion = 41;
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}");
+30 -4
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,19 +39,30 @@ 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 = "";
public bool SyncMain = false;
public string? SmtpHost = null; public string? SmtpHost = null;
public int? SmtpPort = null; public int? SmtpPort = null;
@@ -57,9 +70,10 @@ namespace Elwig.Helpers {
public string? SmtpUsername = null; public string? SmtpUsername = null;
public string? SmtpPassword = null; public string? SmtpPassword = null;
public string? SmtpFrom = null; public string? SmtpFrom = null;
public (string Host, int Port, string Mode, string Username, string Password, string From)? Smtp => public double SmtpWait = 0;
public (string Host, int Port, string Mode, string Username, string Password, string From, double Wait)? Smtp =>
SmtpHost == null || SmtpPort == null || SmtpMode == null || SmtpUsername == null || SmtpPassword == null || SmtpFrom == null ? SmtpHost == null || SmtpPort == null || SmtpMode == null || SmtpUsername == null || SmtpPassword == null || SmtpFrom == null ?
null : (SmtpHost, (int)SmtpPort, SmtpMode, SmtpUsername, SmtpPassword, SmtpFrom); null : (SmtpHost, (int)SmtpPort, SmtpMode, SmtpUsername, SmtpPassword, SmtpFrom, SmtpWait);
public IList<ScaleConfig> Scales; public IList<ScaleConfig> Scales;
private readonly List<ScaleConfig> ScaleList = []; private readonly List<ScaleConfig> ScaleList = [];
@@ -80,11 +94,21 @@ 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"] ?? "";
SyncMain = TrueValues.Contains(config["sync:main"]?.ToLower());
SmtpHost = config["smtp:host"]; SmtpHost = config["smtp:host"];
SmtpPort = config["smtp:port"]?.All(char.IsAsciiDigit) == true && config["smtp:port"]?.Length > 0 ? int.Parse(config["smtp:port"]!) : null; SmtpPort = config["smtp:port"]?.All(char.IsAsciiDigit) == true && config["smtp:port"]?.Length > 0 ? int.Parse(config["smtp:port"]!) : null;
@@ -92,6 +116,7 @@ namespace Elwig.Helpers {
SmtpUsername = config["smtp:username"]; SmtpUsername = config["smtp:username"];
SmtpPassword = config["smtp:password"]; SmtpPassword = config["smtp:password"];
SmtpFrom = config["smtp:from"]; SmtpFrom = config["smtp:from"];
SmtpWait = double.TryParse(config["smtp:wait"], out var res) ? res : 0;
var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key).Order(); var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key).Order();
ScaleList.Clear(); ScaleList.Clear();
@@ -101,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"]
)); ));
} }
+6 -6
View File
@@ -6,7 +6,7 @@ using System;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
using Row = Tuple<(string?, string?, string?, string?, string, int, string, int), (string, int, string, string, string, int, string, double, double)>; using Row = Tuple<(string?, string?, string?, string?, string, int, string, bool, int), (string, int, string, string, string, int, string, double, double)>;
public class Bki : Csv<Row> { public class Bki : Csv<Row> {
@@ -29,7 +29,7 @@ namespace Elwig.Helpers.Export {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
using var cmd = cnx.CreateCommand(); using var cmd = cnx.CreateCommand();
cmd.CommandText = $""" cmd.CommandText = $"""
SELECT lfbis_nr, name, other_names, billing_name, address, plz, ort, area, SELECT lfbis_nr, name, other_names, billing_name, address, plz, ort, vollablieferer, area,
date, weight, type, sortid, qualid, year, hkid, kmw, oe date, weight, type, sortid, qualid, year, hkid, kmw, oe
FROM v_bki_delivery FROM v_bki_delivery
WHERE year = {year} WHERE year = {year}
@@ -38,8 +38,8 @@ namespace Elwig.Helpers.Export {
List<Row> rows = []; List<Row> rows = [];
while (await r.ReadAsync()) { while (await r.ReadAsync()) {
rows.Add(new( rows.Add(new(
(r.IsDBNull(0) ? null : r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetInt32(7)), (r.IsDBNull(0) ? null : r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetBoolean(7), r.GetInt32(8)),
(r.GetString(8), r.GetInt32(9), r.GetString(10), r.GetString(11), r.GetString(12), r.GetInt32(13), r.GetString(14), r.GetDouble(15), r.GetDouble(16)) (r.GetString(9), r.GetInt32(10), r.GetString(11), r.GetString(12), r.GetString(13), r.GetInt32(14), r.GetString(15), r.GetDouble(16), r.GetDouble(17))
)); ));
} }
@@ -52,14 +52,14 @@ namespace Elwig.Helpers.Export {
public override string FormatRow(Row row) { public override string FormatRow(Row row) {
var (member, delivery) = row; var (member, delivery) = row;
var (lfBisNr, familyName, name, billingName, address, plz, ort, area) = member; var (lfBisNr, familyName, name, billingName, address, plz, ort, vollablieferer, area) = member;
var (date, weight, type, sortid, qualid, year, hkid, kmw, oe) = delivery; var (date, weight, type, sortid, qualid, year, hkid, kmw, oe) = delivery;
var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName); var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName);
var (a1, a2) = Utils.SplitAddress(address); var (a1, a2) = Utils.SplitAddress(address);
var memberData = $"{lfBisNr};{n1};{n2};{a1};\t{a2};{plz};{ort}"; var memberData = $"{lfBisNr};{n1};{n2};{a1};\t{a2};{plz};{ort}";
var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}"; var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}";
var vollData = $"N;;;{area / 10_000.0}"; var vollData = $"{(vollablieferer ? "J" : "N")};{(vollablieferer ? area / 10_000.0 : null):N4};{(vollablieferer ? area / 10_000.0 : null):N4};{area / 10_000.0:N4}";
return $"{_clientData};{memberData};{deliveryData};{vollData}"; return $"{_clientData};{memberData};{deliveryData};{vollData}";
} }
+68 -56
View File
@@ -18,73 +18,85 @@ namespace Elwig.Helpers.Export {
} }
public static async Task ExportSqlite(string filename, bool zipFile) { public static async Task ExportSqlite(string filename, bool zipFile) {
if (zipFile) { var tmp = filename + ".tmp";
File.Delete(filename); try {
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create); File.Delete(tmp);
if (zipFile) {
using var zip = ZipFile.Open(tmp, 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)) {
await writer.WriteAsync("elwig:1"); await writer.WriteAsync("elwig:1");
}
var (applId, userVers, schemaVers, size) = await GetMeta();
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
var obj = new JsonObject {
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName,
["database"] = new JsonObject {
["application_id"] = applId,
["user_version"] = userVers,
["schema_version"] = schemaVers,
["file_size"] = size,
},
};
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
}
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
} else {
File.Copy(App.Config.DatabaseFile, tmp);
} }
File.Move(tmp, filename, true);
var (applId, userVers, schemaVers, size) = await GetMeta(); } finally {
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression); File.Delete(tmp);
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
var obj = new JsonObject {
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName,
["database"] = new JsonObject {
["application_id"] = applId,
["user_version"] = userVers,
["schema_version"] = schemaVers,
["file_size"] = size,
},
};
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
}
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
} else {
File.Copy(App.Config.DatabaseFile, filename, true);
} }
} }
public static async Task ExportSql(string filename, bool zipFile) { public static async Task ExportSql(string filename, bool zipFile) {
if (zipFile) { var tmp = filename + ".tmp";
File.Delete(filename); try {
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create); File.Delete(tmp);
if (zipFile) {
using var zip = ZipFile.Open(tmp, 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)) {
await writer.WriteAsync("elwig:1"); await writer.WriteAsync("elwig:1");
} }
var (applId, userVers, schemaVers, size) = await GetMeta(); var (applId, userVers, schemaVers, size) = await GetMeta();
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression); var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) { using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
var obj = new JsonObject { var obj = new JsonObject {
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}", ["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
["zwstid"] = App.ZwstId, ["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName, ["device"] = Environment.MachineName,
["database"] = new JsonObject { ["database"] = new JsonObject {
["application_id"] = applId, ["application_id"] = applId,
["user_version"] = userVers, ["user_version"] = userVers,
["schema_version"] = schemaVers, ["schema_version"] = schemaVers,
["file_size"] = size, ["file_size"] = size,
}, },
}; };
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts)); await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
} }
var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize); var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) { using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) {
await ExportSql(writer);
}
} else {
using var stream = File.OpenWrite(tmp);
using var writer = new StreamWriter(stream, Utils.UTF8);
await ExportSql(writer); await ExportSql(writer);
} }
} else { File.Move(tmp, filename, true);
using var stream = File.OpenWrite(filename); } finally {
using var writer = new StreamWriter(stream, Utils.UTF8); File.Delete(tmp);
await ExportSql(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);
}
} }
} }
+5 -20
View File
@@ -1,21 +1,17 @@
using Elwig.Services; using Elwig.Services;
using Elwig.Windows; using Elwig.Windows;
using System; using System;
using System.Drawing.Printing;
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) {
@@ -32,24 +28,13 @@ namespace Elwig.Helpers.Printing {
}); });
} }
public static async Task Print(string path, int copies = 1, bool doublePaged = false) { public static void Print(string path, int copies = 1, bool doublePaged = false) {
await Print(path, new() {
Copies = (short)copies,
Collate = true,
Duplex = doublePaged ? Duplex.Vertical : Duplex.Simplex,
});
}
public static Task Print(string path, PrinterSettings settings) {
try { try {
using var printDoc = new PdfPrintDocument(path) { var printer = new PdfPrinter();
PrinterSettings = settings, printer.Print(path, copies, doublePaged);
};
printDoc.Print();
} 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;
} }
} }
} }
-102
View File
@@ -1,102 +0,0 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.Runtime.InteropServices;
namespace Elwig.Helpers.Printing {
public class PdfPrintDocument : PrintDocument {
private readonly IntPtr _handle;
private readonly int _pageCount;
private readonly double _dpi;
private int _currentPage;
public PdfPrintDocument(string path, string? password = null, double dpi = 300.0) :
base() {
_handle = PdfiumNative.FPDF_LoadDocument(path, password);
_pageCount = PdfiumNative.FPDF_GetPageCount(_handle);
_dpi = dpi;
}
protected override void Dispose(bool disposing) {
PdfiumNative.FPDF_CloseDocument(_handle);
base.Dispose(disposing);
}
protected override void OnBeginPrint(PrintEventArgs evt) {
_currentPage = (PrinterSettings.FromPage != 0) ? (PrinterSettings.FromPage - 1) : 0;
base.OnBeginPrint(evt);
}
protected override void OnPrintPage(PrintPageEventArgs evt) {
if (_currentPage < _pageCount) {
IntPtr page = PdfiumNative.FPDF_LoadPage(_handle, _currentPage);
double width = PdfiumNative.FPDF_GetPageWidth(page);
double height = PdfiumNative.FPDF_GetPageHeight(page);
int pixelWidth = (int)(width / 72.0 * _dpi);
int pixelHeight = (int)(height / 72.0 * _dpi);
IntPtr bitmap = PdfiumNative.FPDFBitmap_Create(pixelWidth, pixelHeight, 1);
PdfiumNative.FPDF_RenderPageBitmap(bitmap, page, 0, 0, pixelWidth, pixelHeight, 0, 0);
IntPtr buffer = PdfiumNative.FPDFBitmap_GetBuffer(bitmap);
int stride = PdfiumNative.FPDFBitmap_GetStride(bitmap);
using (var bmp = new Bitmap(pixelWidth, pixelHeight, stride, PixelFormat.Format32bppArgb, buffer)) {
evt.Graphics?.DrawImage(bmp, evt.PageBounds);
}
_currentPage++;
PdfiumNative.FPDFBitmap_Destroy(bitmap);
PdfiumNative.FPDF_ClosePage(page);
}
evt.HasMorePages = _currentPage < ((PrinterSettings.ToPage == 0) ? _pageCount : Math.Min(PrinterSettings.ToPage, _pageCount));
base.OnPrintPage(evt);
}
}
internal partial class PdfiumNative {
[LibraryImport("pdfium.dll")]
public static partial void FPDF_InitLibrary();
[LibraryImport("pdfium.dll")]
public static partial void FPDF_DestroyLibrary();
[LibraryImport("pdfium.dll", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr FPDF_LoadDocument(string filePath, string? password);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_CloseDocument(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial int FPDF_GetPageCount(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDF_LoadPage(IntPtr document, int pageIndex);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_ClosePage(IntPtr page);
[LibraryImport("pdfium.dll")]
public static partial double FPDF_GetPageWidth(IntPtr page);
[LibraryImport("pdfium.dll")]
public static partial double FPDF_GetPageHeight(IntPtr page);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDFBitmap_Create(int width, int height, int alpha);
[LibraryImport("pdfium.dll")]
public static partial void FPDFBitmap_Destroy(IntPtr bitmap);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDFBitmap_GetBuffer(IntPtr bitmap);
[LibraryImport("pdfium.dll")]
public static partial int FPDFBitmap_GetStride(IntPtr bitmap);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_RenderPageBitmap(IntPtr bitmap, IntPtr page, int start_x, int start_y, int size_x, int size_y, int rotate, int flags);
}
}
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.IO;
using System.Printing;
namespace Elwig.Helpers.Printing {
public class PdfPrinter {
public string PrinterName { get; init; }
public PdfPrinter(string? printerName = null) {
PrinterName = printerName ?? GetDefaultPrinterName() ?? throw new InvalidOperationException("No default Windows printer is configured.");
}
public void Print(string pdfPath, int copies = 1, bool doublePaged = false) {
if (!File.Exists(pdfPath)) throw new FileNotFoundException("PDF file not found.", pdfPath);
IntPtr document = PdfiumNative.FPDF_LoadDocument(pdfPath, null);
if (document == IntPtr.Zero) throw new InvalidOperationException("PDFium could not open the PDF.");
try {
int pageCount = PdfiumNative.FPDF_GetPageCount(document);
if (pageCount <= 0)
return;
using var printer = new Win32Printer.Printer(PrinterName);
printer.TrySetDuplex(doublePaged ? Duplexing.TwoSidedLongEdge : Duplexing.OneSided);
printer.StartDocument(Path.GetFileName(pdfPath));
bool documentStarted = true;
try {
for (int i = 0; i < copies; i++) {
for (int j = 0; j < pageCount; j++) {
PrintPage(document, printer, j);
}
}
printer.EndDocument();
documentStarted = false;
} finally {
if (documentStarted) printer.AbortDocument();
}
} finally {
PdfiumNative.FPDF_CloseDocument(document);
}
}
private static void PrintPage(IntPtr document, Win32Printer.Printer printer, int pageIndex) {
if (!PdfiumNative.FPDF_GetPageSizeByIndex(document, pageIndex, out double pageWidthPt, out double pageHeightPt)) {
throw new InvalidOperationException($"Unable to obtain PDF page {pageIndex} size.");
}
IntPtr page = PdfiumNative.FPDF_LoadPage(document, pageIndex);
if (page == IntPtr.Zero)
throw new InvalidOperationException($"Unable to load PDF page {pageIndex}.");
try {
int renderWidth = (int)Math.Round(pageWidthPt / 72.0 * printer.DpiX);
int renderHeight = (int)Math.Round(pageHeightPt / 72.0 * printer.DpiY);
int offsetX = (printer.PrintableWidth - renderWidth) / 2;
int offsetY = (printer.PrintableHeight - renderHeight) / 2;
printer.StartPage();
try {
PdfiumNative.FPDF_RenderPage(printer.Hdc, page, offsetX, offsetY, renderWidth, renderHeight, 0, PdfiumNative.FPDF_PRINTING | PdfiumNative.FPDF_ANNOT);
} finally {
printer.EndPage();
}
} finally {
PdfiumNative.FPDF_ClosePage(page);
}
}
private static string? GetDefaultPrinterName() {
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\Windows");
string? printer = key?.GetValue("Device") as string;
if (string.IsNullOrWhiteSpace(printer))
return null;
// Device has the form:
// Printer Name,winspool,PORT
int comma = printer.IndexOf(',');
return comma >= 0 ? printer[..comma] : printer;
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using System.Runtime.InteropServices;
namespace Elwig.Helpers.Printing {
internal partial class PdfiumNative {
public const int FPDF_ANNOT = 0x01;
public const int FPDF_PRINTING = 0x800;
[LibraryImport("pdfium.dll")]
public static partial void FPDF_InitLibrary();
[LibraryImport("pdfium.dll")]
public static partial void FPDF_DestroyLibrary();
[LibraryImport("pdfium.dll", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr FPDF_LoadDocument(string filePath, string? password);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_CloseDocument(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial int FPDF_GetPageCount(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDF_LoadPage(IntPtr document, int pageIndex);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_ClosePage(IntPtr page);
[LibraryImport("pdfium.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool FPDF_GetPageSizeByIndex(IntPtr document, int pageIndex, out double width, out double height);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_RenderPage(IntPtr hdc, IntPtr page, int startX, int startY, int sizeX, int sizeY, int rotate, int flags);
}
}
+239
View File
@@ -0,0 +1,239 @@
using System;
using System.ComponentModel;
using System.Printing;
using System.Runtime.InteropServices;
namespace Elwig.Helpers.Printing {
public partial class Win32Printer {
private const int DM_OUT_BUFFER = 2;
private const int HORZRES = 8;
private const int VERTRES = 10;
private const int LOGPIXELSX = 88;
private const int LOGPIXELSY = 90;
private const int PHYSICALWIDTH = 110;
private const int PHYSICALHEIGHT = 111;
private const int PHYSICALOFFSETX = 112;
private const int PHYSICALOFFSETY = 113;
private const int DM_DUPLEX = 0x1000;
private const short DMDUP_SIMPLEX = 1;
private const short DMDUP_VERTICAL = 2;
private const short DMDUP_HORIZONTAL = 3;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DOCINFO {
public int cbSize;
[MarshalAs(UnmanagedType.LPWStr)]
public string? lpszDocName;
[MarshalAs(UnmanagedType.LPWStr)]
public string? lpszOutput;
[MarshalAs(UnmanagedType.LPWStr)]
public string? lpszDatatype;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEVMODE {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public int dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
}
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);
[DllImport("winspool.drv", SetLastError = true)]
private static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int DocumentProperties(IntPtr hwnd, IntPtr hPrinter, string pDeviceName, IntPtr pDevModeOutput, IntPtr pDevModeInput, int fMode);
[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr CreateDC(string? pDriver, string? pDevice, string? pOutput, IntPtr pInitData);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int GetDeviceCaps(IntPtr hdc, int index);
[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int StartDoc(IntPtr hdc, ref DOCINFO lpdi);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int EndDoc(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int AbortDoc(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int StartPage(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int EndPage(IntPtr hdc);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GlobalFree(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern bool GlobalUnlock(IntPtr hMem);
public class Printer : IDisposable {
private readonly string _printerName;
private readonly IntPtr _printerHandle;
private readonly IntPtr _devMode;
private readonly IntPtr _hdc;
public IntPtr Hdc => _hdc;
public int PrintableWidth => GetDeviceCaps(_hdc, HORZRES);
public int PrintableHeight => GetDeviceCaps(_hdc, VERTRES);
public int DpiX => GetDeviceCaps(_hdc, LOGPIXELSX);
public int DpiY => GetDeviceCaps(_hdc, LOGPIXELSY);
public int PhysicalWidth => GetDeviceCaps(_hdc, PHYSICALWIDTH);
public int PhysicalHeight => GetDeviceCaps(_hdc, PHYSICALHEIGHT);
public int PhysicalOffsetX => GetDeviceCaps(_hdc, PHYSICALOFFSETX);
public int PhysicalOffsetY => GetDeviceCaps(_hdc, PHYSICALOFFSETY);
public Printer(string printerName) {
_printerName = printerName;
if (!OpenPrinter(_printerName, out _printerHandle, IntPtr.Zero)) {
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to open printer '{printerName}'.");
}
try {
_devMode = CreateDevMode();
_hdc = CreateDC(null, _printerName, null, _devMode);
if (_hdc == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to create printer DC for '{_printerName}'.");
} catch {
if (_devMode != IntPtr.Zero) Marshal.FreeHGlobal(_devMode);
ClosePrinter(_printerHandle);
throw;
}
}
public bool TrySetDuplex(Duplexing duplex) {
var devMode = Marshal.PtrToStructure<DEVMODE>(_devMode);
switch (duplex) {
case Duplexing.OneSided: devMode.dmDuplex = DMDUP_SIMPLEX; break;
case Duplexing.TwoSidedShortEdge: devMode.dmDuplex = DMDUP_HORIZONTAL; break;
case Duplexing.TwoSidedLongEdge: devMode.dmDuplex = DMDUP_VERTICAL; break;
}
if ((devMode.dmFields & DM_DUPLEX) != 0) {
Marshal.StructureToPtr(devMode, _devMode, false);
return true;
} else {
devMode.dmFields |= DM_DUPLEX;
Marshal.StructureToPtr(devMode, _devMode, false);
return false;
}
}
private IntPtr CreateDevMode() {
int size = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, IntPtr.Zero, IntPtr.Zero, 0);
if (size <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "DocumentProperties failed.");
IntPtr devMode = Marshal.AllocHGlobal(size);
int result = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, devMode, IntPtr.Zero, DM_OUT_BUFFER);
if (result < 0) {
Marshal.FreeHGlobal(devMode);
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to obtain printer DEVMODE.");
}
return devMode;
}
public void StartDocument(string documentName) {
var docInfo = new DOCINFO {
cbSize = Marshal.SizeOf<DOCINFO>(),
lpszDocName = documentName,
lpszDatatype = null,
lpszOutput = null
};
if (StartDoc(_hdc, ref docInfo) <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "StartDoc failed.");
}
public void StartPage() {
if (Win32Printer.StartPage(_hdc) <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "StartPage failed.");
}
public void EndPage() {
if (Win32Printer.EndPage(_hdc) <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "EndPage failed.");
}
public void EndDocument() {
if (EndDoc(_hdc) <= 0)
throw new Win32Exception( Marshal.GetLastWin32Error(), "EndDoc failed.");
}
public void AbortDocument() {
AbortDoc(_hdc);
}
public void Dispose() {
if (_hdc != IntPtr.Zero)
DeleteDC(_hdc);
if (_devMode != IntPtr.Zero)
Marshal.FreeHGlobal(_devMode);
if (_printerHandle != IntPtr.Zero)
ClosePrinter(_printerHandle);
}
}
}
}
+6 -6
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();
@@ -516,7 +515,7 @@ namespace Elwig.Helpers {
public static async Task<SmtpClient?> GetSmtpClient() { public static async Task<SmtpClient?> GetSmtpClient() {
if (App.Config.Smtp == null) if (App.Config.Smtp == null)
return null; return null;
var (host, port, mode, username, password, _) = App.Config.Smtp.Value; var (host, port, mode, username, password, _, _) = App.Config.Smtp.Value;
var client = new SmtpClient(); var client = new SmtpClient();
await client.ConnectAsync(host, port, mode == "starttls" ? SecureSocketOptions.StartTls : SecureSocketOptions.None); await client.ConnectAsync(host, port, mode == "starttls" ? SecureSocketOptions.StartTls : SecureSocketOptions.None);
await client.AuthenticateAsync(username, password); await client.AuthenticateAsync(username, password);
@@ -536,6 +535,7 @@ namespace Elwig.Helpers {
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From)); msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address))); msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address)));
msg.Subject = subject; msg.Subject = subject;
msg.Date = DateTime.Now;
var body = new Multipart("mixed") { var body = new Multipart("mixed") {
new TextPart("plain") { Text = text } new TextPart("plain") { Text = text }
}; };
@@ -546,10 +546,10 @@ namespace Elwig.Helpers {
msg.Body = body; msg.Body = body;
await client!.SendAsync(msg); await client!.SendAsync(msg);
await AddSentMails([( await AddSentMails([(
"email", member.MgNr, member.AdministrativeName, "email", member.MgNr, member.AdministrativeName,
member.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(), member.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
subject, docs.Select(d => d.Title).ToArray() subject, docs.Select(d => d.Title).ToArray()
)]); )]);
} catch (Exception exc) { } catch (Exception exc) {
InteractionService.ShowException(exc); InteractionService.ShowException(exc);
return false; return false;
@@ -0,0 +1,105 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
class MemberAreaComTotalsData : DataTable<MemberAreaComTotalsRow> {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SortIds", "Sorte", null, 12),
("AttrIds", "Attribut", null, 16),
("Areas", "Fläche", "m²", 22),
("DeliveryObligations", "Lieferpflicht", "kg", 22),
("DeliveryRights", "Lieferrecht", "kg", 22),
];
public MemberAreaComTotalsData(IEnumerable<MemberAreaComTotalsRow> rows, int year) :
base($"Gebundene Fläche", $"Gebundene Fläche pro Mitglied {year}", rows, FieldNames) {
}
public static async Task<MemberAreaComTotalsData> ForSeason(DbSet<MemberAreaComTotalsRowSingle> table, int year) {
return new MemberAreaComTotalsData(
(await FromDbSet(table, year)).GroupBy(
r => r.MgNr,
(k, g) => new MemberAreaComTotalsRow(g)
), year);
}
private static async Task<IEnumerable<MemberAreaComTotalsRowSingle>> FromDbSet(DbSet<MemberAreaComTotalsRowSingle> table, int year) {
return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address,
c.bucket, c.area, c.min_kg, c.max_kg
FROM v_area_commitment_bucket_strict c
LEFT JOIN member m ON m.mgnr = c.mgnr
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz
WHERE c.year = {year}
ORDER BY m.mgnr, c.bucket
""").ToListAsync();
}
}
public class MemberAreaComTotalsRow {
public int MgNr;
public string Name1;
public string? Name2;
public string Address;
public int Plz;
public string Locality;
public string[] SortIds;
public string[] AttrIds;
public int[] Areas;
public int[] DeliveryObligations;
public int[] DeliveryRights;
public MemberAreaComTotalsRow(IEnumerable<MemberAreaComTotalsRowSingle> rows) {
var f = rows.First();
MgNr = f.MgNr;
Name1 = f.Name1;
Name2 = f.Name2;
Address = f.Address;
Plz = f.Plz;
Locality = f.Locality.Split(",")[0];
SortIds = [.. rows.Select(r => r.VtrgId[..2])];
AttrIds = [.. rows.Select(r => r.VtrgId[2..])];
Areas = [.. rows.Select(r => r.Area)];
DeliveryObligations = [.. rows.Select(r => r.MinKg)];
DeliveryRights = [.. rows.Select(r => r.MaxKg)];
}
}
[Keyless]
public class MemberAreaComTotalsRowSingle {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]
public int Plz { get; set; }
[Column("ort")]
public required string Locality { get; set; }
[Column("bucket")]
public required string VtrgId { get; set; }
[Column("area")]
public int Area { get; set; }
[Column("min_kg")]
public int MinKg { get; set; }
[Column("max_kg")]
public int MaxKg { get; set; }
}
}
+30 -48
View File
@@ -14,73 +14,49 @@ namespace Elwig.Models.Dtos {
("Address", "Adresse", null, 60), ("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10), ("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60), ("Locality", "Ort", null, 60),
("SortIds", "Sorte", null, 12), ("SortId", "Sorte", null, 12),
("AttrIds", "Attribut", null, 16), ("AttrId", "Attribut", null, 16),
("Areas", "Fläche", "m²", 22), ("CultId", "Bewirt.", null, 16),
("DeliveryObligations", "Lieferpflicht", "kg", 22), ("GstNr", "Parzelle(n)", null, 25),
("DeliveryRights", "Lieferrecht", "kg", 22), ("Area", "Fläche", "", 22),
("MinKg", "Lieferpflicht", "kg", 22),
("MaxKg", "Lieferrecht", "kg", 22),
]; ];
public MemberAreaComsData(IEnumerable<MemberAreaComsRow> rows, int year) : public MemberAreaComsData(IEnumerable<MemberAreaComsRow> rows, int year) :
base($"Flächenbindungen", $"Flächenbindungen pro Mitglied {year}", rows, FieldNames) { base($"Flächenbindungen", $"Flächenbindungen pro Mitglied {year}", rows, FieldNames) {
} }
public static async Task<MemberAreaComsData> ForSeason(DbSet<MemberAreaComsRowSingle> table, int year) { public static async Task<MemberAreaComsData> ForSeason(DbSet<MemberAreaComsRow> table, int year) {
return new MemberAreaComsData( return new MemberAreaComsData(await FromDbSet(table, year), year);
(await FromDbSet(table, year)).GroupBy(
r => r.MgNr,
(k, g) => new MemberAreaComsRow(g)
), year);
} }
private static async Task<IEnumerable<MemberAreaComsRowSingle>> FromDbSet(DbSet<MemberAreaComsRowSingle> table, int year) { private static async Task<IEnumerable<MemberAreaComsRow>> FromDbSet(DbSet<MemberAreaComsRow> table, int year) {
return await table.FromSql($""" return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1, SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2, COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address, p.plz, o.name AS ort, m.address,
c.bucket, c.area, c.min_kg, c.max_kg t.sortid, t.attrid, c.cultid,
FROM v_area_commitment_bucket_strict c c.gstnr, c.area,
CAST(ROUND(area * COALESCE(t.min_kg_per_ha, 0) / 10000.0, 0) AS INTEGER) AS min_kg,
CAST(ROUND(area * MIN(COALESCE(a.max_kg_per_ha, s.max_kg_per_ha), s.max_kg_per_ha) / 10000.0, 0) AS INTEGER) AS max_kg
FROM v_virtual_season s, area_commitment c
JOIN area_commitment_type t ON t.vtrgid = c.vtrgid
LEFT JOIN wine_attribute a ON a.attrid = t.attrid
LEFT JOIN member m ON m.mgnr = c.mgnr LEFT JOIN member m ON m.mgnr = c.mgnr
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz LEFT JOIN AT_ort o ON o.okz = p.okz
WHERE c.year = {year} WHERE s.year = {year} AND
ORDER BY m.mgnr, c.bucket (year_from IS NULL OR year_from <= s.year) AND
(year_to IS NULL OR year_to >= s.year)
ORDER BY c.mgnr, t.sortid, t.attrid, c.cultid, c.gstnr;
""").ToListAsync(); """).ToListAsync();
} }
} }
public class MemberAreaComsRow {
public int MgNr;
public string Name1;
public string? Name2;
public string Address;
public int Plz;
public string Locality;
public string[] SortIds;
public string[] AttrIds;
public int[] Areas;
public int[] DeliveryObligations;
public int[] DeliveryRights;
public MemberAreaComsRow(IEnumerable<MemberAreaComsRowSingle> rows) {
var f = rows.First();
MgNr = f.MgNr;
Name1 = f.Name1;
Name2 = f.Name2;
Address = f.Address;
Plz = f.Plz;
Locality = f.Locality.Split(",")[0];
SortIds = rows.Select(r => r.VtrgId[..2]).ToArray();
AttrIds = rows.Select(r => r.VtrgId[2..]).ToArray();
Areas = rows.Select(r => r.Area).ToArray();
DeliveryObligations = rows.Select(r => r.MinKg).ToArray();
DeliveryRights = rows.Select(r => r.MaxKg).ToArray();
}
}
[Keyless] [Keyless]
public class MemberAreaComsRowSingle { public class MemberAreaComsRow {
[Column("mgnr")] [Column("mgnr")]
public int MgNr { get; set; } public int MgNr { get; set; }
[Column("name_1")] [Column("name_1")]
@@ -93,8 +69,14 @@ namespace Elwig.Models.Dtos {
public int Plz { get; set; } public int Plz { get; set; }
[Column("ort")] [Column("ort")]
public required string Locality { get; set; } public required string Locality { get; set; }
[Column("bucket")] [Column("sortid")]
public required string VtrgId { get; set; } public required string SortId { get; set; }
[Column("attrid")]
public string? AttrId { get; set; }
[Column("cultid")]
public string? CultId { get; set; }
[Column("gstnr")]
public required string GstNr { get; set; }
[Column("area")] [Column("area")]
public int Area { get; set; } public int Area { get; set; }
[Column("min_kg")] [Column("min_kg")]
+1 -1
View File
@@ -118,7 +118,7 @@ namespace Elwig.Models.Entities {
[Column("acid")] [Column("acid")]
public double? Acid { get; set; } public double? Acid { get; set; }
[Column("scale_id")] [Column("scale_terminal")]
public string? ScaleTerminalId { get; set; } public string? ScaleTerminalId { get; set; }
[Column("weighing_data")] [Column("weighing_data")]
+37
View File
@@ -0,0 +1,37 @@
-- schema version 40 to 41
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = REPLACE(sql, 'scale_id ', 'scale_terminal')
WHERE type = 'table' AND name = 'delivery_part';
DROP VIEW v_bki_delivery;
DROP VIEW v_bki_member;
CREATE VIEW v_bki_member AS
SELECT s.year, m.mgnr, m.lfbis_nr, m.name,
(COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '')) AS other_names,
a.name AS billing_name, COALESCE(a.address, m.address) AS address,
COALESCE(a.country, m.country) AS country, COALESCE(a.postal_dest, m.postal_dest) AS postal_dest,
m.volllieferant AS vollablieferer,
SUM(COALESCE(IIF((c.year_from IS NULL OR c.year_from <= s.year) AND (c.year_to IS NULL OR c.year_to >= s.year), c.area, NULL), 0)) AS area
FROM season s, member m
LEFT JOIN member_billing_address a ON a.mgnr = m.mgnr
LEFT JOIN area_commitment c ON c.mgnr = m.mgnr
GROUP BY s.year, m.mgnr;
CREATE VIEW v_bki_delivery AS
SELECT m.lfbis_nr, m.name, m.other_names, m.billing_name,
m.address, plz.plz, IIF(INSTR(o.name, ',') = 0, o.name, SUBSTR(o.name, 1, INSTR(o.name, ',') - 1)) AS ort,
d.date, d.weight, v.type, v.sortid, d.qualid, d.year, d.hkid, d.kmw, d.oe,
m.vollablieferer, m.area
FROM v_delivery d
JOIN v_bki_member m ON (m.year, m.mgnr) = (d.year, d.mgnr)
JOIN postal_dest pd ON (pd.country, pd.id) = (m.country, m.postal_dest)
LEFT JOIN AT_plz_dest ap ON (ap.country, ap.id) = (pd.country, pd.id)
LEFT JOIN AT_plz plz ON plz.plz = ap.plz
LEFT JOIN AT_ort o ON o.okz = ap.okz
JOIN wine_variety v ON v.sortid = d.sortid
ORDER BY d.date, d.time;
PRAGMA writable_schema = OFF;
+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;
}
}
}
+2 -1
View File
@@ -45,7 +45,8 @@ 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)); var today = DateTime.Today;
deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == vm.FilterSeason && (!vm.FilterOnlyUpcoming || a.Schedule.DateString.CompareTo(today.ToString("yyyy-MM-dd")) >= 0));
filterNames.Add($"{vm.FilterSeason}"); filterNames.Add($"{vm.FilterSeason}");
} }
+3 -2
View File
@@ -49,8 +49,9 @@ 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); var today = DateTime.Today;
filterNames.Add($"ab {Utils.Today:dd.MM.yyyy}"); deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.DateString.CompareTo(today.ToString("yyyy-MM-dd")) >= 0);
filterNames.Add($"ab {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;
} }
+11 -2
View File
@@ -214,7 +214,7 @@ namespace Elwig.Services {
]; ];
} }
public static async Task Download(string url, string username, string password) { public static async Task Download(string url, string username, string password, bool confirmImports) {
try { try {
var import = await GetFilesToImport(url, username, password); var import = await GetFilesToImport(url, username, password);
var paths = new List<string>(); var paths = new List<string>();
@@ -226,7 +226,7 @@ namespace Elwig.Services {
paths.Add(filename); paths.Add(filename);
} }
} }
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches); await ElwigData.Import(paths, confirmImports ? ElwigData.ImportMode.Interactively : ElwigData.ImportMode.FromBranches);
} catch (HttpRequestException exc) { } catch (HttpRequestException exc) {
InteractionService.ShowException("Daten herunterladen", "Eventuell Internetverbindung prüfen!", exc); InteractionService.ShowException("Daten herunterladen", "Eventuell Internetverbindung prüfen!", exc);
} catch (TaskCanceledException exc) { } catch (TaskCanceledException exc) {
@@ -264,5 +264,14 @@ namespace Elwig.Services {
return false; return false;
} }
} }
public static async Task<bool> ChangesAvailable(AppDbContext ctx, string url, string username, string password, int? year = null) {
try {
year ??= Utils.CurrentLastSeason;
return await ctx.Deliveries.Where(d => d.ZwstId == App.ZwstId && d.Year == year).Where(ChangedDeliveries).AnyAsync() || (Utils.HasInternetConnectivity() && (await GetFilesToImport(url, username, password)).Count > 0);
} catch {
return false;
}
}
} }
} }
+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 = '_';
@@ -91,8 +91,9 @@ namespace Elwig.Windows {
.ToListAsync(); .ToListAsync();
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();
var today = DateTime.Today;
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(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);
} }
+6 -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) {
@@ -937,6 +937,7 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
var subject = EmailSubjectInput.Text; var subject = EmailSubjectInput.Text;
var text = EmailBodyInput.Text; var text = EmailBodyInput.Text;
var date = DateTime.Now;
await Task.Run(async () => { await Task.Run(async () => {
await Utils.AddSentMailBody(subject, text, EmailDocuments.Count); await Utils.AddSentMailBody(subject, text, EmailDocuments.Count);
foreach (var (m, docs) in EmailDocuments) { foreach (var (m, docs) in EmailDocuments) {
@@ -944,6 +945,7 @@ namespace Elwig.Windows {
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From)); msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
msg.To.AddRange(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(m.AdministrativeName, a.Address))); msg.To.AddRange(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(m.AdministrativeName, a.Address)));
msg.Subject = subject; msg.Subject = subject;
msg.Date = date;
var body = new Multipart("mixed") { var body = new Multipart("mixed") {
new TextPart("plain") { Text = text } new TextPart("plain") { Text = text }
}; };
@@ -958,6 +960,7 @@ namespace Elwig.Windows {
m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(), m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
subject, docs.Select(d => d.Title).ToArray() subject, docs.Select(d => d.Title).ToArray()
)]); )]);
await Task.Delay((int)(App.Config.SmtpWait * 1000));
} }
}); });
+39 -29
View File
@@ -50,22 +50,27 @@ 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 {
Application.Current.Shutdown();
} }
App.ForceShutdown = true;
} }
} }
Mouse.OverrideCursor = Cursors.AppStarting;
await (Application.Current as App)!.OnClosing();
Application.Current.Shutdown();
} }
private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) { private void Menu_Help_About_Click(object sender, RoutedEventArgs evt) {
@@ -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) {
@@ -200,8 +190,13 @@ namespace Elwig.Windows {
return; return;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); if (App.Config.SyncMain) {
await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword, true);
await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
} else {
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword, false);
await SyncService.UploadBranchDeliveries(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
}
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
@@ -211,7 +206,7 @@ namespace Elwig.Windows {
return; return;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword, App.Config.SyncMain);
}); });
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
@@ -229,6 +224,10 @@ namespace Elwig.Windows {
private async void Menu_Sync_UploadModified_Click(object sender, RoutedEventArgs evt) { private async void Menu_Sync_UploadModified_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null) if (App.Config.SyncUrl == null)
return; return;
if (!App.Config.SyncMain) {
InteractionService.ShowInformation("Mitglieder und Lieferungen hochladen", "Hochladen nicht möglich: Dieses Gerät ist nicht das Hauptgerät!");
return;
}
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => { await Task.Run(async () => {
await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
@@ -296,6 +295,11 @@ namespace Elwig.Windows {
if (App.Config.SyncUrl == null) if (App.Config.SyncUrl == null)
return; return;
if (!App.Config.SyncMain) {
InteractionService.ShowInformation("Datenbank hochladen", "Hochladen nicht möglich: Dieses Gerät ist nicht das Hauptgerät!");
return;
}
if (!InteractionService.AskContinue("Datenbank hochladen", "Sind Sie wirklich sicher, dass Sie die Datenbank dieses\nGerätes hochladen möchten? Das sollte nur vom Hauptgerät aus passieren!")) if (!InteractionService.AskContinue("Datenbank hochladen", "Sind Sie wirklich sicher, dass Sie die Datenbank dieses\nGerätes hochladen möchten? Das sollte nur vom Hauptgerät aus passieren!"))
return; return;
@@ -367,7 +371,11 @@ namespace Elwig.Windows {
await Task.Delay(delay); await Task.Delay(delay);
var ch = false; var ch = false;
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
ch = await SyncService.ChangesAvailable(ctx, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); if (App.Config.SyncMain) {
ch = await SyncService.ChangesAvailable(ctx, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
} else {
ch = await SyncService.ChangesAvailable(ctx, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword, default);
}
} }
await App.MainDispatcher.BeginInvoke(() => { await App.MainDispatcher.BeginInvoke(() => {
if (ch) { if (ch) {
@@ -520,9 +528,11 @@ namespace Elwig.Windows {
App.HintContextChange(); App.HintContextChange();
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var tbl = await MemberAreaComsData.ForSeason(ctx.MemberAreaComsRows, year); var tbl1 = await MemberAreaComTotalsData.ForSeason(ctx.MemberAreaComTotalsRows, year);
var tbl2 = await MemberAreaComsData.ForSeason(ctx.MemberAreaComsRows, year);
using var ods = new OdsFile(filename); using var ods = new OdsFile(filename);
await ods.AddTable(tbl); await ods.AddTable(tbl1);
await ods.AddTable(tbl2);
} catch (Exception exc) { } catch (Exception exc) {
InteractionService.ShowException(exc); InteractionService.ShowException(exc);
} }
@@ -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
+12
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
@@ -26,6 +35,7 @@ auto = true
;username = "" ;username = ""
;password = "" ;password = ""
;from = mail@wg.at ;from = mail@wg.at
;wait = 0.5
;[scale.1] ;[scale.1]
;type = SysTec-IT ;type = SysTec-IT
@@ -37,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.0.0 ([Changelog](./CHANGELOG.md)) **Version:** 1.1.2.1 ([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.0.0 ([Änderungsprotokoll](./CHANGELOG.md)) **Version:** 1.1.2.1 ([Ä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();
}); });
} }
+2 -2
View File
@@ -45,7 +45,7 @@ INSERT INTO delivery (mgnr, year, did, date, time, zwstid, lnr) VALUES
(101, 2020, 1, '2020-10-01', NULL, 'X', 1), (101, 2020, 1, '2020-10-01', NULL, 'X', 1),
(101, 2020, 2, '2020-10-01', NULL, 'X', 2), (101, 2020, 2, '2020-10-01', NULL, 'X', 2),
(101, 2020, 3, '2020-10-01', NULL, 'X', 3); (101, 2020, 3, '2020-10-01', NULL, 'X', 3);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_id, weighing_data, weighing_reason) VALUES INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_terminal, weighing_data, weighing_reason) VALUES
(2020, 1, 1, 'GV', 'K', NULL, 4000, 17, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2020, 1, 1, 'GV', 'K', NULL, 4000, 17, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 1, 2, 'GV', NULL, NULL, 4000, 16, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2020, 1, 2, 'GV', NULL, NULL, 4000, 16, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 2, 1, 'GV', NULL, 'B', 4000, 15, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2020, 2, 1, 'GV', NULL, 'B', 4000, 15, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
@@ -60,7 +60,7 @@ INSERT INTO delivery (mgnr, year, did, date, time, zwstid, lnr) VALUES
(101, 2021, 1, '2021-10-01', NULL, 'X', 1), (101, 2021, 1, '2021-10-01', NULL, 'X', 1),
(101, 2021, 2, '2021-10-01', NULL, 'X', 2), (101, 2021, 2, '2021-10-01', NULL, 'X', 2),
(101, 2021, 3, '2021-10-01', NULL, 'X', 3); (101, 2021, 3, '2021-10-01', NULL, 'X', 3);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, gebunden, net_weight, manual_weighing, spl_check, scale_id, weighing_data, weighing_reason) VALUES INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, gebunden, net_weight, manual_weighing, spl_check, scale_terminal, weighing_data, weighing_reason) VALUES
(2021, 1, 1, 'GV', 'K', NULL, 4000, 17, 'KAB', 'WLNO', 06109, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2021, 1, 1, 'GV', 'K', NULL, 4000, 17, 'KAB', 'WLNO', 06109, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2021, 1, 2, 'GV', NULL, NULL, 4000, 16, 'QUW', 'WLNO', 06109, FALSE, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2021, 1, 2, 'GV', NULL, NULL, 4000, 16, 'QUW', 'WLNO', 06109, FALSE, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2021, 2, 1, 'GV', NULL, 'B', 4000, 15, 'QUW', 'WLNO', 06109, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2021, 2, 1, 'GV', NULL, 'B', 4000, 15, 'QUW', 'WLNO', 06109, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL),
+1 -1
View File
@@ -39,7 +39,7 @@ INSERT INTO delivery (mgnr, year, did, date, time, zwstid, lnr) VALUES
(104, 2020, 11, '2020-10-03', '14:39:22', 'X', 2), (104, 2020, 11, '2020-10-03', '14:39:22', 'X', 2),
(104, 2020, 12, '2020-10-03', '15:15:41', 'X', 3); (104, 2020, 12, '2020-10-03', '15:15:41', 'X', 3);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_id, weighing_data, weighing_reason) VALUES INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_terminal, weighing_data, weighing_reason) VALUES
(2020, 1, 1, 'GV', NULL, NULL, 3219, 15.0, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, '1', '{"id":"321","nr":321,"gross_weight":3219,"tare_weight":0,"net_weight":3219,"date":"2020-10-01","time":"09:02:46"}', NULL), (2020, 1, 1, 'GV', NULL, NULL, 3219, 15.0, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, '1', '{"id":"321","nr":321,"gross_weight":3219,"tare_weight":0,"net_weight":3219,"date":"2020-10-01","time":"09:02:46"}', NULL),
(2020, 2, 1, 'GV', 'K', NULL, 2987, 17.5, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2020, 2, 1, 'GV', 'K', NULL, 2987, 17.5, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 2, 2, 'GV', 'K', NULL, 1873, 17.7, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2020, 2, 2, 'GV', 'K', NULL, 1873, 17.7, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
+1 -1
View File
@@ -76,7 +76,7 @@ INSERT INTO delivery (mgnr, year, did, date, time, zwstid, lnr) VALUES
(101, 2023, 18, '2023-10-10', NULL, 'X', 2), (101, 2023, 18, '2023-10-10', NULL, 'X', 2),
(101, 2023, 19, '2023-10-10', NULL, 'X', 3); (101, 2023, 19, '2023-10-10', NULL, 'X', 3);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_id, weighing_data, weighing_reason) VALUES INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_terminal, weighing_data, weighing_reason) VALUES
(2021, 1, 1, 'GV', NULL, NULL, 3500, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2021, 1, 1, 'GV', NULL, NULL, 3500, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2021, 2, 1, 'GV', NULL, NULL, 4000, 17.0, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2021, 2, 1, 'GV', NULL, NULL, 4000, 17.0, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2021, 2, 2, 'GV', NULL, NULL, 4000, 16.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL), (2021, 2, 2, 'GV', NULL, NULL, 4000, 16.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
+2 -2
View File
@@ -19,11 +19,11 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="Appium.WebDriver" Version="4.4.5" /> <PackageReference Include="Appium.WebDriver" Version="4.4.5" />
<PackageReference Include="NReco.PdfRenderer" Version="1.6.0" /> <PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
<PackageReference Include="NUnit" Version="4.6.1" /> <PackageReference Include="NUnit" Version="4.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" /> <PackageReference Include="NUnit3TestAdapter" Version="6.3.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.14.0"> <PackageReference Include="NUnit.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -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))));
}
}
} }
} }
+1 -1
View File
@@ -1 +1 @@
curl --fail -s -L "https://elwig.at/files/create.sql?v=40" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql" curl --fail -s -L "https://elwig.at/files/create.sql?v=41" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"