From 034c0c4eb7335b693a2b8b194e5e6ec06719070d Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Wed, 9 Sep 2026 15:13:11 +0200 Subject: [PATCH] [WIP] certs --- Elwig/Documents/BusinessDocument.cs | 34 ++++-- Elwig/Documents/DeliveryConfirmation.cs | 6 +- Elwig/Helpers/AppDbUpdater.cs | 58 ++++++++- Elwig/Services/MemberService.cs | 62 +++++++++- Elwig/Services/OrganicService.cs | 143 +++++++++++++++++++++++ Elwig/ViewModels/MemberAdminViewModel.cs | 10 ++ Elwig/Windows/MemberAdminWindow.xaml | 5 + 7 files changed, 297 insertions(+), 21 deletions(-) create mode 100644 Elwig/Services/OrganicService.cs diff --git a/Elwig/Documents/BusinessDocument.cs b/Elwig/Documents/BusinessDocument.cs index c7ecdf4..295fb31 100644 --- a/Elwig/Documents/BusinessDocument.cs +++ b/Elwig/Documents/BusinessDocument.cs @@ -34,19 +34,23 @@ namespace Elwig.Documents { } } - protected Cell NewAsideCell(Paragraph text, int colspan = 1, bool isName = false) { - var cell = NewCell(text, colspan: colspan).SetPaddingsMM(0.25f, 0.5f, 0.25f, isName ? 1 : 0); + protected Cell NewAsideCell(Paragraph text, int colspan = 1, bool isName = false, bool right = false, bool center = false) { + var cell = NewCell(text, colspan: colspan).SetPaddingsMM(0.25f, center ? 0 : right ? 1 : 0.5f, 0.25f, isName ? 1 : 0); if (colspan == 26) { cell.SetTextAlignment(TextAlignment.CENTER).SetFont(BF) .SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)) .SetBorderTop(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness)) .SetPaddingsMM(0.5f, 1, 0.5f, 1); + } else if (isName) { + cell.SetBorderLeft(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness)); } + if (right) cell.SetTextAlignment(TextAlignment.RIGHT); + if (center) cell.SetTextAlignment(TextAlignment.CENTER); return cell; } - protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false) { - return NewAsideCell(new KernedParagraph(text, 10), colspan, isName); + protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false, bool right = false, bool center = false) { + return NewAsideCell(new KernedParagraph(text, 10), colspan, isName, right, center); } public BusinessDocument(string title, Member m, DateOnly? dateFrom, bool includeSender = false) : @@ -60,17 +64,27 @@ namespace Elwig.Documents { protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { base.BeforeRenderBody(doc, pdf); - var uid = new KernedParagraph(Member.UstIdNr ?? "-", 10); - if (!Member.IsBuchführend) uid.Add(Normal(" ")).Add(Italic("(pauschaliert)")); Aside = new Table(ColsMM(2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5)) .SetWidth(UnitValue.CreatePointValue(65 * PtInMM)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetFont(NF).SetFontSize(10) .SetBorder(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness)) - .AddCell(NewAsideCell("Mitglied", 26)) - .AddCell(NewAsideCell("Mitglieds-Nr.:", 9, isName: true)).AddCell(NewAsideCell($"{Member.MgNr}", 17)) - .AddCell(NewAsideCell("Betriebs-Nr.:", 9, isName: true)).AddCell(NewAsideCell((Member.LfbisNr ?? "-") + (Member.OrganicAuthorityCode != null ? $" ({Member.OrganicAuthorityCode})" : ""), 17)) - .AddCell(NewAsideCell("UID:", 9, isName: true)).AddCell(NewAsideCell(uid, 17)); + .AddCell(NewAsideCell("Mitglied", 26)); + if (Member.IsOrganic) { + Aside + .AddCell(NewAsideCell("Mitgl.-Nr.:", 8, isName: true)).AddCell(NewAsideCell($"{Member.MgNr}", 4, right: true)) + .AddCell(NewAsideCell("Bio-KSt.:", 6, isName: true)).AddCell(NewAsideCell(Member.OrganicAuthorityCode ?? "-", 8, right: true, center: Member.OrganicAuthorityCode == null)) + .AddCell(NewAsideCell("Betr.-Nr.:", 6, isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "-", 6, right: true, center: Member.LfbisNr == null)) + .AddCell(NewAsideCell("OOC-ID:", 6, isName: true)).AddCell(NewAsideCell(Member.OrganicOperatorId ?? "-", 8, right: true, center: Member.OrganicOperatorId == null)) + .AddCell(NewAsideCell("UID:", 6, isName: true)).AddCell(NewAsideCell(Member.UstIdNr ?? "-", 10, right: true, center: Member.UstIdNr == null)) + .AddCell(NewAsideCell(new KernedParagraph("", 10).Add(Italic(Member.IsBuchführend ? "(buchführend)" : "(pauschaliert)")), 10, center: true)); + } else { + Aside + .AddCell(NewAsideCell("Mitglieds-Nr.:", 12, isName: true)).AddCell(NewAsideCell($"{Member.MgNr}", 4, right: true)).AddCell(NewAsideCell("", 10)) + .AddCell(NewAsideCell("Betriebs-Nr.:", 10, isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "-", 6, right: true, center: Member.LfbisNr == null)).AddCell(NewAsideCell("", 10)) + .AddCell(NewAsideCell("UID:", 6, isName: true)).AddCell(NewAsideCell(Member.UstIdNr ?? "-", 10, right: true, center: Member.UstIdNr == null)) + .AddCell(NewAsideCell(new KernedParagraph("", 10).Add(Italic(Member.IsBuchführend ? "(buchführend)" : "(pauschaliert)")), 10, center: true)); + } } protected void RenderAddress(Canvas canvas, Rectangle pageSize) { diff --git a/Elwig/Documents/DeliveryConfirmation.cs b/Elwig/Documents/DeliveryConfirmation.cs index 89d4cb1..95e4f9e 100644 --- a/Elwig/Documents/DeliveryConfirmation.cs +++ b/Elwig/Documents/DeliveryConfirmation.cs @@ -56,9 +56,9 @@ namespace Elwig.Documents { var firstDay = Data.Rows.MinBy(r => r.Date)?.Date; var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date; Aside?.AddCell(NewAsideCell("Saison", 26)) - .AddCell(NewAsideCell("Lieferungen:", 9, isName: true)).AddCell(NewAsideCell($"{Data.Rows.DistinctBy(r => r.LsNr).Count():N0} (Teil-Lfrg.: {Data.RowNum:N0})", 17)) - .AddCell(NewAsideCell("Zeitraum:", 9, isName: true)).AddCell(NewAsideCell(firstDay == null || lastDay == null ? "-" : firstDay == lastDay ? $"{firstDay:dd.MM.} (1 Tag)" : $"{firstDay:dd.MM.}\u2013{lastDay:dd.MM.} ({lastDay?.DayNumber - firstDay?.DayNumber + 1:N0} Tage)", 17)) - .AddCell(NewAsideCell("Menge:", 9, isName: true)).AddCell(NewAsideCell($"{MemberStats.Sum(s => s.Weight):N0} kg", 17)); + .AddCell(NewAsideCell("Lieferungen:", 12, isName: true)).AddCell(NewAsideCell($"{Data.Rows.DistinctBy(r => r.LsNr).Count():N0} (Teil-Lfrg.: {Data.RowNum:N0})", 14)) + .AddCell(NewAsideCell("Zeitraum:", 8, isName: true)).AddCell(NewAsideCell(firstDay == null || lastDay == null ? "-" : firstDay == lastDay ? $"{firstDay:dd.MM.} (1 Tag)" : $"{firstDay:dd.MM.}\u2013{lastDay:dd.MM.} ({lastDay?.DayNumber - firstDay?.DayNumber + 1:N0} Tage)", 18, center: true)) + .AddCell(NewAsideCell("Menge:", 12, isName: true)).AddCell(NewAsideCell($"{MemberStats.Sum(s => s.Weight):N0} kg", 14)); } protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { diff --git a/Elwig/Helpers/AppDbUpdater.cs b/Elwig/Helpers/AppDbUpdater.cs index f1c79ee..cdecd57 100644 --- a/Elwig/Helpers/AppDbUpdater.cs +++ b/Elwig/Helpers/AppDbUpdater.cs @@ -1,3 +1,5 @@ +using Elwig.Services; +using Microsoft.Data.Sqlite; using System; using System.Collections.Generic; using System.IO; @@ -68,11 +70,11 @@ namespace Elwig.Helpers { }) .OrderBy(s => s.Item1).ThenBy(s => s.Item2)]; - List toExecute = []; + List<(int ToVersion, string File)> toExecute = []; var vers = fromVersion; while (vers < toVersion) { var (_, to, name) = scripts.Last(s => s.From == vers); - toExecute.Add(name); + toExecute.Add((to, name)); vers = to; } if (toExecute.Count == 0) @@ -87,8 +89,11 @@ namespace Elwig.Helpers { await cnx.IntegrityCheck(); await cnx.ForeignKeyCheck(); - foreach (var script in toExecute) { + foreach (var (to, script) in toExecute) { await cnx.ExecuteEmbeddedScript(asm, script); + if (to == 42) { + await UpdateDbSchema_41_To_42(cnx); + } } await cnx.IntegrityCheck(); @@ -103,5 +108,52 @@ namespace Elwig.Helpers { File.Delete(backup); } } + + private static async Task UpdateDbSchema_41_To_42(SqliteConnection cnx) { + List<(int MgNr, string LfbisNr, string Name, string Address, int Plz)> members = []; + using (var cmd = cnx.CreateCommand()) { + cmd.CommandText = """ + SELECT m.mgnr, m.lfbis_nr, + COALESCE(a.name, (COALESCE(m.prefix || ' ', '') || COALESCE(m.given_name || ' ', '') || m.name || COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, ''))), + COALESCE(a.address, m.address), + COALESCE(aplz.plz, mplz.plz) + FROM member m + JOIN postal_dest mp ON (mp.country, mp.id) = (m.country, m.postal_dest) + JOIN AT_plz_dest ma ON (ma.country, ma.id) = (mp.country, mp.id) + JOIN AT_plz mplz ON mplz.plz = ma.plz + LEFT JOIN member_billing_address a ON a.mgnr = m.mgnr + LEFT JOIN postal_dest ap ON (ap.country, ap.id) = (a.country, a.postal_dest) + LEFT JOIN AT_plz_dest aa ON (aa.country, aa.id) = (ap.country, mp.id) + LEFT JOIN AT_plz aplz ON aplz.plz = aa.plz + WHERE organic + """; + using var reader = await cmd.ExecuteReaderAsync(); + var header = await reader.GetColumnSchemaAsync(); + while (await reader.ReadAsync()) { + members.Add((reader.GetInt32(0), reader.GetString(1), reader.GetString(2), reader.GetString(3), reader.GetInt32(4))); + } + } + + if (members.Count > 0) + InteractionService.ShowInformation("Datenbank-Update", "Es wird versucht, die Bio-Kontrollstellen-Informationen der Mitglieder automatisch zu laden. Dies könnte einen kurzen Moment in Anspruch nehmen."); + + foreach (var m in members) { + string? oocId = null; + string? authCode = null; + + var certs = await OrganicService.FetchTracesCertificates(null, m.LfbisNr, (m.Name, m.Address, m.Plz.ToString())); + + if (certs.Length > 0) { + oocId = certs[0].OperatorId; + authCode = certs[0].AuthorityCode; + } + + if (oocId != null || authCode != null) { + oocId = oocId == null ? "NULL" : $"'{oocId}'"; + authCode = authCode == null ? "NULL" : $"'{authCode}'"; + await cnx.ExecuteBatch($"UPDATE member SET ooc_id = {oocId}, org_auth_code = {authCode} WHERE mgnr = {m.MgNr}"); + } + } + } } } diff --git a/Elwig/Services/MemberService.cs b/Elwig/Services/MemberService.cs index 13155f5..e51495a 100644 --- a/Elwig/Services/MemberService.cs +++ b/Elwig/Services/MemberService.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Windows.Media; namespace Elwig.Services { public static class MemberService { @@ -189,10 +190,9 @@ namespace Elwig.Services { .GroupBy(d => d.Year) .ToDictionaryAsync(g => g.Key, g => g.Any()); - if (m.MgNr != vm.MgNr) - return; - await App.MainDispatcher.BeginInvoke(() => { + if (m.MgNr != vm.MgNr) + return; var (d1Grid, _) = DeliveryService.GenerateToolTip(d1GridData, []); var (d2Grid, _) = DeliveryService.GenerateToolTip(d2GridData, []); var grid = AreaComService.GenerateToolTip(gridData); @@ -205,13 +205,65 @@ namespace Elwig.Services { vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}"; vm.StatusAreaCommitment = text; vm.StatusAreaCommitmentToolTip = grid; - vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => deliveries.GetValueOrDefault(i, false)).ToList(); + vm.MemberHasDeliveries = [.. Enumerable.Range(0, 9999).Select(i => deliveries.GetValueOrDefault(i, false))]; + }); + }); + + vm.OrganicTest1 = null; + vm.OrganicTest2 = "..."; + vm.OrganicTest3 = Brushes.Black; + vm.OrganicTest4 = null; + Utils.RunBackground("Bio-Zertifikatsdaten laden", async () => { + if (App.MainDispatcher == null || !m.IsOrganic) + return; + + var certs = await OrganicService.GetTracesCertificates(m); + + // TODO button for TRACES search url + // TODO button for authority search url + + await App.MainDispatcher.BeginInvoke(() => { + if (m.MgNr != vm.MgNr) + return; + vm.OrganicTest2 = null; + if (certs.Length == 0) { + vm.OrganicTest1 = "Nicht gefunden"; + vm.OrganicTest3 = Brushes.DarkRed; + } else { + var cert = certs[0]; + vm.OrganicTest4 = $"Zertifikat-Nr.: {cert.Id}\nBio-KSt.: {cert.AuthorityCode}\nEU Operator-ID: {cert.OperatorId}\nOperator: {cert.OperatorName}"; + if (cert.IsValidForWineProduction) { + vm.OrganicTest1 = "Gültig"; + vm.OrganicTest2 = $" (bis {cert.ExpiresOn:dd.MM.yyyy})"; + vm.OrganicTest3 = Brushes.DarkGreen; + } else if (cert.IsValid) { + vm.OrganicTest1 = "Teilw. gültig"; + vm.OrganicTest2 = $" (bis {cert.ExpiresOn:dd.MM.yyyy})"; + vm.OrganicTest3 = Brushes.OrangeRed; + } else { + vm.OrganicTest3 = Brushes.DarkRed; + switch (cert.Status) { + case OrganicService.TracesStatus.ISSUED: + vm.OrganicTest1 = "Ausgestellt"; + vm.OrganicTest2 = $" (am {cert.LastStatusUpdateOn:dd.MM.yyyy})"; break; + case OrganicService.TracesStatus.SUSPENDED: + vm.OrganicTest1 = "Gesperrt"; + vm.OrganicTest2 = $" (am {cert.LastStatusUpdateOn:dd.MM.yyyy})"; break; + case OrganicService.TracesStatus.EXPIRED: + vm.OrganicTest1 = "Ausgelaufen"; + vm.OrganicTest2 = $" (am {cert.LastStatusUpdateOn:dd.MM.yyyy})"; break; + case OrganicService.TracesStatus.WITHDRAWN: + vm.OrganicTest1 = "Widerrufen"; + vm.OrganicTest2 = $" (am {cert.LastStatusUpdateOn:dd.MM.yyyy})"; break; + } + } + } }); }); vm.MemberHasEmail = m.EmailAddresses.Count > 0; vm.MemberCanSendEmail = App.Config.Smtp != null && m.EmailAddresses.Count > 0; - vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => false).ToList(); + vm.MemberHasDeliveries = [.. Enumerable.Range(0, 9999).Select(i => false)]; } public static async Task<(List, IQueryable, List)> GetFilters(this MemberAdminViewModel vm, AppDbContext ctx) { diff --git a/Elwig/Services/OrganicService.cs b/Elwig/Services/OrganicService.cs new file mode 100644 index 0000000..d81dbd2 --- /dev/null +++ b/Elwig/Services/OrganicService.cs @@ -0,0 +1,143 @@ +using Elwig.Helpers; +using Elwig.Models.Entities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using System.Web; + +namespace Elwig.Services { + public static class OrganicService { + + public const string TRACES_API_URL = "https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/for/query"; + public const string TRACES_PDF_URL_PREFIX = "https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/digitally-signed/"; + + public enum TracesStatus { + ISSUED, SUSPENDED, WITHDRAWN, EXPIRED + } + + public enum TracesActivity { + PREPARATION, EXPORT, IMPORT, STORING, PRODUCTION, DISTRIBUTION, DISTRIBUTION_PLACING_ON_THE_MARKET + } + + public enum TracesCategoryOfProduct { + UNPROCESSED_PLANT_PRODUCTS_INCLUDING_SEEDS, LIVESTOCK_AND_UNPROCESSED_LIVESTOCK_PRODUCTS, ALGAE_AND_UNPROCESSED_AQUACULTURE_PRODUCTS, + PROCESSED_AGRICULTURAL_PRODUCTS_INCLUDING_AQUACULTURE_FOR_USE_AS_FOOD, FEED, WINE, OTHER_PRODUCTS_NOT_COVERED_BY_PREVIOUS_CATEGORIES + } + + public record TracesCertificate { + public required string Id { get; init; } + public required string AuthorityCode { get; init; } + public required string OperatorId { get; init; } + public required string OperatorName { get; init; } + public required TracesActivity[] Activities { get; init; } + public required TracesCategoryOfProduct[] CategoriesOfProduct { get; init; } + public required DateOnly IssuedOn { get; init; } + public required DateOnly ExpiresOn { get; init; } + public required DateOnly LastStatusUpdateOn { get; init; } + public required TracesStatus Status { get; init; } + + public string PdfUrl => TRACES_PDF_URL_PREFIX + Id + ".pdf"; + public bool IsValid => Status == TracesStatus.ISSUED && IssuedOn.ToDateTime(new()) <= DateTime.Today && ExpiresOn.ToDateTime(new()) >= DateTime.Today; + public bool IsValidForWineProduction => IsValid && Activities.Contains(TracesActivity.PRODUCTION) && + (CategoriesOfProduct.Contains(TracesCategoryOfProduct.UNPROCESSED_PLANT_PRODUCTS_INCLUDING_SEEDS) || CategoriesOfProduct.Contains(TracesCategoryOfProduct.WINE)); + } + + public async static Task GetTracesCertificates(Member m, bool? tryNameAndAddress = null) { + // TODO cache + return await FetchTracesCertificates(m, tryNameAndAddress); + } + + private async static Task TryFetchTracesCertificates(Dictionary query) { + var q = HttpUtility.ParseQueryString(""); + q.Add("sort", "-issuedOn"); + q.Add("countryCode", "AT"); + foreach (var (k, v) in query) { + q.Add(k, v); + } + + using var client = Utils.GetHttpClient(); + using var res = await client.GetAsync($"{TRACES_API_URL}?{q}"); + res.EnsureSuccessStatusCode(); + var resJson = JsonNode.Parse(await res.Content.ReadAsStringAsync()); + var certs = resJson?.AsArray() ?? throw new Exception(); + if (certs.Count == 0) + throw new Exception(); + return certs; + } + + public static Task FetchTracesCertificates(Member m, bool? tryNameAndAddress = null) { + return FetchTracesCertificates(m.OrganicOperatorId, m.LfbisNr, + (tryNameAndAddress ?? m.IsOrganic) ? m.BillingAddress is BillingAddr a ? + (a.FullName, a.Address, $"{a.PostalDest.AtPlz?.Plz}") : + (m.FullName, m.Address, $"{m.PostalDest.AtPlz?.Plz}") : + null); + } + + public async static Task FetchTracesCertificates(string? oocId, string? lfbisNr, (string Name, string Address, string PostalCode)? address) { + bool searchedByName = false; + + JsonArray? jsonCerts = null; + if (oocId != null) { + try { + jsonCerts = await TryFetchTracesCertificates(new Dictionary { + { "operatorIdentifierType", "ooc_identifier" }, + { "operatorIdentifierSearchOperator", "STRICT" }, + { "operatorIdentifier", oocId }, + }); + } catch { + jsonCerts = null; + } + } + + if (jsonCerts == null && lfbisNr != null) { + try { + jsonCerts = await TryFetchTracesCertificates(new Dictionary { + { "operatorIdentifierType", "comp_reg" }, + { "operatorIdentifierSearchOperator", "STRICT" }, + { "operatorIdentifier", lfbisNr.TrimStart('0') }, + }); + } catch { + jsonCerts = null; + } + } + + if (jsonCerts == null && address != null) { + try { + jsonCerts = await TryFetchTracesCertificates(new Dictionary { + { "operatorPostalCode", address.Value.PostalCode }, + { "query", $"{address.Value.Name} {address.Value.Address}" }, + }); + searchedByName = true; + } catch { + jsonCerts = null; + } + } + + if (jsonCerts == null) + return []; + + TracesCertificate[] certs = []; + try { + certs = [.. jsonCerts.Select(j => new TracesCertificate { + Id = j!["reference"]?.GetValue() ?? throw new Exception(), + AuthorityCode = j["issuingBody"]?["code"]?.GetValue() ?? throw new Exception(), + OperatorId = j["operatorIdentifier"]?.GetValue() ?? throw new Exception(), + OperatorName = j["operator"]?["name"]?.GetValue() ?? throw new Exception(), + Activities = [.. j["activities"]?.AsArray().Select(a => Enum.Parse(a?["id"]?.GetValue().ToUpper().Replace("_IMPORT", "IMPORT") ?? throw new Exception())) ?? []], + CategoriesOfProduct = [.. j["categoriesOfProduct"]?.AsArray().Select(c => Enum.Parse(c?["id"]?.GetValue() ?? throw new Exception())) ?? []], + IssuedOn = DateOnly.FromDateTime(DateTime.ParseExact(j["issuedOn"]?.GetValue() ?? throw new Exception(), "yyyy-MM-ddTHH:mm:ss.fffK", CultureInfo.InvariantCulture, DateTimeStyles.None)), + ExpiresOn = DateOnly.ParseExact(j["expiresOn"]?.GetValue() ?? throw new Exception(), "yyyy-MM-dd"), + LastStatusUpdateOn = DateOnly.FromDateTime(DateTime.ParseExact(j["lastStatusUpdateDateTime"]?.GetValue() ?? throw new Exception(), "yyyy-MM-ddTHH:mm:ss.fffK", CultureInfo.InvariantCulture, DateTimeStyles.None)), + Status = Enum.Parse(j["status"]?["id"]?.GetValue() ?? throw new Exception()), + })]; + } catch { + return []; + } + + return (!searchedByName || certs.Where(c => c.Status == TracesStatus.ISSUED).Select(c => c.OperatorId).ToHashSet().Count == 1) ? certs : []; + } + } +} diff --git a/Elwig/ViewModels/MemberAdminViewModel.cs b/Elwig/ViewModels/MemberAdminViewModel.cs index 6fa4555..1970fb3 100644 --- a/Elwig/ViewModels/MemberAdminViewModel.cs +++ b/Elwig/ViewModels/MemberAdminViewModel.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Media; namespace Elwig.ViewModels { public partial class MemberAdminViewModel : ObservableObject { @@ -126,6 +127,15 @@ namespace Elwig.ViewModels { [ObservableProperty] private bool _isOrganic; + [ObservableProperty] + private string? _organicTest1; + [ObservableProperty] + private string? _organicTest2; + [ObservableProperty] + private Brush? _organicTest3; + [ObservableProperty] + private string? _organicTest4; + [ObservableProperty] private string? _entryDate; [ObservableProperty] diff --git a/Elwig/Windows/MemberAdminWindow.xaml b/Elwig/Windows/MemberAdminWindow.xaml index b07ea4a..e87892d 100644 --- a/Elwig/Windows/MemberAdminWindow.xaml +++ b/Elwig/Windows/MemberAdminWindow.xaml @@ -581,6 +581,11 @@ + +