[#67] Elwig: Add OrganicService
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
using Elwig.Services;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
public static class AppDbUpdater {
|
||||
@@ -68,11 +71,11 @@ namespace Elwig.Helpers {
|
||||
})
|
||||
.OrderBy(s => s.Item1).ThenBy(s => s.Item2)];
|
||||
|
||||
List<string> 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 +90,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 +109,57 @@ 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 abzurufen. Dies könnte einen kurzen Moment dauern.");
|
||||
|
||||
try {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
foreach (var m in members) {
|
||||
string? oocId = null;
|
||||
string? authCode = null;
|
||||
|
||||
var certs = await OrganicService.FetchTracesCertificates(lfbisNr: m.LfbisNr, address: (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}");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -19,6 +20,7 @@ namespace Elwig.Services {
|
||||
};
|
||||
|
||||
public static async Task InitInputs(this MemberAdminViewModel vm) {
|
||||
vm.IsMemberSelectedOrCreating = true;
|
||||
using var ctx = new AppDbContext();
|
||||
vm.MgNr = await ctx.NextMgNr();
|
||||
vm.EntryDate = DateTime.Now.ToString("dd.MM.yyyy");
|
||||
@@ -31,6 +33,7 @@ namespace Elwig.Services {
|
||||
|
||||
public static void ClearInputs(this MemberAdminViewModel vm) {
|
||||
vm.IsMemberSelected = false;
|
||||
vm.IsMemberSelectedOrCreating = false;
|
||||
vm.MemberHasEmail = false;
|
||||
vm.MemberCanSendEmail = false;
|
||||
vm.EnableMemberReferenceButton = false;
|
||||
@@ -49,10 +52,15 @@ namespace Elwig.Services {
|
||||
vm.ModifiedAtShort = "-";
|
||||
vm.ExportedAt = "-";
|
||||
vm.ImportedAt = "-";
|
||||
vm.OrganicState = null;
|
||||
vm.OrganicColor = Brushes.Black;
|
||||
vm.OrganicDetails = "-";
|
||||
vm.OrganicToolTip = null;
|
||||
}
|
||||
|
||||
public static void FillInputs(this MemberAdminViewModel vm, Member m) {
|
||||
vm.IsMemberSelected = true;
|
||||
vm.IsMemberSelectedOrCreating = true;
|
||||
vm.MgNr = m.MgNr;
|
||||
vm.PredecessorMgNr = m.PredecessorMgNr;
|
||||
vm.IsJuridicalPerson = m.IsJuridicalPerson;
|
||||
@@ -189,10 +197,9 @@ namespace Elwig.Services {
|
||||
.GroupBy(d => d.Year)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.Any());
|
||||
|
||||
await App.MainDispatcher.BeginInvoke(() => {
|
||||
if (m.MgNr != vm.MgNr)
|
||||
return;
|
||||
|
||||
await App.MainDispatcher.BeginInvoke(() => {
|
||||
var (d1Grid, _) = DeliveryService.GenerateToolTip(d1GridData, []);
|
||||
var (d2Grid, _) = DeliveryService.GenerateToolTip(d2GridData, []);
|
||||
var grid = AreaComService.GenerateToolTip(gridData);
|
||||
@@ -205,13 +212,76 @@ 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.OrganicState = null;
|
||||
vm.OrganicColor = Brushes.Black;
|
||||
vm.OrganicToolTip = null;
|
||||
if (!vm.IsOrganic) {
|
||||
vm.OrganicDetails = "-";
|
||||
} else {
|
||||
vm.OrganicDetails = "...";
|
||||
Utils.RunBackground("Bio-Zertifikatsdaten laden", async () => {
|
||||
if (App.MainDispatcher == null) return;
|
||||
var certs = await OrganicService.GetTracesCertificates(m);
|
||||
await App.MainDispatcher.BeginInvoke(() => {
|
||||
if (m.MgNr != vm.MgNr) return;
|
||||
vm.FillOrganicInputs(certs.Length > 0 ? certs[0] : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 void FillOrganicInputs(this MemberAdminViewModel vm, OrganicService.TracesCertificate? cert) {
|
||||
vm.OrganicDetails = null;
|
||||
if (cert == null) {
|
||||
vm.OrganicState = "Nicht gefunden";
|
||||
vm.OrganicColor = Brushes.DarkRed;
|
||||
vm.OrganicToolTip = null;
|
||||
return;
|
||||
}
|
||||
|
||||
vm.OrganicToolTip = $"""
|
||||
Zertifikat-Nr.: {cert.Id}
|
||||
Status: {cert.Status}
|
||||
Bio-KSt.: {cert.AuthorityCode}
|
||||
EU Operator-ID: {cert.OperatorId}
|
||||
Operator: {cert.OperatorName}
|
||||
Ausgestellt am: {cert.IssuedOn:dd.MM.yyyy}
|
||||
Aktualisiert am: {cert.LastStatusUpdateOn:dd.MM.yyyy}
|
||||
Erlischt am: {cert.ExpiresOn:dd.MM.yyyy}
|
||||
""";
|
||||
if (cert.IsValidForWineProduction) {
|
||||
vm.OrganicState = "Gültig";
|
||||
vm.OrganicDetails = $" ({cert.ExpiresOn:MMM. yyyy})";
|
||||
vm.OrganicColor = Brushes.DarkGreen;
|
||||
} else if (cert.IsValid) {
|
||||
vm.OrganicState = "Gültig";
|
||||
vm.OrganicDetails = $" ({cert.ExpiresOn:MMM. yyyy})";
|
||||
vm.OrganicColor = Brushes.OrangeRed;
|
||||
} else {
|
||||
vm.OrganicColor = Brushes.DarkRed;
|
||||
switch (cert.Status) {
|
||||
case OrganicService.TracesStatus.ISSUED:
|
||||
vm.OrganicState = "Ausgestellt";
|
||||
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
|
||||
case OrganicService.TracesStatus.SUSPENDED:
|
||||
vm.OrganicState = "Gesperrt";
|
||||
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
|
||||
case OrganicService.TracesStatus.EXPIRED:
|
||||
vm.OrganicState = "Ausgelaufen";
|
||||
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
|
||||
case OrganicService.TracesStatus.WITHDRAWN:
|
||||
vm.OrganicState = "Widerrufen";
|
||||
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<(List<string>, IQueryable<Member>, List<string>)> GetFilters(this MemberAdminViewModel vm, AppDbContext ctx) {
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
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));
|
||||
}
|
||||
|
||||
private static readonly Dictionary<int, (DateTime Timestamp, TracesCertificate[] Certificates)> _memberCerts = [];
|
||||
|
||||
public async static Task<TracesCertificate[]> GetTracesCertificates(Member m, bool? tryNameAndAddress = null) {
|
||||
if (_memberCerts.TryGetValue(m.MgNr, out var entry) && entry.Timestamp >= DateTime.Now.AddDays(-1))
|
||||
return entry.Certificates;
|
||||
var certs = await FetchTracesCertificates(m, tryNameAndAddress);
|
||||
_memberCerts[m.MgNr] = (DateTime.Now, certs);
|
||||
return certs;
|
||||
}
|
||||
|
||||
private async static Task<JsonArray> TryFetchTracesCertificates(Dictionary<string, string> 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());
|
||||
return resJson?.AsArray() ?? throw new JsonException();
|
||||
}
|
||||
|
||||
public static Task<TracesCertificate[]> 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<TracesCertificate[]> FetchTracesCertificates(string? oocId = null, string? lfbisNr = null, (string Name, string Address, string PostalCode)? address = null) {
|
||||
bool searchedByName = false;
|
||||
JsonArray jsonCerts = [];
|
||||
|
||||
if (jsonCerts.Count == 0 && oocId != null) {
|
||||
jsonCerts = await TryFetchTracesCertificates(new Dictionary<string, string> {
|
||||
{ "operatorIdentifierType", "ooc_identifier" },
|
||||
{ "operatorIdentifierSearchOperator", "STRICT" },
|
||||
{ "operatorIdentifier", oocId },
|
||||
});
|
||||
}
|
||||
|
||||
if (jsonCerts.Count == 0 && lfbisNr != null) {
|
||||
jsonCerts = await TryFetchTracesCertificates(new Dictionary<string, string> {
|
||||
{ "operatorIdentifierType", "comp_reg" },
|
||||
{ "operatorIdentifierSearchOperator", "STRICT" },
|
||||
{ "operatorIdentifier", lfbisNr.TrimStart('0') },
|
||||
});
|
||||
}
|
||||
|
||||
if (jsonCerts.Count == 0 && address != null) {
|
||||
var query = $"{Regex.Replace(address.Value.Name, @"\(.*?\)", "")} {address.Value.Address}";
|
||||
jsonCerts = await TryFetchTracesCertificates(new Dictionary<string, string> {
|
||||
{ "operatorPostalCode", address.Value.PostalCode },
|
||||
{ "query", query.Length > 40 ? query[..query.LastIndexOf(' ', 40)] : query },
|
||||
});
|
||||
searchedByName = true;
|
||||
}
|
||||
|
||||
if (jsonCerts.Count == 0)
|
||||
return [];
|
||||
|
||||
TracesCertificate[] certs = [.. jsonCerts.Select(j => new TracesCertificate {
|
||||
Id = j!["reference"]?.GetValue<string>() ?? throw new Exception(),
|
||||
AuthorityCode = j["issuingBody"]?["code"]?.GetValue<string>() ?? throw new Exception(),
|
||||
OperatorId = j["operatorIdentifier"]?.GetValue<string>() ?? throw new Exception(),
|
||||
OperatorName = j["operator"]?["name"]?.GetValue<string>() ?? throw new Exception(),
|
||||
Activities = [.. j["activities"]?.AsArray().Select(a => Enum.Parse<TracesActivity>(a?["id"]?.GetValue<string>().ToUpper().Replace("_IMPORT", "IMPORT") ?? throw new Exception())) ?? []],
|
||||
CategoriesOfProduct = [.. j["categoriesOfProduct"]?.AsArray().Select(c => Enum.Parse<TracesCategoryOfProduct>(c?["id"]?.GetValue<string>() ?? throw new Exception())) ?? []],
|
||||
IssuedOn = DateOnly.FromDateTime(DateTime.ParseExact(j["issuedOn"]?.GetValue<string>() ?? throw new Exception(), "yyyy-MM-ddTHH:mm:ss.fffK", CultureInfo.InvariantCulture, DateTimeStyles.None)),
|
||||
ExpiresOn = DateOnly.ParseExact(j["expiresOn"]?.GetValue<string>() ?? throw new Exception(), "yyyy-MM-dd"),
|
||||
LastStatusUpdateOn = DateOnly.FromDateTime(DateTime.ParseExact(j["lastStatusUpdateDateTime"]?.GetValue<string>() ?? throw new Exception(), "yyyy-MM-ddTHH:mm:ss.fffK", CultureInfo.InvariantCulture, DateTimeStyles.None)),
|
||||
Status = Enum.Parse<TracesStatus>(j["status"]?["id"]?.GetValue<string>() ?? throw new Exception()),
|
||||
})];
|
||||
|
||||
return (!searchedByName || certs.Where(c => c.Status == TracesStatus.ISSUED).Select(c => c.OperatorId).ToHashSet().Count == 1) ? certs : [];
|
||||
}
|
||||
|
||||
public static string? GetTracesSearchUrl(Member m, bool? tryNameAndAddress = null) {
|
||||
return GetTracesSearchUrl(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 static string? GetTracesSearchUrl(string? oocId = null, string? lfbisNr = null, (string Name, string Address, string PostalCode)? address = null) {
|
||||
var baseUrl = "https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/index";
|
||||
if (oocId != null) {
|
||||
return $"{baseUrl}#!?countryCode=AT&operatorIdentifierType=ooc_identifier&operatorIdentifierSearchOperator=STRICT&operatorIdentifier={oocId}&states=ISSUED";
|
||||
} else if (lfbisNr != null) {
|
||||
return $"{baseUrl}#!?countryCode=AT&operatorIdentifierType=comp_reg&operatorIdentifierSearchOperator=STRICT&operatorIdentifier={lfbisNr.TrimStart('0')}&states=ISSUED";
|
||||
} else if (address != null) {
|
||||
return $"{baseUrl}#!?countryCode=AT&operatorPostalCode={address?.PostalCode}&query={address?.Name.Replace(' ', '+')}+{address?.Address.Replace(' ', '+')}&states=ISSUED";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? GetEasyCertSearchUrl(string lfbisNr) {
|
||||
var baseUrl = "https://www.easy-cert.com/htm/suchergebnis.htm";
|
||||
return $"{baseUrl}?CountryCode=AT&CustomerNumber={lfbisNr.TrimStart('0')}";
|
||||
}
|
||||
|
||||
public static string? GetBiocSearchUrl(string lfbisNr) {
|
||||
var baseUrl = "https://www.bioc.info/search/producersearchresult";
|
||||
return $"{baseUrl}?producerSearch[country]=a1e51f85-27b5-a1e3-65f7-561569884d38&producerSearch[operatorId]={lfbisNr.TrimStart('0')}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -33,6 +34,8 @@ namespace Elwig.ViewModels {
|
||||
[ObservableProperty]
|
||||
private bool _isMemberSelected;
|
||||
[ObservableProperty]
|
||||
private bool _isMemberSelectedOrCreating;
|
||||
[ObservableProperty]
|
||||
private bool _memberHasEmail;
|
||||
[ObservableProperty]
|
||||
private bool _memberCanSendEmail;
|
||||
@@ -126,6 +129,15 @@ namespace Elwig.ViewModels {
|
||||
[ObservableProperty]
|
||||
private bool _isOrganic;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _organicState;
|
||||
[ObservableProperty]
|
||||
private string? _organicDetails = "-";
|
||||
[ObservableProperty]
|
||||
private Brush? _organicColor = Brushes.Black;
|
||||
[ObservableProperty]
|
||||
private string? _organicToolTip;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _entryDate;
|
||||
[ObservableProperty]
|
||||
|
||||
@@ -566,7 +566,7 @@
|
||||
TextChanged="UstIdNrInput_TextChanged" LostFocus="UstIdNrInput_LostFocus"/>
|
||||
|
||||
<CheckBox x:Name="OrganicInput" Content="Bio" IsChecked="{Binding IsOrganic, Mode=TwoWay}" IsEnabled="False"
|
||||
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
|
||||
Checked="OrganicInput_Changed" Unchecked="OrganicInput_Changed"
|
||||
Grid.Column="2" HorizontalAlignment="Left" Margin="5,15,0,0" VerticalAlignment="Top"/>
|
||||
|
||||
<CheckBox x:Name="BuchführendInput" Content="Buchführend" IsChecked="{Binding IsBuchführend, Mode=TwoWay}" IsEnabled="False"
|
||||
@@ -620,7 +620,8 @@
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition Width="90"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
@@ -657,46 +658,64 @@
|
||||
|
||||
<CheckBox x:Name="ActiveInput" Content="Aktiv" IsChecked="{Binding IsActive, Mode=TwoWay}" IsEnabled="False"
|
||||
Checked="ActiveInput_Changed" Unchecked="ActiveInput_Changed"
|
||||
Grid.Column="2" HorizontalAlignment="Left" Margin="10,15,0,0" VerticalAlignment="Top"/>
|
||||
Grid.Column="2" HorizontalAlignment="Left" Margin="0,15,0,0" VerticalAlignment="Top"/>
|
||||
|
||||
<CheckBox x:Name="VollLieferantInput" Content="Volllieferant" IsChecked="{Binding IsVollLieferant, Mode=TwoWay}" IsEnabled="False"
|
||||
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
|
||||
Grid.Column="2" HorizontalAlignment="Left" Margin="10,45,0,0" VerticalAlignment="Top"/>
|
||||
Grid.Column="2" HorizontalAlignment="Left" Margin="0,45,0,0" VerticalAlignment="Top"/>
|
||||
|
||||
<CheckBox x:Name="FunkionärInput" Content="Funktionär" IsChecked="{Binding IsFunktionär, Mode=TwoWay}" IsEnabled="False"
|
||||
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
|
||||
Grid.Column="2" HorizontalAlignment="Left" Margin="10,75,0,0" VerticalAlignment="Top"/>
|
||||
Grid.Column="2" HorizontalAlignment="Left" Margin="0,75,0,0" VerticalAlignment="Top"/>
|
||||
|
||||
<Label Content="Bio-Zertifikate:" Margin="0,-2.5,20,0" Grid.Column="3" HorizontalAlignment="Right"/>
|
||||
<Button x:Name="OrganicTracesButton" IsEnabled="{Binding IsMemberSelectedOrCreating}" ToolTip="{Binding OrganicToolTip}"
|
||||
Click="OrganicTracesButton_Click"
|
||||
HorizontalAlignment="Right" Margin="0,20,10,0" Width="100" Padding="0" Height="35" VerticalAlignment="Top" Grid.Column="2" Grid.ColumnSpan="2" HorizontalContentAlignment="Stretch">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock FontSize="12" TextAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,-3">EU TRACES</TextBlock>
|
||||
<TextBlock FontSize="10" TextAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,0">
|
||||
<Run Text="{Binding OrganicState}" FontWeight="Bold" Foreground="{Binding OrganicColor}"/><Run Text="{Binding OrganicDetails}"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="OrganicEasyCertButton" IsEnabled="{Binding IsMemberSelectedOrCreating}" Content="Easy-Cert"
|
||||
Click="OrganicEasyCertButton_Click"
|
||||
HorizontalAlignment="Right" Margin="0,60,10,0" Width="100" Height="30" VerticalAlignment="Top" Grid.Column="2" Grid.ColumnSpan="2"/>
|
||||
<Button x:Name="OrganicBiocButton" IsEnabled="{Binding IsMemberSelectedOrCreating}" Content="BioC"
|
||||
Click="OrganicBiocButton_Click"
|
||||
HorizontalAlignment="Right" Margin="0,95,10,0" Width="100" Height="30" VerticalAlignment="Top" Grid.Column="2" Grid.ColumnSpan="2"/>
|
||||
|
||||
<Label Content="Stamm-Zwst.:" Margin="10,130,0,0" Grid.Column="0"/>
|
||||
<ComboBox x:Name="BranchInput" SelectedItem="{Binding Branch, Mode=TwoWay}" ItemsSource="{Binding BranchSource, Mode=TwoWay}"
|
||||
DisplayMemberPath="Name" TextSearch.TextPath="Name"
|
||||
Margin="0,130,10,0" Grid.Column="1" Grid.ColumnSpan="2"/>
|
||||
Margin="0,130,10,0" Grid.Column="1" Grid.ColumnSpan="3"/>
|
||||
|
||||
<Label Content="Stammgemeinde:" Margin="10,160,0,0" Grid.Column="0"/>
|
||||
<ComboBox x:Name="DefaultKgInput" SelectedItem="{Binding DefaultKg, Mode=TwoWay}" ItemsSource="{Binding DefaultKgSource, Mode=TwoWay}"
|
||||
DisplayMemberPath="Name" TextSearch.TextPath="Name"
|
||||
Margin="0,160,40,10" Grid.Column="1" Grid.ColumnSpan="2"/>
|
||||
Margin="0,160,40,10" Grid.Column="1" Grid.ColumnSpan="3"/>
|
||||
<Button x:Name="KgDetailsButton" Content="" FontFamily="Segoe MDL2 Assets" FontSize="14" Padding="0,1,0,0"
|
||||
Click="KgDetailsButton_Click"
|
||||
Grid.Column="2" VerticalAlignment="Top" HorizontalAlignment="Right" Width="25" Height="25" Margin="10,160,10,10"/>
|
||||
Grid.Column="3" VerticalAlignment="Top" HorizontalAlignment="Right" Width="25" Height="25" Margin="10,160,10,10"/>
|
||||
|
||||
<Label Content="Anmerkung:" Margin="10,190,0,0" Grid.Column="0"/>
|
||||
<TextBox x:Name="CommentInput" Text="{Binding Comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0,190,10,70" Grid.Column="1" Grid.ColumnSpan="2" FontSize="12"
|
||||
Margin="0,190,10,70" Grid.Column="1" Grid.ColumnSpan="3" FontSize="12"
|
||||
TextChanged="TextBox_TextChanged"
|
||||
VerticalAlignment="Stretch" Height="auto" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
|
||||
|
||||
<Label Content="Kontaktart:" Margin="10,10,0,40" Grid.Column="0" VerticalAlignment="Bottom"/>
|
||||
<CheckBox x:Name="ContactPostalInput" Content="Post" IsChecked="{Binding ContactViaPost, Mode=TwoWay}" IsEnabled="False"
|
||||
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
|
||||
HorizontalAlignment="Left" Margin="0,0,0,45" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
|
||||
HorizontalAlignment="Left" Margin="0,0,0,45" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="3"/>
|
||||
<CheckBox x:Name="ContactEmailInput" Content="E-Mail" IsChecked="{Binding ContactViaEmail, Mode=TwoWay}" IsEnabled="False"
|
||||
Checked="ContactEmailInput_Changed" Unchecked="ContactEmailInput_Changed"
|
||||
HorizontalAlignment="Left" Margin="60,0,0,45" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="2"/>
|
||||
HorizontalAlignment="Left" Margin="60,0,0,45" VerticalAlignment="Bottom" Grid.Column="1" Grid.ColumnSpan="3"/>
|
||||
|
||||
<Label Content="Zuletzt geändert:" Margin="10,10,0,10" Grid.Column="0" VerticalAlignment="Bottom"/>
|
||||
<TextBlock x:Name="LastModified" Text="{Binding ModifiedAtShort}"
|
||||
Margin="0,10,0,15" Grid.Column="1" TextWrapping="NoWrap" VerticalAlignment="Bottom">
|
||||
Margin="0,10,0,15" Grid.Column="1" Grid.ColumnSpan="2" TextWrapping="NoWrap" VerticalAlignment="Bottom">
|
||||
<TextBlock.ToolTip>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -723,7 +742,7 @@
|
||||
|
||||
<Button x:Name="DeliveryButton" IsEnabled="{Binding IsMemberSelected}"
|
||||
Click="DeliveryButton_Click"
|
||||
HorizontalAlignment="Right" Margin="10,00,10,37" Width="160" VerticalAlignment="Bottom" Grid.ColumnSpan="3" HorizontalContentAlignment="Stretch">
|
||||
HorizontalAlignment="Right" Margin="10,00,10,37" Width="160" VerticalAlignment="Bottom" Grid.ColumnSpan="4" HorizontalContentAlignment="Stretch">
|
||||
<Grid>
|
||||
<TextBlock Margin="0,0,10,0" TextAlignment="Center">Lieferungen</TextBlock>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""
|
||||
@@ -732,7 +751,7 @@
|
||||
</Button>
|
||||
<Button x:Name="AreaCommitmentButton" IsEnabled="{Binding IsMemberSelected}"
|
||||
Click="AreaCommitmentButton_Click"
|
||||
HorizontalAlignment="Right" Margin="10,10,10,5" Width="160" VerticalAlignment="Bottom" Grid.ColumnSpan="3" HorizontalContentAlignment="Stretch">
|
||||
HorizontalAlignment="Right" Margin="10,10,10,5" Width="160" VerticalAlignment="Bottom" Grid.ColumnSpan="4" HorizontalContentAlignment="Stretch">
|
||||
<Grid>
|
||||
<TextBlock Margin="0,0,15,0" TextAlignment="Center">Flächenbindungen</TextBlock>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""
|
||||
|
||||
@@ -7,6 +7,7 @@ using Elwig.ViewModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -502,6 +503,8 @@ namespace Elwig.Windows {
|
||||
UnlockInputs();
|
||||
UpdateContactInfoVisibility(true);
|
||||
ViewModel.EnableSearchInputs = false;
|
||||
|
||||
UpdateOrganic();
|
||||
}
|
||||
|
||||
protected override void ShortcutDelete() {
|
||||
@@ -971,6 +974,7 @@ namespace Elwig.Windows {
|
||||
}
|
||||
|
||||
private async void ActiveInput_Changed(object sender, RoutedEventArgs evt) {
|
||||
CheckBox_Changed(sender, evt);
|
||||
if (MemberList.SelectedItem is not Member m)
|
||||
return;
|
||||
if ((IsEditing || IsCreating) && ViewModel.IsActive == false && ViewModel.MgNr is int mgnr) {
|
||||
@@ -985,7 +989,6 @@ namespace Elwig.Windows {
|
||||
}
|
||||
}
|
||||
}
|
||||
CheckBox_Changed(sender, evt);
|
||||
}
|
||||
|
||||
private void JuridicalPersonInput_Changed(object sender, RoutedEventArgs evt) {
|
||||
@@ -1026,5 +1029,82 @@ namespace Elwig.Windows {
|
||||
InputTextChanged(AddressInput, (i, r) => Validator.CheckAddress(i, r, ViewModel.Ort?.Ort.Name));
|
||||
}
|
||||
}
|
||||
|
||||
private void OrganicInput_Changed(object sender, RoutedEventArgs evt) {
|
||||
CheckBox_Changed(sender, evt);
|
||||
if (IsEditing || IsCreating) {
|
||||
UpdateOrganic();
|
||||
}
|
||||
}
|
||||
|
||||
private new void LfbisNrInput_TextChanged(object sender, TextChangedEventArgs evt) {
|
||||
base.LfbisNrInput_TextChanged(sender, evt);
|
||||
if (IsEditing || IsCreating) {
|
||||
UpdateOrganic();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOrganic() {
|
||||
if (!ViewModel.IsOrganic || string.IsNullOrWhiteSpace(ViewModel.LfbisNr) || !GetInputValid(LfbisNrInput)) {
|
||||
ViewModel.OrganicState = null;
|
||||
ViewModel.OrganicColor = Brushes.Black;
|
||||
ViewModel.OrganicDetails = "-";
|
||||
ViewModel.OrganicToolTip = null;
|
||||
return;
|
||||
} else {
|
||||
ViewModel.OrganicState = null;
|
||||
ViewModel.OrganicColor = Brushes.Black;
|
||||
ViewModel.OrganicToolTip = null;
|
||||
ViewModel.OrganicDetails = "...";
|
||||
var lfbisNr = ViewModel.LfbisNr;
|
||||
Utils.RunBackground("Bio-Zertifikatsdaten laden", async () => {
|
||||
if (App.MainDispatcher == null) return;
|
||||
var certs = await OrganicService.FetchTracesCertificates(lfbisNr: lfbisNr);
|
||||
await App.MainDispatcher.BeginInvoke(() => {
|
||||
if (certs.Length == 0) {
|
||||
ViewModel.FillOrganicInputs(null);
|
||||
} else {
|
||||
var cert = certs[0];
|
||||
ViewModel.FillOrganicInputs(cert);
|
||||
ViewModel.OrganicAuthorityCode = cert.AuthorityCode;
|
||||
ViewModel.OrganicOperatorId = cert.OperatorId;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OrganicTracesButton_Click(object sender, RoutedEventArgs evt) {
|
||||
string? url = null;
|
||||
if ((IsCreating || IsEditing) && !string.IsNullOrWhiteSpace(ViewModel.LfbisNr) && GetInputValid(LfbisNrInput)) {
|
||||
url = OrganicService.GetTracesSearchUrl(lfbisNr: ViewModel.LfbisNr);
|
||||
} else if (ViewModel.SelectedMember is Member m) {
|
||||
url = OrganicService.GetTracesSearchUrl(m);
|
||||
}
|
||||
if (url != null)
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private void OrganicEasyCertButton_Click(object sender, RoutedEventArgs evt) {
|
||||
string? url = null;
|
||||
if ((IsCreating || IsEditing) && !string.IsNullOrWhiteSpace(ViewModel.LfbisNr) && GetInputValid(LfbisNrInput)) {
|
||||
url = OrganicService.GetEasyCertSearchUrl(ViewModel.LfbisNr);
|
||||
} else if (ViewModel.SelectedMember is Member m && m.LfbisNr != null) {
|
||||
url = OrganicService.GetEasyCertSearchUrl(m.LfbisNr);
|
||||
}
|
||||
if (url != null)
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private void OrganicBiocButton_Click(object sender, RoutedEventArgs evt) {
|
||||
string? url = null;
|
||||
if ((IsCreating || IsEditing) && !string.IsNullOrWhiteSpace(ViewModel.LfbisNr) && GetInputValid(LfbisNrInput)) {
|
||||
url = OrganicService.GetBiocSearchUrl(ViewModel.LfbisNr);
|
||||
} else if (ViewModel.SelectedMember is Member m && m.LfbisNr != null) {
|
||||
url = OrganicService.GetBiocSearchUrl(m.LfbisNr);
|
||||
}
|
||||
if (url != null)
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user