From 583384278e593d9f1c9fb5f871511178c2d8ca16 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Wed, 3 Jul 2024 12:37:40 +0200 Subject: [PATCH] [#10] DeliveryAdminWindow: Implement MVVM --- Elwig/App.xaml.cs | 4 +- Elwig/Services/DeliveryService.cs | 640 ++++++++++++++ Elwig/ViewModels/DeliveryAdminViewModel.cs | 171 ++++ Elwig/Windows/DeliveryAdminWindow.xaml | 113 ++- Elwig/Windows/DeliveryAdminWindow.xaml.cs | 974 ++++----------------- 5 files changed, 1046 insertions(+), 856 deletions(-) create mode 100644 Elwig/Services/DeliveryService.cs create mode 100644 Elwig/ViewModels/DeliveryAdminViewModel.cs diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs index 04b6d51..309c313 100644 --- a/Elwig/App.xaml.cs +++ b/Elwig/App.xaml.cs @@ -264,11 +264,11 @@ namespace Elwig { } public static DeliveryAdminWindow FocusReceipt() { - return FocusWindow(() => new(true), w => w.IsReceipt); + return FocusWindow(() => new(true), w => w.ViewModel.IsReceipt); } public static DeliveryAdminWindow FocusMemberDeliveries(int mgnr) { - return FocusWindow(() => new(mgnr), w => w.MgNr == mgnr); + return FocusWindow(() => new(mgnr), w => w.ViewModel.FilterMgNr == mgnr); } public static AreaComAdminWindow FocusMemberAreaComs(int mgnr) { diff --git a/Elwig/Services/DeliveryService.cs b/Elwig/Services/DeliveryService.cs new file mode 100644 index 0000000..8e0194d --- /dev/null +++ b/Elwig/Services/DeliveryService.cs @@ -0,0 +1,640 @@ +using Elwig.Documents; +using Elwig.Helpers.Export; +using Elwig.Helpers; +using Elwig.Models.Dtos; +using Elwig.Models.Entities; +using Microsoft.Win32; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows; +using System; +using Elwig.ViewModels; +using LinqKit; +using System.Globalization; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using System.IO; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace Elwig.Services { + public static class DeliveryService { + + public static async Task GetMemberAsync(int mgnr) { + using var ctx = new AppDbContext(); + return await ctx.Members + .Include(m => m.PostalDest.AtPlz!.Ort) + .Include(m => m.DefaultWbKg!.AtKg) + .FirstOrDefaultAsync(m => m.MgNr == mgnr); + } + + public static Member? GetMember(int mgnr) { + return GetMemberAsync(mgnr).GetAwaiter().GetResult(); + } + + public static void ClearInputs(this DeliveryAdminViewModel vm) { + } + + public static void FillInputs(this DeliveryAdminViewModel vm, Delivery d) { + vm.MgNrString = $"{d.MgNr}"; + vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, d.ZwstId); + vm.LsNr = d.LsNr; + vm.Date = $"{d.Date:dd.MM.yyyy}"; + vm.Time = $"{d.Time:HH:mm}"; + vm.Comment = d.Comment ?? ""; + + vm.SortId = ""; + vm.GradationKmwString = ""; + vm.WeightString = ""; + vm.IsManualWeighing = false; + vm.PartComment = ""; + vm.TemperatureString = ""; + vm.AcidString = ""; + } + + public static void FillInputs(this DeliveryAdminViewModel vm, DeliveryPart p) { + vm.SortId = p.SortId; + vm.WineAttr = ControlUtils.GetItemFromSourceWithPk(vm.WineAttrSource, p.AttrId) as WineAttr; + vm.WineCult = ControlUtils.GetItemFromSourceWithPk(vm.WineCultSource, p.CultId) as WineCult; + vm.GradationKmwString = $"{p.Kmw:N1}"; + vm.WineQualityLevel = (WineQualLevel?)ControlUtils.GetItemFromSourceWithPk(vm.WineQualityLevelSource, p.QualId); + vm.WineKg = ControlUtils.GetItemFromSourceWithPk(vm.WineKgSource, p.KgNr) as AT_Kg; + vm.WineRd = ControlUtils.GetItemFromSourceWithPk(vm.WineRdSource, p.KgNr, p.RdNr) as WbRd; + vm.WineOrigin = ControlUtils.GetItemFromSourceWithPk(vm.WineOriginSource, p.HkId) as WineOrigin; + vm.WeightString = $"{p.Weight:N0}"; + vm.IsManualWeighing = p.IsManualWeighing; + vm.IsNetWeight = p.IsNetWeight; + + vm.Modifiers.Clear(); + foreach (var m in p.Modifiers) { + vm.Modifiers.Add((Modifier)ControlUtils.GetItemFromSourceWithPk(vm.ModifiersSource, m.Year, m.ModId)!); + } + + vm.PartComment = p.Comment ?? ""; + vm.TemperatureString = (p.Temperature != null) ? $"{p.Temperature:N1}" : ""; + vm.AcidString = (p.Acid != null) ? $"{p.Acid:N1}" : ""; + vm.IsLesewagen = p.IsLesewagen ?? false; + vm.IsHandPicked = p.IsHandPicked; + vm.IsGebunden = p.IsGebunden; + + vm.ScaleId = p.ScaleId; + vm.WeighingId = p.WeighingId; + vm.ManualWeighingReason = p.WeighingReason; + } + + public static async Task<(List, IQueryable, IQueryable, Predicate, List)> GetFilters(this DeliveryAdminViewModel vm, AppDbContext ctx) { + List filterNames = []; + IQueryable deliveryQuery = ctx.Deliveries; + if (vm.IsReceipt && App.BranchNum > 1) { + deliveryQuery = deliveryQuery.Where(d => d.ZwstId == App.ZwstId); + filterNames.Add($"Zweigstelle {App.BranchName}"); + } + if (vm.FilterMember != null) { + deliveryQuery = deliveryQuery.Where(d => d.MgNr == vm.FilterMember.MgNr); + filterNames.Add(vm.FilterMember.AdministrativeName); + } + if (vm.FilterTodayOnly) { + deliveryQuery = deliveryQuery + .Where(d => (d.DateString == Utils.Today.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") > 0)) || + (d.DateString == Utils.Today.AddDays(1).ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") <= 0))); + filterNames.Add(Utils.Today.ToString("dd.MM.yyyy")); + } else if (!vm.FilterAllSeasons) { + deliveryQuery = deliveryQuery.Where(d => d.Year == vm.FilterSeason); + filterNames.Add($"{vm.FilterSeason}"); + } + + Expression> prd = p => true; + + var filterVar = new List(); + var filterNotVar = new List(); + var filterQual = new List(); + var filterNotQual = new List(); + var filterMgNr = new List(); + var filterZwst = new List(); + var filterAttr = new List(); + var filterNotAttr = new List(); + var filterCult = new List(); + var filterNotCult = new List(); + var filterDate = new List<(string?, string?)>(); + var filterTime = new List<(string?, string?)>(); + int filterYearGt = 0, filterYearLt = 0; + double filterKmwGt = 0, filterKmwLt = 0; + double filterOeGt = 0, filterOeLt = 0; + + var filter = vm.TextFilter; + if (filter.Count > 0) { + var var = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v); + var qual = await ctx.WineQualityLevels.Where(q => !q.IsPredicate).ToDictionaryAsync(q => q.QualId, q => q); + var mgnr = await ctx.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m); + var zwst = await ctx.Branches.ToDictionaryAsync(b => b.Name.ToLower().Split(" ")[0], b => b); + var attr = await ctx.WineAttributes.ToDictionaryAsync(a => a.Name.ToLower().Split(' ')[0], a => a); + var cult = await ctx.WineCultivations.ToDictionaryAsync(c => c.Name.ToLower().Split(' ')[0], c => c); + + for (int i = 0; i < filter.Count; i++) { + var e = filter[i]; + if (e.ToLower() is "r" or "rot") { + filterVar.AddRange(var.Values.Where(v => v.IsRed).Select(v => v.SortId)); + filter.RemoveAt(i--); + filterNames.Add("Rotweinsorten"); + } else if (e.ToLower() is "w" or "weiß" or "weiss") { + filterVar.AddRange(var.Values.Where(v => v.IsWhite).Select(v => v.SortId)); + filter.RemoveAt(i--); + filterNames.Add("Weißweinsorten"); + } else if (e.Length >= 3 && e.Length <= 8 && "gebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsGebunden == true); + filter.RemoveAt(i--); + filterNames.Add("gebunden"); + } else if (e.Length >= 4 && e.Length <= 9 && "!gebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsGebunden != true); + filter.RemoveAt(i--); + filterNames.Add("nicht gebunden"); + } else if (e.Length >= 5 && e.Length <= 10 && "ungebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsGebunden == false); + filter.RemoveAt(i--); + filterNames.Add("ungebunden"); + } else if (e.Length >= 6 && e.Length <= 11 && "!ungebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsGebunden != false); + filter.RemoveAt(i--); + filterNames.Add("nicht ungebunden"); + } else if (e.Length >= 5 && e.Length <= 8 && "handlese".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsHandPicked == true); + filter.RemoveAt(i--); + filterNames.Add("Handlese"); + } else if (e.Length >= 6 && e.Length <= 9 && "!handlese".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsHandPicked == false); + filter.RemoveAt(i--); + filterNames.Add("keine Handlese"); + } else if (e.Length >= 5 && e.Length <= 11 && "handwiegung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsManualWeighing == true); + filter.RemoveAt(i--); + filterNames.Add("Handwiegung"); + } else if (e.Length >= 6 && e.Length <= 12 && "!handwiegung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsManualWeighing == false); + filter.RemoveAt(i--); + filterNames.Add("keine Handwiegung"); + } else if (e.ToLower() is "bto" or "brut" or "brutt" or "brutto" or "!gerebelt") { + prd = prd.And(p => p.IsNetWeight == false); + filter.RemoveAt(i--); + filterNames.Add("brutto Wiegung"); + } else if (e.ToLower() is "nto" or "net" or "nett" or "netto" or "gerebelt") { + prd = prd.And(p => p.IsNetWeight == true); + filter.RemoveAt(i--); + filterNames.Add("netto Wiegung"); + } else if (e.Length >= 5 && e.Length <= 9 && "lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsLesewagen == true); + filter.RemoveAt(i--); + filterNames.Add("Lesewagen"); + } else if (e.Length >= 6 && e.Length <= 10 && "!lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + prd = prd.And(p => p.IsLesewagen == false); + filter.RemoveAt(i--); + filterNames.Add("kein Lesewagen"); + } else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) { + filterVar.Add(e.ToUpper()); + filter.RemoveAt(i--); + filterNames.Add(var[e.ToUpper()].Name); + } else if (e.Length == 3 && e[0] == '!' && var.ContainsKey(e[1..].ToUpper())) { + filterNotVar.Add(e[1..].ToUpper()); + filter.RemoveAt(i--); + filterNames.Add("außer " + var[e[1..].ToUpper()].Name); + } else if (e.Length == 3 && qual.ContainsKey(e.ToUpper())) { + var qualId = e.ToUpper(); + filterQual.Add(qualId); + filter.RemoveAt(i--); + filterNames.Add(qualId == "WEI" ? "abgewertet" : qual[e.ToUpper()].Name); + } else if (e[0] == '!' && qual.ContainsKey(e[1..].ToUpper())) { + var qualId = e[1..].ToUpper(); + filterNotQual.Add(qualId); + filter.RemoveAt(i--); + filterNames.Add(qualId == "WEI" ? "nicht abgewertet" : "außer " + qual[e[1..].ToUpper()].Name); + } else if (e.Length >= 5 && e.Length <= 10 && "abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + filterQual.Add("WEI"); + filter.RemoveAt(i--); + filterNames.Add("abgewertet"); + } else if (e.Length >= 6 && e.Length <= 11 && "!abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + filterNotQual.Add("WEI"); + filter.RemoveAt(i--); + filterNames.Add("nicht abgewertet"); + } else if (e.All(char.IsAsciiDigit) && mgnr.TryGetValue(e, out var member)) { + filterMgNr.Add(int.Parse(e)); + filter.RemoveAt(i--); + filterNames.Add(member.AdministrativeName); + } else if (attr.ContainsKey(e.ToLower())) { + var a = attr[e.ToLower()]; + filterAttr.Add(a.AttrId); + filter.RemoveAt(i--); + filterNames.Add($"Attribut {a.Name}"); + } else if (e[0] == '!' && attr.ContainsKey(e[1..].ToLower())) { + var a = attr[e[1..].ToLower()]; + filterNotAttr.Add(a.AttrId); + filter.RemoveAt(i--); + filterNames.Add($"ohne Attribut {a.Name}"); + } else if (cult.ContainsKey(e.ToLower())) { + var c = cult[e.ToLower()]; + filterCult.Add(c.CultId); + filter.RemoveAt(i--); + filterNames.Add($"Bewirtschaftung {c.Name}"); + } else if (e[0] == '!' && cult.ContainsKey(e[1..].ToLower())) { + var c = cult[e[1..].ToLower()]; + filterNotCult.Add(c.CultId); + filter.RemoveAt(i--); + filterNames.Add($"ohne Bewirtschaftung {c.Name}"); + } else if (zwst.ContainsKey(e.ToLower())) { + var b = zwst[e.ToLower()]; + filterZwst.Add(b.ZwstId); + filter.RemoveAt(i--); + filterNames.Add($"Zweigstelle {b.Name}"); + } else if (e.StartsWith('>') || e.StartsWith('<')) { + if (double.TryParse(e[1..], out var num)) { + switch ((e[0], num)) { + case ('>', <= 30): filterKmwGt = num; break; + case ('<', <= 30): filterKmwLt = num; break; + case ('>', >= 1900): filterYearGt = (int)num; break; + case ('<', >= 1900): filterYearLt = (int)num; break; + case ('>', _): filterOeGt = num; break; + case ('<', _): filterOeLt = num; break; + } + filter.RemoveAt(i--); + } + if (e.Length == 1) filter.RemoveAt(i--); + } else if (e.Length > 1 && Utils.FromToRegex.IsMatch(e)) { + var parts = e.Split("-"); + double? from = (parts[0].Length > 0) ? double.Parse(parts[0], CultureInfo.InvariantCulture) : null; + double? to = (parts[1].Length > 0) ? double.Parse(parts[1], CultureInfo.InvariantCulture) : null; + switch ((from, to)) { + case ( <= 30, <= 30): + case ( <= 30, null): + case (null, <= 30): + filterKmwGt = from ?? 0; + filterKmwLt = to ?? 0; + break; + case ( >= 1900, >= 1900): + case ( >= 1900, null): + case (null, >= 1900): + filterYearGt = (int)(from ?? 0); + filterYearLt = (int)(to ?? -1) + 1; + break; + case (_, _): + filterOeGt = from ?? 0; + filterOeLt = to ?? 0; + break; + } + filter.RemoveAt(i--); + } else if (e.Length > 1 && Utils.FromToTimeRegex.IsMatch(e)) { + var parts = e.Split("-"); + filterTime.Add((TimeOnly.TryParse(parts[0], out var from) ? $"{from:HH:mm}" : null, TimeOnly.TryParse(parts[1], out var to) ? $"{to:HH:mm}" : null)); + filter.RemoveAt(i--); + var t = filterTime.Last(); + if (t.Item1 != null && t.Item2 != null) { + filterNames.Add($"{t.Item1}–{t.Item2}"); + } else if (t.Item1 != null) { + filterNames.Add($"ab {t.Item1}"); + } else if (t.Item2 != null) { + filterNames.Add($"bis {t.Item2}"); + } + } else if (DateOnly.TryParse(e, out var date)) { + var s = date.ToString("yyyy-MM-dd"); + filterDate.Add((s, s)); + filter.RemoveAt(i--); + if (filterNames.Contains($"{vm.FilterSeason}") && vm.FilterSeason == date.Year) + filterNames.Remove($"{vm.FilterSeason}"); + filterNames.Add(date.ToString("dd.MM.yyyy")); + } else if (Utils.DateFromToRegex.IsMatch(e)) { + var parts = e.Split("-"); + if (parts.Length == 1) { + // single date + var dParts = parts[0].Split('.'); + var s = $"{dParts[2]}-{dParts[1].PadLeft(2, '0')}-{dParts[0].PadLeft(2, '0')}"; + filterDate.Add((s, s)); + filter.RemoveAt(i--); + var n = string.Join('.', s.Split('-').Reverse()); + if (dParts[2] == "") { + filterNames.Remove($"{vm.FilterSeason}"); + filterNames.Add(n + $"{vm.FilterSeason}"); + } else { + if ($"{vm.FilterSeason}" == dParts[2]) + filterNames.Remove($"{vm.FilterSeason}"); + filterNames.Add(n); + } + } else if (parts.Length == 2) { + // from/to date + var d1Parts = parts[0].Split('.'); + var d2Parts = parts[1].Split('.'); + var s1 = d1Parts.Length < 2 ? null : $"{d1Parts.ElementAtOrDefault(2)}-{d1Parts[1].PadLeft(2, '0')}-{d1Parts[0].PadLeft(2, '0')}"; + var s2 = d2Parts.Length < 2 ? null : $"{d2Parts.ElementAtOrDefault(2)}-{d2Parts[1].PadLeft(2, '0')}-{d2Parts[0].PadLeft(2, '0')}"; + filterDate.Add((s1, s2)); + filter.RemoveAt(i--); + var n1 = s1 == null ? null : string.Join('.', s1.Split('-').Reverse()); + var n2 = s2 == null ? null : string.Join('.', s2.Split('-').Reverse()); + if (n1 != null && n2 != null) { + filterNames.Add($"{n1}–{n2}"); + } else if (n1 != null) { + filterNames.Add($"ab dem {n1}"); + } else if (n2 != null) { + filterNames.Add($"bis zum {n2}"); + } + } + } else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) { + filter[i] = e[1..^1]; + } else if (e.Length <= 2) { + filter.RemoveAt(i--); + } + } + + if (filterYearGt > 0) prd = prd.And(p => p.Year >= filterYearGt); + if (filterYearLt > 0) prd = prd.And(p => p.Year < filterYearLt); + if (filterMgNr.Count > 0) prd = prd.And(p => filterMgNr.Contains(p.Delivery.MgNr)); + if (filterDate.Count > 0) { + var pr = PredicateBuilder.New(false); + foreach (var (d1, d2) in filterDate) + pr.Or(p => (d1 == null || d1.CompareTo(p.Delivery.DateString.Substring(10 - d1.Length)) <= 0) && (d2 == null || d2.CompareTo(p.Delivery.DateString.Substring(10 - d2.Length)) >= 0)); + prd = prd.And(pr); + } + if (filterTime.Count > 0) { + var pr = PredicateBuilder.New(false); + foreach (var (t1, t2) in filterTime) + pr.Or(p => (t1 == null || t1.CompareTo(p.Delivery.TimeString) <= 0) && (t2 == null || t2.CompareTo(p.Delivery.TimeString) > 0)); + prd = prd.And(p => p.Delivery.TimeString != null).And(pr); + } + if (filterVar.Count > 0) prd = prd.And(p => filterVar.Contains(p.SortId)); + if (filterNotVar.Count > 0) prd = prd.And(p => !filterNotVar.Contains(p.SortId)); + if (filterQual.Count > 0) prd = prd.And(p => filterQual.Contains(p.QualId)); + if (filterNotQual.Count > 0) prd = prd.And(p => !filterNotQual.Contains(p.QualId)); + if (filterZwst.Count > 0) prd = prd.And(p => filterZwst.Contains(p.Delivery.ZwstId)); + if (filterAttr.Count > 0) prd = prd.And(p => p.AttrId != null && filterAttr.Contains(p.AttrId)); + if (filterNotAttr.Count > 0) prd = prd.And(p => p.AttrId == null || !filterNotAttr.Contains(p.AttrId)); + if (filterCult.Count > 0) prd = prd.And(p => p.CultId != null && filterCult.Contains(p.CultId)); + if (filterNotCult.Count > 0) prd = prd.And(p => p.CultId == null || !filterNotCult.Contains(p.CultId)); + if (filterKmwGt > 0) prd = prd.And(p => p.Kmw >= filterKmwGt); + if (filterKmwLt > 0) prd = prd.And(p => p.Kmw < filterKmwLt); + if (filterOeGt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) >= filterOeGt); + if (filterOeLt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) < filterOeLt); + + if (filterYearGt > 0 && filterYearLt > 0) { + filterNames.Insert(0, $"{filterYearGt}–{filterYearLt - 1}"); + } else if (filterYearGt > 0) { + filterNames.Insert(0, $"ab {filterYearGt}"); + } else if (filterYearLt > 0) { + filterNames.Insert(0, $"bis {filterYearLt - 1}"); + } + if (filterKmwGt > 0 && filterKmwLt > 0) { + filterNames.Add($"{filterKmwGt:N1}–{filterKmwLt:N1} °KMW"); + } else if (filterKmwGt > 0) { + filterNames.Add($"ab {filterKmwGt:N1} °KMW"); + } else if (filterKmwLt > 0) { + filterNames.Add($"unter {filterKmwLt:N1} °KMW"); + } + if (filterOeGt > 0 && filterOeLt > 0) { + filterNames.Add($"{filterOeGt:N1}–{filterOeLt:N1} °Oe"); + } else if (filterOeGt > 0) { + filterNames.Add($"ab {filterOeGt:N1} °Oe"); + } else if (filterOeLt > 0) { + filterNames.Add($"unter {filterOeLt:N1} °Oe"); + } + } + + IQueryable dpq = deliveryQuery + .SelectMany(d => d.Parts) + .Where(prd) + .OrderBy(p => p.Delivery.DateString) + .ThenBy(p => p.Delivery.TimeString) + .ThenBy(p => p.Delivery.LsNr) + .ThenBy(p => p.DPNr); + + return (filterNames, dpq.Select(p => p.Delivery).Distinct().OrderBy(d => d.DateString).ThenBy(d => d.TimeString), dpq, prd.Invoke, filter); + } + + public static async Task UpdateDeliveryPart(this DeliveryAdminViewModel vm, int? oldYear, int? oldDid, int? oldDpnr, bool dateHasChanged, bool timeHasChanged, bool timeIsDefault) { + using var ctx = new AppDbContext(); + + int year = oldYear ?? Utils.CurrentYear; + int did = oldDid ?? await ctx.NextDId(year); + int dpnr = oldDpnr ?? await ctx.NextDPNr(year, did); + + var oldDelivery = await ctx.Deliveries.FindAsync(year, did); + bool deliveryNew = (oldDid == null); + bool partNew = (oldDpnr == null); + var originalMgNr = oldDelivery?.MgNr; + var originalMemberKgNr = oldDelivery?.Member.DefaultKgNr; + + var date = DateOnly.ParseExact(vm.Date!, "dd.MM.yyyy"); + int? newLnr = (deliveryNew || dateHasChanged) ? await ctx.NextLNr(date) : null; + + string? newTimeString = null; + if (partNew && timeIsDefault) { + newTimeString = DateTime.Now.ToString("HH:mm:ss"); + } else if (partNew || timeHasChanged) { + newTimeString = string.IsNullOrEmpty(vm.Time) ? null : vm.Time + ":00"; + } + + var d = new Delivery { + Year = year, + DId = did, + DateString = $"{date:yyyy-MM-dd}", + TimeString = newTimeString ?? oldDelivery?.TimeString, + LNr = newLnr ?? oldDelivery!.LNr, + ZwstId = vm.Branch!.ZwstId, + LsNr = vm.LsNr!, + MgNr = (int)vm.MgNr!, + Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment, + }; + + var p = new DeliveryPart { + Year = year, + DId = did, + DPNr = dpnr, + + SortId = vm.WineVar!.SortId, + AttrId = vm.WineAttr?.AttrId, + CultId = vm.WineCult?.CultId, + Kmw = (double)vm.GradationKmw!, + QualId = vm.WineQualityLevel!.QualId, + HkId = vm.WineOrigin!.HkId, + KgNr = vm.WineKg?.KgNr, + RdNr = vm.WineRd?.RdNr, + + IsNetWeight = vm.IsNetWeight, + IsHandPicked = vm.IsHandPicked, + IsLesewagen = vm.IsLesewagen, + IsGebunden = vm.IsGebunden, + Temperature = vm.Temperature, + Acid = vm.Acid, + Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment, + + Weight = (int)vm.Weight!, + IsManualWeighing = vm.IsManualWeighing, + ScaleId = vm.ScaleId, + WeighingId = vm.WeighingId, + WeighingReason = vm.ManualWeighingReason, + }; + + try { + if (oldDelivery != null && ctx.Entry(oldDelivery) is EntityEntry entry) { + entry.State = EntityState.Detached; + } + if (!deliveryNew) { + ctx.Update(d); + } else { + ctx.Add(d); + } + if (!partNew) { + ctx.Update(p); + } else { + ctx.Add(p); + } + + ctx.UpdateDeliveryPartModifiers(p, await ctx.DeliveryPartModifiers + .Where(m => m.Year == p.Year && m.DId == p.DId && m.DPNr == p.DPNr) + .Select(m => m.Modifier) + .ToListAsync(), vm.Modifiers); + + if (originalMgNr != null && originalMgNr.Value != d.MgNr) { + // update origin (KgNr), if default is selected + var newKgNr = (await ctx.Members.FindAsync(d.MgNr))?.DefaultKgNr; + foreach (var part in d.Parts.Where(part => part.DPNr != dpnr && part.KgNr == originalMemberKgNr)) { + part.KgNr = newKgNr; + ctx.Update(part); + } + } + + await ctx.SaveChangesAsync(); + } catch (Exception exc) { + var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message; + if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; + MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error); + } + + return p; + } + + public static async Task GenerateDeliveryNote(int year, int did, ExportMode mode) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + using var ctx = new AppDbContext(); + var d = (await ctx.Deliveries.FindAsync(year, did))!; + using var doc = new DeliveryNote(d, ctx); + await Utils.ExportDocument(doc, mode, d.LsNr, (d.Member, $"{DeliveryNote.Name} Nr. {d.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {d.LsNr}")); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + + public static async Task GenerateDeliveryJournal(this DeliveryAdminViewModel vm, int modeWho, ExportMode exportMode) { + using var ctx = new AppDbContext(); + IQueryable query; + List filterNames = []; + if (modeWho == 0) { + var (f, _, q, _, _) = await vm.GetFilters(ctx); + query = q; + filterNames.AddRange(f); + } 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) + .ThenBy(p => p.Delivery.TimeString) + .ThenBy(p => p.Delivery.LsNr) + .ThenBy(p => p.DPNr); + + if (exportMode == ExportMode.SaveList) { + var d = new SaveFileDialog() { + FileName = $"{DeliveryJournal.Name}.ods", + DefaultExt = "ods", + Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", + Title = $"{DeliveryJournal.Name} speichern unter - Elwig" + }; + if (d.ShowDialog() == true) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var data = await DeliveryJournalData.FromQuery(query, filterNames); + using var ods = new OdsFile(d.FileName); + await ods.AddTable(data); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + } else if (exportMode == ExportMode.Export) { + var d = new SaveFileDialog() { + FileName = $"Lieferungen.zip", + DefaultExt = "zip", + Filter = "ZIP-Datei (*.zip)|*.zip", + Title = $"{DeliveryJournal.Name} speichern unter - Elwig" + }; + if (d.ShowDialog() == true) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + await ElwigData.ExportDeliveries(d.FileName, await query.Select(p => p.Delivery).Distinct().ToListAsync(), filterNames); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + } else if (exportMode == ExportMode.Upload && App.Config.SyncUrl != null) { + Mouse.OverrideCursor = Cursors.AppStarting; + 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(); + if (list.Count == 0) { + MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Fehler", + MessageBoxButton.OK, MessageBoxImage.Error); + } else { + await ElwigData.ExportDeliveries(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); + } + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } else { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var data = await DeliveryJournalData.FromQuery(query, filterNames); + using var doc = new DeliveryJournal(string.Join(" / ", filterNames), data); + await Utils.ExportDocument(doc, exportMode); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + } + + public static async Task GenerateWineQualityStatistics(this DeliveryAdminViewModel vm, int modeWho, ExportMode exportMode) { + using var ctx = new AppDbContext(); + IQueryable query; + List filterNames = []; + if (modeWho == 0) { + var (f, _, q, _, _) = await vm.GetFilters(ctx); + query = q; + filterNames.AddRange(f); + } else { + var date = $"{Utils.Today:yyyy-MM-dd}"; + query = ctx.DeliveryParts + .Where(p => p.Delivery.DateString == date); + filterNames.Add($"{Utils.Today:dd.MM.yyyy}"); + } + + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var data = await WineQualityStatisticsData.FromQuery(query, App.Client.OrderingMemberList); + using var doc = new WineQualityStatistics(string.Join(" / ", filterNames), data); + await Utils.ExportDocument(doc, exportMode); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + } +} diff --git a/Elwig/ViewModels/DeliveryAdminViewModel.cs b/Elwig/ViewModels/DeliveryAdminViewModel.cs new file mode 100644 index 0000000..0b3d90a --- /dev/null +++ b/Elwig/ViewModels/DeliveryAdminViewModel.cs @@ -0,0 +1,171 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Elwig.Helpers; +using Elwig.Models.Entities; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Elwig.ViewModels { + public partial class DeliveryAdminViewModel : ObservableObject { + + public bool IsReceipt = false; + + [ObservableProperty] + private string _title = "Lieferungen - Elwig"; + + [ObservableProperty] + private string? _searchQuery = ""; + public List TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0)]; + + public Member? FilterMember { get; set; } + public int? FilterMgNr => FilterMember?.MgNr; + + [ObservableProperty] + private string? _lastScaleError; + [ObservableProperty] + private string? _manualWeighingReason; + [ObservableProperty] + private string? _scaleId; + [ObservableProperty] + private string? _weighingId; + + [ObservableProperty] + private bool _filterTodayOnly; + [ObservableProperty] + private bool _filterAllSeasons; + [ObservableProperty] + private bool _enableAllSeasons; + [ObservableProperty] + private string? _filterSeasonString; + public int? FilterSeason { + get => int.TryParse(FilterSeasonString, out var year) ? year : null; + set => FilterSeasonString = $"{value}"; + } + + [ObservableProperty] + private Delivery? _selectedDelivery; + [ObservableProperty] + private IEnumerable _deliveries = []; + + [ObservableProperty] + private string? _mgNrString; + public int? MgNr { + get => int.TryParse(MgNrString, out var mgnr) ? mgnr : null; + set => MgNrString = $"{value}"; + } + [ObservableProperty] + private Member? _member; + [ObservableProperty] + private IEnumerable _memberSource = []; + [ObservableProperty] + private string? _memberAddress; + [ObservableProperty] + private string? _lsNr; + [ObservableProperty] + private string? _date; + [ObservableProperty] + private string? _time; + [ObservableProperty] + private Branch? _branch; + [ObservableProperty] + private IEnumerable _branchSource = []; + [ObservableProperty] + private string? _comment; + + [ObservableProperty] + private string? _sortId; + [ObservableProperty] + private WineVar? _wineVar; + [ObservableProperty] + private IEnumerable _wineVarSource = []; + [ObservableProperty] + private object? _wineAttrObj; + public WineAttr? WineAttr { + get => WineAttrObj as WineAttr; + set => WineAttrObj = value ?? WineAttrSource.FirstOrDefault(); + } + [ObservableProperty] + private IEnumerable _wineAttrSource = []; + [ObservableProperty] + private object? _wineCultObj; + public WineCult? WineCult { + get => WineCultObj as WineCult; + set => WineCultObj = value ?? WineCultSource.FirstOrDefault(); + } + [ObservableProperty] + private IEnumerable _wineCultSource = []; + [ObservableProperty] + private string? _gradationOeString; + public double? GradationOe => double.TryParse(GradationOeString, out var oe) ? oe : null; + [ObservableProperty] + private string? _gradationKmwString; + public double? GradationKmw => double.TryParse(GradationKmwString, out var kmw) ? kmw : null; + [ObservableProperty] + private WineQualLevel? _wineQualityLevel; + [ObservableProperty] + private IEnumerable _wineQualityLevelSource = []; + [ObservableProperty] + private bool _abgewertet; + + [ObservableProperty] + private string? _weightString; + public int? Weight { + get => int.TryParse(WeightString?.Replace(Utils.GroupSeparator, ""), out var w) ? w : null; + set => WeightString = $"{value:N0}"; + } + [ObservableProperty] + private bool _isManualWeighing; + [ObservableProperty] + private bool _isNetWeight; + + [ObservableProperty] + private WineOrigin? _wineOrigin; + [ObservableProperty] + private IEnumerable _wineOriginSource = []; + [ObservableProperty] + private object? _wineKgObj; + public AT_Kg? WineKg { + get => WineKgObj as AT_Kg; + set => WineKgObj = value ?? WineKgSource.FirstOrDefault(); + } + [ObservableProperty] + private IEnumerable _wineKgSource = []; + [ObservableProperty] + private object? _wineRdObj; + public WbRd? WineRd { + get => WineRdObj as WbRd; + set => WineRdObj = value ?? WineRdSource.FirstOrDefault(); + } + [ObservableProperty] + private IEnumerable _wineRdSource = []; + [ObservableProperty] + private bool? _isGebunden; + + public ObservableCollection Modifiers { get; set; } = []; + [ObservableProperty] + private IEnumerable _modifiersSource = []; + [ObservableProperty] + private string? _partComment; + [ObservableProperty] + private string? _temperatureString; + public double? Temperature => double.TryParse(TemperatureString, out var t) ? t : null; + [ObservableProperty] + private string? _acidString; + public double? Acid => double.TryParse(AcidString, out var a) ? a : null; + [ObservableProperty] + private bool _isLesewagen; + [ObservableProperty] + private bool? _isHandPicked; + + [ObservableProperty] + private string _statusMembers = "Mitglieder: -"; + [ObservableProperty] + private string _statusDeliveries = "Lieferungen: -"; + [ObservableProperty] + private string _statusVarieties = "Sorten: -"; + [ObservableProperty] + private string _statusWeight = "Gewicht: -"; + [ObservableProperty] + private string _statusGradation = "Gradation: -"; + } +} diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml b/Elwig/Windows/DeliveryAdminWindow.xaml index 49d915d..4513ca3 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml +++ b/Elwig/Windows/DeliveryAdminWindow.xaml @@ -4,9 +4,13 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Elwig.Windows" + xmlns:vm="clr-namespace:Elwig.ViewModels" xmlns:ctrl="clr-namespace:Elwig.Controls" - Title="Lieferungen - Elwig" Height="720" Width="1100" MinHeight="720" MinWidth="1000" + Title="{Binding Title}" Height="720" Width="1100" MinHeight="720" MinWidth="1000" Loaded="Window_Loaded"> + + +