[#14] Documents: Add DeliveryAncmtList
All checks were successful
Test / Run tests (push) Successful in 2m35s

This commit is contained in:
2024-08-10 15:44:40 +02:00
parent b091bd0ec3
commit 025ff08d84
12 changed files with 303 additions and 2 deletions

View File

@ -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<DeliveryAncmtListRow> Announcements;
public DeliveryAncmtList(string filter, IEnumerable<DeliveryAncmtListRow> announcements) : base($"{Name} {filter}") {
Filter = filter;
Announcements = announcements;
}
public DeliveryAncmtList(string filter, DeliveryAncmtListData data) :
this(filter, data.Rows) {
}
}
}

View File

@ -0,0 +1,46 @@
@using RazorLight
@inherits TemplatePage<Elwig.Documents.DeliveryAncmtList>
@model Elwig.Documents.DeliveryAncmtList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryAncmtList.css" />
<main>
<h1>Anmeldeliste</h1>
<h2>@Model.Filter</h2>
<table class="announcement-list">
<colgroup>
<col style="width: 18mm;"/>
<col style="width: 12mm;"/>
<col style="width: 81mm;"/>
<col style="width: 40mm;"/>
<col style="width: 14mm;"/>
</colgroup>
<thead>
<tr>
<th rowspan="2">Datum</th>
<th rowspan="2">MgNr.</th>
<th rowspan="2" style="text-align: left;">Mitglied</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th>Gewicht</th>
</tr>
<tr>
<th class="unit">[kg]</th>
</tr>
</thead>
<tbody>
@foreach (var a in Model.Announcements) {
<tr>
<td>@($"{a.Date:dd.MM.yyyy}")</td>
<td class="number">@a.MgNr</td>
<td>@a.AdministrativeName</td>
<td>@a.Variety</td>
<td class="number">@($"{a.Weight:N0}")</td>
</tr>
}
<tr class="sum bold">
<td colspan="2">Gesamt:</td>
<td colspan="2">Anmeldungen: @($"{Model.Announcements.Count():N0}")</td>
<td class="number">@($"{Model.Announcements.Sum(a => a.Weight):N0}")</td>
</tr>
</tbody>
</table>
</main>

View File

@ -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;
}

View File

@ -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");
}

View File

@ -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<DeliveryAncmtListRow> {
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<DeliveryAncmtListRow> rows, List<string> filterNames) :
base(DeliveryAncmtList.Name, DeliveryAncmtList.Name, string.Join(" / ", filterNames), rows, FieldNames) {
}
public static async Task<DeliveryAncmtListData> FromQuery(IQueryable<DeliveryAncmt> query, List<string> 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;
}
}
}

View File

@ -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<DeliveryAncmt> 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<DeliveryAncmt> query;
List<string> 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;
}
}
}
}

View File

@ -67,6 +67,16 @@
</Grid.ColumnDefinitions>
<Menu Grid.ColumnSpan="3" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Anmeldeliste">
<MenuItem x:Name="Menu_DeliveryAncmtList_SaveSelected" Header="...von ausgewähltem Leseplan speichern... (Excel)"
Click="Menu_DeliveryAncmtList_SaveSelected_Click"/>
<MenuItem x:Name="Menu_DeliveryAncmtList_ShowSelected" Header="...von ausgewähltem Leseplan anzeigen (PDF)"
Click="Menu_DeliveryAncmtList_ShowSelected_Click" InputGestureText="Strg+P"/>
<MenuItem x:Name="Menu_DeliveryAncmtList_SavePdfSelected" Header="...von ausgewähltem Leseplan speichern... (PDF)"
Click="Menu_DeliveryAncmtList_SavePdfSelected_Click"/>
<MenuItem x:Name="Menu_DeliveryAncmtList_PrintSelected" Header="...von ausgewähltem Leseplan drucken"
Click="Menu_DeliveryAncmtList_PrintSelected_Click" InputGestureText="Strg+Shift+P"/>
</MenuItem>
</Menu>
<Grid Grid.Row="1" Margin="5,0,0,0">

View File

@ -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) {

View File

@ -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"],
}));
});
}
}
}

View File

@ -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();
}

View File

@ -1,5 +1,6 @@
-- deletes for DocumentTests
DELETE FROM delivery_schedule;
DELETE FROM payment_variant;
DELETE FROM delivery;
DELETE FROM season;

View File

@ -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);