From af80e827b76eb0a23c3d547a1263409177709aea Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Wed, 3 Jul 2024 12:37:13 +0200 Subject: [PATCH] [#10] MemberAdminWindow: Implement MVVM --- Elwig/Elwig.csproj | 1 + Elwig/Helpers/ControlUtils.cs | 10 +- Elwig/Helpers/Utils.cs | 5 + Elwig/Services/MemberService.cs | 503 ++++++++++++++ Elwig/ViewModels/MemberAdminViewModel.cs | 163 +++++ Elwig/Windows/AdministrationWindow.cs | 32 +- Elwig/Windows/MemberAdminWindow.xaml | 290 +++++--- Elwig/Windows/MemberAdminWindow.xaml.cs | 799 ++++------------------- 8 files changed, 1005 insertions(+), 798 deletions(-) create mode 100644 Elwig/Services/MemberService.cs create mode 100644 Elwig/ViewModels/MemberAdminViewModel.cs diff --git a/Elwig/Elwig.csproj b/Elwig/Elwig.csproj index 92f309d..14127e6 100644 --- a/Elwig/Elwig.csproj +++ b/Elwig/Elwig.csproj @@ -25,6 +25,7 @@ + diff --git a/Elwig/Helpers/ControlUtils.cs b/Elwig/Helpers/ControlUtils.cs index 45c2bdf..a65bc37 100644 --- a/Elwig/Helpers/ControlUtils.cs +++ b/Elwig/Helpers/ControlUtils.cs @@ -171,15 +171,19 @@ namespace Elwig.Helpers { return item; } - public static object? GetItemFromSource(IEnumerable source, object? item) { - return GetItemFromSource(source, Utils.GetEntityIdentifier(item)); + public static T? GetItemFromSource(IEnumerable source, T? item) { + return (T?)GetItemFromSource(source, Utils.GetEntityIdentifier(item)); + } + + public static object? GetItemFromSourceWithPk(IEnumerable source, params object?[] primaryKey) { + return GetItemFromSource(source, (int?)Utils.GetEntityIdetifierForPk(primaryKey)); } public static void SelectItemWithHash(Selector input, int? hash) { if (hash == null) { input.SelectedItem = null; } else { - input.SelectedItem = GetItemFromSource(input.ItemsSource, (int)hash); + input.SelectedItem = GetItemFromSource(input.ItemsSource, hash); } if (input is ListBox lb && lb.SelectedItem is object lbItem) { lb.ScrollIntoView(lbItem); diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index f9be152..8ee9184 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -537,6 +537,11 @@ namespace Elwig.Helpers { } } + public static int GetEntityIdetifierForPk(params object?[] primaryKey) { + var pk = primaryKey.Select(k => k?.GetHashCode() ?? 0).ToArray(); + return ((IStructuralEquatable)pk).GetHashCode(EqualityComparer.Default); + } + public static int? GetEntityIdentifier(object? obj) { if (obj == null) { return null; diff --git a/Elwig/Services/MemberService.cs b/Elwig/Services/MemberService.cs new file mode 100644 index 0000000..6ec2be1 --- /dev/null +++ b/Elwig/Services/MemberService.cs @@ -0,0 +1,503 @@ +using Elwig.Helpers; +using Elwig.Models.Entities; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; +using Microsoft.EntityFrameworkCore; +using Elwig.Documents; +using System.Windows.Input; +using System.Windows; +using Elwig.Helpers.Billing; +using Elwig.Models.Dtos; +using Elwig.Helpers.Export; +using Microsoft.Win32; +using Elwig.ViewModels; + +namespace Elwig.Services { + public static class MemberService { + + public static async Task InitInputs(this MemberAdminViewModel vm) { + using var ctx = new AppDbContext(); + vm.MgNrString = $"{await ctx.NextMgNr()}"; + vm.EntryDate = DateTime.Now.ToString("dd.MM.yyyy"); + if (vm.BranchSource.Count() == 1) + vm.Branch = vm.BranchSource.First(); + vm.IsActive = true; + vm.ContactViaPost = true; + vm.EnableMemberReferenceButton = false; + } + + public static void ClearInputs(this MemberAdminViewModel vm) { + vm.IsMemberSelected = false; + vm.MemberHasEmail = false; + vm.MemberCanSendEmail = false; + vm.EnableMemberReferenceButton = false; + vm.StatusDeliveriesLastSeason = "-"; + vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}"; + vm.StatusDeliveriesLastSeasonToolTip = null; + vm.StatusDeliveriesThisSeason = "-"; + vm.StatusDeliveriesThisSeasonInfo = $"{Utils.CurrentLastSeason}"; + vm.StatusDeliveriesThisSeasonToolTip = null; + vm.StatusAreaCommitment = "-"; + vm.Age = "-"; + } + + public static void FillInputs(this MemberAdminViewModel vm, Member m) { + vm.IsMemberSelected = true; + vm.MgNrString = $"{m.MgNr}"; + vm.PredecessorMgNrString = $"{m.PredecessorMgNr}"; + vm.EnableMemberReferenceButton = m.PredecessorMgNr != null; + vm.Prefix = m.Prefix; + vm.GivenName = m.GivenName; + vm.FamilyName = m.FamilyName; + vm.Suffix = m.Suffix; + vm.Birthday = (m.Birthday != null) ? string.Join(".", m.Birthday.Split("-").Reverse()) : null; + if (m.Birthday?.Length == 10) { + vm.Age = Utils.GetAge(DateOnly.ParseExact(m.Birthday, "yyyy-MM-dd")).ToString(); + } else if (m.Birthday != null) { + vm.Age = "ca. " + (DateTime.Now.Year - int.Parse(m.Birthday[^4..])).ToString(); + } else { + vm.Age = "-"; + } + vm.IsDeceased = m.IsDeceased; + vm.Address = m.Address; + if (m.PostalDest.AtPlz is AT_PlzDest p) { + vm.PlzString = $"{p.Plz}"; + vm.Ort = ControlUtils.GetItemFromSource(vm.OrtSource, p); + } else { + vm.PlzString = null; + vm.Ort = null; + } + + var emailAddrs = m.EmailAddresses.OrderBy(a => a.Nr).ToList(); + for (int i = 0; i < vm.EmailAddresses.Count; i++) { + if (i < emailAddrs.Count) { + var emailAddr = emailAddrs[i]; + vm.EmailAddresses[i] = emailAddr.Address; + } else { + vm.EmailAddresses[i] = null; + } + } + + var phoneNrs = m.TelephoneNumbers.OrderBy(p => p.Nr).ToList(); + for (int i = 0; i < vm.PhoneNrs.Count; i++) { + if (i < phoneNrs.Count) { + var phoneNr = phoneNrs[i]; + var idx = vm.PhoneNrTypes.Select((e, i) => (e, i)).FirstOrDefault(kv => kv.e.Key == phoneNr.Type).i; + vm.PhoneNrs[i] = new(idx, phoneNr.Number, phoneNr.Comment); + } else { + vm.PhoneNrs[i] = new(); + } + } + + vm.Iban = m.Iban; + vm.Bic = m.Bic; + + vm.UstIdNr = m.UstIdNr; + vm.LfbisNr = m.LfbisNr; + vm.IsBuchführend = m.IsBuchführend; + vm.IsOrganic = m.IsOrganic; + + var billingAddr = m.BillingAddress; + if (billingAddr != null) { + vm.BillingName = billingAddr.Name; + vm.BillingAddress = billingAddr.Address; + if (billingAddr.PostalDest.AtPlz is AT_PlzDest b) { + vm.BillingPlzString = $"{b.Plz}"; + vm.BillingOrt = ControlUtils.GetItemFromSource(vm.BillingOrtSource, b); + } + } else { + vm.BillingName = null; + vm.BillingAddress = null; + vm.BillingPlzString = null; + vm.BillingOrt = null; + } + + vm.EntryDate = (m.EntryDateString != null) ? string.Join(".", m.EntryDateString.Split("-").Reverse()) : null; + vm.ExitDate = (m.ExitDateString != null) ? string.Join(".", m.ExitDateString.Split("-").Reverse()) : null; + vm.BusinessSharesString = $"{m.BusinessShares}"; + vm.AccountingNr = m.AccountingNr; + vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, m.ZwstId); + vm.DefaultKg = (AT_Kg?)ControlUtils.GetItemFromSourceWithPk(vm.DefaultKgSource, m.DefaultKgNr); + vm.Comment = m.Comment; + vm.IsActive = m.IsActive; + vm.IsVollLieferant = m.IsVollLieferant; + vm.IsFunktionär = m.IsFunktionär; + vm.ContactViaPost = m.ContactViaPost; + vm.ContactViaEmail = m.ContactViaEmail; + + Dictionary deliveries; + using (var ctx = new AppDbContext()) { + var d1 = ctx.Deliveries.Where(d => d.Year == Utils.CurrentLastSeason && d.MgNr == m.MgNr); + var d2 = ctx.Deliveries.Where(d => d.Year == Utils.CurrentLastSeason - 1 && d.MgNr == m.MgNr); + vm.StatusDeliveriesLastSeason = $"Lieferungen ({Utils.CurrentLastSeason - 1}): {d2.Count():N0} ({d2.Sum(d => d.Parts.Count):N0}), {d2.SelectMany(d => d.Parts).Sum(p => p.Weight):N0} kg"; + vm.StatusDeliveriesThisSeason = $"Lieferungen ({Utils.CurrentLastSeason}): {d1.Count():N0} ({d1.Sum(d => d.Parts.Count):N0}), {d1.SelectMany(d => d.Parts).Sum(p => p.Weight):N0} kg"; + vm.StatusAreaCommitment = $"Gebundene Fläche: {m.ActiveAreaCommitments(ctx).Select(c => c.Area).Sum():N0} m²"; + deliveries = ctx.Deliveries + .Where(d => d.MgNr == m.MgNr) + .SelectMany(d => d.Parts) + .GroupBy(d => d.Year) + .ToDictionary(g => g.Key, g => g.Sum(d => d.Weight)); + } + + vm.MemberHasEmail = m.EmailAddresses.Count > 0; + vm.MemberCanSendEmail = App.Config.Smtp != null && m.EmailAddresses.Count > 0; + vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => deliveries.GetValueOrDefault(i, 0) > 0).ToList(); + } + + public static async Task<(List, IQueryable, List)> GetFilters(this MemberAdminViewModel vm, AppDbContext ctx) { + List filterNames = []; + IQueryable memberQuery = ctx.Members; + if (vm.ShowOnlyActiveMembers) { + memberQuery = memberQuery.Where(m => m.IsActive); + filterNames.Add("aktive Mitglieder"); + } + + var filterMgNr = new List(); + var filterZwst = new List(); + var filterKgNr = new List(); + var filterLfbisNr = new List(); + var filterUstIdNr = new List(); + var filterAreaCom = new List(); + + var filter = vm.TextFilter; + if (filter.Count > 0) { + var branches = await ctx.Branches.ToListAsync(); + var mgnr = await ctx.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m); + var kgs = await ctx.WbKgs.ToDictionaryAsync(k => k.AtKg.Name.ToLower(), k => k.AtKg); + var areaComs = await ctx.AreaCommitmentTypes.ToDictionaryAsync(t => $"{t.SortId}{t.AttrId}", t => t); + + for (int i = 0; i < filter.Count; i++) { + var e = filter[i]; + + if (e.Length >= 5 && e.Length <= 10 && "funktionär".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => m.IsFunktionär); + filter.RemoveAt(i--); + filterNames.Add("Funktionäre"); + } else if (e.Length >= 6 && e.Length <= 11 && e[0] == '!' && "funktionär".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => !m.IsFunktionär); + filter.RemoveAt(i--); + filterNames.Add("Nicht-Funktionäre"); + } else if (e.Equals("bio", StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => m.IsOrganic); + filter.RemoveAt(i--); + filterNames.Add("Bio-Betriebe"); + } else if (e.Equals("!bio", StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => !m.IsOrganic); + filter.RemoveAt(i--); + filterNames.Add("Nicht-Bio-Betriebe"); + } else if (e.Length >= 4 && e.Length <= 13 && "volllieferant".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => m.IsVollLieferant); + filter.RemoveAt(i--); + filterNames.Add("Volllieferanten"); + } else if (e.Length >= 5 && e.Length <= 14 && e[0] == '!' && "volllieferant".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => !m.IsVollLieferant); + filter.RemoveAt(i--); + filterNames.Add("Nicht-Vollieferanten"); + } else if (e.Length >= 5 && e.Length <= 11 && "buchführend".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => m.IsBuchführend); + filter.RemoveAt(i--); + filterNames.Add("buchführend"); + } else if (e.Length >= 6 && e.Length <= 12 && e[0] == '!' && "buchführend".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => !m.IsBuchführend); + filter.RemoveAt(i--); + filterNames.Add("pauschaliert"); + } else if (e.Length >= 8 && e.Length <= 12 && "pauschaliert".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => !m.IsBuchführend); + filter.RemoveAt(i--); + filterNames.Add("pauschaliert"); + } else if (e.Length >= 9 && e.Length <= 13 && e[0] == '!' && "pauschaliert".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { + memberQuery = memberQuery.Where(m => m.IsBuchführend); + filter.RemoveAt(i--); + filterNames.Add("buchführend"); + } else if (e.All(char.IsAsciiDigit) && mgnr.ContainsKey(e)) { + filterMgNr.Add(int.Parse(e)); + filter.RemoveAt(i--); + filterNames.Add($"MgNr. {e}"); + } else if (kgs.TryGetValue(e, out var kg)) { + filterKgNr.Add(kg.KgNr); + filter.RemoveAt(i--); + filterNames.Add($"Stammgemeinde {kg.Name}"); + } else if (e.StartsWith("zwst:")) { + try { + var branch = branches.Where(b => b.Name.StartsWith(e[5..], StringComparison.CurrentCultureIgnoreCase)).Single(); + filterZwst.Add(branch.ZwstId); + filter.RemoveAt(i--); + filterNames.Add($"Zweigstelle {branch.Name}"); + } catch (InvalidOperationException) { } + } else if (e.StartsWith('+') && e[1..].All(char.IsAsciiDigit)) { + memberQuery = memberQuery.Where(m => m.TelephoneNumbers.Any(t => t.Number.Replace(" ", "").StartsWith(e))); + filter.RemoveAt(i--); + filterNames.Add($"Tel.-Nr. {e}"); + } else if (areaComs.ContainsKey(e.ToUpper())) { + filterAreaCom.Add(e.ToUpper()); + filter.RemoveAt(i--); + filterNames.Add($"Flächenbindung {e.ToUpper()}"); + } else if (Validator.CheckLfbisNr(e)) { + filterLfbisNr.Add(e); + filter.RemoveAt(i--); + filterNames.Add($"Betriebsnummer {e}"); + } else if (Validator.CheckUstIdNr(e.ToUpper())) { + filterUstIdNr.Add(e.ToUpper()); + filter.RemoveAt(i--); + filterNames.Add($"UID {e.ToUpper()}"); + } else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) { + filter[i] = e[1..^1]; + } else if (e.Length < 2) { + filter.RemoveAt(i--); + } + } + + if (filterMgNr.Count > 0) memberQuery = memberQuery.Where(m => filterMgNr.Contains(m.MgNr)); + if (filterKgNr.Count > 0) memberQuery = memberQuery.Where(m => m.DefaultKgNr != null && filterKgNr.Contains((int)m.DefaultKgNr)); + if (filterZwst.Count > 0) memberQuery = memberQuery.Where(m => m.ZwstId != null && filterZwst.Contains(m.ZwstId)); + if (filterAreaCom.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments()).Any(c => filterAreaCom.Contains(c.VtrgId))); + if (filterLfbisNr.Count > 0) memberQuery = memberQuery.Where(m => m.LfbisNr != null && filterLfbisNr.Contains(m.LfbisNr)); + if (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr)); + } + + return (filterNames, memberQuery, filter); + } + + public static async Task GenerateMemberDataSheet(Member m, ExportMode mode) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + using var ctx = new AppDbContext(); + using var doc = new MemberDataSheet(m, ctx); + await Utils.ExportDocument(doc, mode, emailData: (m, MemberDataSheet.Name, "Im Anhang finden Sie das aktuelle Stammdatenblatt")); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + + public static async Task GenerateDeliveryConfirmation(Member m, int year, ExportMode mode) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var b = new Billing(year); + await b.FinishSeason(); + await b.CalculateBuckets(); + await App.HintContextChange(); + + using var ctx = new AppDbContext(); + var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, year, m); + using var doc = new DeliveryConfirmation(ctx, year, m, data); + await Utils.ExportDocument(doc, mode, emailData: (m, $"{DeliveryConfirmation.Name} {year}", $"Im Anhang finden Sie die Anlieferungsbestätigung {year}")); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + + public static async Task GenerateCreditNote(Member m, int year, int avnr, ExportMode mode) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + using var ctx = new AppDbContext(); + var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!; + var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.Seasons, year, avnr); + var p = (await ctx.MemberPayments.FindAsync(year, avnr, m.MgNr))!; + var b = BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data); + + using var doc = new CreditNote(ctx, p, data[m.MgNr], + b.ConsiderContractPenalties, b.ConsiderTotalPenalty, b.ConsiderAutoBusinessShares, b.ConsiderCustomModifiers, + await ctx.GetMemberUnderDelivery(year, m.MgNr)); + await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {v.Name}", $"Im Anhang finden Sie die Traubengutschrift {v.Name}")); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + + public static async Task GenerateMemberList(this MemberAdminViewModel vm, int modeWho, ExportMode exportMode) { + using var ctx = new AppDbContext(); + IQueryable query; + List filterNames = []; + if (modeWho == 0) { + query = ctx.Members.Where(m => m.IsActive); + filterNames.Add("aktive Mitglieder"); + } else if (modeWho == 1) { + var (f, q, _) = await vm.GetFilters(ctx); + query = q; + filterNames.AddRange(f); + } else { + query = ctx.Members; + } + + if (vm.MemberListOrderByMgNr) { + query = query + .OrderBy(m => m.Branch!.Name) + .ThenBy(m => m.MgNr); + } else if (vm.MemberListOrderByName) { + query = query + .OrderBy(m => m.Branch!.Name) + .ThenBy(m => m.FamilyName) + .ThenBy(m => m.GivenName) + .ThenBy(m => m.MgNr); + } else if (vm.MemberListOrderByOrt) { + query = query + .OrderBy(m => m.Branch!.Name) + .ThenBy(m => m.DefaultWbKg!.AtKg.Name) + .ThenBy(m => m.FamilyName) + .ThenBy(m => m.GivenName) + .ThenBy(m => m.MgNr); + } + + if (exportMode == ExportMode.SaveList) { + var d = new SaveFileDialog() { + FileName = $"{MemberList.Name}.ods", + DefaultExt = "ods", + Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", + Title = $"{MemberList.Name} speichern unter - Elwig" + }; + if (d.ShowDialog() == true) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var data = await MemberListData.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 { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var data = await MemberListData.FromQuery(query, filterNames); + using var doc = new MemberList(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 UpdateMember(this MemberAdminViewModel vm, int? oldMgNr) { + using var ctx = new AppDbContext(); + var newMgNr = (int)vm.MgNr!; + var m = new Member { + MgNr = oldMgNr ?? newMgNr, + PredecessorMgNr = vm.PredecessorMgNr, + Prefix = string.IsNullOrEmpty(vm.Prefix) ? null : vm.Prefix, + GivenName = vm.GivenName!, + FamilyName = vm.FamilyName!, + Suffix = string.IsNullOrEmpty(vm.Suffix) ? null : vm.Suffix, + Birthday = string.IsNullOrEmpty(vm.Birthday) ? null : string.Join("-", vm.Birthday!.Split(".").Reverse()), + IsDeceased = vm.IsDeceased, + CountryNum = 40, // Austria AT AUT + PostalDestId = vm.Ort!.Id, + Address = vm.Address!, + + Iban = string.IsNullOrEmpty(vm.Iban) ? null : vm.Iban?.Replace(" ", ""), + Bic = string.IsNullOrEmpty(vm.Bic) ? null : vm.Bic, + + UstIdNr = string.IsNullOrEmpty(vm.UstIdNr) ? null : vm.UstIdNr, + LfbisNr = string.IsNullOrEmpty(vm.LfbisNr) ? null : vm.LfbisNr, + IsBuchführend = vm.IsBuchführend, + IsOrganic = vm.IsOrganic, + + EntryDateString = string.IsNullOrEmpty(vm.EntryDate) ? null : string.Join("-", vm.EntryDate.Split(".").Reverse()), + ExitDateString = string.IsNullOrEmpty(vm.ExitDate) ? null : string.Join("-", vm.ExitDate.Split(".").Reverse()), + BusinessShares = (int)vm.BusinessShares!, + AccountingNr = string.IsNullOrEmpty(vm.AccountingNr) ? null : vm.AccountingNr, + IsActive = vm.IsActive, + IsVollLieferant = vm.IsVollLieferant, + IsFunktionär = vm.IsFunktionär, + ZwstId = vm.Branch?.ZwstId, + DefaultKgNr = vm.DefaultKg?.KgNr, + Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment, + ContactViaPost = vm.ContactViaPost, + ContactViaEmail = vm.ContactViaEmail, + }; + + if (oldMgNr != null) { + ctx.Update(m); + } else { + ctx.Add(m); + } + + ctx.RemoveRange(ctx.BillingAddresses.Where(a => a.MgNr == oldMgNr)); + if (vm.BillingOrt != null && vm.BillingName != null) { + var p = vm.BillingOrt; + ctx.Add(new BillingAddr { + MgNr = m.MgNr, + Name = vm.BillingName, + Address = vm.BillingAddress ?? "", + CountryNum = p.CountryNum, + PostalDestId = p.Id, + }); + } + + ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(t => t.MgNr == oldMgNr)); + ctx.AddRange(vm.PhoneNrs + .Where(input => input.Number != null) + .Select((input, i) => new MemberTelNr { + MgNr = m.MgNr, + Nr = i + 1, + Type = input.Type == -1 ? (input.Number!.StartsWith("+43 ") && input.Number![4] == '6' ? "mobile" : "landline") : vm.PhoneNrTypes[input.Type].Key, + Number = input.Number!, + Comment = input.Comment, + })); + + ctx.RemoveRange(ctx.MemberEmailAddrs.Where(e => e.MgNr == oldMgNr)); + ctx.AddRange(vm.EmailAddresses + .Where(input => input != null && input != "") + .Select((input, i) => new MemberEmailAddr { + MgNr = m.MgNr, + Nr = i + 1, + Address = input!, + Comment = null, + })); + + await ctx.SaveChangesAsync(); + + if (vm.TransferPredecessorAreaComs is int year && m.PredecessorMgNr is int predecessor) { + var areaComs = await ctx.AreaCommitments + .Where(c => c.MgNr == predecessor && (c.YearTo == null || c.YearTo >= year)) + .ToListAsync(); + + var fbNr = await ctx.NextFbNr(); + ctx.AddRange(areaComs.Select((c, i) => new AreaCom { + FbNr = fbNr + i, + MgNr = m.MgNr, + VtrgId = c.VtrgId, + CultId = c.CultId, + Area = c.Area, + KgNr = c.KgNr, + GstNr = c.GstNr, + RdNr = c.RdNr, + YearFrom = year, + YearTo = c.YearTo, + })); + + foreach (var ac in areaComs) + ac.YearTo = year - 1; + ctx.UpdateRange(areaComs); + await ctx.SaveChangesAsync(); + } + vm.TransferPredecessorAreaComs = null; + + if (vm.CancelAreaComs is int yearTo) { + var areaComs = await ctx.AreaCommitments + .Where(c => c.MgNr == m.MgNr && (c.YearTo == null || c.YearTo > yearTo)) + .ToListAsync(); + + foreach (var ac in areaComs) + ac.YearTo = yearTo; + ctx.UpdateRange(areaComs); + await ctx.SaveChangesAsync(); + } + vm.CancelAreaComs = null; + + if (newMgNr != m.MgNr) { + await ctx.Database.ExecuteSqlAsync($"UPDATE member SET mgnr = {newMgNr} WHERE mgnr = {oldMgNr}"); + } + + await App.HintContextChange(); + + return newMgNr; + } + } +} diff --git a/Elwig/ViewModels/MemberAdminViewModel.cs b/Elwig/ViewModels/MemberAdminViewModel.cs new file mode 100644 index 0000000..e48936f --- /dev/null +++ b/Elwig/ViewModels/MemberAdminViewModel.cs @@ -0,0 +1,163 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Elwig.Helpers; +using Elwig.Models.Entities; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; + +namespace Elwig.ViewModels { + public partial class MemberAdminViewModel : ObservableObject { + + public int? TransferPredecessorAreaComs = null; + public int? CancelAreaComs = null; + + public ObservableCollection> PhoneNrTypes { get; set; } = new(Utils.PhoneNrTypes); + + [ObservableProperty] + private string? _searchQuery = ""; + public List TextFilter => [.. SearchQuery?.ToLower().Split(' ').ToList().FindAll(e => e.Length > 0)]; + + [ObservableProperty] + private bool _showOnlyActiveMembers; + + [ObservableProperty] + private Member? _selectedMember; + [ObservableProperty] + private IEnumerable _members = []; + + [ObservableProperty] + private bool _isMemberSelected; + [ObservableProperty] + private bool _memberHasEmail; + [ObservableProperty] + private bool _memberCanSendEmail; + [ObservableProperty] + private bool _enableMemberReferenceButton; + [ObservableProperty] + private bool _enableSearchInputs = true; + [ObservableProperty] + public IEnumerable _memberHasDeliveries = [ .. Enumerable.Range(0, 9999).Select(i => false) ]; + + [ObservableProperty] + private bool _memberListOrderByMgNr; + [ObservableProperty] + private bool _memberListOrderByName; + [ObservableProperty] + private bool _memberListOrderByOrt; + + [ObservableProperty] + private string? _mgNrString; + public int? MgNr => int.TryParse(MgNrString, out var mgnr) ? mgnr : null; + [ObservableProperty] + private string? _predecessorMgNrString; + public int? PredecessorMgNr => int.TryParse(PredecessorMgNrString, out var mgnr) ? mgnr : null; + [ObservableProperty] + private string? _prefix; + [ObservableProperty] + private string? _givenName; + [ObservableProperty] + private string? _familyName; + [ObservableProperty] + private string? _suffix; + [ObservableProperty] + private string? _birthday; + [ObservableProperty] + private string? _age; + [ObservableProperty] + private bool _isDeceased; + [ObservableProperty] + private string? _address; + [ObservableProperty] + private string? _plzString; + public int? Plz => int.TryParse(PlzString, out var plz) ? plz : null; + [ObservableProperty] + private AT_PlzDest? _ort; + [ObservableProperty] + private IEnumerable _ortSource = []; + + [ObservableProperty] + private string? _billingName; + [ObservableProperty] + private string? _billingAddress; + [ObservableProperty] + private string? _billingPlzString; + public int? BillingPlz => int.TryParse(BillingPlzString, out var plz) ? plz : null; + [ObservableProperty] + private AT_PlzDest? _billingOrt; + [ObservableProperty] + private IEnumerable _billingOrtSource = []; + + [ObservableProperty] + private string? _iban; + [ObservableProperty] + private string? _bic; + + [ObservableProperty] + private string? _ustIdNr; + [ObservableProperty] + private string? _lfbisNr; + [ObservableProperty] + private bool _isBuchführend; + [ObservableProperty] + private bool _isOrganic; + + [ObservableProperty] + private string? _entryDate; + [ObservableProperty] + private string? _exitDate; + [ObservableProperty] + private string? _businessSharesString; + public int? BusinessShares => int.TryParse(BusinessSharesString, out var bs) ? bs : null; + [ObservableProperty] + private string? _accountingNr; + [ObservableProperty] + private bool _isActive; + [ObservableProperty] + private bool _isVollLieferant; + [ObservableProperty] + private bool _isFunktionär; + [ObservableProperty] + private Branch? _branch; + [ObservableProperty] + private IEnumerable _branchSource = []; + [ObservableProperty] + private AT_Kg? _defaultKg; + [ObservableProperty] + private IEnumerable _defaultKgSource = []; + [ObservableProperty] + private string? _comment; + [ObservableProperty] + private bool _contactViaPost; + [ObservableProperty] + private bool _contactViaEmail; + + public ObservableCollection EmailAddresses { get; private set; } = [null, null, null, null, null, null, null, null, null]; + + public partial class PhoneNr(int? type = null, string? number = null, string? comment = null) : ObservableObject { + [ObservableProperty] + public int _type = type ?? -1; + [ObservableProperty] + public string? _number = number; + [ObservableProperty] + public string? _comment = comment; + } + public ObservableCollection PhoneNrs { get; private set; } = [new(), new(), new(), new(), new(), new(), new(), new(), new()]; + + [ObservableProperty] + private string _statusMembers = "Mitglieder: -"; + [ObservableProperty] + private string _statusBusinessShares = "Geschäftsanteile: -"; + [ObservableProperty] + private string _statusDeliveriesLastSeason = "Lieferungen (letzte Saison): -"; + [ObservableProperty] + private string _statusDeliveriesThisSeason = "Lieferungen (aktuelle Saison): -"; + [ObservableProperty] + private string _statusAreaCommitment = "Gebundene Fläche: -"; + + [ObservableProperty] + private Visibility _controlButtonsVisibility = Visibility.Visible; + [ObservableProperty] + private Visibility _editingButtonsVisibility = Visibility.Hidden; + } +} diff --git a/Elwig/Windows/AdministrationWindow.cs b/Elwig/Windows/AdministrationWindow.cs index 1075f88..9532f50 100644 --- a/Elwig/Windows/AdministrationWindow.cs +++ b/Elwig/Windows/AdministrationWindow.cs @@ -162,9 +162,9 @@ namespace Elwig.Windows { if (input is TextBox tb && tb.Text.Length == 0) { ControlUtils.SetInputInvalid(input); Valid[input] = false; - } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null) { + } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast().Any()) { ControlUtils.SetInputInvalid(input); - } else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null) { + } else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast().Any()) { ControlUtils.SetInputInvalid(input); } else if (input is CheckBox ckb && ckb.IsChecked != true) { ControlUtils.SetInputInvalid(input); @@ -271,16 +271,28 @@ namespace Elwig.Windows { } protected void ClearInputs(bool validate = true) { - foreach (var tb in TextBoxInputs) + foreach (var tb in TextBoxInputs) { tb.Text = ""; - foreach (var cb in ComboBoxInputs) + var binding = tb.GetBindingExpression(TextBox.TextProperty); + binding?.UpdateSource(); + } + foreach (var cb in ComboBoxInputs) { cb.SelectedItem = null; + var binding = cb.GetBindingExpression(ComboBox.SelectedItemProperty); + binding?.UpdateSource(); + } foreach (var lb in ListBoxInputs) lb.SelectedItems.Clear(); - foreach (var cb in CheckBoxInputs) + foreach (var cb in CheckBoxInputs) { cb.IsChecked = cb.IsThreeState ? null : false; - foreach (var rb in RadioButtonInputs) + var binding = cb.GetBindingExpression(CheckBox.IsCheckedProperty); + binding?.UpdateSource(); + } + foreach (var rb in RadioButtonInputs) { rb.IsChecked = rb.IsThreeState ? null : false; + var binding = rb.GetBindingExpression(RadioButton.IsCheckedProperty); + binding?.UpdateSource(); + } if (validate) ValidateRequiredInputs(); } @@ -327,7 +339,7 @@ namespace Elwig.Windows { protected async Task UpdatePlz(TextBox plzInput, ComboBox ortInput) { var plzInputValid = Validator.CheckPlz(plzInput, RequiredInputs.Contains(plzInput)).IsValid; - List? list = null; + List list = []; if (plzInputValid && plzInput.Text.Length == 4) { var plz = int.Parse(plzInput.Text); using var ctx = new AppDbContext(); @@ -338,7 +350,7 @@ namespace Elwig.Windows { } ControlUtils.RenewItemsSource(ortInput, list); - if (list != null && ortInput.SelectedItem == null && list.Count == 1) + if (ortInput.SelectedItem == null && list.Count == 1) ortInput.SelectedItem = list[0]; UpdateComboBox(ortInput); } @@ -444,9 +456,9 @@ namespace Elwig.Windows { private void UpdateComboBox(Control input) { bool valid = false; if (input is ComboBox cb) { - valid = cb.ItemsSource == null || cb.SelectedItem != null || !RequiredInputs.Contains(input); + valid = cb.ItemsSource == null || cb.SelectedItem != null || !RequiredInputs.Contains(input) || !cb.ItemsSource.Cast().Any(); } else if (input is ListBox lb) { - valid = lb.ItemsSource == null || lb.SelectedItem != null || !RequiredInputs.Contains(input); + valid = lb.ItemsSource == null || lb.SelectedItem != null || !RequiredInputs.Contains(input) || !lb.ItemsSource.Cast().Any(); } if (valid) { ValidateInput(input, true); diff --git a/Elwig/Windows/MemberAdminWindow.xaml b/Elwig/Windows/MemberAdminWindow.xaml index d4f857b..efce3df 100644 --- a/Elwig/Windows/MemberAdminWindow.xaml +++ b/Elwig/Windows/MemberAdminWindow.xaml @@ -4,8 +4,12 @@ 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" Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150" Loaded="Window_Loaded"> + + +