diff --git a/Elwig/Documents/DeliveryAncmtList.cs b/Elwig/Documents/DeliveryAncmtList.cs new file mode 100644 index 0000000..d99ed1f --- /dev/null +++ b/Elwig/Documents/DeliveryAncmtList.cs @@ -0,0 +1,21 @@ +using Elwig.Models.Dtos; +using System.Collections.Generic; + +namespace Elwig.Documents { + public class DeliveryAncmtList : Document { + + public new static string Name => "Anmeldeliste"; + + public string Filter; + public IEnumerable Announcements; + + public DeliveryAncmtList(string filter, IEnumerable announcements) : base($"{Name} {filter}") { + Filter = filter; + Announcements = announcements; + } + + public DeliveryAncmtList(string filter, DeliveryAncmtListData data) : + this(filter, data.Rows) { + } + } +} diff --git a/Elwig/Documents/DeliveryAncmtList.cshtml b/Elwig/Documents/DeliveryAncmtList.cshtml new file mode 100644 index 0000000..c8d945e --- /dev/null +++ b/Elwig/Documents/DeliveryAncmtList.cshtml @@ -0,0 +1,46 @@ +@using RazorLight +@inherits TemplatePage +@model Elwig.Documents.DeliveryAncmtList +@{ Layout = "Document"; } + +
+

Anmeldeliste

+

@Model.Filter

+ + + + + + + + + + + + + + + + + + + + + + @foreach (var a in Model.Announcements) { + + + + + + + + } + + + + + + +
DatumMgNr.MitgliedSorteGewicht
[kg]
@($"{a.Date:dd.MM.yyyy}")@a.MgNr@a.AdministrativeName@a.Variety@($"{a.Weight:N0}")
Gesamt:Anmeldungen: @($"{Model.Announcements.Count():N0}")@($"{Model.Announcements.Sum(a => a.Weight):N0}")
+
diff --git a/Elwig/Documents/DeliveryAncmtList.css b/Elwig/Documents/DeliveryAncmtList.css new file mode 100644 index 0000000..f0d3b56 --- /dev/null +++ b/Elwig/Documents/DeliveryAncmtList.css @@ -0,0 +1,13 @@ + +h1 { + text-align: center; + font-size: 24pt; + margin-top: 10mm; + margin-bottom: 2mm; +} + +h2 { + text-align: center; + font-size: 14pt; + margin-top: 2mm; +} diff --git a/Elwig/Documents/Document.cs b/Elwig/Documents/Document.cs index 77fa503..a381f62 100644 --- a/Elwig/Documents/Document.cs +++ b/Elwig/Documents/Document.cs @@ -90,6 +90,8 @@ namespace Elwig.Documents { name = "WineQualityStatistics"; } else if (this is PaymentVariantSummary) { name = "PaymentVariantSummary"; + } else if (this is DeliveryAncmtList) { + name = "DeliveryAncmtList"; } else { throw new InvalidOperationException("Invalid document object"); } diff --git a/Elwig/Models/Dtos/DeliveryAncmtListData.cs b/Elwig/Models/Dtos/DeliveryAncmtListData.cs new file mode 100644 index 0000000..78b8ba3 --- /dev/null +++ b/Elwig/Models/Dtos/DeliveryAncmtListData.cs @@ -0,0 +1,62 @@ +using Elwig.Documents; +using Elwig.Models.Entities; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Elwig.Models.Dtos { + public class DeliveryAncmtListData : DataTable { + + private static readonly (string, string, string?, int?)[] FieldNames = [ + ("Date", "Datum", null, 20), + ("Branch", "Zweigstelle", null, 30), + ("MgNr", "MgNr.", null, 12), + ("Name1", "Name", null, 40), + ("Name2", "Vorname", null, 40), + ("SortId", "Sorte", null, 10), + ("Weight", "Gewicht", "kg", 20), + ]; + + public DeliveryAncmtListData(IEnumerable rows, List filterNames) : + base(DeliveryAncmtList.Name, DeliveryAncmtList.Name, string.Join(" / ", filterNames), rows, FieldNames) { + } + + public static async Task FromQuery(IQueryable query, List filterNames) { + return new((await query + .Include(a => a.Schedule.Branch) + .Include(a => a.Member) + .Include(a => a.Variety) + .AsSplitQuery() + .ToListAsync()).Select(d => new DeliveryAncmtListRow(d)), filterNames); + } + } + + public class DeliveryAncmtListRow { + public DateOnly Date; + public string Branch; + public int MgNr; + public string Name1; + public string Name2; + public string AdministrativeName; + public string SortId; + public string Variety; + public int Weight; + + public DeliveryAncmtListRow(DeliveryAncmt a) { + var s = a.Schedule; + var m = a.Member; + + Date = s.Date; + Branch = s.Branch.Name; + MgNr = m.MgNr; + Name1 = m.AdministrativeName1; + Name2 = m.AdministrativeName2; + AdministrativeName = m.AdministrativeName; + SortId = a.SortId; + Variety = a.Variety.Name; + Weight = a.Weight; + } + } +} diff --git a/Elwig/Services/DeliveryAncmtService.cs b/Elwig/Services/DeliveryAncmtService.cs index c672df7..b947cb7 100644 --- a/Elwig/Services/DeliveryAncmtService.cs +++ b/Elwig/Services/DeliveryAncmtService.cs @@ -5,10 +5,22 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Elwig.Documents; +using Elwig.Helpers.Export; +using Elwig.Models.Dtos; +using Microsoft.Win32; +using System.Net.Http; +using System.Windows.Input; +using System.Windows; +using System; namespace Elwig.Services { public static class DeliveryAncmtService { + public enum ExportSubject { + FromSelectedSchedule, + }; + public static void InitInputs(this DeliveryAncmtAdminViewModel vm) { if (vm.SelectedDeliverySchedule is DeliverySchedule s) vm.DeliverySchedule = (DeliverySchedule?)ControlUtils.GetItemFromSourceWithPk(vm.DeliveryScheduleSource, s.Year, s.DsNr); @@ -29,7 +41,7 @@ namespace Elwig.Services { IQueryable deliveryAncmtQuery = ctx.DeliveryAnnouncements; if (vm.SelectedDeliverySchedule is DeliverySchedule s) { deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == s.Year && a.DsNr == s.DsNr); - filterNames.Add($"{s.Date:dd.MM.yyyy} - {s.Branch.Name} - {s.Description}"); + filterNames.Add($"{s.Date:dd.MM.yyyy} – {s.Branch.Name} – {s.Description}"); } else { deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == vm.FilterSeason); filterNames.Add($"{vm.FilterSeason}"); @@ -114,5 +126,58 @@ namespace Elwig.Services { return (year, dsnr, newMgNr, newSortId); } + + public static async Task GenerateDeliveryAncmtList(this DeliveryAncmtAdminViewModel vm, ExportSubject subject, ExportMode mode) { + using var ctx = new AppDbContext(); + IQueryable query; + List filterNames = []; + if (subject == ExportSubject.FromSelectedSchedule) { + var s = vm.SelectedDeliverySchedule; + if (s == null) return; + query = ctx.DeliveryAnnouncements + .Where(a => a.Year == s.Year && a.DsNr == s.DsNr); + filterNames.Add($"{s.Date:dd.MM.yyyy} – {s.Branch.Name} – {s.Description}"); + } else { + throw new ArgumentException("Invalid value for ExportSubject"); + } + + query = query + .OrderBy(a => a.Schedule.DateString) + .ThenBy(a => a.Schedule.Branch.Name) + .ThenBy(a => a.Schedule.Description) + .ThenBy(a => a.Member.FamilyName) + .ThenBy(a => a.Member.GivenName) + .ThenBy(a => a.Member.MgNr); + + if (mode == ExportMode.SaveList) { + var d = new SaveFileDialog() { + FileName = $"{DeliveryAncmtList.Name}.ods", + DefaultExt = "ods", + Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", + Title = $"{DeliveryAncmtList.Name} speichern unter - Elwig" + }; + if (d.ShowDialog() == true) { + Mouse.OverrideCursor = Cursors.AppStarting; + try { + var data = await DeliveryAncmtListData.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 DeliveryAncmtListData.FromQuery(query, filterNames); + using var doc = new DeliveryAncmtList(string.Join(" / ", filterNames), data); + await Utils.ExportDocument(doc, mode); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } + Mouse.OverrideCursor = null; + } + } } } diff --git a/Elwig/Windows/DeliveryAncmtAdminWindow.xaml b/Elwig/Windows/DeliveryAncmtAdminWindow.xaml index aee0938..8c22afc 100644 --- a/Elwig/Windows/DeliveryAncmtAdminWindow.xaml +++ b/Elwig/Windows/DeliveryAncmtAdminWindow.xaml @@ -67,6 +67,16 @@ + + + + + + diff --git a/Elwig/Windows/DeliveryAncmtAdminWindow.xaml.cs b/Elwig/Windows/DeliveryAncmtAdminWindow.xaml.cs index aaef724..04dd8ab 100644 --- a/Elwig/Windows/DeliveryAncmtAdminWindow.xaml.cs +++ b/Elwig/Windows/DeliveryAncmtAdminWindow.xaml.cs @@ -16,10 +16,14 @@ namespace Elwig.Windows { public DeliveryAncmtAdminViewModel ViewModel => (DeliveryAncmtAdminViewModel)DataContext; private readonly RoutedCommand CtrlF = new("CtrlF", typeof(DeliveryAncmtAdminWindow), [new KeyGesture(Key.F, ModifierKeys.Control)]); + private readonly RoutedCommand CtrlP = new("CtrlP", typeof(DeliveryAncmtAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control)]); + private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(DeliveryAncmtAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]); public DeliveryAncmtAdminWindow() { InitializeComponent(); CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput)); + CommandBindings.Add(new CommandBinding(CtrlP, Menu_DeliveryAncmtList_ShowSelected_Click)); + CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_DeliveryAncmtList_PrintSelected_Click)); ExemptInputs = [ SearchInput, SeasonInput, OnlyUpcomingInput, DeliveryScheduleList, DeliveryAncmtList, ]; @@ -51,6 +55,30 @@ namespace Elwig.Windows { } } + private async void Menu_DeliveryAncmtList_SaveSelected_Click(object sender, RoutedEventArgs evt) { + if (DeliveryScheduleList.SelectedItem is not DeliverySchedule s) + return; + await ViewModel.GenerateDeliveryAncmtList(DeliveryAncmtService.ExportSubject.FromSelectedSchedule, ExportMode.SaveList); + } + + private async void Menu_DeliveryAncmtList_ShowSelected_Click(object sender, RoutedEventArgs evt) { + if (DeliveryScheduleList.SelectedItem is not DeliverySchedule s) + return; + await ViewModel.GenerateDeliveryAncmtList(DeliveryAncmtService.ExportSubject.FromSelectedSchedule, ExportMode.Show); + } + + private async void Menu_DeliveryAncmtList_SavePdfSelected_Click(object sender, RoutedEventArgs evt) { + if (DeliveryScheduleList.SelectedItem is not DeliverySchedule s) + return; + await ViewModel.GenerateDeliveryAncmtList(DeliveryAncmtService.ExportSubject.FromSelectedSchedule, ExportMode.SavePdf); + } + + private async void Menu_DeliveryAncmtList_PrintSelected_Click(object sender, RoutedEventArgs evt) { + if (DeliveryScheduleList.SelectedItem is not DeliverySchedule s) + return; + await ViewModel.GenerateDeliveryAncmtList(DeliveryAncmtService.ExportSubject.FromSelectedSchedule, ExportMode.Print); + } + private async Task RefreshDeliveryScheduleList() { using var ctx = new AppDbContext(); var deliverySchedules = await ctx.DeliverySchedules @@ -148,6 +176,17 @@ namespace Elwig.Windows { private async void DeliveryScheduleList_SelectionChanged(object sender, RoutedEventArgs evt) { await RefreshList(); + if (DeliveryScheduleList.SelectedItem is DeliverySchedule s) { + Menu_DeliveryAncmtList_SaveSelected.IsEnabled = !IsEditing && !IsCreating; + Menu_DeliveryAncmtList_ShowSelected.IsEnabled = !IsEditing && !IsCreating; + Menu_DeliveryAncmtList_SavePdfSelected.IsEnabled = !IsEditing && !IsCreating; + Menu_DeliveryAncmtList_PrintSelected.IsEnabled = !IsEditing && !IsCreating; + } else { + Menu_DeliveryAncmtList_SaveSelected.IsEnabled = false; + Menu_DeliveryAncmtList_ShowSelected.IsEnabled = false; + Menu_DeliveryAncmtList_SavePdfSelected.IsEnabled = false; + Menu_DeliveryAncmtList_PrintSelected.IsEnabled = false; + } } private void DeliveryScheduleInput_SelectionChanged(object sender, RoutedEventArgs evt) { diff --git a/Tests/DocumentTests/DeliveryAncmtListTest.cs b/Tests/DocumentTests/DeliveryAncmtListTest.cs new file mode 100644 index 0000000..382f012 --- /dev/null +++ b/Tests/DocumentTests/DeliveryAncmtListTest.cs @@ -0,0 +1,30 @@ +using Elwig.Documents; +using Elwig.Helpers; +using Elwig.Models.Dtos; + +namespace Tests.DocumentTests { + [TestFixture] + public class DeliveryAncmtListTest { + + [Test] + public async Task Test_01_AllAnnouncements2020() { + using var ctx = new AppDbContext(); + var filter = "01.10.2020 – Matzen – GV Kabinettaktion"; + var data = await DeliveryAncmtListData.FromQuery(ctx.DeliveryAnnouncements.Where(a => a.Year == 2020 && a.DsNr == 1), [filter]); + using var doc = new DeliveryAncmtList(filter, data); + var text = await Utils.GeneratePdfText(doc, true); + var table = Utils.ExtractTable(text); + Assert.Multiple(() => { + Assert.That(text, Contains.Substring("Anmeldeliste")); + Assert.That(text, Contains.Substring("01.10.2020 – Matzen – GV Kabinettaktion")); + Assert.That(table, Is.EqualTo(new string[][] { + ["01.10.2020", "101 MUSTERMANN Max", "Grüner Veltliner", "5 000"], + ["01.10.2020", "102 WEINBAUER Wernhardt", "Grüner Veltliner", "10 000"], + ["01.10.2020", "103 MUSTERBAUER Matthäus", "Grüner Veltliner", "8 000"], + ["01.10.2020", "104 WINZER Waltraud", "Grüner Veltliner", "2 000"], + ["Gesamt:", "Anmeldungen: 4", "25 000"], + })); + }); + } + } +} diff --git a/Tests/DocumentTests/Utils.cs b/Tests/DocumentTests/Utils.cs index a2c06c3..904868d 100644 --- a/Tests/DocumentTests/Utils.cs +++ b/Tests/DocumentTests/Utils.cs @@ -21,7 +21,7 @@ namespace Tests.DocumentTests { public static string[][] ExtractTable(string text) { return text.Split('\n') .Select(row => Regex.Split(row, @"\s{2,}").Select(c => c.Trim()).Where(c => c.Length > 0).ToArray()) - .Where(row => row.Length > 3) + .Where(row => row.Length >= 3) .Skip(1) .ToArray(); } diff --git a/Tests/Resources/Sql/DocumentDelete.sql b/Tests/Resources/Sql/DocumentDelete.sql index 1e48e6d..b5ee7ee 100644 --- a/Tests/Resources/Sql/DocumentDelete.sql +++ b/Tests/Resources/Sql/DocumentDelete.sql @@ -1,5 +1,6 @@ -- deletes for DocumentTests +DELETE FROM delivery_schedule; DELETE FROM payment_variant; DELETE FROM delivery; DELETE FROM season; diff --git a/Tests/Resources/Sql/DocumentInsert.sql b/Tests/Resources/Sql/DocumentInsert.sql index 22b3b96..a61dfe5 100644 --- a/Tests/Resources/Sql/DocumentInsert.sql +++ b/Tests/Resources/Sql/DocumentInsert.sql @@ -9,6 +9,18 @@ INSERT INTO wine_attribute (attrid, name, active, max_kg_per_ha, strict, fill_lo INSERT INTO season (year, currency, min_kg_per_bs, max_kg_per_bs, penalty_per_kg, penalty_amount, penalty_none, start_date, end_date) VALUES (2020, 'EUR', 1000, 2000, NULL, NULL, NULL, NULL, NULL); +INSERT INTO delivery_schedule (year, dsnr, date, zwstid, description, max_weight, ancmt_from, ancmt_to) VALUES +(2020, 1, '2020-10-01', 'X', 'GV Kabinettaktion', 100000, NULL, NULL); + +INSERT INTO delivery_schedule_wine_variety (year, dsnr, sortid, priority) VALUES +(2020, 1, 'GV', 1); + +INSERT INTO delivery_announcement (year, dsnr, mgnr, sortid, weight, type) VALUES +(2020, 1, 101, 'GV', 5000, 'manual'), +(2020, 1, 102, 'GV', 10000, 'manual'), +(2020, 1, 103, 'GV', 8000, 'manual'), +(2020, 1, 104, 'GV', 2000, 'manual'); + INSERT INTO modifier (year, modid, ordering, name, abs, rel, active) VALUES (2020, 'S', 0, 'Geschädigte Trauben', NULL, -0.1, TRUE), (2020, 'A', 0, 'Keine Voranmeldung', -1000, NULL, TRUE);