670 lines
31 KiB
C#
670 lines
31 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Diagnostics;
|
|
using System.Text.RegularExpressions;
|
|
using System.IO.Ports;
|
|
using System.Net.Sockets;
|
|
using Elwig.Dialogs;
|
|
using System.Text;
|
|
using System.Numerics;
|
|
using Elwig.Models.Entities;
|
|
using Elwig.Helpers.Billing;
|
|
using System.Runtime.InteropServices;
|
|
using System.Net.Http;
|
|
using System.Text.Json.Nodes;
|
|
using System.IO;
|
|
using MailKit.Net.Smtp;
|
|
using MailKit.Security;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Reflection;
|
|
using System.Collections;
|
|
using Elwig.Documents;
|
|
using MimeKit;
|
|
using System.Windows.Input;
|
|
using LinqKit;
|
|
using System.Linq.Expressions;
|
|
using Elwig.Models;
|
|
using Microsoft.Win32;
|
|
using System.Globalization;
|
|
using System.Threading;
|
|
using System.Windows.Markup;
|
|
|
|
namespace Elwig.Helpers {
|
|
public static partial class Utils {
|
|
|
|
public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
|
|
|
|
public static int CurrentYear => DateTime.Now.Year;
|
|
public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
|
|
public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 6 ? 1 : 0);
|
|
public static int FollowingSeason => DateTime.Now.Year + (DateTime.Now.Month >= 11 ? 1 : 0);
|
|
public static DateTime Today => (DateTime.Now.Hour >= 3) ? DateTime.Today : DateTime.Today.AddDays(-1);
|
|
|
|
[GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)]
|
|
private static partial Regex GeneratedSerialRegex();
|
|
public static readonly Regex SerialRegex = GeneratedSerialRegex();
|
|
|
|
[GeneratedRegex("^tcp://([A-Za-z0-9._-]+):([0-9]+)$", RegexOptions.Compiled)]
|
|
private static partial Regex GeneratedTcpRegex();
|
|
public static readonly Regex TcpRegex = GeneratedTcpRegex();
|
|
|
|
[GeneratedRegex(@"^(-?(0?[1-9]|[12][0-9]|3[01])\.(0?[1-9]|1[0-2])\.([0-9]{4})?-?){1,2}$", RegexOptions.Compiled)]
|
|
private static partial Regex GeneratedFromToDateRegex();
|
|
public static readonly Regex DateFromToRegex = GeneratedFromToDateRegex();
|
|
|
|
[GeneratedRegex(@"^([0-9]+([\.,][0-9]+)?)?-([0-9]+([\.,][0-9]+)?)?$", RegexOptions.Compiled)]
|
|
private static partial Regex GeneratedFromToRegex();
|
|
public static readonly Regex FromToRegex = GeneratedFromToRegex();
|
|
|
|
[GeneratedRegex(@"^([0-9]{1,2}:[0-9]{2})?-([0-9]{1,2}:[0-9]{2})?$", RegexOptions.Compiled)]
|
|
private static partial Regex GeneratedFromToTimeRegex();
|
|
public static readonly Regex FromToTimeRegex = GeneratedFromToTimeRegex();
|
|
|
|
[GeneratedRegex(@"^(.*?) +([0-9].*)$", RegexOptions.Compiled)]
|
|
private static partial Regex GeneratedAddressRegex();
|
|
public static readonly Regex AddressRegex = GeneratedAddressRegex();
|
|
|
|
[GeneratedRegex(@"[^A-Za-z0-9ÄÜÖẞäöüß-]+")]
|
|
private static partial Regex GeneratedInvalidFileNamePartsRegex();
|
|
public static readonly Regex InvalidFileNamePartsRegex = GeneratedInvalidFileNamePartsRegex();
|
|
|
|
public static readonly string GroupSeparator = "\u202F";
|
|
public static readonly string UnitSeparator = "\u00A0";
|
|
|
|
public static readonly KeyValuePair<string, string>[] PhoneNrTypes = [
|
|
new("landline", "Tel.-Nr. (Festnetz)"),
|
|
new("mobile", "Tel.-Nr. (mobil)"),
|
|
new("fax", "Fax-Nr."),
|
|
];
|
|
|
|
public static string PhoneNrTypeToString(string type) {
|
|
return PhoneNrTypes.Where(t => t.Key == type).Select(t => t.Value).FirstOrDefault(type);
|
|
}
|
|
|
|
private static readonly string[] TempWildcards = ["*.html", "*.pdf", "*.exe", "*.zip"];
|
|
|
|
private static readonly ushort[] Crc16ModbusTable = [
|
|
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
|
|
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
|
|
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
|
|
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
|
|
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
|
|
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
|
|
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
|
|
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
|
|
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
|
|
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
|
|
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
|
|
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
|
|
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
|
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
|
|
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
|
|
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
|
|
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
|
|
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
|
|
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
|
|
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
|
|
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
|
|
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
|
|
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
|
|
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
|
|
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
|
|
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
|
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
|
|
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
|
|
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
|
|
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
|
|
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
|
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,
|
|
];
|
|
|
|
public static void OverrideCulture() {
|
|
var locale = new CultureInfo("de-AT", false);
|
|
locale.NumberFormat.CurrencyGroupSeparator = Utils.GroupSeparator;
|
|
locale.NumberFormat.NumberGroupSeparator = Utils.GroupSeparator;
|
|
locale.NumberFormat.PercentGroupSeparator = Utils.GroupSeparator;
|
|
CultureInfo.CurrentCulture = locale;
|
|
CultureInfo.CurrentUICulture = locale;
|
|
Thread.CurrentThread.CurrentCulture = locale;
|
|
Thread.CurrentThread.CurrentUICulture = locale;
|
|
CultureInfo.DefaultThreadCurrentCulture = locale;
|
|
CultureInfo.DefaultThreadCurrentUICulture = locale;
|
|
FrameworkElement.LanguageProperty.OverrideMetadata(
|
|
typeof(FrameworkElement),
|
|
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name))
|
|
);
|
|
}
|
|
|
|
public static SerialPort OpenSerialConnection(string connection) {
|
|
var m = SerialRegex.Match(connection);
|
|
if (!m.Success)
|
|
throw new ArgumentException("Invalid connection string for scheme \"serial\"");
|
|
|
|
var stop = m.Groups[6].Value;
|
|
var parity = m.Groups[5].Value.ToUpper();
|
|
var data = m.Groups[4].Value;
|
|
var port = new SerialPort() {
|
|
PortName = m.Groups[1].Value,
|
|
BaudRate = int.Parse(m.Groups[2].Value),
|
|
Parity = parity == "E" ? Parity.Even :
|
|
parity == "O" ? Parity.Odd :
|
|
parity == "M" ? Parity.Mark :
|
|
parity == "S" ? Parity.Space :
|
|
Parity.None,
|
|
DataBits = data == "" ? 8 : int.Parse(data),
|
|
StopBits = (StopBits)(stop == "" ? 1 : stop == "1.5" ? 3 : stop[0] - '0'),
|
|
Handshake = Handshake.None,
|
|
WriteTimeout = 250,
|
|
ReadTimeout = 11000,
|
|
};
|
|
port.Open();
|
|
return port;
|
|
}
|
|
|
|
public static TcpClient OpenTcpConnection(string connection) {
|
|
var m = TcpRegex.Match(connection);
|
|
if (!m.Success)
|
|
throw new ArgumentException("Invalid connection string for scheme \"tcp\"");
|
|
|
|
var client = new TcpClient() {
|
|
SendTimeout = 250,
|
|
ReceiveTimeout = 11000,
|
|
};
|
|
client.Connect(m.Groups[1].Value, int.Parse(m.Groups[2].Value));
|
|
return client;
|
|
}
|
|
|
|
public static int Modulo(string a, int b) {
|
|
if (a.Length == 0 || !a.All(char.IsAsciiDigit)) {
|
|
throw new ArgumentException("First argument has to be a decimal string");
|
|
} else if (b < 2) {
|
|
throw new ArgumentException("Second argument has to be greater than 1");
|
|
}
|
|
return a.Select(ch => ch - '0').Aggregate((sum, n) => (sum * 10 + n) % b);
|
|
}
|
|
|
|
public static ushort CalcCrc16Modbus(byte[] data) {
|
|
// https://www.modbustools.com/modbus_crc16.htm
|
|
byte temp;
|
|
ushort crc = 0xFFFF;
|
|
foreach (byte b in data) {
|
|
temp = (byte)(b ^ crc);
|
|
crc >>= 8;
|
|
crc ^= Crc16ModbusTable[temp];
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
public static ushort CalcCrc16Modbus(string data) {
|
|
return CalcCrc16Modbus(Encoding.ASCII.GetBytes(data));
|
|
}
|
|
|
|
public static string FormatIban(string iban) {
|
|
return Regex.Replace(iban.Trim(), ".{4}", "$0 ").Trim();
|
|
}
|
|
|
|
public static void RunBackground(string title, Func<Task> a) {
|
|
Task.Run(async () => {
|
|
try {
|
|
await a();
|
|
} catch (Exception e) {
|
|
MessageBox.Show(e.ToString(), title, MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static void MailTo(string emailAddress) {
|
|
MailTo([emailAddress]);
|
|
}
|
|
|
|
public static void MailTo(IEnumerable<string> emailAddresses) {
|
|
Process.Start(new ProcessStartInfo() {
|
|
FileName = $"mailto:{string.Join(",%20", emailAddresses)}",
|
|
UseShellExecute = true,
|
|
});
|
|
}
|
|
|
|
public static double KmwToOe(double kmw) {
|
|
return Math.Round(kmw * (4.54 + 0.022 * kmw), 0);
|
|
}
|
|
|
|
public static double OeToKmw(double oe) {
|
|
return Math.Round((-4.54 + Math.Sqrt(4.54 * 4.54 - 4 * 0.022 * -oe)) / (2 * 0.022), 1);
|
|
}
|
|
|
|
public static decimal DecFromDb(long value, byte precision) {
|
|
bool neg = value < 0;
|
|
if (neg) value = -value;
|
|
return new((int)(value & 0xFFFFFFFF), (int)((value >> 32) & 0x7FFFFFFF), 0, neg, precision);
|
|
}
|
|
|
|
public static long DecToDb(decimal value, byte precision) {
|
|
return (long)decimal.Round(value * (decimal)Math.Pow(10, precision), 0);
|
|
}
|
|
|
|
public static int GetAge(DateOnly birthday) {
|
|
var today = DateTime.Today;
|
|
var a = (today.Year * 100 + today.Month) * 100 + today.Day;
|
|
var b = (birthday.Year * 100 + birthday.Month) * 100 + birthday.Day;
|
|
return (a - b) / 10000;
|
|
}
|
|
|
|
public static int GetSearchScore(IEnumerable<string?> words, IEnumerable<string> searchKeywords) {
|
|
searchKeywords = searchKeywords.Where(s => s.Length >= 2 || (s.Length > 0 && s.All(c => char.IsAsciiDigit(c))));
|
|
if (!searchKeywords.Any())
|
|
return 0;
|
|
|
|
return searchKeywords
|
|
.Select(k => {
|
|
k = k.ToLower();
|
|
var scores = words.Select(w => {
|
|
w = w?.ToLower();
|
|
var p = w?.ToLower()?.Split(" ");
|
|
if (w == null || p == null) {
|
|
return 0;
|
|
} else if (k == w) {
|
|
return 4 + k.Length;
|
|
} else if (p.Any(a => a == k)) {
|
|
return 3 + k.Length;
|
|
} else if (p.Any(a => a.StartsWith(k))) {
|
|
return 2 + k.Length;
|
|
} else if (w.Contains(k)) {
|
|
return 1 + k.Length;
|
|
} else {
|
|
return 0;
|
|
}
|
|
});
|
|
return scores.Max() + scores.Count(s => s > 0);
|
|
})
|
|
.Sum();
|
|
}
|
|
|
|
public static (int, string?)? ShowManualWeighingDialog(string? reason = null) {
|
|
var d = new ManualWeighingDialog(reason);
|
|
return d.ShowDialog() == true ? (d.Weight, d.Reason) : null;
|
|
}
|
|
|
|
public static (string?, int[])? ShowDeliverySplittingDialog(Delivery delivery) {
|
|
var d = new DeliverySplittingDialog(delivery);
|
|
return d.ShowDialog() == true ? (d.MgNr?.ToString() ?? d.LsNr, d.Weights ?? []) : null;
|
|
}
|
|
|
|
public static double? ShowLinearPriceIncreaseDialog() {
|
|
var d = new LinearPriceIncreaseDialog();
|
|
return d.ShowDialog() == true ? d.Price : null;
|
|
}
|
|
|
|
public static Footer GenerateFooter(string lineBreak, string seperator) {
|
|
return new Footer(lineBreak, seperator);
|
|
}
|
|
|
|
public class Footer {
|
|
private string Text = "";
|
|
private readonly string LineBreak;
|
|
private readonly string Seperator;
|
|
private bool FirstLine = true;
|
|
private bool FirstItemInLine = true;
|
|
|
|
public Footer(string lineBreak, string seperator) {
|
|
LineBreak = lineBreak;
|
|
Seperator = seperator;
|
|
}
|
|
|
|
public Footer Item(string? text) {
|
|
if (text == null) return this;
|
|
Text += FirstItemInLine ? (FirstLine ? "" : LineBreak) : Seperator;
|
|
Text += text;
|
|
FirstLine = false;
|
|
FirstItemInLine = false;
|
|
return this;
|
|
}
|
|
|
|
public Footer Item(string name, string? text) {
|
|
return text == null ? this : Item($"{name}: {text}");
|
|
}
|
|
|
|
public Footer NextLine() {
|
|
FirstItemInLine = true;
|
|
return this;
|
|
}
|
|
|
|
public override string ToString() {
|
|
return Text;
|
|
}
|
|
}
|
|
|
|
public static string GetSign<T>(T number) where T : INumber<T>
|
|
=> T.Sign(number) switch {
|
|
< 0 => "\u2212", // minus
|
|
0 => "\u00b1", // plus minus
|
|
> 0 => "+",
|
|
};
|
|
|
|
public static double AggregateDeliveryPartsKmw(IEnumerable<IDelivery> parts)
|
|
=> parts.Aggregate(
|
|
(Weight: 0, Kmw: 0.0),
|
|
(sum, item) => (
|
|
sum.Weight + item.Weight,
|
|
(sum.Kmw * sum.Weight + item.Kmw * item.Weight) / (sum.Weight + item.Weight)
|
|
),
|
|
sum => sum.Kmw
|
|
);
|
|
|
|
public static string GenerateLsNr(Delivery d) => GenerateLsNr(d.Date, d.ZwstId, d.LNr);
|
|
|
|
public static string GenerateLsNr(DateOnly date, string zwstid, int lnr) => $"{date:yyyyMMdd}{zwstid}{lnr:000}";
|
|
|
|
public static (string, string?) SplitAddress(string address) {
|
|
var m = AddressRegex.Match(address);
|
|
return (m.Groups[1].Value, m.Groups[2].Value);
|
|
}
|
|
|
|
public static (string, string?) SplitName(string fullName, string? familyName) {
|
|
if (string.IsNullOrWhiteSpace(familyName)) return (fullName, null);
|
|
var p0 = fullName.IndexOf(familyName, StringComparison.CurrentCultureIgnoreCase);
|
|
if (p0 == -1) return (fullName, null);
|
|
var p1 = fullName.IndexOf(" und ");
|
|
var p2 = fullName.ToLower().LastIndexOf(" und ");
|
|
if (p1 != p2) {
|
|
if (p0 > p1) {
|
|
// A und B familyName [und ...]
|
|
return (fullName[p0..^0], fullName[0..(p0 - 1)]);
|
|
} else {
|
|
// familyName und ... A und B
|
|
var p3 = fullName.LastIndexOf(' ', p2 - 1);
|
|
return (fullName[0..p3], fullName[(p3 + 1)..^0]);
|
|
}
|
|
} else if (p0 + familyName.Length >= fullName.Length || fullName[p0 + familyName.Length] == ' ') {
|
|
return (familyName, fullName.Replace(familyName, "").Replace(" ", " ").Trim());
|
|
} else {
|
|
return (fullName, null);
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<IEnumerable<T>> Permutate<T>(IEnumerable<T> input, IEnumerable<T>? forced = null) {
|
|
HashSet<IEnumerable<T>> output = [];
|
|
for (int i = 0; i < Math.Pow(2, input.Count()); i++) {
|
|
List<T> t = [];
|
|
for (int j = 0; j < 30; j++) {
|
|
var e = input.ElementAtOrDefault(j);
|
|
if (e != null && ((forced?.Contains(e) ?? false) || (i & (1 << j)) != 0)) {
|
|
t.Add(e);
|
|
}
|
|
}
|
|
output.Add(t);
|
|
}
|
|
return output.OrderByDescending(l => l.Count());
|
|
}
|
|
|
|
public static List<RawVaribute> GetVaributes(AppDbContext ctx, int year, bool onlyDelivered = true) {
|
|
var varieties = ctx.WineVarieties.Select(v => new RawVaribute(v.SortId, "", null)).ToList();
|
|
var delivered = ctx.DeliveryParts
|
|
.Where(d => d.Year == year)
|
|
.Select(d => new RawVaribute(d.SortId, d.AttrId ?? "", d.CultId ?? ""))
|
|
.Distinct()
|
|
.ToList();
|
|
return [.. (onlyDelivered ? delivered : delivered.Union(varieties)).Order()];
|
|
}
|
|
|
|
public static List<Varibute> GetVaributeList(AppDbContext ctx, int year, bool onlyDelivered = true) {
|
|
var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
|
|
var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
|
|
var cultivations = ctx.WineCultivations.ToDictionary(c => c.CultId, c => c);
|
|
return GetVaributes(ctx, year, onlyDelivered)
|
|
.Select(s => new Varibute(s, varieties, attributes, cultivations))
|
|
.ToList();
|
|
}
|
|
|
|
[LibraryImport("wininet.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static partial bool InternetGetConnectedState(out int description, int reservedValue);
|
|
|
|
public static bool HasInternetConnectivity() {
|
|
return InternetGetConnectedState(out var _, 0);
|
|
}
|
|
|
|
public static HttpClient GetHttpClient(string? username = null, string? password = null, string? accept = null) {
|
|
var client = new HttpClient() {
|
|
Timeout = TimeSpan.FromSeconds(5),
|
|
};
|
|
client.DefaultRequestHeaders.Accept.Clear();
|
|
if (accept != null)
|
|
client.DefaultRequestHeaders.Accept.Add(new(accept));
|
|
if (username != null || password != null)
|
|
client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(
|
|
Utils.UTF8.GetBytes($"{username}:{password}")));
|
|
return client;
|
|
}
|
|
|
|
public static async Task<(string Version, string Url, long Size)?> GetLatestInstallerUrl(string url) {
|
|
try {
|
|
using var client = GetHttpClient(accept: "application/json");
|
|
using var res = await client.GetAsync(url);
|
|
if (!res.IsSuccessStatusCode)
|
|
return null;
|
|
var resJson = JsonNode.Parse(await res.Content.ReadAsStringAsync());
|
|
var data = resJson!["data"]!.AsArray()[^1]!;
|
|
return ((string)data["version"]!, (string)data["url"]!, (long)data["size"]!);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static async Task UploadExportData(string zip, string url, string username, string password) {
|
|
if (!url.EndsWith('/')) url += "/";
|
|
using var client = GetHttpClient(username, password, accept: "application/json");
|
|
var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
|
|
content.Headers.ContentType = new("application/zip");
|
|
using var res = await client.PutAsync(url + Path.GetFileName(zip), content);
|
|
res.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public static async Task<JsonArray> GetExportMetaData(string url, string username, string password) {
|
|
using var client = GetHttpClient(username, password, accept: "application/json");
|
|
using var res = await client.GetAsync(url);
|
|
res.EnsureSuccessStatusCode();
|
|
var resJson = JsonNode.Parse(await res.Content.ReadAsStringAsync());
|
|
var data = resJson!["data"]!;
|
|
return data.AsArray();
|
|
}
|
|
|
|
public static void CleanupTempFiles() {
|
|
var dir = new DirectoryInfo(App.TempPath);
|
|
foreach (var file in TempWildcards.SelectMany(dir.EnumerateFiles)) {
|
|
file.Delete();
|
|
}
|
|
}
|
|
|
|
public static string NormalizeFileName(string filename) {
|
|
return InvalidFileNamePartsRegex.Replace(filename.Replace('/', '-'), "_");
|
|
}
|
|
|
|
public static async Task<SmtpClient?> GetSmtpClient() {
|
|
if (App.Config.Smtp == null)
|
|
return null;
|
|
var (host, port, mode, username, password, _) = App.Config.Smtp.Value;
|
|
var client = new SmtpClient();
|
|
await client.ConnectAsync(host, port, mode == "starttls" ? SecureSocketOptions.StartTls : SecureSocketOptions.None);
|
|
await client.AuthenticateAsync(username, password);
|
|
return client;
|
|
}
|
|
|
|
public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
|
|
if (App.Config.Smtp == null)
|
|
return false;
|
|
|
|
SmtpClient? client = null;
|
|
try {
|
|
Mouse.OverrideCursor = Cursors.AppStarting;
|
|
client = await GetSmtpClient();
|
|
|
|
using var msg = new MimeMessage();
|
|
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
|
|
msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address)));
|
|
msg.Subject = subject;
|
|
var body = new Multipart("mixed") {
|
|
new TextPart("plain") { Text = text }
|
|
};
|
|
foreach (var doc in docs) {
|
|
var name = NormalizeFileName(doc.Title);
|
|
body.Add(doc.AsEmailAttachment($"{name}.pdf"));
|
|
}
|
|
msg.Body = body;
|
|
await client!.SendAsync(msg);
|
|
} catch (Exception exc) {
|
|
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
return false;
|
|
} finally {
|
|
if (client != null)
|
|
await client.DisconnectAsync(true);
|
|
client?.Dispose();
|
|
Mouse.OverrideCursor = null;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member, string, string)? emailData = null) {
|
|
if (mode == ExportMode.Print && !App.Config.Debug) {
|
|
await doc.Generate();
|
|
await doc.Print();
|
|
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
|
|
await doc.Generate();
|
|
var success = await SendEmail(e.Item1, e.Item2, e.Item3, [doc]);
|
|
if (success)
|
|
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt",
|
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
|
} else if (mode == ExportMode.SavePdf) {
|
|
var d = new SaveFileDialog() {
|
|
FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf",
|
|
DefaultExt = "pdf",
|
|
Filter = "PDF-Datei (*.pdf)|*.pdf",
|
|
Title = $"{doc.Title} speichern unter - Elwig"
|
|
};
|
|
if (d.ShowDialog() == true) {
|
|
await doc.Generate();
|
|
doc.SaveTo(d.FileName);
|
|
Process.Start("explorer.exe", d.FileName);
|
|
}
|
|
} else {
|
|
await doc.Generate();
|
|
doc.Show();
|
|
}
|
|
}
|
|
|
|
public static List<EventLogEntry> GetLogEntries() {
|
|
using var log = new EventLog {
|
|
Log = "Application",
|
|
Source = ".NET Runtime",
|
|
};
|
|
return log.Entries.Cast<EventLogEntry>()
|
|
.Where(e => e.Message.StartsWith("Application: Elwig.exe"))
|
|
.ToList();
|
|
}
|
|
|
|
public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
|
|
var pk = primaryKey.Select(k => k?.GetHashCode() ?? 0).ToArray();
|
|
return ((IStructuralEquatable)pk).GetHashCode(EqualityComparer<int>.Default);
|
|
}
|
|
|
|
public static int? GetEntityIdentifier(object? obj) {
|
|
if (obj == null) {
|
|
return null;
|
|
} else if (obj is IEnumerable list && obj is not string) {
|
|
var arr = list.Cast<object>().Select(GetEntityIdentifier).ToArray();
|
|
return ((IStructuralEquatable)arr).GetHashCode(EqualityComparer<int?>.Default);
|
|
} else if (obj.GetType().GetCustomAttribute(typeof(PrimaryKeyAttribute), true) is PrimaryKeyAttribute pkAttr) {
|
|
var pk = pkAttr.PropertyNames.Select(name => obj.GetType().GetProperty(name)!.GetValue(obj)?.GetHashCode() ?? 0).ToArray();
|
|
return ((IStructuralEquatable)pk).GetHashCode(EqualityComparer<int>.Default);
|
|
} else {
|
|
return obj.GetHashCode();
|
|
}
|
|
}
|
|
|
|
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments() => ActiveAreaCommitments(CurrentYear);
|
|
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments(int year) =>
|
|
c => (c.YearFrom == null || c.YearFrom <= year) && (c.YearTo == null || c.YearTo >= year);
|
|
|
|
public static IQueryable<AreaCom> ActiveAreaCommitments(IQueryable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear);
|
|
public static IQueryable<AreaCom> ActiveAreaCommitments(IQueryable<AreaCom> query, int year) =>
|
|
query.Where(ActiveAreaCommitments(year));
|
|
|
|
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear);
|
|
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query, int year) =>
|
|
query.Where(c => ActiveAreaCommitments(year).Invoke(c));
|
|
|
|
public static async Task<(DateTime DateTime, string Type, int MgNr, string Name, string[] Addresses, string Subject, string[] Attachments)[]> GetSentMails(DateOnly? fromDate = null, DateOnly? toDate = null) {
|
|
var f = $"{fromDate:yyyy-MM-dd}";
|
|
var t = $"{toDate:yyyy-MM-dd}";
|
|
try {
|
|
var rows = new List<(DateTime, string, int, string, string[], string, string[])>();
|
|
var filenames = Directory.GetFiles(App.MailsPath, "????.csv")
|
|
.Where(n => fromDate == null || Path.GetFileName(n).CompareTo($"{fromDate.Value.Year}.csv") >= 0)
|
|
.Where(n => toDate == null || Path.GetFileName(n).CompareTo($"{toDate.Value.Year}.csv") <= 0)
|
|
.Order();
|
|
foreach (var filename in filenames) {
|
|
using var reader = new StreamReader(filename, Utils.UTF8);
|
|
string? line;
|
|
while ((line = await reader.ReadLineAsync()) != null) {
|
|
try {
|
|
if (line.Length < 20 || line[10] != ';' || line[19] != ';')
|
|
continue;
|
|
var date = line[..10];
|
|
if (fromDate != null && date.CompareTo(f) < 0) {
|
|
continue;
|
|
} else if (toDate != null && date.CompareTo(t) > 0) {
|
|
break;
|
|
}
|
|
var p = line.Split(';');
|
|
rows.Add((
|
|
DateOnly.ParseExact(p[0], "yyyy-MM-dd").ToDateTime(TimeOnly.ParseExact(p[1], "HH:mm:ss")),
|
|
p[2],
|
|
int.Parse(p[3]),
|
|
p[4],
|
|
p[5].Split(',').Select(a => a.Replace(" | ", "\n")).ToArray(),
|
|
p[6],
|
|
p[7].Split(',')
|
|
));
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return [.. rows];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public static async Task AddSentMails(IEnumerable<(string Type, int MgNr, string Name, string[] Addresses, string Subject, string[] Attachments)> data) {
|
|
var now = DateTime.Now;
|
|
var filename = Path.Combine(App.MailsPath, $"{now.Year}.csv");
|
|
await File.AppendAllLinesAsync(filename, data.Select(d =>
|
|
$"{now:yyyy-MM-dd;HH:mm:ss};{d.Type};" +
|
|
$"{d.MgNr};{d.Name.Replace(';', ' ')};" +
|
|
$"{string.Join(',', d.Addresses.Select(a => a.Replace(';', ' ').Replace(',', ' ').Replace("\n", " | ")))};" +
|
|
$"{d.Subject.Replace(';', ' ')};" +
|
|
$"{string.Join(',', d.Attachments.Select(a => a.Replace(';', ' ').Replace(',', ' ')))}"
|
|
), Utils.UTF8);
|
|
}
|
|
|
|
public static async Task<string?> FindSentMailBody(DateTime target) {
|
|
var dt = $"{target:yyyy-MM-dd_HH-mm-ss}_";
|
|
var filename = Directory.GetFiles(App.MailsPath, "????-??-??_??-??-??_*.txt")
|
|
.Where(n => Path.GetFileName(n).CompareTo(dt) <= 0)
|
|
.Order()
|
|
.LastOrDefault();
|
|
if (filename == null)
|
|
return null;
|
|
return await File.ReadAllTextAsync(filename, Utils.UTF8);
|
|
}
|
|
|
|
public static async Task AddSentMailBody(string subject, string body, int recipients) {
|
|
var filename = Path.Combine(App.MailsPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{NormalizeFileName(subject)}.txt");
|
|
await File.WriteAllTextAsync(filename, $"# {subject}\r\n# Vorgesehene Empfänger: {recipients}\r\n\r\n" + body, Utils.UTF8);
|
|
}
|
|
}
|
|
}
|