diff --git a/Elwig/Elwig.csproj b/Elwig/Elwig.csproj index 5fd4b0b..2cd4e77 100644 --- a/Elwig/Elwig.csproj +++ b/Elwig/Elwig.csproj @@ -19,6 +19,7 @@ + diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index 452e79c..6c6387e 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -24,7 +24,7 @@ namespace Elwig.Helpers { public static readonly Regex SerialRegex = GeneratedSerialRegex(); public static readonly Regex TcpRegex = GeneratedTcpRegex(); - public static readonly Regex PartialDateRegex = GeneratedPartialDateRegex(); + public static readonly Regex DateFromToRegex = GeneratedFromToDateRegex(); public static readonly Regex FromToRegex = GeneratedFromToRegex(); public static readonly Regex FromToTimeRegex = GeneratedFromToTimeRegex(); public static readonly Regex AddressRegex = GeneratedAddressRegex(); @@ -35,8 +35,8 @@ namespace Elwig.Helpers { [GeneratedRegex("^tcp://([A-Za-z0-9._-]+):([0-9]+)$", RegexOptions.Compiled)] private static partial Regex GeneratedTcpRegex(); - [GeneratedRegex(@"^(0?[1-9]|[12][0-9]|3[01])\.(0?[1-9]|1[0-2])\.$", RegexOptions.Compiled)] - private static partial Regex GeneratedPartialDateRegex(); + [GeneratedRegex(@"^(-?(0?[1-9]|[12][0-9]|3[01])\.(0?[1-9]|1[0-2])\.([0-9]{4})?-?){1,2}$", RegexOptions.Compiled)] + private static partial Regex GeneratedFromToDateRegex(); [GeneratedRegex(@"^([0-9]+([\.,][0-9]+)?)?-([0-9]+([\.,][0-9]+)?)?$", RegexOptions.Compiled)] private static partial Regex GeneratedFromToRegex(); diff --git a/Elwig/Models/AT_PlzDest.cs b/Elwig/Models/AT_PlzDest.cs index d080829..bd6b04a 100644 --- a/Elwig/Models/AT_PlzDest.cs +++ b/Elwig/Models/AT_PlzDest.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations.Schema; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { [Table("AT_plz_dest"), PrimaryKey("Id"), Index("Plz", "Okz", IsUnique = true)] diff --git a/Elwig/Models/AreaComType.cs b/Elwig/Models/AreaComType.cs index e981da9..ce532de 100644 --- a/Elwig/Models/AreaComType.cs +++ b/Elwig/Models/AreaComType.cs @@ -1,6 +1,7 @@ using Elwig.Helpers; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations.Schema; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { [Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId1", "AttrId2", "Discriminator")] diff --git a/Elwig/Models/Country.cs b/Elwig/Models/Country.cs index 9134a95..564cd53 100644 --- a/Elwig/Models/Country.cs +++ b/Elwig/Models/Country.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations.Schema; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { [Table("country"), PrimaryKey("Num"), Index("Alpha2", IsUnique = true), Index("Alpha3", IsUnique = true)] diff --git a/Elwig/Models/Credit.cs b/Elwig/Models/Credit.cs index b6ea2f9..cae904a 100644 --- a/Elwig/Models/Credit.cs +++ b/Elwig/Models/Credit.cs @@ -2,6 +2,7 @@ using Elwig.Helpers; using Microsoft.EntityFrameworkCore; using System; using System.ComponentModel.DataAnnotations.Schema; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { [Table("credit"), PrimaryKey("Year", "TgNr"), Index("Year", "AvNr", "MgNr", IsUnique = true)] diff --git a/Elwig/Models/Delivery.cs b/Elwig/Models/Delivery.cs index e548137..44f8548 100644 --- a/Elwig/Models/Delivery.cs +++ b/Elwig/Models/Delivery.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { [Table("delivery"), PrimaryKey("Year", "DId"), Index("DateString", "ZwstId", "LNr", IsUnique = true), Index("LsNr", IsUnique = true)] diff --git a/Elwig/Models/WineOrigin.cs b/Elwig/Models/WineOrigin.cs index ac7bff6..4291146 100644 --- a/Elwig/Models/WineOrigin.cs +++ b/Elwig/Models/WineOrigin.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { [Table("wine_origin"), PrimaryKey("HkId"), Index("Name", IsUnique = true)] diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml b/Elwig/Windows/DeliveryAdminWindow.xaml index edcec92..ce757dc 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml +++ b/Elwig/Windows/DeliveryAdminWindow.xaml @@ -91,7 +91,8 @@ + TextChanged="SearchInput_TextChanged" + ToolTip="Lieferungen filtern und durchsuchen. Die Filter sind beliebig kombinierbar. Filtern nach: Sorte: z.B. GV, ZW, rr, sa, ... Qualitätsstufe: z.B. QUW, kab, ldw, ... Gradation: z.B. >73, <15, 17-18, 15-, >17,5, 62-75, ... Mitglied: z.B. 1234, 987, ... Saison: z.B. 2020, >2015, 2017-2019, <2005, 2019-, ... Zweigstelle: z.B. musterort, ... Attribute: z.B. kabinett, !kabinett (alle außer kabinett), ... Datum: z.B. 1.9., 15.9.-10.10., -15.10.2020, ... Uhrzeit: z.B. 06:00-08:00, 18:00-, ... Freitext: z.B. Lieferscheinnummern, "quw" (sucht nach dem Text "quw")"/> diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml.cs b/Elwig/Windows/DeliveryAdminWindow.xaml.cs index fa95407..7513f13 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml.cs +++ b/Elwig/Windows/DeliveryAdminWindow.xaml.cs @@ -2,6 +2,7 @@ using Elwig.Documents; using Elwig.Helpers; using Elwig.Helpers.Export; using Elwig.Models; +using LinqKit; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Win32; @@ -292,28 +293,25 @@ namespace Elwig.Windows { .ThenBy(p => p.Delivery.LsNr) .ThenBy(p => p.DPNr); - // TODO add filter for: - // attributes - // branches var filterVar = new List(); var filterQual = new List(); var filterMgNr = new List(); - var filterDate = new List(); - var filterPartDate = new List(); - string? filterTimeGt = null; - string? filterTimeLt = null; - int filterYearGt = 0; - int filterYearLt = 0; - double filterKmwGt = 0; - double filterKmwLt = 0; - double filterOeGt = 0; - double filterOeLt = 0; + var filterZwst = new List(); + var filterAttr = new List(); + var filterNotAttr = 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 = TextFilter.ToList(); if (filter.Count > 0) { var var = await Context.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v); var qual = await Context.WineQualityLevels.ToDictionaryAsync(q => q.QualId, q => q); var mgnr = await Context.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m); + var zwst = await Context.Branches.ToDictionaryAsync(b => b.Name.ToLower().Split(" ")[0], b => b); + var attr = await Context.WineAttributes.ToDictionaryAsync(a => a.Name.ToLower().Split(" ")[0], a => a); for (int i = 0; i < filter.Count; i++) { var e = filter[i]; @@ -329,6 +327,21 @@ namespace Elwig.Windows { filterMgNr.Add(int.Parse(e)); filter.RemoveAt(i--); filterNames.Add(mgnr[e].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 (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)) { @@ -340,12 +353,6 @@ namespace Elwig.Windows { case ('<', _): filterOeLt = num; break; } filter.RemoveAt(i--); - } else if (TimeOnly.TryParse(e[1..], out var time)) { - switch ((e[0], time)) { - case ('>', _): filterTimeGt = $"{time:HH:mm}"; break; - case ('<', _): filterTimeLt = $"{time:HH:mm}"; break; - } - filter.RemoveAt(i--); } if (e.Length == 1) filter.RemoveAt(i--); } else if (e.Length > 1 && Utils.FromToRegex.IsMatch(e)) { @@ -353,14 +360,14 @@ namespace Elwig.Windows { double? from = (parts[0].Length > 0) ? double.Parse(parts[0].Replace(".", ",")) : null; double? to = (parts[1].Length > 0) ? double.Parse(parts[1].Replace(".", ",")) : null; switch ((from, to)) { - case (<= 30, <= 30): - case (<= 30, null): + case ( <= 30, <= 30): + case ( <= 30, null): case (null, <= 30): filterKmwGt = from ?? 0; filterKmwLt = to ?? 0; break; - case (>= 1900, >= 1900): - case (>= 1900, null): + case ( >= 1900, >= 1900): + case ( >= 1900, null): case (null, >= 1900): filterYearGt = (int)(from ?? 0); filterYearLt = (int)(to ?? -1) + 1; @@ -373,26 +380,57 @@ namespace Elwig.Windows { filter.RemoveAt(i--); } else if (e.Length > 1 && Utils.FromToTimeRegex.IsMatch(e)) { var parts = e.Split("-"); - filterTimeGt = TimeOnly.TryParse(parts[0], out var from) ? $"{from:HH:mm}" : null; - filterTimeLt = TimeOnly.TryParse(parts[1], out var to) ? $"{to:HH:mm}" : null; + 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)) { - // TODO allow date ranges - filterDate.Add(date.ToString("yyyy-MM-dd")); + var s = date.ToString("yyyy-MM-dd"); + filterDate.Add((s, s)); filter.RemoveAt(i--); - filterNames.Add(date.ToString("dd.MM.yyyy")); - } else if (Utils.PartialDateRegex.IsMatch(e)) { - // TODO allow date ranges - var parts = e.Split("."); - var p0 = int.Parse(parts[0]); - var p1 = int.Parse(parts[1]); - filterPartDate.Add($"-{p1:00}-{p0:00}"); - filter.RemoveAt(i--); - if (filterNames.Contains(SeasonInput.Value.ToString())) { + if (filterNames.Contains(SeasonInput.Value.ToString()) && SeasonInput.Value == date.Year) filterNames.Remove(SeasonInput.Value.ToString()); - filterNames.Add($"{p0:00}.{p1:00}.{SeasonInput.Value:0000}"); - } else { - filterNames.Add($"{p0:00}.{p1:00}."); + 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(SeasonInput.Value.ToString()); + filterNames.Add(n + SeasonInput.Value.ToString()); + } else { + if (SeasonInput.Value.ToString() == dParts[2]) + filterNames.Remove(SeasonInput.Value.ToString()); + 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]; @@ -401,15 +439,30 @@ namespace Elwig.Windows { } } - if (filterMgNr.Count > 0) dpq = dpq.Where(p => filterMgNr.Contains(p.Delivery.MgNr)); - if (filterDate.Count > 0) dpq = dpq.Where(p => filterDate.Contains(p.Delivery.DateString)); - if (filterPartDate.Count > 0) dpq = dpq.Where(p => filterPartDate.Contains(p.Delivery.DateString.Substring(4))); if (filterYearGt > 0) dpq = dpq.Where(p => p.Year >= filterYearGt); if (filterYearLt > 0) dpq = dpq.Where(p => p.Year < filterYearLt); - if (filterTimeGt != null) dpq = dpq.Where(p => p.Delivery.TimeString != null && filterTimeGt.CompareTo(p.Delivery.TimeString) <= 0); - if (filterTimeLt != null) dpq = dpq.Where(p => p.Delivery.TimeString != null && filterTimeLt.CompareTo(p.Delivery.TimeString) > 0); + if (filterMgNr.Count > 0) dpq = dpq.Where(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)); + dpq = dpq.Where(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)); + dpq = dpq.Where(p => p.Delivery.TimeString != null).Where(pr); + } if (filterVar.Count > 0) dpq = dpq.Where(p => filterVar.Contains(p.SortId)); if (filterQual.Count > 0) dpq = dpq.Where(p => filterQual.Contains(p.QualId)); + if (filterZwst.Count > 0) dpq = dpq.Where(p => filterZwst.Contains(p.Delivery.ZwstId)); + if (filterAttr.Count > 0) + foreach (var a in filterAttr) + dpq = dpq.Where(p => p.PartAttributes.Select(a => a.Attr.AttrId).Contains(a)); + if (filterNotAttr.Count > 0) + foreach (var a in filterNotAttr) + dpq = dpq.Where(p => !p.PartAttributes.Select(a => a.Attr.AttrId).Contains(a)); if (filterKmwGt > 0) dpq = dpq.Where(p => p.Kmw >= filterKmwGt); if (filterKmwLt > 0) dpq = dpq.Where(p => p.Kmw < filterKmwLt); if (filterOeGt > 0) dpq = dpq.Where(p => p.Kmw * (4.54 + 0.022 * p.Kmw) >= filterOeGt); @@ -436,13 +489,6 @@ namespace Elwig.Windows { } else if (filterOeLt > 0) { filterNames.Add($"unter {filterOeLt:N1} °Oe"); } - if (filterTimeGt != null && filterTimeLt != null) { - filterNames.Add($"{filterTimeGt}–{filterTimeLt}"); - } else if (filterTimeGt != null) { - filterNames.Add($"ab {filterTimeGt}"); - } else if (filterTimeLt != null) { - filterNames.Add($"bis {filterTimeLt}"); - } } return (filterNames, dpq.Select(p => p.Delivery).Distinct().OrderBy(d => d.DateString).ThenBy(d => d.TimeString), dpq, filter);