Compare commits
17 Commits
555ddeea5e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e9e90b1e98 | |||
| 640dbf705e | |||
| 42121fe7da | |||
| d45c3f867f | |||
| a90be2644d | |||
| 01739ba42e | |||
| 7bea4d9ee0 | |||
| b6497fd422 | |||
| f141485537 | |||
| b31603554a | |||
| 90def81cc5 | |||
| 87903227b5 | |||
| 6d6776f0f9 | |||
| bf3cc2ea1e | |||
| c1697dc4f3 | |||
| 3b335a568e | |||
| beacdab54f |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -2,6 +2,47 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
[v1.0.3.0][v1.0.3.0] (2026-01-16) {#v1.0.3.0}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.3.0-features}
|
||||
|
||||
* Bei Zu-/Abschlägen ist es nun möglich den konkreten Wert dieser auf Lieferscheinen nicht anzuzeigen (Stammdaten -> Saisons). (495aa8a691)
|
||||
* Bei Teillieferungen kann nun die Art der Anlieferung angegeben werden: Planenwagen/Kipper, Lesewagen, Kiste(n). (bf6297f63b, 3419113dec, 6d6776f0f9)
|
||||
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) können nun die Gesamtmengen der gefilterten Lieferungen nach Mitglied und Sorte als Excel-Liste exportiert werden ("Liefermengen"). ([#73][i73], e97c29db43)
|
||||
* Im Datenbankabfragen-Fenster (`QueryWindow`) gibt es die Möglichkeit die Ergebnisse als CSV-Datei zu exportieren. (9af498287d, 5cec5b3556)
|
||||
* PDF-Dokumente wurden überarbeitet:
|
||||
* Traubengutschriften können nur noch ausgedruckt bzw. verschickt werden, wenn die zugehörige Auszahlungsvariante festgesetzt ist. (b31603554a)
|
||||
* Auf Dokumenten für interne Verwendung (alle außer an Mitglieder adressierte) wurden Kopf- und Fußzeile auf ein Minimum beschränkt. (f141485537)
|
||||
* Das Synchronisieren zwischen Zweigstellen ist für den Benutzer nun wesentlich vereinfacht. Geänderte Daten von Mitgliedern, Flächenbindungen oder Lieferungen werden beim Synchronisieren automatsich hochgeladen bzw. heruntergeladen. ([#66][i66], 3b335a568e)
|
||||
* Im Rundschreiben-Fenster (`MailWindow`) wurde ein Knopf zum Abbrechen des Generierens hinzugefügt. ([#50][i50])
|
||||
|
||||
### Behobene Fehler {#v1.0.3.0-bugfixes}
|
||||
|
||||
* Einzelne (nicht über das Rundschreiben-Fenster) verschickte E-Mails werde korrekt im Ausgangsprotokoll angeführt. (ac6d559e5d)
|
||||
* Bei automatischen Datenbank-Updates sind auftretende Fehler ignoriert worden. (f228ba3019)
|
||||
* Beim Aufteilen von Lieferungen sind Zu-/Abschläge nicht auf neu erstellen Teillieferungen übernommen worden. (da05a49e10)
|
||||
* Das Verbinden und Trennen von Waagen mittels Serial-/COM-Port ist nun bei laufendem Programm möglich. ([#71][i71])
|
||||
* In den Auszahlungsvarianten-Daten (`PaymentVariantSummary`) ist die statistische Summe der gebundenen und ungebundenen Menge ohne die Spalte _attributlos gebunden_ berechnet worden. (90def81cc5)
|
||||
* Seit [v0.13.7](#v0.13.7) (2025-01-21) wurde auch bei der ersten Auszahlung der Saison ein "Bisher berücksichtigt: € 0,00" auf den Traubengutschriften angeführt. (7bea4d9ee0)
|
||||
* Das Speichern der Parameter `MAIL_SEND_POSTAL` und `MAIL_SEND_EMAIL` war fehlerhaft. (01739ba42e)
|
||||
* Bessere Isolation der automatisierten Dokumenten-Tests. (640dbf705e)
|
||||
|
||||
### Sonstiges {#v1.0.3.0-misc}
|
||||
|
||||
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) wird bei Zu-/Abschlägen nur "Alle" angezeigt, wenn mehr als 1 ausgewählt sind. (811916a8b9)
|
||||
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) wird die letzte Variante standardmäßig ausgewählt und beim Löschen wird um Bestätigung gebeten. (b6497fd422)
|
||||
* Abhängigkeiten aktualisiert. (889a17b21c, 9b37330362, 3b6333a6a2, d45c3f867f, 42121fe7da)
|
||||
|
||||
[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
|
||||
[i66]: https://git.necronda.net/winzer/elwig/issues/60
|
||||
[i71]: https://git.necronda.net/winzer/elwig/issues/71
|
||||
[i73]: https://git.necronda.net/winzer/elwig/issues/73
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.2.0][v1.0.2.0] (2025-11-10) {#v1.0.2.0}
|
||||
---------------------------------------------
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Elwig {
|
||||
} catch (Exception e) {
|
||||
list.Add(new InvalidScale(s.Id));
|
||||
if (s.Required)
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagen-Fehler",
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
@@ -249,15 +249,19 @@ namespace Elwig {
|
||||
private void OnSerialPortConnected(object? sender, string name) {
|
||||
for (var i = 0; i < Config.Scales.Count; i++) {
|
||||
var s = Config.Scales[i];
|
||||
if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is InvalidScale) {
|
||||
if (s.Connection?.StartsWith($"serial://{name}:") ?? false) {
|
||||
if (Scales[i] is InvalidScale) {
|
||||
try {
|
||||
Scales[i] = Scale.FromConfig(s);
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} catch (Exception e) {
|
||||
Scales[i] = new InvalidScale(s.Id);
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagen-Fehler",
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
} else if (Scales[i] is IEventScale) {
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateScales();
|
||||
@@ -268,6 +272,7 @@ namespace Elwig {
|
||||
var s = Config.Scales[i];
|
||||
if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) {
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
if (Scales[i] is ICommandScale) {
|
||||
try {
|
||||
Scales[i].Dispose();
|
||||
} catch {
|
||||
@@ -276,6 +281,7 @@ namespace Elwig {
|
||||
Scales[i] = new InvalidScale(s.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateScales();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,16 @@ namespace Elwig.Documents {
|
||||
Member = m;
|
||||
Location = App.BranchLocation;
|
||||
IncludeSender = includeSender;
|
||||
var c = App.Client;
|
||||
Header = $"<div class='name'>{c.Name}</div><div class='suffix'>{c.NameSuffix}</div><div class='type'>{c.NameTypeFull}</div>";
|
||||
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
|
||||
.Item(c.NameFull).NextLine()
|
||||
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
|
||||
.Item(c.EmailAddress != null ? $"<a href=\"mailto:{c.Name} {c.NameSuffix} <{c.EmailAddress}>\">{c.EmailAddress}</a>" : null)
|
||||
.Item(c.Website != null ? $"<a href=\"http://{c.Website}/\">{c.Website}</a>" : null)
|
||||
.Item("Betriebs-Nr.", c.LfbisNr).Item("Bio-KSt.", c.OrganicAuthority).NextLine()
|
||||
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
|
||||
.ToString();
|
||||
var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " <i>(pauschaliert)</i>");
|
||||
Aside = $"<table><colgroup><col span='1' style='width: 22.5mm;'/><col span='1' style='width: 42.5mm;'/></colgroup>" +
|
||||
$"<thead><tr><th colspan='2'>Mitglied</th></tr></thead><tbody>" +
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Elwig.Documents {
|
||||
Data = data;
|
||||
Payment = p;
|
||||
Credit = p.Credit;
|
||||
IsPreview = Payment == null || Credit == null;
|
||||
var season = p.Variant.Season;
|
||||
if (considerCustomModifiers) {
|
||||
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elwig.Helpers.Printing;
|
||||
using MimeKit;
|
||||
using System.Threading;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public abstract partial class Document : IDisposable {
|
||||
@@ -18,10 +19,11 @@ namespace Elwig.Documents {
|
||||
protected string? _pdfPath;
|
||||
protected string? PdfPath => _pdfPath ?? _pdfFile?.FilePath;
|
||||
public int? TotalPages { get; private set; }
|
||||
public int? Pages => TotalPages / (DoublePaged ? 2 : 1);
|
||||
public int? Pages => TotalPages / (IsDoublePaged ? 2 : 1);
|
||||
|
||||
public bool ShowFoldMarks = App.Config.Debug;
|
||||
public bool DoublePaged = false;
|
||||
public bool IsDoublePaged = false;
|
||||
public bool IsPreview = false;
|
||||
|
||||
public string DocumentsPath;
|
||||
public int CurrentNextSeason;
|
||||
@@ -38,15 +40,8 @@ namespace Elwig.Documents {
|
||||
CurrentNextSeason = Utils.CurrentNextSeason;
|
||||
Title = title;
|
||||
Author = c.NameFull;
|
||||
Header = $"<div class='name'>{c.Name}</div><div class='suffix'>{c.NameSuffix}</div><div class='type'>{c.NameTypeFull}</div>";
|
||||
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
|
||||
.Item(c.NameFull).NextLine()
|
||||
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
|
||||
.Item(c.EmailAddress != null ? $"<a href=\"mailto:{c.Name} {c.NameSuffix} <{c.EmailAddress}>\">{c.EmailAddress}</a>" : null)
|
||||
.Item(c.Website != null ? $"<a href=\"http://{c.Website}/\">{c.Website}</a>" : null)
|
||||
.Item("Betriebs-Nr.", c.LfbisNr).Item("Bio-KSt.", c.OrganicAuthority).NextLine()
|
||||
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
|
||||
.ToString();
|
||||
Header = "";
|
||||
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ").Item(c.NameFull).ToString();
|
||||
Date = DateOnly.FromDateTime(Utils.Today);
|
||||
}
|
||||
|
||||
@@ -104,7 +99,7 @@ namespace Elwig.Documents {
|
||||
return await Html.CompileRenderAsync(name, this); ;
|
||||
}
|
||||
|
||||
public async Task Generate(IProgress<double>? progress = null) {
|
||||
public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
|
||||
if (_pdfFile != null)
|
||||
return;
|
||||
progress?.Report(0.0);
|
||||
@@ -114,6 +109,7 @@ namespace Elwig.Documents {
|
||||
var pdf = new TempFile("pdf");
|
||||
var tmpHtmls = new List<TempFile>();
|
||||
var tmpFiles = new List<string>();
|
||||
try {
|
||||
var n = m.Documents.Count();
|
||||
int i = 0;
|
||||
foreach (var doc in m.Documents) {
|
||||
@@ -121,6 +117,8 @@ namespace Elwig.Documents {
|
||||
tmpFiles.Add(doc.PdfPath!);
|
||||
continue;
|
||||
}
|
||||
if (cancelToken?.IsCancellationRequested ?? false)
|
||||
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
|
||||
var tmpHtml = new TempFile("html");
|
||||
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
|
||||
tmpHtmls.Add(tmpHtml);
|
||||
@@ -129,21 +127,32 @@ namespace Elwig.Documents {
|
||||
progress?.Report(GenerationProportion * 100 * i / n);
|
||||
}
|
||||
progress?.Report(GenerationProportion * 100);
|
||||
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, DoublePaged, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
|
||||
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, IsDoublePaged, cancelToken, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
|
||||
TotalPages = pages.Pages;
|
||||
_pdfFile = pdf;
|
||||
} catch {
|
||||
pdf.Dispose();
|
||||
throw;
|
||||
} finally {
|
||||
foreach (var tmp in tmpHtmls) {
|
||||
tmp.Dispose();
|
||||
}
|
||||
_pdfFile = pdf;
|
||||
}
|
||||
} else {
|
||||
if (cancelToken?.IsCancellationRequested ?? false)
|
||||
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
|
||||
var pdf = new TempFile("pdf");
|
||||
using (var tmpHtml = new TempFile("html")) {
|
||||
try {
|
||||
using var tmpHtml = new TempFile("html");
|
||||
await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
|
||||
progress?.Report(50.0);
|
||||
var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, DoublePaged);
|
||||
var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, IsDoublePaged, cancelToken);
|
||||
TotalPages = pages.Pages;
|
||||
}
|
||||
_pdfFile = pdf;
|
||||
} catch {
|
||||
pdf.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
progress?.Report(100.0);
|
||||
}
|
||||
@@ -155,7 +164,7 @@ namespace Elwig.Documents {
|
||||
|
||||
public async Task Print(int copies = 1) {
|
||||
if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
|
||||
await Pdf.Print(PdfPath, copies, DoublePaged);
|
||||
await Pdf.Print(PdfPath, copies, IsDoublePaged);
|
||||
}
|
||||
|
||||
public void Show() {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.css" />
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Page.css" />
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Table.css" />
|
||||
@if (Model.DoublePaged) {
|
||||
@if (Model.IsDoublePaged) {
|
||||
<style>
|
||||
@@page :left {
|
||||
margin: 25mm 25mm 35mm 20mm;
|
||||
@@ -34,14 +34,14 @@
|
||||
<div class="pre-footer">
|
||||
<span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
|
||||
<span class="doc-id">@Model.DocumentId</span>
|
||||
<span class="page"></span>
|
||||
<span><span class="page"></span>@Raw(Model.IsPreview ? " <b>(vorläufig)</b>" : "")</span>
|
||||
</div>
|
||||
<footer>@Raw(Model.Footer)</footer>
|
||||
</div>
|
||||
@if (Model.DoublePaged) {
|
||||
@if (Model.IsDoublePaged) {
|
||||
<div class="footer-wrapper left">
|
||||
<div class="pre-footer">
|
||||
<span class="page"></span>
|
||||
<span>@Raw(Model.IsPreview ? "<b>(vorläufig)</b> " : "")<span class="page"></span></span>
|
||||
<span class="doc-id">@Model.DocumentId</span>
|
||||
<span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Elwig.Documents {
|
||||
Data = data;
|
||||
CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code;
|
||||
MemberNum = v.Credits.Count;
|
||||
IsPreview = MemberNum == 0;
|
||||
DeliveryNum = v.DeliveryPartPayments.DistinctBy(p => p.DeliveryPart.Delivery).Count();
|
||||
DeliveryPartNum = v.DeliveryPartPayments.Count;
|
||||
ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult();
|
||||
|
||||
@@ -164,14 +164,14 @@
|
||||
<td class="number">@Utils.GetSign(considered)</td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(considered):N2}")</td>
|
||||
<th class="lborder">Menge (gebunden):</th>
|
||||
<td colspan="2" class="number">@($"{Model.Data.Rows.Sum(r => r.Geb.Weight):N0}") kg</td>
|
||||
<td colspan="2" class="number">@($"{Model.Data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0}") kg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Auszahlungsbetrag:</th>
|
||||
<td class="number tborder"></td>
|
||||
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{totalSum:N2}")</td>
|
||||
<th class="lborder">Gesamtmenge:</th>
|
||||
<td colspan="2" class="number tborder">@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight + r.Geb.Weight):N0}") kg</td>
|
||||
<td colspan="2" class="number tborder">@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0}") kg</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
|
||||
<Version>1.0.2.0</Version>
|
||||
<Version>1.0.3.0</Version>
|
||||
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
@@ -25,20 +25,20 @@
|
||||
<PackageReference Include="LinqKit" Version="1.3.9" />
|
||||
<PackageReference Include="MailKit" Version="4.14.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3595.46" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
|
||||
<PackageReference Include="NJsonSchema" Version="11.5.2" />
|
||||
<PackageReference Include="PdfiumViewer" Version="2.13.0" />
|
||||
<PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
|
||||
<PackageReference Include="RazorLight" Version="2.3.1" />
|
||||
<PackageReference Include="ScottPlot.WPF" Version="5.1.57" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Ports" Version="10.0.0" />
|
||||
<PackageReference Include="System.Management" Version="10.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.2" />
|
||||
<PackageReference Include="System.IO.Ports" Version="10.0.2" />
|
||||
<PackageReference Include="System.Management" Version="10.0.2" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -10,7 +10,6 @@ using Microsoft.Data.Sqlite;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using Elwig.Models.Dtos;
|
||||
using System.Reflection;
|
||||
using System.Data;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
@@ -117,40 +116,6 @@ namespace Elwig.Helpers {
|
||||
return cnx;
|
||||
}
|
||||
|
||||
public static async Task ExecuteBatch(SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.NextResultAsync());
|
||||
}
|
||||
|
||||
public static async Task ExecuteEmbeddedScript(SqliteConnection cnx, Assembly asm, string name) {
|
||||
using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource");
|
||||
using var reader = new StreamReader(stream);
|
||||
await ExecuteBatch(cnx, await reader.ReadToEndAsync());
|
||||
}
|
||||
|
||||
public static async Task<object?> ExecuteScalar(SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
return await cmd.ExecuteScalarAsync();
|
||||
}
|
||||
|
||||
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(SqliteConnection cnx) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA foreign_key_check";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var list = new List<(string, long, string, long)>();
|
||||
while (await reader.ReadAsync()) {
|
||||
var table = reader.GetString(0);
|
||||
var rowid = reader.GetInt64(1);
|
||||
var parent = reader.GetString(2);
|
||||
var fkid = reader.GetInt64(3);
|
||||
list.Add((table, rowid, parent, fkid));
|
||||
}
|
||||
return [.. list];
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
|
||||
optionsBuilder.UseSqlite(ConnectionString);
|
||||
optionsBuilder.UseLazyLoadingProxies();
|
||||
|
||||
@@ -16,10 +16,10 @@ namespace Elwig.Helpers {
|
||||
public static async Task<Version> CheckDb() {
|
||||
using var cnx = AppDbContext.Connect();
|
||||
|
||||
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
|
||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
||||
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
|
||||
|
||||
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0;
|
||||
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
|
||||
VersionOffset = (int)(schemaVers % 100);
|
||||
if (VersionOffset != 0) {
|
||||
// schema was modified manually/externally
|
||||
@@ -27,12 +27,12 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
|
||||
|
||||
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
|
||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
|
||||
var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
|
||||
|
||||
if (App.Version > v) {
|
||||
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision;
|
||||
await AppDbContext.ExecuteBatch(cnx, $"PRAGMA user_version = {vers}");
|
||||
await cnx.ExecuteBatch($"PRAGMA user_version = {vers}");
|
||||
}
|
||||
|
||||
return v;
|
||||
@@ -67,20 +67,20 @@ namespace Elwig.Helpers {
|
||||
if (toExecute.Count == 0)
|
||||
return;
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, """
|
||||
await cnx.ExecuteBatch("""
|
||||
PRAGMA locking_mode = EXCLUSIVE;
|
||||
BEGIN EXCLUSIVE;
|
||||
""");
|
||||
foreach (var script in toExecute) {
|
||||
await AppDbContext.ExecuteEmbeddedScript(cnx, asm, script);
|
||||
await cnx.ExecuteEmbeddedScript(asm, script);
|
||||
}
|
||||
var violations = await AppDbContext.ForeignKeyCheck(cnx);
|
||||
var violations = await cnx.ForeignKeyCheck();
|
||||
if (violations.Length > 0) {
|
||||
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
|
||||
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
|
||||
}
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
COMMIT;
|
||||
VACUUM;
|
||||
PRAGMA schema_version = {toVersion * 100 + VersionOffset};
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
public async Task FinishSeason() {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE season
|
||||
SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year})
|
||||
WHERE year = {Year};
|
||||
@@ -37,7 +37,7 @@ namespace Elwig.Helpers.Billing {
|
||||
public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceBs = 0, int allowanceKgPerBs = 0, double allowanceRel = 0, int addMinBs = 1) {
|
||||
if (addMinBs < 1) addMinBs = 1;
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE member
|
||||
SET business_shares = member.business_shares - h.business_shares
|
||||
FROM member_history h
|
||||
@@ -66,7 +66,7 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
public async Task UnAdjustBusinessShares() {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE member
|
||||
SET business_shares = member.business_shares - h.business_shares
|
||||
FROM member_history h
|
||||
@@ -157,9 +157,9 @@ namespace Elwig.Helpers.Billing {
|
||||
lastMgNr = mgnr;
|
||||
}
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, $"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}");
|
||||
await cnx.ExecuteBatch($"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}");
|
||||
if (inserts.Count > 0) {
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
|
||||
VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
|
||||
ON CONFLICT DO UPDATE
|
||||
@@ -237,7 +237,7 @@ namespace Elwig.Helpers.Billing {
|
||||
if (needed == 0) break;
|
||||
}
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
|
||||
VALUES {string.Join(",\n ", posChanges.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '', {i.Item4})"))}
|
||||
ON CONFLICT DO UPDATE
|
||||
|
||||
@@ -47,21 +47,21 @@ namespace Elwig.Helpers.Billing {
|
||||
public async Task Commit() {
|
||||
await Revert();
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers)
|
||||
SELECT s.year,
|
||||
COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr,
|
||||
m.mgnr,
|
||||
v.avnr,
|
||||
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
|
||||
IIF(lc.amount >= 0, ROUND(lp.amount / POW(10, s.precision - 2)), 0) AS prev_net_amount,
|
||||
IIF(lc.amount < 0, 0, ROUND(lp.amount / POW(10, s.precision - 2))) AS prev_net_amount,
|
||||
IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
|
||||
ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
|
||||
ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
|
||||
ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) +
|
||||
IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
|
||||
AS modifiers,
|
||||
IIF(lc.amount >= 0, lc.modifiers, 0) AS prev_modifiers
|
||||
IIF(lc.amount < 0, 0, lc.modifiers) AS prev_modifiers
|
||||
FROM season s
|
||||
JOIN payment_variant v ON v.year = s.year
|
||||
LEFT JOIN payment_variant l ON l.year = s.year
|
||||
@@ -82,27 +82,27 @@ namespace Elwig.Helpers.Billing {
|
||||
LEFT JOIN payment_custom x ON (x.year, x.mgnr) = (s.year, m.mgnr)
|
||||
WHERE s.year = {Year} AND v.avnr = {AvNr};
|
||||
""");
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
""");
|
||||
}
|
||||
|
||||
public async Task Revert() {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
""");
|
||||
}
|
||||
|
||||
protected async Task SetCalcTime(SqliteConnection cnx) {
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr})
|
||||
""");
|
||||
}
|
||||
|
||||
protected async Task DeleteInDb(SqliteConnection cnx) {
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
@@ -116,7 +116,7 @@ namespace Elwig.Helpers.Billing {
|
||||
var multiplier = 0.50;
|
||||
var includePredecessor = true;
|
||||
var modName = "Treue%";
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
|
||||
SELECT c.year, {AvNr}, s.mgnr, 0,
|
||||
ROUND(s.sum * COALESCE(m.abs, 0)),
|
||||
@@ -138,7 +138,7 @@ namespace Elwig.Helpers.Billing {
|
||||
mod_rel = mod_rel + excluded.mod_rel
|
||||
""");
|
||||
}
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
|
||||
SELECT x.year, {AvNr}, x.mgnr, 0, COALESCE(x.mod_abs * POW(10, s.precision - 2), 0), COALESCE(x.mod_rel, 0)
|
||||
FROM payment_custom x
|
||||
@@ -194,7 +194,7 @@ namespace Elwig.Helpers.Billing {
|
||||
var msg = invalid.Count == 0 ? null : "Für folgende Sorten wurde noch keine Preiskurve festgelegt: " + string.Join(", ", invalid);
|
||||
if (msg != null && strict)
|
||||
throw new KeyNotFoundException(msg);
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
|
||||
VALUES {string.Join(",\n ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))};
|
||||
""");
|
||||
@@ -205,7 +205,7 @@ namespace Elwig.Helpers.Billing {
|
||||
protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
|
||||
var netMod = Data.NetWeightModifier.ToString().Replace(',', '.');
|
||||
var grossMod = Data.GrossWeightModifier.ToString().Replace(',', '.');
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
|
||||
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, 0, IIF(d.net_weight, {netMod}, {grossMod})
|
||||
FROM delivery_part d
|
||||
|
||||
@@ -206,15 +206,15 @@ namespace Elwig.Helpers {
|
||||
case 1: orderingMemberList = "NAME"; break;
|
||||
case 2: orderingMemberList = "KG"; break;
|
||||
}
|
||||
string mailSendPostal = "MGNR";
|
||||
switch (MailOrdering) {
|
||||
string mailSendPostal = "WISH";
|
||||
switch (MailSendPostal) {
|
||||
case 0: mailSendPostal = "NONE"; break;
|
||||
case 1: mailSendPostal = "NO_EMAIL"; break;
|
||||
case 2: mailSendPostal = "WISH"; break;
|
||||
case 3: mailSendPostal = "ALL"; break;
|
||||
}
|
||||
string mailSendEmail = "MGNR";
|
||||
switch (MailOrdering) {
|
||||
string mailSendEmail = "WISH";
|
||||
switch (MailSendEmail) {
|
||||
case 0: mailSendEmail = "NONE"; break;
|
||||
case 1: mailSendEmail = "WISH"; break;
|
||||
case 2: mailSendEmail = "ALL"; break;
|
||||
|
||||
@@ -11,9 +11,9 @@ namespace Elwig.Helpers.Export {
|
||||
private static async Task<(long? ApplicationId, string? UserVersion, long? SchemaVersion, long FileSize)> GetMeta() {
|
||||
long size = new FileInfo(App.Config.DatabaseFile).Length;
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id");
|
||||
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version");
|
||||
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version");
|
||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id");
|
||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version");
|
||||
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version");
|
||||
return (applId, userVers != null ? $"{userVers >> 24}.{(userVers >> 16) & 0xFF}.{(userVers >> 8) & 0xFF}.{userVers & 0xFF}" : null, schemaVers, size);
|
||||
}
|
||||
|
||||
@@ -100,9 +100,9 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
}
|
||||
|
||||
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
|
||||
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
|
||||
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0;
|
||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
|
||||
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
|
||||
|
||||
await writer.WriteLineAsync($"-- Elwig database dump, {DateTime.Now:yyyy-MM-dd, HH:mm:ss}");
|
||||
await writer.WriteLineAsync($"-- {Environment.MachineName}, Zwst. {App.BranchName}, {App.Client.Name}");
|
||||
@@ -224,7 +224,7 @@ namespace Elwig.Helpers.Export {
|
||||
File.Move(filename, App.Config.DatabaseFile, false);
|
||||
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, "VACUUM");
|
||||
await cnx.ExecuteBatch("VACUUM");
|
||||
}
|
||||
|
||||
public static async Task ImportSql(StreamReader reader) {
|
||||
@@ -232,7 +232,7 @@ namespace Elwig.Helpers.Export {
|
||||
File.Delete(newName);
|
||||
try {
|
||||
using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) {
|
||||
await AppDbContext.ExecuteBatch(cnx, await reader.ReadToEndAsync());
|
||||
await cnx.ExecuteBatch(await reader.ReadToEndAsync());
|
||||
}
|
||||
await ImportSqlite(newName);
|
||||
} finally {
|
||||
|
||||
@@ -345,7 +345,7 @@ namespace Elwig.Helpers.Export {
|
||||
$"mtime = {((DateTimeOffset)m.ModifiedAt.ToUniversalTime()).ToUnixTimeSeconds()} " +
|
||||
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2});"));
|
||||
using var cnx = AppDbContext.Connect();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
BEGIN;
|
||||
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
{string.Join("\n", updateStmts)}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Hashing;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
static partial class Extensions {
|
||||
public static partial class Extensions {
|
||||
|
||||
[LibraryImport("msvcrt.dll")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
||||
@@ -108,5 +111,39 @@ namespace Elwig.Helpers {
|
||||
throw new InvalidDataException($"CRC-32 mismatch in '{entry.FullName}'");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExecuteBatch(this SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.NextResultAsync()) ;
|
||||
}
|
||||
|
||||
public static async Task ExecuteEmbeddedScript(this SqliteConnection cnx, Assembly asm, string name) {
|
||||
using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource");
|
||||
using var reader = new StreamReader(stream);
|
||||
await ExecuteBatch(cnx, await reader.ReadToEndAsync());
|
||||
}
|
||||
|
||||
public static async Task<object?> ExecuteScalar(this SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
return await cmd.ExecuteScalarAsync();
|
||||
}
|
||||
|
||||
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(this SqliteConnection cnx) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA foreign_key_check";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var list = new List<(string, long, string, long)>();
|
||||
while (await reader.ReadAsync()) {
|
||||
var table = reader.GetString(0);
|
||||
var rowid = reader.GetInt64(1);
|
||||
var parent = reader.GetString(2);
|
||||
var fkid = reader.GetInt64(3);
|
||||
list.Add((table, rowid, parent, fkid));
|
||||
}
|
||||
return [.. list];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Elwig.Windows;
|
||||
using System.Diagnostics;
|
||||
using PdfiumViewer;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing.Printing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using PdfiumViewer;
|
||||
using System.Drawing.Printing;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public static class Pdf {
|
||||
|
||||
private static readonly string WinziPrint = new string[] { App.InstallPath }
|
||||
private static readonly string WinziPrint = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
|
||||
Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Installer/Files/WinziPrint.exe") :
|
||||
new string[] { App.InstallPath }
|
||||
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
|
||||
.Select(x => Path.Combine(x, "WinziPrint.exe"))
|
||||
.Where(File.Exists)
|
||||
@@ -46,24 +50,45 @@ namespace Elwig.Helpers.Printing {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) {
|
||||
return await Convert([htmlPath], pdfPath, doublePaged, progress);
|
||||
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
|
||||
return await Convert([htmlPath], pdfPath, doublePaged, cancelToken, progress);
|
||||
}
|
||||
|
||||
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) {
|
||||
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
|
||||
if (WinziPrintProc == null) throw new InvalidOperationException("The WinziPrint process has not been initialized yet");
|
||||
progress?.Report(0.0);
|
||||
using var client = new TcpClient("127.0.0.1", 30983);
|
||||
using var stream = client.GetStream();
|
||||
string cnxId;
|
||||
using var reader = new StreamReader(stream);
|
||||
var first = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
|
||||
if (first.StartsWith("id:")) {
|
||||
cnxId = first[3..].Trim();
|
||||
} else {
|
||||
throw new IOException("Invalid response from WinziPrint");
|
||||
}
|
||||
await stream.WriteAsync(Utils.UTF8.GetBytes(
|
||||
"-e utf-8;-p;" + (doublePaged ? "-2;" : "") +
|
||||
$"{string.Join(';', htmlPath)};{pdfPath}" +
|
||||
"\r\n"));
|
||||
using var reader = new StreamReader(stream);
|
||||
bool cancelled = false;
|
||||
while (true) {
|
||||
if (!cancelled && (cancelToken?.IsCancellationRequested ?? false)) {
|
||||
try {
|
||||
using var cancelClient = new TcpClient("127.0.0.1", 30983);
|
||||
using var cancelStream = cancelClient.GetStream();
|
||||
using var cancelReader = new StreamReader(cancelStream);
|
||||
await cancelReader.ReadLineAsync();
|
||||
await cancelStream.WriteAsync(Utils.UTF8.GetBytes($"cancel;{cnxId}\r\n"));
|
||||
} catch { }
|
||||
cancelled = true;
|
||||
}
|
||||
var line = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
|
||||
if (line.StartsWith("error:")) {
|
||||
throw new IOException($"WinziPrint: {line[6..].Trim()}");
|
||||
var msg = line[6..].Trim();
|
||||
if (msg == "aborted")
|
||||
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
|
||||
throw new IOException($"WinziPrint: {msg}");
|
||||
} else if (line.StartsWith("progress:")) {
|
||||
var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray();
|
||||
progress?.Report(100.0 * parts[0] / parts[1]);
|
||||
|
||||
@@ -17,28 +17,34 @@ namespace Elwig.Helpers {
|
||||
public SerialPortWatcher() {
|
||||
_knownPorts = SerialPort.GetPortNames();
|
||||
_deviceArrivalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
|
||||
_deviceArrivalWatcher.EventArrived += (s, e) => OnDeviceArrived();
|
||||
_deviceArrivalWatcher.EventArrived += OnDeviceArrived;
|
||||
_deviceRemovalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
|
||||
_deviceRemovalWatcher.EventArrived += (s, e) => OnDeviceRemoved();
|
||||
_deviceRemovalWatcher.EventArrived += OnDeviceRemoved;
|
||||
_deviceArrivalWatcher.Start();
|
||||
_deviceRemovalWatcher.Start();
|
||||
|
||||
}
|
||||
|
||||
private void OnDeviceArrived() {
|
||||
private void OnDeviceArrived(object sender, EventArrivedEventArgs evt) {
|
||||
App.MainDispatcher.Invoke(() => {
|
||||
// "synchronized"
|
||||
string[] currentPorts = SerialPort.GetPortNames();
|
||||
var newPorts = currentPorts.Except(_knownPorts).ToArray();
|
||||
foreach (var port in newPorts)
|
||||
SerialPortConnected?.Invoke(this, port);
|
||||
_knownPorts = currentPorts;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDeviceRemoved() {
|
||||
private void OnDeviceRemoved(object sender, EventArrivedEventArgs evt) {
|
||||
App.MainDispatcher.Invoke(() => {
|
||||
// "synchronized"
|
||||
string[] currentPorts = SerialPort.GetPortNames();
|
||||
var removedPorts = _knownPorts.Except(currentPorts).ToArray();
|
||||
foreach (var port in removedPorts)
|
||||
SerialPortDisconnected?.Invoke(this, port);
|
||||
_knownPorts = currentPorts;
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
@@ -540,15 +540,29 @@ namespace Elwig.Helpers {
|
||||
|
||||
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) {
|
||||
if (mode == ExportMode.Print && !App.Config.Debug) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
await doc.Generate();
|
||||
await doc.Print();
|
||||
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
await doc.Generate();
|
||||
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
|
||||
if (success)
|
||||
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mail im Posteingang des Empfängers aufscheint.", "E-Mail verschickt",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} else if (mode == ExportMode.SavePdf) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
var d = new SaveFileDialog() {
|
||||
FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf",
|
||||
DefaultExt = "pdf",
|
||||
|
||||
@@ -15,15 +15,17 @@ namespace Elwig.Helpers.Weighing {
|
||||
public bool IsReady { get; private set; }
|
||||
public bool HasFillingClearance { get; private set; }
|
||||
|
||||
public event IEventScale.EventHandler<WeighingEventArgs> WeighingEvent;
|
||||
public event IEventScale.EventHandler<WeighingEventArgs>? WeighingEvent;
|
||||
|
||||
private bool IsRunning = true;
|
||||
private readonly Thread BackgroundThread;
|
||||
private readonly string Connection;
|
||||
|
||||
public AveryEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
|
||||
base(cnx, empty, filling, limit, log) {
|
||||
public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) :
|
||||
base(cnx, null, null, null, log, true, !required) {
|
||||
ScaleId = id;
|
||||
Model = model;
|
||||
Connection = cnx;
|
||||
IsReady = true;
|
||||
HasFillingClearance = false;
|
||||
Stream.WriteTimeout = -1;
|
||||
@@ -50,19 +52,49 @@ namespace Elwig.Helpers.Weighing {
|
||||
var data = await Receive();
|
||||
if (data != null)
|
||||
RaiseWeighingEvent(new WeighingEventArgs(data.Value));
|
||||
} catch (ThreadInterruptedException) {
|
||||
// ignore
|
||||
} catch (IOException) {
|
||||
await Task.Delay(500);
|
||||
await Reconnect();
|
||||
} catch (TimeoutException) {
|
||||
await Task.Delay(500);
|
||||
await Reconnect();
|
||||
} catch (Exception ex) {
|
||||
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "Waagenfehler",
|
||||
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task Reconnect() {
|
||||
try { Reader.Close(); } catch { }
|
||||
try { Stream.Close(); } catch { }
|
||||
try { Serial?.Close(); } catch { }
|
||||
while (IsRunning) {
|
||||
try {
|
||||
if (Connection.StartsWith("serial:")) {
|
||||
Serial = Utils.OpenSerialConnection(Connection);
|
||||
Stream = Serial.BaseStream;
|
||||
} else if (Connection.StartsWith("tcp:")) {
|
||||
Tcp = Utils.OpenTcpConnection(Connection);
|
||||
Stream = Tcp.GetStream();
|
||||
}
|
||||
Reader = new(Stream, Encoding.ASCII, false, 512);
|
||||
break;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult?> Receive() {
|
||||
var line = "";
|
||||
while (line.Length < 33) {
|
||||
var ch = Reader.Read();
|
||||
if (ch == -1) {
|
||||
return null;
|
||||
throw new IOException("Connection closed");
|
||||
} else if (line.Length > 0 || ch == ' ') {
|
||||
line += char.ToString((char)ch);
|
||||
}
|
||||
@@ -71,7 +103,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
if (line == null || line == "") {
|
||||
return null;
|
||||
} else if (line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
|
||||
throw new IOException($"Invalid event from scale: '{line}'");
|
||||
throw new FormatException($"Invalid event from scale: '{line}'");
|
||||
}
|
||||
|
||||
var date = line[ 1.. 9];
|
||||
@@ -81,7 +113,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
var unit = line[30..32];
|
||||
|
||||
if (unit != "kg") {
|
||||
throw new IOException($"Unsupported unit in weighing event: '{unit}'");
|
||||
throw new WeighingException($"Unsupported unit in weighing event: '{unit}'");
|
||||
}
|
||||
|
||||
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
var line = await Reader.ReadUntilAsync('\x03');
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
|
||||
if (line == null || line.Length < 4 || !line.StartsWith('\x02')) {
|
||||
throw new IOException("Invalid response from scale");
|
||||
throw new FormatException("Invalid response from scale");
|
||||
}
|
||||
|
||||
var status = line[1..3];
|
||||
@@ -45,9 +45,9 @@ namespace Elwig.Helpers.Weighing {
|
||||
switch (status[1]) {
|
||||
case 'M': msg = "Waage in Bewegung"; break;
|
||||
}
|
||||
throw new IOException($"Waagenfehler {status}: {msg}");
|
||||
throw new WeighingException($"Waagenfehler {status}: {msg}");
|
||||
} else if (status[0] != ' ') {
|
||||
throw new IOException($"Invalid response from scale (error code {status})");
|
||||
throw new WeighingException($"Invalid response from scale (error code {status})");
|
||||
}
|
||||
|
||||
return line[1..^1];
|
||||
@@ -57,7 +57,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
await SendCommand(incIdentNr ? '\x05' : '?');
|
||||
string record = await ReceiveResponse();
|
||||
if (record.Length != 45)
|
||||
throw new IOException("Invalid response from scale: Received record has invalid size");
|
||||
throw new FormatException("Invalid response from scale: Received record has invalid size");
|
||||
var line = record[2..];
|
||||
|
||||
var brutto = line[ 0.. 7].Trim();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.IO.Ports;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
namespace Elwig.Helpers.Weighing {
|
||||
public abstract class Scale : IDisposable {
|
||||
@@ -27,7 +28,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
if (config.Type == "SysTec-IT") {
|
||||
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else if (config.Type == "Avery-Async") {
|
||||
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Log, config.Required);
|
||||
} else if (config.Type == "Gassner") {
|
||||
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else {
|
||||
@@ -35,10 +36,17 @@ namespace Elwig.Helpers.Weighing {
|
||||
}
|
||||
}
|
||||
|
||||
protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) {
|
||||
protected Scale(string cnx, string? empty, string? filling, int? limit, string? log, bool softFail = false, bool failSilent = false) {
|
||||
if (cnx.StartsWith("serial:")) {
|
||||
try {
|
||||
Serial = Utils.OpenSerialConnection(cnx);
|
||||
Stream = Serial.BaseStream;
|
||||
} catch (Exception e) {
|
||||
if (!softFail) throw;
|
||||
if (!failSilent)
|
||||
MessageBox.Show($"Verbindung zu Waage konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
Stream = Serial?.BaseStream ?? Stream.Null;
|
||||
} else if (cnx.StartsWith("tcp:")) {
|
||||
Tcp = Utils.OpenTcpConnection(cnx);
|
||||
Stream = Tcp.GetStream();
|
||||
|
||||
@@ -34,14 +34,14 @@ namespace Elwig.Helpers.Weighing {
|
||||
var line = await Reader.ReadUntilAsync("\r\n");
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
|
||||
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
|
||||
throw new IOException("Invalid response from scale");
|
||||
throw new FormatException("Invalid response from scale");
|
||||
}
|
||||
|
||||
var error = line[1..3];
|
||||
string msg = $"Unbekannter Fehler (Fehler code {error})";
|
||||
if (error[0] == '0') {
|
||||
if (error[1] != '0') {
|
||||
throw new IOException($"Invalid response from scale (error code {error})");
|
||||
throw new WeighingException($"Invalid response from scale (error code {error})");
|
||||
}
|
||||
} else if (error[0] == '1') {
|
||||
switch (error[1]) {
|
||||
@@ -52,21 +52,21 @@ namespace Elwig.Helpers.Weighing {
|
||||
case '6': msg = "Drucker nicht bereit"; break;
|
||||
case '7': msg = "Druckmuster enthält ungültiges Kommando"; break;
|
||||
}
|
||||
throw new IOException($"Waagenfehler {error}: {msg}");
|
||||
throw new WeighingException($"Waagenfehler {error}: {msg}");
|
||||
} else if (error[0] == '2') {
|
||||
switch (error[1]) {
|
||||
case '0': msg = "Brutto negativ"; break;
|
||||
}
|
||||
throw new IOException($"Fehler {error}: {msg}");
|
||||
throw new WeighingException($"Fehler {error}: {msg}");
|
||||
} else if (error[0] == '3') {
|
||||
switch (error[1]) {
|
||||
case '1': msg = "Übertragunsfehler"; break;
|
||||
case '2': msg = "Ungültiger Befehl"; break;
|
||||
case '3': msg = "Ungültiger Parameter"; break;
|
||||
}
|
||||
throw new IOException($"Kommunikationsfehler {error}: {msg}");
|
||||
throw new WeighingException($"Kommunikationsfehler {error}: {msg}");
|
||||
} else {
|
||||
throw new IOException($"Invalid response from scale (error code {error})");
|
||||
throw new WeighingException($"Invalid response from scale (error code {error})");
|
||||
}
|
||||
|
||||
return line[1..^3];
|
||||
@@ -76,7 +76,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}");
|
||||
string record = await ReceiveResponse();
|
||||
if (record.Length != 62)
|
||||
throw new IOException("Invalid response from scale: Received record has invalid size");
|
||||
throw new FormatException("Invalid response from scale: Received record has invalid size");
|
||||
var line = record[2..];
|
||||
|
||||
var status = line[ 0.. 2];
|
||||
@@ -94,9 +94,9 @@ namespace Elwig.Helpers.Weighing {
|
||||
var crc16 = line[52..60].Trim();
|
||||
|
||||
if (Utils.CalcCrc16Modbus(record[..54]) != ushort.Parse(crc16)) {
|
||||
throw new IOException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})");
|
||||
throw new WeighingException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})");
|
||||
} else if (unit != "kg") {
|
||||
throw new IOException($"Unsupported unit in weighing response: '{unit}'");
|
||||
throw new WeighingException($"Unsupported unit in weighing response: '{unit}'");
|
||||
}
|
||||
|
||||
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
|
||||
|
||||
6
Elwig/Helpers/Weighing/WeighingException.cs
Normal file
6
Elwig/Helpers/Weighing/WeighingException.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Elwig.Helpers.Weighing {
|
||||
public class WeighingException(string? message = null) : Exception(message) {
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,8 @@ using LinqKit;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.IO;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using System.Windows.Controls;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Elwig.Services {
|
||||
public static class DeliveryService {
|
||||
@@ -220,6 +218,22 @@ namespace Elwig.Services {
|
||||
prd = prd.And(p => p.Unloading != DeliveryPart.Box);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("keine Kisten");
|
||||
} else if ("upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => (p.Delivery.XTime == null || p.Delivery.MTime > p.Delivery.XTime) && (p.Delivery.ITime == null || p.Delivery.MTime > p.Delivery.ITime));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Export");
|
||||
} else if ("!upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => !((p.Delivery.XTime == null || p.Delivery.MTime > p.Delivery.XTime) && (p.Delivery.ITime == null || p.Delivery.MTime > p.Delivery.ITime)));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Export");
|
||||
} else if (">import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.ITime != null && p.Delivery.MTime > p.Delivery.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Import");
|
||||
} else if ("<import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Delivery.MTime <= p.Delivery.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Import");
|
||||
} else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
|
||||
filterVar.Add(e.ToUpper());
|
||||
filter.RemoveAt(i--);
|
||||
@@ -799,40 +813,7 @@ namespace Elwig.Services {
|
||||
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var list = await query
|
||||
.Select(p => p.Delivery)
|
||||
.Distinct()
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = list
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (list.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
await ElwigData.Export(path, list, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
} else {
|
||||
|
||||
@@ -9,9 +9,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
@@ -315,6 +313,22 @@ namespace Elwig.Services {
|
||||
memberQuery = memberQuery.Where(m => !m.ContactViaPost);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("nicht Kontaktart Post");
|
||||
} else if ("upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => (p.XTime == null || p.MTime > p.XTime) && (p.ITime == null || p.MTime > p.ITime));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Export");
|
||||
} else if ("!upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => !((p.XTime == null || p.MTime > p.XTime) && (p.ITime == null || p.MTime > p.ITime)));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Export");
|
||||
} else if (">import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => p.MTime > p.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Import");
|
||||
} else if ("<import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => p.MTime <= p.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Import");
|
||||
} else if (e.All(char.IsAsciiDigit) && mgnr.ContainsKey(e)) {
|
||||
filterMgNr.Add(int.Parse(e));
|
||||
filter.RemoveAt(i--);
|
||||
@@ -562,46 +576,7 @@ namespace Elwig.Services {
|
||||
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var members = await query
|
||||
.OrderBy(m => m.MgNr)
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var areaComs = await query
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.ToListAsync();
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Distinct()
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (members.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
} else {
|
||||
|
||||
277
Elwig/Services/SyncService.cs
Normal file
277
Elwig/Services/SyncService.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Helpers.Export;
|
||||
using Elwig.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Elwig.Services {
|
||||
public static class SyncService {
|
||||
|
||||
public static readonly Expression<Func<Member, bool>> ChangedMembers = (m) => ((m.XTime == null && m.MTime > 1751328000) || m.MTime > m.XTime) && (m.ITime == null || m.MTime > m.ITime);
|
||||
public static readonly Expression<Func<Delivery, bool>> ChangedDeliveries = (d) => ((d.XTime == null && d.MTime > 1751328000) || d.MTime > d.XTime) && (d.ITime == null || d.MTime > d.ITime);
|
||||
|
||||
public static async Task Upload(string url, string username, string password, IQueryable<Member> query, IEnumerable<string> filterNames) {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var members = await query
|
||||
.OrderBy(m => m.MgNr)
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var areaComs = await query
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.ToListAsync();
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Distinct()
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (members.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt(members, [], exportedAt);
|
||||
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Upload(string url, string username, string password, IQueryable<DeliveryPart> query, IEnumerable<string> filterNames) {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var list = await query
|
||||
.Select(p => p.Delivery)
|
||||
.Distinct()
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = list
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (list.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await ElwigData.Export(path, list, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt([], list, exportedAt);
|
||||
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task UploadModified(string url, string username, string password) {
|
||||
try {
|
||||
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
|
||||
List<Member> members;
|
||||
List<AreaCom> areaComs;
|
||||
List<Delivery> deliveries;
|
||||
using (var ctx = new AppDbContext()) {
|
||||
members = await ctx.Members
|
||||
.Where(ChangedMembers)
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.OrderBy(m => m.MgNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
areaComs = await ctx.Members
|
||||
.Where(ChangedMembers)
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
|
||||
.ToListAsync();
|
||||
deliveries = await ctx.Deliveries
|
||||
.Where(ChangedDeliveries)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
|
||||
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Union(deliveries.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!))
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (members.Count == 0 && deliveries.Count == 0) {
|
||||
MessageBox.Show("Es gibt keine geänderten Mitglieder oder Lieferungen, die hochgeladen werden könnten!", "Mitglieder und Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await (new ElwigData.ElwigExport {
|
||||
Members = (members, ["geändert seit letztem Export"]),
|
||||
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
|
||||
Deliveries = (deliveries, ["geändert seit letzem Export"]),
|
||||
WbKgs = (wbKgs, ["von exportierten Mitgliedern, Flächenbindungen und Lieferungen"]),
|
||||
}).Export(path);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt(members, deliveries, exportedAt);
|
||||
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern und {deliveries.Count:N0} Lieferungen erfolgreich!", "Mitglieder und Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task UploadBranchDeliveries(string url, string username, string password, int? year = null) {
|
||||
try {
|
||||
year ??= Utils.CurrentLastSeason;
|
||||
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
|
||||
using var ctx = new AppDbContext();
|
||||
var deliveries = await ctx.Deliveries
|
||||
.Where(d => d.Year == year && d.ZwstId == App.ZwstId)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
|
||||
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = deliveries
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (deliveries.Count == 0) {
|
||||
MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await ElwigData.Export(path, deliveries, wbKgs, [$"{year}", $"Zweigstelle {App.BranchName}"]);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt([], deliveries, exportedAt);
|
||||
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<List<(string Name, string Url)>> GetFilesToImport(string url, string username, string password) {
|
||||
var data = await Utils.GetExportMetaData(url, username, password);
|
||||
var files = data
|
||||
.Select(f => new {
|
||||
Name = f!["name"]!.AsValue().GetValue<string>(),
|
||||
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
|
||||
ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
|
||||
Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
|
||||
Url = f!["url"]!.AsValue().GetValue<string>(),
|
||||
Size = f!["size"]!.AsValue().GetValue<long>(),
|
||||
})
|
||||
.Where(f => f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
|
||||
.ToList();
|
||||
|
||||
var imported = await ElwigData.GetImportedFiles();
|
||||
return [.. files
|
||||
.Where(f => f.Device != Environment.MachineName && !imported.Contains(f.Name))
|
||||
.Select(f => (f.Name, f.Url))
|
||||
];
|
||||
}
|
||||
|
||||
public static async Task Download(string url, string username, string password) {
|
||||
try {
|
||||
var import = await GetFilesToImport(url, username, password);
|
||||
var paths = new List<string>();
|
||||
using (var client = Utils.GetHttpClient(username, password)) {
|
||||
foreach (var f in import) {
|
||||
var filename = Path.Combine(App.TempPath, f.Name);
|
||||
using var stream = new FileStream(filename, FileMode.Create);
|
||||
await client.DownloadAsync(f.Url, stream);
|
||||
paths.Add(filename);
|
||||
}
|
||||
}
|
||||
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task UpdateExportedAt(IEnumerable<Member> member, IEnumerable<Delivery> deliveries, DateTime dateTime) {
|
||||
var timestamp = ((DateTimeOffset)dateTime.ToUniversalTime()).ToUnixTimeSeconds();
|
||||
var mgnrs = string.Join(",", member.Select(m => $"{m.MgNr}").Append("0"));
|
||||
var dids = string.Join(",", deliveries.Select(d => $"({d.Year},{d.DId})").Append("(0,0)"));
|
||||
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||
await cnx.ExecuteBatch($"""
|
||||
BEGIN;
|
||||
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
UPDATE member SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
|
||||
UPDATE area_commitment SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
|
||||
UPDATE delivery SET xtime = {timestamp} WHERE (year, did) IN ({dids});
|
||||
UPDATE delivery_part SET xtime = {timestamp} WHERE (year, did) IN ({dids});
|
||||
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
COMMIT;
|
||||
""");
|
||||
}
|
||||
App.HintContextChange();
|
||||
}
|
||||
|
||||
public static async Task<bool> ChangesAvailable(AppDbContext ctx, string url, string username, string password) {
|
||||
return await ctx.Members.AnyAsync(ChangedMembers) || await ctx.Deliveries.AnyAsync(ChangedDeliveries) || (Utils.HasInternetConnectivity() && (await GetFilesToImport(url, username, password)).Count > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,6 +226,7 @@
|
||||
<Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/>
|
||||
<Bold>Handwiegung</Bold>: handw[iegung], !Handw[iegung] (alle ohne Handwiegung)<LineBreak/>
|
||||
<Bold>Handlese</Bold>: Handl[ese], !handl[ese] (alle ohne Handlese)<LineBreak/>
|
||||
<Bold>Anlieferung</Bold>: Plane[nwagen]/Kipp[er], !plane[nwagen]/!kipp[er], Lesew[agen], !lesew[agen], kiste[n], !kiste[n]<LineBreak/>
|
||||
<Bold>Gebunden</Bold>: geb[unden], ungeb[unden], !geb[unden], !ungeb[unden]<LineBreak/>
|
||||
<Bold>Gerebelt</Bold>: gerebelt, !Gerebelt (nicht gerebelt gewogen)<LineBreak/>
|
||||
<Bold>Freitext</Bold>: z.B. Lieferscheinnummern, Anmerkung, "quw" (sucht nach dem Text "quw")
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Elwig.Windows {
|
||||
NewDeliveryButton_Click(null, null);
|
||||
using var ctx = new AppDbContext();
|
||||
if (ctx.Seasons.Find(Utils.CurrentYear) == null) {
|
||||
MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.",
|
||||
MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.\n\n(Stammdaten -> Saisons -> Neu anlegen...)",
|
||||
"Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +302,9 @@
|
||||
<Button x:Name="GenerateButton" Content="Generieren"
|
||||
Grid.Row="0" Grid.Column="0" FontSize="14"
|
||||
Click="GenerateButton_Click"/>
|
||||
<Button x:Name="AbortButton" Content="Abbrechen" Visibility="Hidden" IsEnabled="False"
|
||||
Grid.Row="0" Grid.Column="0" FontSize="14"
|
||||
Click="AbortButton_Click"/>
|
||||
<ProgressBar x:Name="ProgressBar"
|
||||
Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True"/>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -61,6 +62,8 @@ namespace Elwig.Windows {
|
||||
protected Dictionary<Member, List<Document>>? PrintMemberDocuments;
|
||||
protected Dictionary<Member, List<Document>>? EmailDocuments;
|
||||
|
||||
private CancellationTokenSource? CancelGeneration;
|
||||
|
||||
public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow));
|
||||
public int PostalAllCount {
|
||||
get => (int)GetValue(PostalAllCountProperty);
|
||||
@@ -594,20 +597,32 @@ namespace Elwig.Windows {
|
||||
}
|
||||
|
||||
private void Window_Closed(object sender, EventArgs evt) {
|
||||
CancelGeneration?.Dispose();
|
||||
DisposeDocs();
|
||||
}
|
||||
|
||||
private async void AbortButton_Click(object sender, RoutedEventArgs evt) {
|
||||
AbortButton.IsEnabled = false;
|
||||
CancelGeneration?.Cancel();
|
||||
}
|
||||
|
||||
private async void GenerateButton_Click(object sender, RoutedEventArgs evt) {
|
||||
LockInputs();
|
||||
PreviewButton.IsEnabled = false;
|
||||
PrintButton.IsEnabled = false;
|
||||
EmailButton.IsEnabled = false;
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
AbortButton.IsEnabled = true;
|
||||
AbortButton.Visibility = Visibility.Visible;
|
||||
GenerateButton.IsEnabled = false;
|
||||
GenerateButton.Visibility = Visibility.Hidden;
|
||||
|
||||
DisposeDocs();
|
||||
await UpdateClientParameters();
|
||||
|
||||
CancelGeneration?.Dispose();
|
||||
CancelGeneration = new();
|
||||
|
||||
using var ctx = new AppDbContext();
|
||||
|
||||
var doublePaged = DoublePagedInput.IsChecked == true;
|
||||
@@ -662,6 +677,9 @@ namespace Elwig.Windows {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
UnlockInputs();
|
||||
GenerateButton.IsEnabled = true;
|
||||
GenerateButton.Visibility = Visibility.Visible;
|
||||
AbortButton.IsEnabled = false;
|
||||
AbortButton.Visibility = Visibility.Hidden;
|
||||
Mouse.OverrideCursor = null;
|
||||
return;
|
||||
}
|
||||
@@ -679,6 +697,9 @@ namespace Elwig.Windows {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
UnlockInputs();
|
||||
GenerateButton.IsEnabled = true;
|
||||
GenerateButton.Visibility = Visibility.Visible;
|
||||
AbortButton.IsEnabled = false;
|
||||
AbortButton.Visibility = Visibility.Hidden;
|
||||
Mouse.OverrideCursor = null;
|
||||
return;
|
||||
}
|
||||
@@ -733,6 +754,21 @@ namespace Elwig.Windows {
|
||||
}).ToList()
|
||||
}).ToList();
|
||||
|
||||
var hasPreviewDocs = memberDocs.Any(m => m.Docs.Any(d => d.Doc.IsPreview));
|
||||
if (hasPreviewDocs) {
|
||||
var res = MessageBox.Show("Einige der ausgewählten Dokumente sind nur als vorläufig zu betrachten und können daher nicht verschickt/ausgedruckt werden!\n\nDies könnte an einer noch nicht festgesetzen Auszahlungsvariante liegen.\n\nSoll mit dem Generieren fortgefahren werden?",
|
||||
"Vorläufige Dokumente", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
|
||||
if (res != MessageBoxResult.OK) {
|
||||
UnlockInputs();
|
||||
GenerateButton.IsEnabled = true;
|
||||
GenerateButton.Visibility = Visibility.Visible;
|
||||
AbortButton.IsEnabled = false;
|
||||
AbortButton.Visibility = Visibility.Hidden;
|
||||
Mouse.OverrideCursor = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var printMode = PostalAllInput.IsChecked == true ? 3 :
|
||||
PostalWishInput.IsChecked == true ? 2 :
|
||||
PostalNoEmailInput.IsChecked == true ? 1 : 0;
|
||||
@@ -747,7 +783,7 @@ namespace Elwig.Windows {
|
||||
.ToDictionary(d => d.Member, m => {
|
||||
var docs = m.Docs.Select(d => d.Doc).ToList();
|
||||
foreach (var doc in docs) {
|
||||
doc!.DoublePaged = false;
|
||||
doc!.IsDoublePaged = false;
|
||||
if (doc is BusinessDocument b) {
|
||||
b.IncludeSender = false;
|
||||
b.Location = location;
|
||||
@@ -759,7 +795,7 @@ namespace Elwig.Windows {
|
||||
try {
|
||||
foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) {
|
||||
foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) {
|
||||
await item2.Doc.Generate(new Progress<double>(v => {
|
||||
await item2.Doc.Generate(CancelGeneration.Token, new Progress<double>(v => {
|
||||
ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum;
|
||||
}));
|
||||
}
|
||||
@@ -768,6 +804,9 @@ namespace Elwig.Windows {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
UnlockInputs();
|
||||
GenerateButton.IsEnabled = true;
|
||||
GenerateButton.Visibility = Visibility.Visible;
|
||||
AbortButton.IsEnabled = false;
|
||||
AbortButton.Visibility = Visibility.Hidden;
|
||||
Mouse.OverrideCursor = null;
|
||||
return;
|
||||
}
|
||||
@@ -788,7 +827,7 @@ namespace Elwig.Windows {
|
||||
docs.Insert(0, new Letterhead(m.Member));
|
||||
}
|
||||
docs.ForEach(doc => {
|
||||
doc.DoublePaged = doublePaged;
|
||||
doc.IsDoublePaged = doublePaged;
|
||||
if (doc is BusinessDocument b)
|
||||
b.Location = location;
|
||||
});
|
||||
@@ -801,8 +840,8 @@ namespace Elwig.Windows {
|
||||
if (printDocs.Count > 0) {
|
||||
try {
|
||||
var print = Document.Merge(printDocs);
|
||||
print.DoublePaged = doublePaged;
|
||||
await print.Generate(new Progress<double>(v => {
|
||||
print.IsDoublePaged = doublePaged;
|
||||
await print.Generate(CancelGeneration.Token, new Progress<double>(v => {
|
||||
ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum;
|
||||
}));
|
||||
PrintDocument = print;
|
||||
@@ -811,6 +850,9 @@ namespace Elwig.Windows {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
UnlockInputs();
|
||||
GenerateButton.IsEnabled = true;
|
||||
GenerateButton.Visibility = Visibility.Visible;
|
||||
AbortButton.IsEnabled = false;
|
||||
AbortButton.Visibility = Visibility.Hidden;
|
||||
Mouse.OverrideCursor = null;
|
||||
return;
|
||||
}
|
||||
@@ -822,10 +864,13 @@ namespace Elwig.Windows {
|
||||
|
||||
UnlockInputs();
|
||||
GenerateButton.IsEnabled = true;
|
||||
GenerateButton.Visibility = Visibility.Visible;
|
||||
AbortButton.IsEnabled = false;
|
||||
AbortButton.Visibility = Visibility.Hidden;
|
||||
Mouse.OverrideCursor = null;
|
||||
PreviewButton.IsEnabled = true;
|
||||
PrintButton.IsEnabled = PrintDocument != null;
|
||||
EmailButton.IsEnabled = EmailDocuments != null && App.Config.Smtp != null;
|
||||
PrintButton.IsEnabled = PrintDocument != null && !hasPreviewDocs;
|
||||
EmailButton.IsEnabled = EmailDocuments != null && !hasPreviewDocs && App.Config.Smtp != null;
|
||||
}
|
||||
|
||||
private async void PreviewButton_Click(object sender, RoutedEventArgs evt) {
|
||||
|
||||
@@ -64,6 +64,23 @@
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Synchronisieren" x:Name="Menu_Sync" IsEnabled="false">
|
||||
<MenuItem x:Name="Menu_Sync_Download" Header="Mitgliederdaten und Lieferungen herunterladen" Click="Menu_Sync_Download_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="Menu_Sync_UploadBranchDeliveries" Header="Lieferungen dieser Saison/Zweigstelle hochladen" Click="Menu_Sync_UploadBranchDeliveries_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="Menu_Sync_UploadModified" Header="Geänderte Mitglieder und Lieferungen hochladen" Click="Menu_Sync_UploadModified_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Waage">
|
||||
<MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click">
|
||||
<MenuItem.Icon>
|
||||
@@ -174,16 +191,18 @@
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button x:Name="DownloadButton" Click="DownloadButton_Click"
|
||||
Margin="310,135,0,0" Padding="0.375,0.5,0,0" Height="30" Width="30"
|
||||
Content="" FontFamily="Segoe MDL2 Assets" FontSize="16"
|
||||
HorizontalContentAlignment="Center"
|
||||
ToolTip="Lieferungen und Mitgliederdaten anderer Zweigstellen herunterladen"/>
|
||||
<Button x:Name="UploadButton" Click="UploadButton_Click"
|
||||
<Button x:Name="SyncButton" Click="SyncButton_Click"
|
||||
Margin="375,135,0,0" Padding="1.0,0.5,0,0" Height="30" Width="30"
|
||||
Content="" FontFamily="Segoe MDL2 Assets" FontSize="16"
|
||||
FontFamily="Segoe MDL2 Assets" FontSize="16"
|
||||
HorizontalContentAlignment="Center"
|
||||
ToolTip="Lieferungen dieser Zweigstelle hochladen"/>
|
||||
ToolTip="Geänderte Mitgliederdaten und Lieferungen synchronisieren">
|
||||
<Button.Content>
|
||||
<Grid TextElement.FontFamily="Segoe MDL2 Assets">
|
||||
<TextBlock x:Name="SyncButton_1" Text=""/>
|
||||
<TextBlock x:Name="SyncButton_2" Text="" Foreground="DarkOrange"/>
|
||||
</Grid>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
|
||||
<Expander x:Name="SeasonFinish" Header="Leseabschluss" SnapsToDevicePixels="True"
|
||||
Expanded="SeasonFinish_Expanded" Collapsed="SeasonFinish_Collapsed"
|
||||
|
||||
@@ -2,26 +2,30 @@ using Elwig.Helpers;
|
||||
using Elwig.Helpers.Billing;
|
||||
using Elwig.Helpers.Export;
|
||||
using Elwig.Models.Dtos;
|
||||
using Elwig.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Elwig.Windows {
|
||||
public partial class MainWindow : ContextWindow {
|
||||
|
||||
private readonly DispatcherTimer _syncTimer = new() { Interval = TimeSpan.FromHours(1) };
|
||||
|
||||
public MainWindow() {
|
||||
InitializeComponent();
|
||||
var v = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
@@ -29,14 +33,21 @@ namespace Elwig.Windows {
|
||||
if (App.Client.Client == null) VersionField.Text += " (Unbekannt)";
|
||||
Menu_Help_Update.IsEnabled = App.Config.UpdateUrl != null;
|
||||
Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null;
|
||||
DownloadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
|
||||
UploadButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
|
||||
Menu_Sync.IsEnabled = App.Config.SyncUrl != null;
|
||||
SyncButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
|
||||
Menu_Database_Upload.IsEnabled = App.Config.SyncUrl != null;
|
||||
Menu_Database_Download.IsEnabled = App.Config.SyncUrl != null;
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs evt) {
|
||||
SeasonInput.Value = Utils.CurrentLastSeason;
|
||||
|
||||
if (Utils.HasInternetConnectivity()) {
|
||||
CheckSync(200);
|
||||
}
|
||||
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
||||
_syncTimer.Tick += new EventHandler(OnSyncTimer);
|
||||
_syncTimer.Start();
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs evt) {
|
||||
@@ -195,92 +206,43 @@ namespace Elwig.Windows {
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
private async void DownloadButton_Click(object sender, RoutedEventArgs evt) {
|
||||
private async void SyncButton_Click(object sender, RoutedEventArgs evt) {
|
||||
if (App.Config.SyncUrl == null)
|
||||
return;
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
var files = data
|
||||
.Select(f => new {
|
||||
Name = f!["name"]!.AsValue().GetValue<string>(),
|
||||
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
|
||||
ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
|
||||
Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
|
||||
Url = f!["url"]!.AsValue().GetValue<string>(),
|
||||
Size = f!["size"]!.AsValue().GetValue<long>(),
|
||||
})
|
||||
.Where(f => f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
|
||||
.ToList();
|
||||
|
||||
var imported = await ElwigData.GetImportedFiles();
|
||||
var import = files
|
||||
.Where(f => f.Device != Environment.MachineName && !imported.Contains(f.Name))
|
||||
.ToList();
|
||||
var paths = new List<string>();
|
||||
using (var client = Utils.GetHttpClient(App.Config.SyncUsername, App.Config.SyncPassword)) {
|
||||
foreach (var f in import) {
|
||||
var filename = Path.Combine(App.TempPath, f.Name);
|
||||
using var stream = new FileStream(filename, FileMode.Create);
|
||||
await client.DownloadAsync(f.Url, stream);
|
||||
paths.Add(filename);
|
||||
}
|
||||
}
|
||||
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
private async void UploadButton_Click(object sender, RoutedEventArgs evt) {
|
||||
private async void Menu_Sync_Download_Click(object sender, RoutedEventArgs evt) {
|
||||
if (App.Config.SyncUrl == null)
|
||||
return;
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
|
||||
using var ctx = new AppDbContext();
|
||||
var deliveries = await ctx.Deliveries
|
||||
.Where(d => d.Year == Utils.CurrentLastSeason && d.ZwstId == App.ZwstId)
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.Kg)
|
||||
.ThenInclude(k => k!.Gl)
|
||||
.OrderBy(d => d.DateString)
|
||||
.ThenBy(d => d.TimeString)
|
||||
.ThenBy(d => d.LsNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = deliveries
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (deliveries.Count == 0) {
|
||||
MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
await ElwigData.Export(path, deliveries, wbKgs, [$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]);
|
||||
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
|
||||
private async void Menu_Sync_UploadBranchDeliveries_Click(object sender, RoutedEventArgs evt) {
|
||||
if (App.Config.SyncUrl == null)
|
||||
return;
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
await SyncService.UploadBranchDeliveries(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
private async void Menu_Sync_UploadModified_Click(object sender, RoutedEventArgs evt) {
|
||||
if (App.Config.SyncUrl == null)
|
||||
return;
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
await SyncService.UploadModified(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
@@ -394,9 +356,42 @@ namespace Elwig.Windows {
|
||||
App.FocusMailWindow();
|
||||
}
|
||||
|
||||
protected override Task OnRenewContext(AppDbContext ctx) {
|
||||
protected async override Task OnRenewContext(AppDbContext ctx) {
|
||||
SeasonInput_TextChanged(null, null);
|
||||
return Task.CompletedTask;
|
||||
CheckSync();
|
||||
}
|
||||
|
||||
private void OnSyncTimer(object? sender, EventArgs? evt) {
|
||||
if (Utils.HasInternetConnectivity()) {
|
||||
CheckSync();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs evt) {
|
||||
if (!evt.IsAvailable) return;
|
||||
if (Utils.HasInternetConnectivity()) {
|
||||
CheckSync(1000);
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckSync(int delay = 0) {
|
||||
if (App.Config.SyncUrl == null) return;
|
||||
Utils.RunBackground("Daten Synchronisieren", async () => {
|
||||
await Task.Delay(delay);
|
||||
var ch = false;
|
||||
using (var ctx = new AppDbContext()) {
|
||||
ch = await SyncService.ChangesAvailable(ctx, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
|
||||
}
|
||||
await App.MainDispatcher.BeginInvoke(() => {
|
||||
if (ch) {
|
||||
SyncButton_1.Text = "\uEA6A";
|
||||
SyncButton_2.Text = "\uEA81";
|
||||
} else {
|
||||
SyncButton_1.Text = "\uE895";
|
||||
SyncButton_2.Text = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void SeasonFinish_Expanded(object sender, RoutedEventArgs evt) {
|
||||
|
||||
@@ -55,6 +55,9 @@ namespace Elwig.Windows {
|
||||
.OrderBy(v => v.AvNr)
|
||||
.Include(v => v.Season.Currency)
|
||||
.ToListAsync());
|
||||
if (PaymentVariantList.SelectedItem == null && PaymentVariantList.Items.Count > 0) {
|
||||
PaymentVariantList.SelectedIndex = PaymentVariantList.Items.Count - 1;
|
||||
}
|
||||
Update();
|
||||
}
|
||||
|
||||
@@ -119,6 +122,11 @@ namespace Elwig.Windows {
|
||||
private async void DeleteButton_Click(object sender, RoutedEventArgs evt) {
|
||||
if (PaymentVariantList.SelectedItem is not PaymentVar v || !v.TestVariant)
|
||||
return;
|
||||
var res = MessageBox.Show(
|
||||
$"Soll die Auszahlungsvariante \"{v.Name}\" wirklich unwiderruflich gelöscht werden?",
|
||||
"Auszahlungsvariante löschen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
|
||||
if (res != MessageBoxResult.OK)
|
||||
return;
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
try {
|
||||
await PaymentVariantService.DeletePaymentVariant(v.Year, v.AvNr);
|
||||
|
||||
Binary file not shown.
@@ -13,7 +13,7 @@ About
|
||||
**Product:** Elwig
|
||||
**Description:** Electronic Management for Vintners' Cooperatives
|
||||
**Type:** ERP system
|
||||
**Version:** 1.0.2.0 ([Changelog](./CHANGELOG.md))
|
||||
**Version:** 1.0.3.0 ([Changelog](./CHANGELOG.md))
|
||||
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
|
||||
**Website:** https://elwig.at/
|
||||
**Source code:** https://git.necronda.net/winzer/elwig
|
||||
@@ -33,7 +33,7 @@ Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
|
||||
**Produkt:** Elwig
|
||||
**Beschreibung:** Elektronische Winzergenossenschaftsverwaltung
|
||||
**Typ:** Warenwirtschaftssystem (ERP-System)
|
||||
**Version:** 1.0.2.0 ([Änderungsprotokoll](./CHANGELOG.md))
|
||||
**Version:** 1.0.3.0 ([Änderungsprotokoll](./CHANGELOG.md))
|
||||
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
|
||||
**Website:** https://elwig.at/
|
||||
**Quellcode:** https://git.necronda.net/winzer/elwig
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Tests {
|
||||
public async Task Setup_1_Database() {
|
||||
AppDbContext.ConnectionStringOverride = $"Data Source=ElwigTestDB; Mode=Memory; Foreign Keys=True; Cache=Shared";
|
||||
Connection = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
|
||||
}
|
||||
|
||||
[OneTimeSetUp]
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace Tests.E2ETests {
|
||||
public static async Task SetupDatabase() {
|
||||
if (File.Exists(Utils.TestDatabasePath)) File.Delete(Utils.TestDatabasePath);
|
||||
using var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{Utils.TestDatabasePath}\"; Mode=ReadWriteCreate; Foreign Keys=True; Cache=Default; Pooling=False");
|
||||
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
|
||||
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
|
||||
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.E2EInsert.sql");
|
||||
await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
|
||||
await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
|
||||
await cnx.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.E2EInsert.sql");
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Appium.WebDriver" Version="4.4.5" />
|
||||
<PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.11.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -12,13 +12,13 @@ namespace Tests.UnitTests.DocumentTests {
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupDatabase() {
|
||||
Connection = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentInsert.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentInsert.sql");
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public async Task TeardownDatabase() {
|
||||
if (Connection == null) return;
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentDelete.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentDelete.sql");
|
||||
await Connection.DisposeAsync();
|
||||
Connection = null;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@ namespace Tests.UnitTests.HelperTests {
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupDatabase() {
|
||||
Connection = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingInsert.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingInsert.sql");
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public async Task TeardownDatabase() {
|
||||
if (Connection == null) return;
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingDelete.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingDelete.sql");
|
||||
await Connection.DisposeAsync();
|
||||
Connection = null;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace Tests.UnitTests.HelperTests {
|
||||
[TearDown]
|
||||
public async Task CleanupDatabasePayment() {
|
||||
if (Connection == null) return;
|
||||
await AppDbContext.ExecuteBatch(Connection, """
|
||||
await Connection.ExecuteBatch("""
|
||||
DELETE FROM credit;
|
||||
DELETE FROM payment_variant;
|
||||
DELETE FROM delivery_part_bucket;
|
||||
@@ -115,7 +115,7 @@ namespace Tests.UnitTests.HelperTests {
|
||||
}
|
||||
|
||||
private Task InsertPaymentVariant(int year, int avnr, string data) {
|
||||
return AppDbContext.ExecuteBatch(Connection!, $"""
|
||||
return Connection!.ExecuteBatch($"""
|
||||
INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, data)
|
||||
VALUES ({year}, {avnr}, 'Test', '2021-01-15', NULL, TRUE, NULL, '{data}');
|
||||
""");
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Tests.UnitTests.ServiceTests {
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupDatabase() {
|
||||
Connection = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceInsert.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceInsert.sql");
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public async Task TeardownDatabase() {
|
||||
if (Connection == null) return;
|
||||
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceDelete.sql");
|
||||
await Connection.ExecuteEmbeddedScript(Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceDelete.sql");
|
||||
await Connection.DisposeAsync();
|
||||
Connection = null;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_03_Moving() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "moving";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_04_Overloaded() {
|
||||
Mock.Weight = 10_000;
|
||||
Mock.Error = "overloaded";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
|
||||
}
|
||||
|
||||
@@ -87,14 +87,14 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_05_InvalidResponse() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "invalid";
|
||||
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_06_InvalidCrc() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "crc";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_07_InvalidUnit() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "unit";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Tare = 41;
|
||||
Mock.Error = "moving";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Tare = 41;
|
||||
Mock.Error = "invalid";
|
||||
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_03_Moving() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "moving";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_04_Overloaded() {
|
||||
Mock.Weight = 10_000;
|
||||
Mock.Error = "overloaded";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
|
||||
}
|
||||
|
||||
@@ -87,14 +87,14 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_05_InvalidResponse() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "invalid";
|
||||
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_06_InvalidCrc() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "crc";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_07_InvalidUnit() {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Error = "unit";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Tare = 41;
|
||||
Mock.Error = "moving";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await Scale!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
Mock.Weight = 1_000;
|
||||
Mock.Tare = 41;
|
||||
Mock.Error = "invalid";
|
||||
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||
Assert.ThrowsAsync<FormatException>(async () => await Scale!.Weigh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_03_Moving() {
|
||||
MockA.Weight = 1_000;
|
||||
MockA.Error = "moving";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_04_Overloaded() {
|
||||
MockA.Weight = 10_000;
|
||||
MockA.Error = "overloaded";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
|
||||
}
|
||||
|
||||
@@ -107,14 +107,14 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_05_InvalidResponse() {
|
||||
MockA.Weight = 1_000;
|
||||
MockA.Error = "invalid";
|
||||
Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||
Assert.ThrowsAsync<FormatException>(async () => await ScaleA!.Weigh());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test_06_InvalidCrc() {
|
||||
MockA.Weight = 1_000;
|
||||
MockA.Error = "crc";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
|
||||
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace Tests.UnitTests.WeighingTests {
|
||||
public void Test_07_InvalidUnit() {
|
||||
MockA.Weight = 1_000;
|
||||
MockA.Error = "unit";
|
||||
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||
var ex = Assert.ThrowsAsync<WeighingException>(async () => await ScaleA!.Weigh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user