Compare commits

...

4 Commits

Author SHA1 Message Date
5c14c06c1d [#71] Weighing: Fix reconnection behaviour when COM port is connected/disconnected
All checks were successful
Test / Run tests (push) Successful in 1m52s
2026-01-03 16:32:50 +01:00
c45800099c MainWindow: Merge both sync buttons into a single one
All checks were successful
Test / Run tests (push) Successful in 2m32s
2025-12-22 00:10:20 +01:00
36288682dc AbbDbContext: Move SqliteConnection extensions to Extensions class
All checks were successful
Test / Run tests (push) Successful in 2m25s
2025-12-20 11:06:03 +01:00
2b7c16a2a1 Services: Add SyncService
All checks were successful
Test / Run tests (push) Successful in 2m23s
2025-12-19 23:26:30 +01:00
29 changed files with 594 additions and 319 deletions

View File

@@ -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,14 +249,18 @@ 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) {
try {
Scales[i] = Scale.FromConfig(s);
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}", "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);
} 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",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
@@ -268,12 +272,14 @@ 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);
try {
Scales[i].Dispose();
} catch {
// ignore
if (Scales[i] is ICommandScale) {
try {
Scales[i].Dispose();
} catch {
// ignore
}
Scales[i] = new InvalidScale(s.Id);
}
Scales[i] = new InvalidScale(s.Id);
}
}
UpdateScales();

View File

@@ -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();

View File

@@ -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};

View File

@@ -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

View File

@@ -47,7 +47,7 @@ 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,
@@ -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

View File

@@ -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 {

View File

@@ -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)}

View File

@@ -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];
}
}
}

View File

@@ -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() {
string[] currentPorts = SerialPort.GetPortNames();
var newPorts = currentPorts.Except(_knownPorts).ToArray();
foreach (var port in newPorts)
SerialPortConnected?.Invoke(this, port);
_knownPorts = currentPorts;
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() {
string[] currentPorts = SerialPort.GetPortNames();
var removedPorts = _knownPorts.Except(currentPorts).ToArray();
foreach (var port in removedPorts)
SerialPortDisconnected?.Invoke(this, port);
_knownPorts = currentPorts;
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() {

View File

@@ -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;

View File

@@ -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();

View File

@@ -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:")) {
Serial = Utils.OpenSerialConnection(cnx);
Stream = Serial.BaseStream;
try {
Serial = Utils.OpenSerialConnection(cnx);
} 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();

View File

@@ -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;

View File

@@ -0,0 +1,6 @@
using System;
namespace Elwig.Helpers.Weighing {
public class WeighingException(string? message = null) : Exception(message) {
}
}

View File

@@ -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 {
@@ -799,40 +797,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 {

View File

@@ -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;
@@ -562,46 +560,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 {

View 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);
}
}
}

View File

@@ -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="&#xE896;"/>
</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="&#xE898;"/>
</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="&#xECC5;"/>
</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="&#xE896;" 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="&#xE898;" 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="&#xE895;"/>
<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"

View File

@@ -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);
}
} 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);
}
await SyncService.Download(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
});
Mouse.OverrideCursor = null;
}
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) {

View File

@@ -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]

View File

@@ -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]

View File

@@ -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;
}

View File

@@ -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}');
""");

View File

@@ -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;
}

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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());
}
}
}