From b49c9c65b18c86aa9f15bf9e5889b1acd19fd833 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Fri, 26 Jul 2024 14:58:15 +0200 Subject: [PATCH] [#3] Elwig: Add user-friendly sync method --- Elwig/App.xaml.cs | 3 +- Elwig/Helpers/Export/ElwigData.cs | 487 ++++++++++++++++++---- Elwig/Services/DeliveryService.cs | 30 +- Elwig/Windows/DeliveryAdminWindow.xaml | 8 +- Elwig/Windows/DeliveryAdminWindow.xaml.cs | 10 +- Elwig/Windows/MainWindow.xaml | 14 +- Elwig/Windows/MainWindow.xaml.cs | 67 ++- 7 files changed, 517 insertions(+), 102 deletions(-) diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs index 414532a..a704b88 100644 --- a/Elwig/App.xaml.cs +++ b/Elwig/App.xaml.cs @@ -29,10 +29,11 @@ namespace Elwig { private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) }; public static readonly string DataPath = @"C:\ProgramData\Elwig\"; + public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini"); public static readonly string ExePath = @"C:\Program Files\Elwig\"; public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig"); - public static Config Config { get; private set; } = new(Path.Combine(DataPath, "config.ini")); + public static Config Config { get; private set; } = new(ConfigPath); public static int VersionMajor { get; private set; } public static int VersionMinor { get; private set; } public static int VersionPatch { get; private set; } diff --git a/Elwig/Helpers/Export/ElwigData.cs b/Elwig/Helpers/Export/ElwigData.cs index 77f9cd2..3401bce 100644 --- a/Elwig/Helpers/Export/ElwigData.cs +++ b/Elwig/Helpers/Export/ElwigData.cs @@ -12,6 +12,8 @@ using Microsoft.EntityFrameworkCore; namespace Elwig.Helpers.Export { public static class ElwigData { + public enum ImportMode { Auto, Interactively, FromBranches } + public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt"); public static async Task GetImportedFiles() { @@ -26,36 +28,61 @@ namespace Elwig.Helpers.Export { await File.AppendAllLinesAsync(ImportedTxt, filenames, Utils.UTF8); } - public static Task Import(string filename, bool interactive) => Import([filename], interactive); + public static Task Import(string filename, ImportMode mode) => Import([filename], mode); - public static async Task Import(IEnumerable filenames, bool interactive) { + public static async Task Import(IEnumerable filenames, ImportMode mode) { try { using var ctx = new AppDbContext(); + var branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId); var currentDids = await ctx.Deliveries .GroupBy(d => d.Year) .ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId)); + var currentWbRde = await ctx.WbRde + .GroupBy(r => r.KgNr) + .ToDictionaryAsync(g => g.Key, g => g.ToList()); - var deliveries = new List(); - var deliveryParts = new List(); - var modifiers = new List(); + var data = new List<( + List Members, + List BillingAddresses, + List TelephoneNumbers, + List EmailAddresses, + List AreaCommitments, + List Riede, + List Deliveries, + List DeliveryParts, + List Modifiers)>(); - var metaData = new List<(string Name, int DeliveryNum, string Filters)>(); + var metaData = new List<(string FileName, string ZwstId, string Device, + int? MemberNum, string? MemberFilters, + int? AreaComNum, string? AreaComFilters, + int? DeliveryNum, string? DeliveryFilters)>(); foreach (var filename in filenames) { + // TODO read encrypted files using var zip = ZipFile.Open(filename, ZipArchiveMode.Read); var version = zip.GetEntry("version"); using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) { if (await reader.ReadToEndAsync() != "elwig:1") - throw new FileFormatException("Ungültige Export-Datei"); + throw new FileFormatException($"Ungültige Export-Datei ({filename})"); } var metaJson = zip.GetEntry("meta.json"); var meta = await JsonNode.ParseAsync(metaJson!.Open()); - var deliveryCount = meta!["deliveries"]?["count"]!.AsValue().GetValue(); - var deliveryFilters = meta!["deliveries"]?["filters"]!.AsArray().Select(f => f!.AsValue().GetValue()).ToArray(); - if (deliveryCount != null && deliveryFilters != null) - metaData.Add((Path.GetFileName(filename), (int)deliveryCount, string.Join(" / ", deliveryFilters))); + var memberCount = meta!["members"]?["count"]?.AsValue().GetValue(); + var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue()).ToArray(); + var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue(); + var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue()).ToArray(); + var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue(); + var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue()).ToArray(); + metaData.Add((Path.GetFileName(filename), + meta["zwstid"]!.AsValue().GetValue(), meta["device"]!.AsValue().GetValue(), + memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null, + areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null, + deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null)); + + data.Add(new([], [], [], [], [], [], [], new([], []))); + var r = data[^1]; var membersJson = zip.GetEntry("members.json"); if (membersJson != null) { @@ -63,7 +90,11 @@ namespace Elwig.Helpers.Export { string? line; while ((line = await reader.ReadLineAsync()) != null) { var obj = JsonNode.Parse(line)!.AsObject(); - // TODO import members.json + var (m, b, telNrs, emailAddrs) = obj.ToMember(); + r.Members.Add(m); + if (b != null) data[^1].BillingAddresses.Add(b); + r.TelephoneNumbers.AddRange(telNrs); + r.EmailAddresses.AddRange(emailAddrs); } } @@ -73,7 +104,9 @@ namespace Elwig.Helpers.Export { string? line; while ((line = await reader.ReadLineAsync()) != null) { var obj = JsonNode.Parse(line)!.AsObject(); - // TODO import area_commitments.json + var (areaCom, wbrd) = obj.ToAreaCom(currentWbRde); + r.AreaCommitments.Add(areaCom); + if (wbrd != null) r.Riede.Add(wbrd); } } @@ -83,85 +116,399 @@ namespace Elwig.Helpers.Export { string? line; while ((line = await reader.ReadLineAsync()) != null) { var obj = JsonNode.Parse(line)!.AsObject(); - var (d, parts, mods) = JsonToDelivery(obj, currentDids); - deliveries.Add(d); - deliveryParts.AddRange(parts); - modifiers.AddRange(mods); + var (d, parts, mods) = obj.ToDelivery(currentDids); + r.Deliveries.Add(d); + r.DeliveryParts.AddRange(parts); + r.Modifiers.AddRange(mods); } } } - var lsnrs = deliveries.Select(d => d.LsNr).ToList(); - var duplicateLsNrs = await ctx.Deliveries - .Where(d => lsnrs.Contains(d.LsNr)) - .Select(d => d.LsNr) - .ToListAsync(); - var duplicateDIds = deliveries - .Where(d => duplicateLsNrs.Contains(d.LsNr)) - .Select(d => (d.Year, d.DId)) - .ToList(); - bool overwriteDelivieries = false; - if (duplicateLsNrs.Count > 0) { - var res = MessageBox.Show($"Sollen {duplicateLsNrs.Count} Lieferungen überschreiben werden?", "Lieferungen überschreiben", - MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); - overwriteDelivieries = res == MessageBoxResult.Yes; + var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>(); + var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>(); + + foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers), meta) in data.Zip(metaData)) { + var branch = branches[meta.ZwstId]; + var device = meta.Device; + + var mgnrs = members.Select(m => m.MgNr).ToList(); + var duplicateMgNrs = await ctx.Members + .Where(m => mgnrs.Contains(m.MgNr)) + .Select(m => m.MgNr) + .ToListAsync(); + bool importNewMembers = false, importDuplicateMembers = false; + if (mode == ImportMode.Interactively) { + if (mgnrs.Count - duplicateMgNrs.Count > 0) + importNewMembers = ImportQuestion(branch.Name, device, "Mitglieder", false, mgnrs.Count - duplicateMgNrs.Count); + } else { + importNewMembers = true; + } + if (duplicateMgNrs.Count > 0) + importDuplicateMembers = ImportQuestion(branch.Name, device, "Mitglieder", true, duplicateMgNrs.Count); + + var lsnrs = deliveries.Select(d => d.LsNr).ToList(); + var duplicateLsNrs = await ctx.Deliveries + .Where(d => lsnrs.Contains(d.LsNr)) + .Select(d => d.LsNr) + .ToListAsync(); + var duplicateDIds = deliveries + .Where(d => duplicateLsNrs.Contains(d.LsNr)) + .Select(d => (d.Year, d.DId)) + .ToList(); + var allowedDuplicateLsNrs = new List(); + bool importNewDeliveries = false, importDuplicateDeliveries = false; + if (mode == ImportMode.Interactively) { + if (lsnrs.Count - duplicateLsNrs.Count > 0) + importNewDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", false, lsnrs.Count - duplicateLsNrs.Count); + if (duplicateLsNrs.Count > 0) + importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count); + } else if (mode == ImportMode.FromBranches) { + importNewDeliveries = true; + if (duplicateLsNrs.Count > 0) { + allowedDuplicateLsNrs = await ctx.Deliveries + .Where(d => lsnrs.Contains(d.LsNr) && d.ZwstId == branch.ZwstId) + .Select(d => d.LsNr) + .ToListAsync(); + if (duplicateLsNrs.Count - allowedDuplicateLsNrs.Count > 0) + importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count - allowedDuplicateLsNrs.Count); + } + } else { + importNewDeliveries = true; + if (duplicateLsNrs.Count > 0) + importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count); + } + + if (importDuplicateMembers) { + ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr))); + ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr))); + ctx.RemoveRange(ctx.MemberEmailAddrs.Where(a => duplicateMgNrs.Contains(a.MgNr))); + ctx.UpdateRange(members.Where(m => duplicateMgNrs.Contains(m.MgNr))); + ctx.AddRange(billingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr))); + ctx.AddRange(telephoneNumbers.Where(n => duplicateMgNrs.Contains(n.MgNr))); + ctx.AddRange(emailAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr))); + } + if (importNewMembers) { + ctx.AddRange(members.Where(m => !duplicateMgNrs.Contains(m.MgNr))); + ctx.AddRange(billingAddresses.Where(a => !duplicateMgNrs.Contains(a.MgNr))); + ctx.AddRange(telephoneNumbers.Where(n => !duplicateMgNrs.Contains(n.MgNr))); + ctx.AddRange(emailAddresses.Where(a => !duplicateMgNrs.Contains(a.MgNr))); + } + if (members.Count > 0) { + var n = importNewMembers ? members.Count - duplicateMgNrs.Count : 0; + var o = importDuplicateDeliveries ? duplicateMgNrs.Count : 0; + importedMembers.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, members.Count - n - o, meta.MemberFilters)); + } + + if (allowedDuplicateLsNrs.Count > 0) { + var dids = deliveries + .Where(d => allowedDuplicateLsNrs.Contains(d.LsNr)) + .Select(d => (d.Year, d.DId)) + .ToList(); + ctx.RemoveRange(ctx.DeliveryParts.Where(p => allowedDuplicateLsNrs.Contains(p.Delivery.LsNr))); + ctx.UpdateRange(deliveries.Where(d => dids.Contains((d.Year, d.DId)))); + ctx.AddRange(deliveryParts.Where(p => dids.Contains((p.Year, p.DId)))); + ctx.AddRange(modifiers.Where(m => dids.Contains((m.Year, m.DId)))); + } + if (importDuplicateDeliveries) { + var l = duplicateLsNrs.Except(allowedDuplicateLsNrs).ToList(); + var dids = deliveries + .Where(d => l.Contains(d.LsNr)) + .Select(d => (d.Year, d.DId)) + .ToList(); + ctx.RemoveRange(ctx.DeliveryParts.Where(p => l.Contains(p.Delivery.LsNr))); + ctx.UpdateRange(deliveries.Where(d => dids.Contains((d.Year, d.DId)))); + ctx.AddRange(deliveryParts.Where(p => dids.Contains((p.Year, p.DId)))); + ctx.AddRange(modifiers.Where(m => dids.Contains((m.Year, m.DId)))); + } + if (importNewDeliveries) { + ctx.AddRange(deliveries.Where(d => !duplicateDIds.Contains((d.Year, d.DId)))); + ctx.AddRange(deliveryParts.Where(p => !duplicateDIds.Contains((p.Year, p.DId)))); + ctx.AddRange(modifiers.Where(m => !duplicateDIds.Contains((m.Year, m.DId)))); + } + if (deliveries.Count > 0) { + var n = importNewDeliveries ? deliveries.Count - duplicateDIds.Count : 0; + var o = allowedDuplicateLsNrs.Count + (importDuplicateDeliveries ? duplicateDIds.Count - allowedDuplicateLsNrs.Count : 0); + importedDeliveries.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, deliveries.Count - n - o, meta.DeliveryFilters)); + } + + try { + await ctx.SaveChangesAsync(); + } catch (DbUpdateConcurrencyException) { } + await AddImportedFiles(filenames.Select(f => Path.GetFileName(f))); } - if (overwriteDelivieries) { - ctx.RemoveRange(ctx.Deliveries.Where(d => duplicateLsNrs.Contains(d.LsNr))); - ctx.AddRange(deliveries); - ctx.AddRange(deliveryParts); - ctx.AddRange(modifiers); - } else { - ctx.AddRange(deliveries.Where(d => !duplicateDIds.Contains((d.Year, d.DId)))); - ctx.AddRange(deliveryParts.Where(p => !duplicateDIds.Contains((p.Year, p.DId)))); - ctx.AddRange(modifiers.Where(m => !duplicateDIds.Contains((m.Year, m.DId)))); - } - await ctx.SaveChangesAsync(); - await AddImportedFiles(filenames.Select(f => Path.GetFileName(f))); await App.HintContextChange(); MessageBox.Show( $"Das importieren der Daten war erfolgreich!\n" + $"Folgendes wurde importiert:\n" + - $" Lieferungen: {deliveries.Count}\n" + - string.Join("\n", metaData.Select(d => $" {d.Name} ({d.DeliveryNum})\n {d.Filters}")) + - "\n", "Importieren erfolgreich", + string.Join("\n", [ + $"Mitglieder: {importedMembers.Sum(d => d.New + d.Overwritten)}", + ..importedMembers.Select(d => + $" {d.FileName} ({d.New + d.Overwritten})\n" + + $" ({d.New} neu, {d.Overwritten} überschrieben, {d.NotImported} nicht importiert)\n" + + $" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" + + $" Filter: {d.Filters}"), + $"Lieferungen: {importedDeliveries.Sum(d => d.New + d.Overwritten)}", + ..importedDeliveries.Select(d => + $" {d.FileName} ({d.New + d.Overwritten})\n" + + $" ({d.New} neu, {d.Overwritten} überschr., {d.NotImported} nicht importiert)\n" + + $" Zwst.: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" + + $" Filter: {d.Filters}") + ]), + "Importieren erfolgreich", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } } - public static async Task ExportDeliveries(string filename, IEnumerable deliveries, IEnumerable filters) { - File.Delete(filename); - using var zip = ZipFile.Open(filename, ZipArchiveMode.Create); + private static bool ImportQuestion(string branch, string device, string subject, bool duplicate, int number) { + return MessageBox.Show( + $"Sollen {number} {(duplicate ? "" : "neue ")}{subject} durch die Zweigstelle\n" + + $"{branch} (Gerät {device}) {(duplicate ? "überschrieben" : "importiert")} werden?", + $"{subject} importieren", + MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes + ) == MessageBoxResult.Yes; + } - var version = zip.CreateEntry("version", CompressionLevel.NoCompression); - using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { - await writer.WriteAsync("elwig:1"); - } + public static Task Export(string filename, IEnumerable members, IEnumerable filters) { + return new ElwigExport { + Members = (members, filters) + }.Export(filename); + } - var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression); - using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) { - await writer.WriteAsync( - $"{{\"timestamp\": \"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}\", " + - $"\"zwstid\": \"{App.ZwstId}\", \"device\": \"{Environment.MachineName}\", " + - $"\"deliveries\": {{" + - $"\"count\": {deliveries.Count()}, " + - $"\"parts\": {deliveries.Sum(d => d.Parts.Count)}, " + - $"\"filters\": [{string.Join(", ", filters.Select(f => '"' + f + '"'))}]" + - $"}}}}"); - } + public static Task Export(string filename, IEnumerable deliveries, IEnumerable filters) { + return new ElwigExport { + Deliveries = (deliveries, filters) + }.Export(filename); + } - var json = zip.CreateEntry("deliveries.json"); - using (var writer = new StreamWriter(json.Open(), Utils.UTF8)) { - foreach (var d in deliveries) { - await writer.WriteLineAsync(DeliveryToJson(d).ToJsonString()); + public class ElwigExport { + public (IEnumerable Members, IEnumerable Filters)? Members { get; set; } + public (IEnumerable AreaComs, IEnumerable Filters)? AreaComs { get; set; } + public (IEnumerable Deliveries, IEnumerable Filters)? Deliveries { get; set; } + + public async Task Export(string filename) { + File.Delete(filename); + using var zip = ZipFile.Open(filename, ZipArchiveMode.Create); + + var version = zip.CreateEntry("version", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { + await writer.WriteAsync("elwig:1"); + } + + var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression); + using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) { + var obj = new JsonObject { + ["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}", + ["zwstid"] = App.ZwstId, + ["device"] = Environment.MachineName, + }; + if (Members != null) + obj["members"] = new JsonObject { + ["count"] = Members.Value.Members.Count(), + ["filters"] = new JsonArray(Members.Value.Filters.Select(f => (JsonNode)f).ToArray()), + }; + if (AreaComs != null) + obj["area_commitments"] = new JsonObject { + ["count"] = AreaComs.Value.AreaComs.Count(), + ["filters"] = new JsonArray(AreaComs.Value.Filters.Select(f => (JsonNode)f).ToArray()), + }; + if (Deliveries != null) + obj["deliveries"] = new JsonObject { + ["count"] = Deliveries.Value.Deliveries.Count(), + ["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count), + ["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()), + }; + await writer.WriteAsync(obj.ToJsonString()); + } + + // TODO encrypt files + if (Members != null) { + var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize); + using var writer = new StreamWriter(json.Open(), Utils.UTF8); + foreach (var m in Members.Value.Members) { + await writer.WriteLineAsync(m.ToJson().ToJsonString()); + } + } + if (AreaComs != null) { + var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize); + using var writer = new StreamWriter(json.Open(), Utils.UTF8); + foreach (var c in AreaComs.Value.AreaComs) { + await writer.WriteLineAsync(c.ToJson().ToJsonString()); + } + } + if (Deliveries != null) { + var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize); + using var writer = new StreamWriter(json.Open(), Utils.UTF8); + foreach (var d in Deliveries.Value.Deliveries) { + await writer.WriteLineAsync(d.ToJson().ToJsonString()); + } } } } - public static JsonObject DeliveryToJson(Delivery d) { + public static JsonObject ToJson(this Member m) { + return new JsonObject { + ["mgnr"] = m.MgNr, + ["predecessor_mgnr"] = m.PredecessorMgNr, + ["prefix"] = m.Prefix, + ["given_name"] = m.GivenName, + ["middle_names"] = m.MiddleName, + ["family_name"] = m.FamilyName, + ["suffix"] = m.Suffix, + ["birthday"] = m.Birthday, + ["entry_date"] = m.EntryDate != null ? $"{m.EntryDate:yyyy-MM-dd}" : null, + ["exit_date"] = m.ExitDate != null ? $"{m.ExitDate:yyyy-MM-dd}" : null, + ["business_shares"] = m.BusinessShares, + ["accounting_nr"] = m.AccountingNr, + ["zwstid"] = m.ZwstId, + ["lfbis_nr"] = m.LfbisNr, + ["ustid_nr"] = m.UstIdNr, + ["volllieferant"] = m.IsVollLieferant, + ["buchführend"] = m.IsBuchführend, + ["organic"] = m.IsOrganic, + ["funktionär"] = m.IsFunktionär, + ["active"] = m.IsActive, + ["deceased"] = m.IsDeceased, + ["iban"] = m.Iban, + ["bic"] = m.Bic, + ["default_kgnr"] = m.DefaultKgNr, + ["contact_postal"] = m.ContactViaPost, + ["contact_email"] = m.ContactViaEmail, + ["address"] = new JsonObject { + ["address"] = m.Address, + ["postal_dest"] = m.PostalDestId, + ["country"] = m.CountryNum, + }, + ["billing_address"] = m.BillingAddress != null ? new JsonObject { + ["name"] = m.BillingAddress.Name, + ["address"] = m.BillingAddress.Address, + ["postal_dest"] = m.BillingAddress.PostalDestId, + ["country"] = m.BillingAddress.CountryNum, + } : null, + ["telephone_numbers"] = new JsonArray(m.TelephoneNumbers.OrderBy(n => n.Nr).Select(n => { + var obj = new JsonObject { + ["number"] = n.Number, + ["type"] = n.Type, + }; + if (n.Comment != null) obj["comment"] = n.Comment; + return obj; + }).ToArray()), + ["email_addresses"] = new JsonArray(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => { + var obj = new JsonObject { + ["address"] = a.Address, + }; + if (a.Comment != null) obj["comment"] = a.Comment; + return obj; + }).ToArray()), + ["comment"] = m.Comment, + }; + } + + public static (Member, BillingAddr?, List, List) ToMember(this JsonNode json) { + var mgnr = json["mgnr"]!.AsValue().GetValue(); + return (new Member { + MgNr = mgnr, + PredecessorMgNr = json["predecessor_mgnr"]?.AsValue().GetValue(), + Prefix = json["prefix"]?.AsValue().GetValue(), + GivenName = json["given_name"]!.AsValue().GetValue(), + MiddleName = json["middle_names"]?.AsValue().GetValue(), + FamilyName = json["family_name"]!.AsValue().GetValue(), + Suffix = json["suffix"]?.AsValue().GetValue(), + Birthday = json["birthday"]?.AsValue().GetValue(), + EntryDateString = json["entry_date"]?.AsValue().GetValue(), + ExitDateString = json["exit_date"]?.AsValue().GetValue(), + BusinessShares = json["business_shares"]?.AsValue().GetValue() ?? 0, + AccountingNr = json["accounting_nr"]?.AsValue().GetValue(), + ZwstId = json["zwstid"]?.AsValue().GetValue(), + LfbisNr = json["lfbis_nr"]?.AsValue().GetValue(), + UstIdNr = json["ustid_nr"]?.AsValue().GetValue(), + IsVollLieferant = json["volllieferant"]?.AsValue().GetValue() ?? false, + IsBuchführend = json["buchführend"]?.AsValue().GetValue() ?? false, + IsOrganic = json["organic"]?.AsValue().GetValue() ?? false, + IsFunktionär = json["funktionär"]?.AsValue().GetValue() ?? false, + IsActive = json["active"]?.AsValue().GetValue() ?? false, + IsDeceased = json["deceased"]?.AsValue().GetValue() ?? false, + Iban = json["iban"]?.AsValue().GetValue(), + Bic = json["bic"]?.AsValue().GetValue(), + CountryNum = json["address"]!["country"]!.AsValue().GetValue(), + PostalDestId = json["address"]!["postal_dest"]!.AsValue().GetValue(), + Address = json["address"]!["address"]!.AsValue().GetValue(), + DefaultKgNr = json["default_kgnr"]?.AsValue().GetValue(), + ContactViaPost = json["contact_postal"]?.AsValue().GetValue() ?? false, + ContactViaEmail = json["contact_email"]?.AsValue().GetValue() ?? false, + Comment = json["comment"]?.AsValue().GetValue(), + }, json["billing_address"] is JsonObject a ? new BillingAddr { + MgNr = mgnr, + Name = a["name"]!.AsValue().GetValue(), + CountryNum = a["country"]!.AsValue().GetValue(), + PostalDestId = a["postal_dest"]!.AsValue().GetValue(), + Address = a["address"]!.AsValue().GetValue(), + } : null, json["telephone_numbers"]!.AsArray().Select(n => n!.AsObject()).Select((n, i) => new MemberTelNr { + MgNr = mgnr, + Nr = i + 1, + Type = n["type"]!.AsValue().GetValue(), + Number = n["number"]!.AsValue().GetValue(), + Comment = n["comment"]?.AsValue().GetValue(), + }).ToList(), json["email_addresses"]!.AsArray().Select(a => a!.AsObject()).Select((a, i) => new MemberEmailAddr { + MgNr = mgnr, + Nr = i + 1, + Address = a["address"]!.AsValue().GetValue(), + Comment = a["comment"]?.AsValue().GetValue(), + }).ToList()); + } + + public static JsonObject ToJson(this AreaCom c) { + return new JsonObject { + ["fbnr"] = c.FbNr, + ["mgnr"] = c.MgNr, + ["vtrgid"] = c.VtrgId, + ["cultid"] = c.CultId, + ["area"] = c.Area, + ["kgnr"] = c.KgNr, + ["gstnr"] = c.GstNr, + ["ried"] = c.Rd?.Name, + ["year_from"] = c.YearFrom, + ["year_to"] = c.YearTo, + ["comment"] = c.Comment, + }; + } + + public static (AreaCom, WbRd?) ToAreaCom(this JsonNode json, Dictionary> riede) { + var kgnr = json["kgnr"]!.AsValue().GetValue(); + var ried = json["ried"]?.AsValue().GetValue(); + WbRd? rd = null; + bool newRd = false; + if (ried != null) { + var rde = riede[kgnr] ?? throw new ArgumentException($"KG {kgnr:00000} is no WbKg"); + rd = rde.FirstOrDefault(r => r.Name == ried); + if (rd == null) { + newRd = true; + rd = new WbRd { + KgNr = kgnr, + RdNr = (rde.Count == 0 ? 0 : rde.Max(r => r.RdNr)) + 1, + Name = ried, + }; + rde.Add(rd); + } + } + return (new AreaCom { + FbNr = json["fbnr"]!.AsValue().GetValue(), + MgNr = json["mgnr"]!.AsValue().GetValue(), + VtrgId = json["vtrgid"]!.AsValue().GetValue(), + CultId = json["cultid"]?.AsValue().GetValue(), + Area = json["area"]!.AsValue().GetValue(), + KgNr = kgnr, + GstNr = json["gstnr"]?.AsValue().GetValue() ?? "-", + RdNr = rd?.RdNr, + YearFrom = json["year_from"]!.AsValue().GetValue(), + YearTo = json["year_to"]?.AsValue().GetValue(), + Comment = json["comment"]?.AsValue().GetValue(), + }, newRd ? rd : null); + } + + public static JsonObject ToJson(this Delivery d) { return new JsonObject { ["lsnr"] = d.LsNr, ["year"] = d.Year, @@ -202,7 +549,7 @@ namespace Elwig.Helpers.Export { }; } - public static (Delivery, List, List) JsonToDelivery(JsonNode json, Dictionary currentDids) { + public static (Delivery, List, List) ToDelivery(this JsonNode json, Dictionary currentDids) { var year = json["year"]!.AsValue().GetValue(); var did = ++currentDids[year]; return (new Delivery { diff --git a/Elwig/Services/DeliveryService.cs b/Elwig/Services/DeliveryService.cs index 6ddacbd..b5ca032 100644 --- a/Elwig/Services/DeliveryService.cs +++ b/Elwig/Services/DeliveryService.cs @@ -529,16 +529,16 @@ namespace Elwig.Services { var (f, _, q, _, _) = await vm.GetFilters(ctx); query = q; filterNames.AddRange(f); + } else if (modeWho == 2) { + query = ctx.DeliveryParts + .Where(p => p.Year == Utils.CurrentLastSeason && p.Delivery.ZwstId == App.ZwstId); + filterNames.AddRange([$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]); } else { var date = $"{Utils.Today:yyyy-MM-dd}"; query = ctx.DeliveryParts .Where(p => p.Delivery.DateString == date); filterNames.Add($"{Utils.Today:dd.MM.yyyy}"); } - if (exportMode == ExportMode.Upload && !filterNames.Contains($"Zweigstelle {App.BranchName}")) { - query = query.Where(p => p.Delivery.ZwstId == App.ZwstId); - filterNames.Add($"Zweigstelle {App.BranchName}"); - } query = query .OrderBy(p => p.Delivery.DateString) @@ -566,7 +566,7 @@ namespace Elwig.Services { } } else if (exportMode == ExportMode.Export) { var d = new SaveFileDialog() { - FileName = $"Lieferungen.zip", + FileName = $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.zip", DefaultExt = "zip", Filter = "ZIP-Datei (*.zip)|*.zip", Title = $"{DeliveryJournal.Name} speichern unter - Elwig" @@ -574,7 +574,13 @@ namespace Elwig.Services { if (d.ShowDialog() == true) { Mouse.OverrideCursor = Cursors.AppStarting; try { - await ElwigData.ExportDeliveries(d.FileName, await query.Select(p => p.Delivery).Distinct().ToListAsync(), filterNames); + await ElwigData.Export(d.FileName, await query + .Select(p => p.Delivery) + .Distinct() + .Include(d => d.Parts) + .ThenInclude(p => p.PartModifiers) + .AsSplitQuery() + .ToListAsync(), filterNames); } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } @@ -585,12 +591,18 @@ namespace Elwig.Services { try { var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.zip"; var path = Path.Combine(App.TempPath, filename); - var list = await query.Select(p => p.Delivery).Distinct().ToListAsync(); + var list = await query + .Select(p => p.Delivery) + .Distinct() + .Include(d => d.Parts) + .ThenInclude(p => p.PartModifiers) + .AsSplitQuery() + .ToListAsync(); if (list.Count == 0) { - MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Fehler", + MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error); } else { - await ElwigData.ExportDeliveries(path, list, filterNames); + await ElwigData.Export(path, list, filterNames); await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword); MessageBox.Show($"Lieferungen erfolgreich hochgeladen!", "Lieferungen hochgeladen", MessageBoxButton.OK, MessageBoxImage.Information); diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml b/Elwig/Windows/DeliveryAdminWindow.xaml index 4601fff..dcf8cbe 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml +++ b/Elwig/Windows/DeliveryAdminWindow.xaml @@ -129,10 +129,10 @@ - - + + - @@ -30,7 +29,9 @@ - + + + @@ -66,6 +67,15 @@