Compare commits

..

12 Commits

Author SHA1 Message Date
f96ebdcf60 DeliveryAdminWindow: Fix creation of new deliveries
All checks were successful
Test / Run tests (push) Successful in 2m2s
2026-04-02 20:58:01 +02:00
e593175e72 ContextWindow: Use Task.Run to load data outside main thread
All checks were successful
Test / Run tests (push) Successful in 2m29s
2026-04-02 14:18:06 +02:00
1f4fe2129d Bump version to 1.0.4.1
All checks were successful
Deploy / Build and Deploy (push) Successful in 1m49s
2026-03-27 12:29:28 +01:00
cdb4b0a2bd Tests: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 1m54s
2026-03-27 12:19:39 +01:00
ddfc86197d Elwig: Update dependencies 2026-03-27 12:19:31 +01:00
ae0a082421 MailWindow: Fail silently when disposing documents
All checks were successful
Test / Run tests (push) Successful in 1m49s
2026-03-27 12:16:41 +01:00
3c52156b7e Documents: Fix letterhead and other problems
All checks were successful
Test / Run tests (push) Successful in 2m39s
2026-03-27 12:06:19 +01:00
982a6616e1 DeliveryNote: Set date to be mtime
All checks were successful
Test / Run tests (push) Successful in 2m36s
2026-03-19 20:37:18 +01:00
b1075d1e69 DeliveryAdminWindow: Ensure that bulk actions are not performed while editing or creating
All checks were successful
Test / Run tests (push) Successful in 2m1s
2026-03-16 22:58:35 +01:00
cc018ded10 ContextWindow: Use EnsureContextRenewed() correctly 2026-03-16 22:58:04 +01:00
0aefab5d63 DeliveryConfirmation: Fix text alignment
All checks were successful
Test / Run tests (push) Successful in 2m3s
2026-03-16 19:47:33 +01:00
149f455256 Bump version to 1.0.4.0
All checks were successful
Test / Run tests (push) Successful in 1m44s
Deploy / Build and Deploy (push) Successful in 2m3s
2026-03-16 19:14:32 +01:00
33 changed files with 445 additions and 322 deletions

View File

@@ -2,6 +2,52 @@
Changelog
=========
[v1.0.4.1][v1.0.4.1] (2026-03-27) {#v1.0.4.1}
---------------------------------------------
### Behobene Fehler {#v1.0.4.1-bugfixes}
* In der Anlieferungsbestätigung (`DeliveryConfirmation`) war in einer Spalte der Text nicht rechtsbündig. (0aefab5d63)
* Die zweite Zeile des Absenders wurde nicht mehr abgedruckt und der Briefkopf (`Letterhead`) beinhaltete zu viele Informationen. (3c52156b7e)
### Sonstiges {#v1.0.4.1-misc}
* Evtl. wurden bei Änderungen in der Datenbank unnötigerweise einige Daten in Elwig doppelt angefordert. (cc018ded10)
* Massenaktionen im Lieferungen-Fenster (`DeliveryAdminWindow`) sind nun nicht mehr während dem Bearbeiten oder Erstellen möglich. (b1075d1e69)
* Das Datum auf Lieferscheinen (`DeliveryNote`) ist nun statt des heutigen, das Datum der letzten Änderung der Lieferung. (982a6616e1)
* Sollte es im Rundschreiben-Fenster (`MailWindow`) zu einem Fehler während dem Bereinigen der Dokumenten kommen wird dieser ignoriert. (ae0a082421)
* Abhängigkeiten aktualisiert. (ddfc86197d, cdb4b0a2bd)
[v1.0.4.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.4.1
[v1.0.4.0][v1.0.4.0] (2026-03-16) {#v1.0.4.0}
---------------------------------------------
### Neue Funktionen {#v1.0.4.0-features}
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) gibt es nun über die Menü-Leiste die Möglichkeit _Massenaktionen_ für mehrere Lieferungen durchzuführen (z.B. Attribut oder Zu-/Abschlag setzen). ([#78][i78])
### Behobene Fehler {#v1.0.4.0-bugfixes}
* Waagen mit Netzwerkschnittstelle versuchen die Verbindung nun wieder neu aufzubauen, sollte diese unterbrochen werden. ([#74][i74])
### Sonstiges {#v1.0.4.0-misc}
* Die bisher verwendeten Programm-Bibliotheken zum Erstellen von PDF-Dokumente wurden vollständig durch [iText](https://itextpdf.com/) ersetzt.
Das ermöglicht ein schnelleres, effizienteres, und stabileres Erstellen der PDF-Dokumente.
Außerdem konnte so die Größe der Installations-Datei von ~138 MB auf ~98 MB um ca. 30 % reduziert werden. (d8c967b2f2, 8054a024f4)
* Abhängigkeiten aktualisiert. (af73226c90, f4fddd111f)
[v1.0.4.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.4.0
[i74]: https://git.necronda.net/winzer/elwig/issues/74
[i78]: https://git.necronda.net/winzer/elwig/issues/78
[v1.0.3.4][v1.0.3.4] (2026-02-19) {#v1.0.3.4}
---------------------------------------------

View File

@@ -218,7 +218,8 @@ namespace Elwig {
MainDispatcher.Invoke(() => {
foreach (Window w in CurrentApp.Windows) {
if (w is not ContextWindow c) continue;
MainDispatcher.BeginInvoke(c.HintContextChange);
MainDispatcher.Invoke(c.HintContextChange);
MainDispatcher.BeginInvoke(c.TryContextReload);
}
});
}

View File

@@ -2,6 +2,7 @@ using Elwig.Helpers;
using Elwig.Models;
using Elwig.Models.Entities;
using iText.Kernel.Colors;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Action;
using iText.Kernel.Pdf.Canvas;
@@ -53,9 +54,8 @@ namespace Elwig.Documents {
IncludeSender = includeSender;
}
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
base.RenderHeader(doc, pdf);
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(22.5, 42.5))
@@ -68,6 +68,20 @@ namespace Elwig.Documents {
.AddCell(NewAsideCell("UID:", isName: true)).AddCell(NewAsideCell(uid));
}
protected void RenderAddress(Canvas canvas, Rectangle pageSize) {
canvas.Add(new Div()
.SetFixedPositionMM(25, 50, 80, 45, pageSize)
.SetFont(NF)
.Add(new KernedParagraph(IncludeSender ? $"{App.Client.Sender1}\n{App.Client.Sender2}" : "", 8).SetMargins(8, 0, 8, 0).SetHeight(16.0625f))
.Add(new KernedParagraph(Address, 12).SetMargin(0).SetHeight(12 * 5)));
}
protected static void RenderAside(Table aside, Canvas canvas, Rectangle pageSize) {
canvas.Add(new Div()
.SetFixedPositionMM(125, 50, 65, 50, pageSize)
.Add(aside));
}
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.RenderBody(doc, pdf);
@@ -84,16 +98,10 @@ namespace Elwig.Documents {
header.Add(new KernedParagraph(App.Client.NameTypeFull, 12).SetFont(NF).SetMargin(0));
canvas.Add(header);
// address
canvas.Add(new Div()
.SetFixedPositionMM(25, 50, 80, 45, pageSize)
.SetFont(NF)
.Add(new KernedParagraph(IncludeSender ? $"{App.Client.Sender1}\n{App.Client.Sender2}" : "", 8).SetMargin(0).SetHeight(16).SetPaddings(8, 0, 8, 0))
.Add(new KernedParagraph(Address, 12).SetMargin(0).SetHeight(12 * 5)));
RenderAddress(canvas, pageSize);
// aside
if (Aside != null) {
canvas.Add(new Div()
.SetFixedPositionMM(125, 50, 65, 50, pageSize)
.Add(Aside));
RenderAside(Aside, canvas, pageSize);
}
}

View File

@@ -95,8 +95,8 @@ namespace Elwig.Documents {
}
}
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
base.RenderHeader(doc, pdf);
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf);
Aside?.AddCell(NewAsideCell("Gutschrift", 2))
.AddCell(NewAsideCell("TG-Nr.:", isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-"))
.AddCell(NewAsideCell("Datum:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}"))

View File

@@ -31,8 +31,8 @@ namespace Elwig.Documents {
MemberStats = AppDbContext.GetMemberStats(Season.Year, m.MgNr).GetAwaiter().GetResult();
}
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
base.RenderHeader(doc, pdf);
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf);
var firstDay = Data.Rows.MinBy(r => r.Date)?.Date;
var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date;
Aside?.AddCell(NewAsideCell("Saison", 2))
@@ -120,8 +120,8 @@ namespace Elwig.Documents {
}
if (i == p.Buckets.Length - 1) {
sub.AddCell(NewTd($"{p.Weight:N0}"));
sub.AddCell(NewTd(p.IsNetWeight ? "\u2611" : "\u2610", 7, center: true).SetFont(SF).SetPadding(0));
sub.AddCell(NewTd($"{p.Weight:N0}", right: true));
sub.AddCell(NewTd(p.IsNetWeight ? "\u2611" : "\u2610", 7, right: true).SetFont(SF).SetPadding(0));
} else {
sub.AddCell(NewCell(colspan: 2));
}

View File

@@ -32,12 +32,13 @@ namespace Elwig.Documents {
Delivery = d;
Text = App.Client.TextDeliveryNote;
DocumentId = d.LsNr;
Date = DateOnly.FromDateTime(d.ModifiedAt);
IsDoublePaged = true;
MemberBuckets = ctx?.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult() ?? [];
}
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
base.RenderHeader(doc, pdf);
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf);
Aside?.AddCell(NewAsideCell("Lieferung", 2))
.AddCell(NewAsideCell("LS-Nr.:", isName: true)).AddCell(NewAsideCell(Delivery.LsNr))
.AddCell(NewAsideCell("Datum/Zeit:", isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}"))

View File

@@ -88,11 +88,15 @@ namespace Elwig.Documents {
}
public int Render(string path) {
using var writer = new PdfWriter(path);
return Render(writer);
using (var writer = new PdfWriter(path)) {
Render(writer);
}
using var reader = new PdfReader(path);
using var pdf = new PdfDocument(reader);
return pdf.GetNumberOfPages();
}
private int Render(PdfWriter writer) {
private void Render(PdfWriter writer) {
NF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\times.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
BF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesbd.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
IF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesi.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
@@ -117,16 +121,14 @@ namespace Elwig.Documents {
_doc = new iText.Layout.Document(pdf, iText.Kernel.Geom.PageSize.A4);
try {
_doc.SetFont(NF).SetFontSize(12);
RenderHeader(_doc, pdf);
BeforeRenderBody(_doc, pdf);
RenderBody(_doc, pdf);
var pageNum = pdf.GetNumberOfPages();
return pageNum;
} finally {
_doc.Close();
}
}
protected virtual void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { }
protected virtual void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { }
protected virtual void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { }
@@ -150,84 +152,77 @@ namespace Elwig.Documents {
using var mergedPdf = new PdfDocument(writer);
var merger = new PdfMerger(mergedPdf);
PdfPage? letterheadPage = null;
int letterheadInsertIndex = 0;
int letterheadDocIndex = 0;
(PdfPage Page, int InsertIndex, int DocIndex)? letterhead = null;
int p = mergedPdf.GetNumberOfPages();
for (int i = 0; i < m.Documents.Count; i++) {
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var doc = m.Documents[i];
int p0 = mergedPdf.GetNumberOfPages();
int p0 = p;
if (letterheadPage != null && doc is Letterhead) {
if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) {
mergedPdf.AddPage(letterheadPage);
if (letterhead != null && doc is Letterhead) {
if (p0 <= letterhead.Value.InsertIndex) {
mergedPdf.AddPage(letterhead.Value.Page);
mergedPdf.AddNewPage();
} else {
mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage);
mergedPdf.AddNewPage(letterheadInsertIndex + 2);
mergedPdf.AddPage(letterhead.Value.InsertIndex + 1, letterhead.Value.Page);
mergedPdf.AddNewPage(letterhead.Value.InsertIndex + 2);
}
pageNums[letterheadDocIndex] = 1;
letterheadPage = null;
pageNums[letterhead.Value.DocIndex] = 1;
letterhead = null;
p += 2;
}
if (doc is RawPdfDocument) {
if (IsDoublePaged && doc is Letterhead) {
using var reader = new PdfReader(doc.PdfPath);
using var src = new PdfDocument(reader);
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
letterheadInsertIndex = p0;
letterheadDocIndex = i;
} else {
using var reader = new PdfReader(doc.PdfPath);
using var src = new PdfDocument(reader);
merger.Merge(src, 1, src.GetNumberOfPages());
}
p += src.GetNumberOfPages();
} else {
int pageNum = doc.Render(tmpPdf.FilePath);
if (IsDoublePaged && doc is Letterhead) {
using var reader = new PdfReader(tmpPdf.FilePath);
using var src = new PdfDocument(reader);
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
letterheadInsertIndex = p0;
letterheadDocIndex = i;
letterhead = (src.GetPage(1).CopyTo(mergedPdf), p0, i);
} else {
using var reader = new PdfReader(tmpPdf.FilePath);
using var src = new PdfDocument(reader);
merger.Merge(src, 1, pageNum);
p += pageNum;
}
}
int p1 = mergedPdf.GetNumberOfPages();
int p1 = p;
pageNums.Add(p1 - p0);
if (IsDoublePaged && doc is not Letterhead && mergedPdf.GetNumberOfPages() % 2 != 0) {
if (letterheadPage != null) {
mergedPdf.AddPage(letterheadPage);
letterheadPage = null;
if (IsDoublePaged && doc is not Letterhead && p % 2 != 0) {
if (letterhead != null) {
mergedPdf.AddPage(letterhead.Value.Page);
letterhead = null;
} else {
mergedPdf.AddNewPage();
}
p++;
}
progress?.Report(100.0 * (i + 1) / (m.Documents.Count + 1));
}
if (letterheadPage != null) {
if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) {
mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf));
if (letterhead != null) {
if (p <= letterhead.Value.InsertIndex) {
mergedPdf.AddPage(letterhead.Value.Page);
mergedPdf.AddNewPage();
} else {
mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage.CopyTo(mergedPdf));
mergedPdf.AddNewPage(letterheadInsertIndex + 2);
mergedPdf.AddPage(letterhead.Value.InsertIndex + 1, letterhead.Value.Page);
mergedPdf.AddNewPage(letterhead.Value.InsertIndex + 2);
}
pageNums[letterhead.Value.DocIndex] = 1;
p += 2;
}
pageNums[letterheadDocIndex] = 1;
}
TotalPages = pageNums.Sum();
TotalPages = p;
} catch {
pdf.Dispose();
throw;
@@ -297,7 +292,7 @@ namespace Elwig.Documents {
protected Cell NewTh(string? text, float fontSize = 8, int rowspan = 1, int colspan = 1, bool left = false, bool rotated = false) {
var p = new KernedParagraph(text ?? "", fontSize);
if (rotated) p.SetRotationAngle(rotated ? 0.5 * Math.PI : 0);
if (rotated) p.SetRotationAngle(0.5 * Math.PI);
var cell = NewCell(p, rowspan: rowspan, colspan: colspan)
.SetTextAlignment(left ? TextAlignment.LEFT : TextAlignment.CENTER)
.SetVerticalAlignment(VerticalAlignment.MIDDLE)
@@ -394,6 +389,8 @@ namespace Elwig.Documents {
}
private void OnPageEnd(PdfDocumentEvent evt) {
if (_doc is Letterhead) return;
var pdf = evt.GetDocument();
var page = evt.GetPage();
var pageNum = pdf.GetPageNumber(page);

View File

@@ -1,10 +1,21 @@
using Elwig.Models.Entities;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Layout;
namespace Elwig.Documents {
public class Letterhead : BusinessDocument {
public Letterhead(Member m) :
base($"Briefkopf {m.FullName}", m, true) {
Aside = null;
}
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
// do not render anything except this
var page = pdf.AddNewPage();
var pageSize = page.GetPageSize();
var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdf);
using var canvas = new Canvas(pdfCanvas, pageSize);
RenderAddress(canvas, pageSize);
}
}
}

View File

@@ -9,7 +9,7 @@
<UseWindowsForms>true</UseWindowsForms>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.0.3.4</Version>
<Version>1.0.4.1</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
@@ -23,8 +23,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="bblanchon.PDFium.Win32" Version="148.0.7734" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="bblanchon.PDFium.Win32" Version="148.0.7749" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="itext" Version="9.5.0" />
<PackageReference Include="itext.bouncy-castle-adapter" Version="9.5.0" />
<PackageReference Include="LinqKit" Version="1.3.11" />

View File

@@ -318,18 +318,13 @@ namespace Elwig.Services {
AddToolTipCell(grid, $"{weight * 100.0 / total2:N1} %", row, 4, 1, bold, true);
}
public static async Task<(string, Grid)> GenerateToolTip(IQueryable<DeliveryAncmt> deliveryAncmts) {
var grid = new Grid();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
public static async Task<(string Text, (string?, string?, int, int?, int)[] Grid)> GenerateToolTipData(IQueryable<DeliveryAncmt> deliveryAncmts) {
var grid = new List<(string?, string?, int, int?, int)>();
var text = "-";
var weight = await deliveryAncmts.SumAsync(p => p.Weight);
text = $"{weight:N0} kg";
AddToolTipRow(grid, 0, "Menge", null, weight, null, weight);
grid.Add(("Menge", null, weight, null, weight));
if (await deliveryAncmts.AnyAsync()) {
var attrGroups = await deliveryAncmts
@@ -370,13 +365,11 @@ namespace Elwig.Services {
.ThenBy(g => g.SortId)
.ToListAsync();
int rowNum = 1;
foreach (var attrG in attrGroups) {
rowNum++;
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
AddToolTipRow(grid, rowNum++, name, null, attrG.Weight, attrG.Weight, weight);
grid.Add((name, null, attrG.Weight, attrG.Weight, weight));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
AddToolTipRow(grid, rowNum++, null, g.SortId, g.Weight, attrG.Weight, weight);
grid.Add((null, g.SortId, g.Weight, attrG.Weight, weight));
}
}
@@ -395,7 +388,22 @@ namespace Elwig.Services {
}
}
return (text, grid);
return (text, grid.ToArray());
}
public static Grid GenerateToolTip((string?, string?, int, int?, int)[] data) {
var grid = new Grid();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
int rowNum = 0;
foreach (var row in data) {
if (rowNum != 0 && row.Item2 == null) rowNum++;
AddToolTipRow(grid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
return grid;
}
}
}

View File

@@ -16,13 +16,13 @@
<Bold>Entwickler:</Bold> Lorenz Stechauner, Thomas Hilscher<LineBreak/>
<Bold>Kontakt:</Bold> <Hyperlink NavigateUri="mailto:lorenz.stechauner@necronda.net" RequestNavigate="Hyperlink_RequestNavigate">lorenz.stechauner@necronda.net</Hyperlink>, <Hyperlink NavigateUri="mailto:thomas.hilscher@gmail.com" RequestNavigate="Hyperlink_RequestNavigate">thomas.hilscher@gmail.com</Hyperlink><LineBreak/>
<Bold>Quellcode:</Bold> <Hyperlink NavigateUri="https://git.necronda.net/winzer/elwig" RequestNavigate="Hyperlink_RequestNavigate">https://git.necronda.net/winzer/elwig</Hyperlink><LineBreak/>
<Bold>Entwicklungszeitraum:</Bold> 20222025<LineBreak/>
<Bold>Entwicklungszeitraum:</Bold> 20222026<LineBreak/>
<LineBreak/>
<Bold>Verwendete Technologien:</Bold><LineBreak/>
Programmiersprache: C#<LineBreak/>
Framework: Windows Presentation Framework (WPF)<LineBreak/>
Datenbank: <Hyperlink NavigateUri="https://sqlite.org/" RequestNavigate="Hyperlink_RequestNavigate">SQLite</Hyperlink><LineBreak/>
PDF-Erstellung: <Hyperlink NavigateUri="https://itextpdf.com/" RequestNavigate="Hyperlink_RequestNavigate">iText</Hyperlink>, <Hyperlink NavigateUri="https://github.com/toddams/RazorLight" RequestNavigate="Hyperlink_RequestNavigate">RazorLight</Hyperlink>, <Hyperlink NavigateUri="https://github.com/bblanchon/pdfium-binaries" RequestNavigate="Hyperlink_RequestNavigate">Pdfium</Hyperlink><LineBreak/>
PDF-Erstellung: <Hyperlink NavigateUri="https://itextpdf.com/" RequestNavigate="Hyperlink_RequestNavigate">iText</Hyperlink>, <Hyperlink NavigateUri="https://github.com/bblanchon/pdfium-binaries" RequestNavigate="Hyperlink_RequestNavigate">Pdfium</Hyperlink><LineBreak/>
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
</TextBlock>

View File

@@ -7,8 +7,7 @@
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls"
xmlns:vm="clr-namespace:Elwig.ViewModels"
Title="{Binding Title}" Height="600" MinHeight="450" Width="1000" MinWidth="860"
Loaded="Window_Loaded">
Title="{Binding Title}" Height="600" MinHeight="450" Width="1000" MinWidth="860">
<Window.DataContext>
<vm:AreaComAdminViewModel/>
</Window.DataContext>

View File

@@ -37,10 +37,10 @@ namespace Elwig.Windows {
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ActiveAreaCommitmentInput.Content = ((string)ActiveAreaCommitmentInput.Content).Replace("2020", $"{Utils.CurrentLastSeason}");
ActiveAreaCommitmentInput.IsChecked = true;
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
ActiveAreaCommitmentInput.IsChecked = true;
protected override async Task OnInit(AppDbContext ctx) {
LockInputs();
}
@@ -52,8 +52,13 @@ namespace Elwig.Windows {
}
private async Task RefreshList(bool updateSort = false) {
var vm = ViewModel;
var cursor = Mouse.OverrideCursor != null;
if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
var query = (vm.SearchQuery, vm.ShowOnlyActiveAreaComs);
var (filter, areaComs, areaComCount, stat) = await Task.Run(async () => {
using var ctx = new AppDbContext();
var (_, areaComQuery, filter) = await ViewModel.GetFilters(ctx);
var (_, areaComQuery, filter) = await vm.GetFilters(ctx);
var areaComs = await areaComQuery
.Include(a => a.Kg.AtKg)
.Include(a => a.Rd!.Kg.AtKg)
@@ -64,23 +69,30 @@ namespace Elwig.Windows {
if (filter.Count > 0 && areaComs.Count > 0) {
var dict = areaComs.AsParallel()
.ToDictionary(d => d, d => d.SearchScore(ViewModel.TextFilter))
.ToDictionary(d => d, d => d.SearchScore(vm.TextFilter))
.OrderByDescending(c => c.Value);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
areaComs = dict
var threshold = dict.Max(a => a.Value) * 3 / 4;
areaComs = [.. dict
.Where(a => a.Value > threshold)
.Select(a => a.Key)
.ToList();
.Select(a => a.Key)];
}
var areaComCount = await areaComQuery.CountAsync();
var season = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year));
var stat = await AreaComService.GenerateToolTipData(areaComQuery, season?.MaxKgPerHa ?? 10_000);
return (filter, areaComs, areaComCount, stat);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.ShowOnlyActiveAreaComs)) return;
ControlUtils.RenewItemsSource(AreaCommitmentList, areaComs,
AreaCommitmentList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
AreaCommitmentList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
RefreshInputs();
if (filter.Count == 0) {
ViewModel.StatusAreaCommitments = $"{await areaComQuery.CountAsync():N0}";
var s = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year));
var (text, gridData) = await AreaComService.GenerateToolTipData(areaComQuery, s?.MaxKgPerHa ?? 10_000);
ViewModel.StatusAreaCommitments = $"{areaComCount:N0}";
var (text, gridData) = stat;
ViewModel.StatusArea = text;
ViewModel.StatusAreaToolTip = AreaComService.GenerateToolTip(gridData);
} else {
@@ -287,8 +299,7 @@ namespace Elwig.Windows {
LockInputs();
UnlockSearchInputs();
FinishInputFilling();
await RefreshList();
RefreshInputs();
await EnsureContextRenewed();
Mouse.OverrideCursor = null;
ViewModel.SearchQuery = "";
ControlUtils.SelectItem(AreaCommitmentList, AreaCommitmentList.ItemsSource.Cast<AreaCom>().Where(a => a.FbNr == fbnr).FirstOrDefault());

View File

@@ -8,8 +8,7 @@
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls"
mc:Ignorable="d"
Title="Stammdaten - Elwig" Height="520" MinHeight="400" Width="860" MinWidth="810"
Loaded="Window_Loaded">
Title="Stammdaten - Elwig" Height="520" MinHeight="400" Width="860" MinWidth="810">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>

View File

@@ -153,7 +153,7 @@ namespace Elwig.Windows {
ParameterExportEbicsAddress.IsEnabled = true;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
protected override async Task OnInit(AppDbContext ctx) {
LockInputs();
}

View File

@@ -9,7 +9,6 @@
xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
mc:Ignorable="d"
Title="Auszahlung - Elwig" Height="700" Width="1500" MinWidth="1000" MinHeight="500"
Loaded="Window_Loaded"
Closing="Window_Closing">
<Window.Resources>

View File

@@ -80,9 +80,6 @@ namespace Elwig.Windows {
LockContext = true;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
if (HasChanged) {
var r = MessageBox.Show("Soll das Fenster wirklich geschlossen werden? Nicht gespeicherte Änderungen werden NICHT übernommen!", "Schließen bestätigen",

View File

@@ -13,7 +13,7 @@ namespace Elwig.Windows {
set {
_lockContext = value;
if (!_lockContext && _renewPending) {
Dispatcher.BeginInvoke(async () => await RenewContext());
Dispatcher.BeginInvoke(async () => await EnsureContextRenewed());
}
}
}
@@ -30,26 +30,39 @@ namespace Elwig.Windows {
}
public async void ForceContextReload(object sender, EventArgs evt) {
await HintContextChange();
await ForceContextReload();
}
public async Task HintContextChange() {
public async Task ForceContextReload() {
HintContextChange();
await TryContextReload();
}
public void HintContextChange() {
_renewPending = true;
}
public async Task TryContextReload() {
if (LockContext) return;
await RenewContext();
await EnsureContextRenewed();
}
protected async void OnLoaded(object? sender, RoutedEventArgs? evt) {
Mouse.OverrideCursor = Cursors.AppStarting;
using var ctx = new AppDbContext();
await OnRenewContext(ctx);
await OnInit(ctx);
Mouse.OverrideCursor = null;
}
protected async Task EnsureContextRenewed() {
if (!_renewPending) return;
_renewPending = false;
using var ctx = new AppDbContext();
await OnRenewContext(ctx);
}
protected async Task RenewContext() {
if (!_renewPending) return;
using var ctx = new AppDbContext();
await OnRenewContext(ctx);
_renewPending = false;
}
virtual protected async Task OnInit(AppDbContext ctx) { }
abstract protected Task OnRenewContext(AppDbContext ctx);
}

View File

@@ -6,8 +6,7 @@
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels"
xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="{Binding Title}" Height="720" Width="1150" MinHeight="720" MinWidth="1000"
Loaded="Window_Loaded">
Title="{Binding Title}" Height="720" Width="1150" MinHeight="720" MinWidth="1000">
<Window.DataContext>
<vm:DeliveryAdminViewModel/>
</Window.DataContext>

View File

@@ -128,14 +128,14 @@ namespace Elwig.Windows {
ViewModel.EnableAllSeasons = true;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
protected override async Task OnInit(AppDbContext ctx) {
await base.OnInit(ctx);
OnSecondPassed(null, null);
SecondsTimer.Start();
LockInputs();
if (ViewModel.IsReceipt) {
NewDeliveryButton_Click(null, null);
using var ctx = new AppDbContext();
if (ctx.Seasons.Find(Utils.CurrentYear) == null) {
if (await ctx.Seasons.FindAsync(Utils.CurrentYear) == null) {
MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.\n\n(Stammdaten -> Saisons -> Neu anlegen...)",
"Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning);
}
@@ -285,17 +285,17 @@ namespace Elwig.Windows {
await ViewModel.GenerateDeliveryDataList(DeliveryService.ExportSubject.FromFilters, ExportMode.SaveList);
private async void Menu_BulkAction_SetAttribute_Click(object sender, RoutedEventArgs evt) {
if (sender is not MenuItem item) return;
if (IsEditing || IsCreating || sender is not MenuItem item) return;
await ViewModel.BulkSetAttribute(item.Header as string);
}
private async void Menu_BulkAction_AddModifier_Click(object sender, RoutedEventArgs evt) {
if (sender is not MenuItem item || item.Header is not string name) return;
if (IsEditing || IsCreating || sender is not MenuItem item || item.Header is not string name) return;
await ViewModel.BulkAddModifier(name);
}
private async void Menu_BulkAction_RemoveModifier_Click(object sender, RoutedEventArgs evt) {
if (sender is not MenuItem item || item.Header is not string name) return;
if (IsEditing || IsCreating || sender is not MenuItem item || item.Header is not string name) return;
await ViewModel.BulkRemoveModifier(name);
}
@@ -420,8 +420,13 @@ namespace Elwig.Windows {
}
private async Task RefreshList(bool updateSort = false) {
var vm = ViewModel;
var cursor = Mouse.OverrideCursor != null;
if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
var query = (vm.SearchQuery, vm.FilterSeason, vm.FilterAllSeasons, vm.FilterTodayOnly);
var (filter, deliveries, deliveryPartsNum, varieties, members, stat) = await Task.Run(async () => {
using var ctx = new AppDbContext();
var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await ViewModel.GetFilters(ctx);
var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await vm.GetFilters(ctx);
var deliveries = await deliveryQuery
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.Include(d => d.Parts).ThenInclude(p => p.Attribute)
@@ -434,31 +439,38 @@ namespace Elwig.Windows {
if (filter.Count > 0 && deliveries.Count > 0) {
var dict = deliveries.AsParallel()
.ToDictionary(d => d, d => d.SearchScore(ViewModel.TextFilter))
.ToDictionary(d => d, d => d.SearchScore(vm.TextFilter))
.OrderByDescending(a => a.Value)
.ThenBy(a => a.Key.DateTime);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
deliveries = dict
deliveries = [.. dict
.Where(a => a.Value > threshold)
.Select(a => a.Key)
.ToList();
.Select(a => a.Key)];
}
deliveries.ForEach(d => { d.PartFilter = predicate; });
var deliveryPartsNum = await deliveryPartsQuery.CountAsync();
var varieties = await deliveryPartsQuery.Select(d => d.SortId).Distinct().ToListAsync();
var members = await deliveryQuery.Select(d => d.Member).Distinct().ToListAsync();
var stat = await DeliveryService.GenerateToolTipData(deliveryPartsQuery);
return (filter, deliveries, deliveryPartsNum, varieties, members, stat);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.FilterSeason, ViewModel.FilterAllSeasons, ViewModel.FilterTodayOnly)) return;
ControlUtils.RenewItemsSource(DeliveryList, deliveries,
DeliveryList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
DeliveryList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
await RefreshDeliveryParts();
var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList();
ViewModel.StatusMembers = $"{members.Count:N0}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : "");
ViewModel.StatusDeliveries = $"{deliveries.Count:N0}";
if (filter.Count == 0) {
var deliveryParts = deliveryPartsQuery;
ViewModel.StatusDeliveries = $"{deliveries.Count:N0} ({await deliveryParts.CountAsync():N0})";
var varieties = await deliveryParts.Select(d => d.SortId).Distinct().ToListAsync();
ViewModel.StatusDeliveries = $"{deliveries.Count:N0} ({deliveryPartsNum:N0})";
ViewModel.StatusVarieties = $"{varieties.Count:N0}" + (varieties.Count > 0 && varieties.Count <= 10 ? $" ({string.Join(", ", varieties)})" : "");
var (wText, wData, gText, gData) = await DeliveryService.GenerateToolTipData(deliveryParts);
var (wText, wData, gText, gData) = stat;
ViewModel.StatusWeight = wText;
ViewModel.StatusGradation = gText;
(ViewModel.StatusWeightToolTip, ViewModel.StatusGradationToolTip) = DeliveryService.GenerateToolTip(wData, gData);
@@ -564,7 +576,6 @@ namespace Elwig.Windows {
UpdateRdInput();
if (IsCreating) await UpdateLsNr();
await RefreshDeliveryParts();
RefreshInputs();
}
@@ -812,13 +823,12 @@ namespace Elwig.Windows {
}
EmptyScale();
await RefreshList();
await RefreshDeliveryParts();
await EnsureContextRenewed();
Mouse.OverrideCursor = null;
ControlUtils.SelectItem(DeliveryList, p?.Delivery);
ControlUtils.SelectItemWithPk(DeliveryList, p?.Year, p?.DId);
DeliveryPartList.SelectedItem = null;
DeliveryPartList.ScrollIntoView(DeliveryPartList.ItemsSource.Cast<object>().Last());
RefreshInputs();
DeliveryPartList.ScrollIntoView(DeliveryPartList.ItemsSource.Cast<object>().LastOrDefault());
InitialInputs();
}
@@ -851,10 +861,8 @@ namespace Elwig.Windows {
}
EmptyScale();
await RefreshList();
await RefreshDeliveryParts();
if (p?.Delivery != null) {
try {
Utils.RunBackground("Lieferschein drucken", async () => {
using var ctx = new AppDbContext();
using var doc = new DeliveryNote((await ctx.Deliveries.FindAsync(p.Year, p.DId))!, ctx);
await doc.Generate();
@@ -863,15 +871,11 @@ namespace Elwig.Windows {
} else {
await doc.Print(2);
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
});
await EnsureContextRenewed();
Mouse.OverrideCursor = null;
DeliveryList.SelectedItem = null;
await RenewContext();
RefreshInputs();
InitInputs();
}
@@ -902,7 +906,7 @@ namespace Elwig.Windows {
DisableWeighingButtons();
HideFinishNewPartDeliveryCancelButtons();
ShowNewEditDeleteButtons();
await RenewContext();
await EnsureContextRenewed();
RefreshInputs();
ClearInputStates();
LockInputs();
@@ -1082,9 +1086,7 @@ namespace Elwig.Windows {
LockInputs();
UnlockSearchInputs();
FinishInputFilling();
await RefreshList();
await RefreshDeliveryParts();
RefreshInputs();
await EnsureContextRenewed();
Mouse.OverrideCursor = null;
DepreciateButton.IsEnabled = true;
@@ -1184,6 +1186,9 @@ namespace Elwig.Windows {
SeasonInput.IsEnabled = false;
TodayOnlyInput.IsEnabled = false;
AllSeasonsInput.IsEnabled = false;
Menu_BulkAction_SetAttribute.IsEnabled = false;
Menu_BulkAction_AddModifier.IsEnabled = false;
Menu_BulkAction_RemoveModifier.IsEnabled = false;
}
private void UnlockSearchInputs() {
@@ -1191,6 +1196,9 @@ namespace Elwig.Windows {
SeasonInput.IsEnabled = true;
TodayOnlyInput.IsEnabled = true;
AllSeasonsInput.IsEnabled = (ViewModel.FilterMember != null);
Menu_BulkAction_SetAttribute.IsEnabled = true;
Menu_BulkAction_AddModifier.IsEnabled = true;
Menu_BulkAction_RemoveModifier.IsEnabled = true;
}
new protected void UnlockInputs() {

View File

@@ -8,8 +8,7 @@
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels"
xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Anmeldungen - Elwig" Height="700" Width="980" MinWidth="600" MinHeight="400"
Loaded="Window_Loaded">
Title="Anmeldungen - Elwig" Height="700" Width="980" MinWidth="600" MinHeight="400">
<Window.DataContext>
<vm:DeliveryAncmtAdminViewModel/>
</Window.DataContext>

View File

@@ -36,10 +36,10 @@ namespace Elwig.Windows {
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason;
ViewModel.FilterOnlyUpcoming = true;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
ViewModel.FilterOnlyUpcoming = true;
protected override async Task OnInit(AppDbContext ctx) {
LockInputs();
}
@@ -103,8 +103,13 @@ namespace Elwig.Windows {
}
private async Task RefreshList(bool updateSort = false) {
var vm = ViewModel;
var cursor = Mouse.OverrideCursor != null;
if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
var query = (vm.SearchQuery, vm.FilterSeason, vm.FilterOnlyUpcoming, vm.FilterOnlyUpcoming);
var (filter, deliveryAncmts, stat) = await Task.Run(async () => {
using var ctx = new AppDbContext();
var (_, deliveryAncmtQuery, filter) = await ViewModel.GetFilters(ctx);
var (_, deliveryAncmtQuery, filter) = await vm.GetFilters(ctx);
var deliveryAncmts = await deliveryAncmtQuery
.Include(a => a.Member.BillingAddress)
.Include(a => a.Schedule)
@@ -120,20 +125,25 @@ namespace Elwig.Windows {
.ThenBy(a => a.Key.Member.Name)
.ThenBy(a => a.Key.Member.GivenName)
.ThenBy(a => a.Key.Member.MgNr);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
deliveryAncmts = dict
var threshold = dict.Max(a => a.Value) * 3 / 4;
deliveryAncmts = [.. dict
.Where(a => a.Value > threshold)
.Select(a => a.Key)
.ToList();
.Select(a => a.Key)];
} else {
deliveryAncmts = deliveryAncmts
deliveryAncmts = [.. deliveryAncmts
.OrderBy(a => a.Schedule.DateString)
.ThenBy(a => a.Member.Name)
.ThenBy(a => a.Member.GivenName)
.ThenBy(a => a.Member.MgNr)
.ToList();
.ThenBy(a => a.Member.MgNr)];
}
var stat = await DeliveryAncmtService.GenerateToolTipData(deliveryAncmtQuery);
return (filter, deliveryAncmts, stat);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.FilterSeason, ViewModel.FilterOnlyUpcoming, ViewModel.FilterOnlyUpcoming)) return;
ControlUtils.RenewItemsSource(DeliveryAncmtList, deliveryAncmts,
DeliveryAncmtList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
if (updateSort && DeliveryAncmtList.SelectedItem != null)
@@ -141,9 +151,9 @@ namespace Elwig.Windows {
ViewModel.StatusAncmts = $"{deliveryAncmts.Count:N0}";
if (filter.Count == 0) {
var (text, grid) = await DeliveryAncmtService.GenerateToolTip(deliveryAncmtQuery);
var (text, data) = stat;
ViewModel.StatusWeight = text;
ViewModel.StatusWeightToolTip = grid;
ViewModel.StatusWeightToolTip = DeliveryAncmtService.GenerateToolTip(data);
} else {
ViewModel.StatusWeight = $"{deliveryAncmts.Sum(a => a.Weight):N0} kg";
ViewModel.StatusWeightToolTip = null;
@@ -374,8 +384,7 @@ namespace Elwig.Windows {
LockInputs();
ViewModel.EnableSearchInputs = true;
FinishInputFilling();
await RefreshList();
RefreshInputs();
await EnsureContextRenewed();
ViewModel.SearchQuery = "";
ControlUtils.SelectItemWithPk(DeliveryScheduleList, year, dsnr);
if (sortid != null)

View File

@@ -7,8 +7,7 @@
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels"
xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Leseplanung - Elwig" Height="600" Width="850" MinHeight="450" MinWidth="800"
Loaded="Window_Loaded">
Title="Leseplanung - Elwig" Height="600" Width="850" MinHeight="450" MinWidth="800">
<Window.DataContext>
<vm:DeliveryScheduleAdminViewModel/>
</Window.DataContext>

View File

@@ -30,16 +30,21 @@ namespace Elwig.Windows {
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason;
ViewModel.FilterOnlyUpcoming = true;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
ViewModel.FilterOnlyUpcoming = true;
protected override async Task OnInit(AppDbContext ctx) {
LockInputs();
}
private async Task RefreshList(bool updateSort = false) {
var vm = ViewModel;
var cursor = Mouse.OverrideCursor != null;
if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
var query = (vm.SearchQuery, vm.FilterSeason, vm.FilterOnlyUpcoming);
var deliverySchedules = await Task.Run(async () => {
using var ctx = new AppDbContext();
var (_, deliveryScheduleQuery, filter) = await ViewModel.GetFilters(ctx);
var (_, deliveryScheduleQuery, filter) = await vm.GetFilters(ctx);
var deliverySchedules = await deliveryScheduleQuery
.Include(s => s.Varieties)
.Include(s => s.Branch)
@@ -53,19 +58,22 @@ namespace Elwig.Windows {
.ThenBy(a => a.Key.DateString)
.ThenBy(a => a.Key.Branch.Name)
.ThenBy(a => a.Key.Description);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
deliverySchedules = dict
var threshold = dict.Max(a => a.Value) * 3 / 4;
deliverySchedules = [.. dict
.Where(a => a.Value > threshold)
.Select(a => a.Key)
.ToList();
.Select(a => a.Key)];
} else {
deliverySchedules = deliverySchedules
deliverySchedules = [.. deliverySchedules
.OrderBy(s => s.DateString)
.ThenBy(s => s.Branch.Name)
.ThenBy(s => s.Description)
.ToList();
.ThenBy(s => s.Description)];
}
return deliverySchedules;
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != ((ViewModel.SearchQuery, ViewModel.FilterSeason, ViewModel.FilterOnlyUpcoming))) return;
ControlUtils.RenewItemsSource(DeliveryScheduleList, deliverySchedules,
DeliveryScheduleList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
if (updateSort && DeliveryScheduleList.SelectedItem != null)
@@ -225,8 +233,7 @@ namespace Elwig.Windows {
LockInputs();
ViewModel.EnableSearchInputs = true;
FinishInputFilling();
await RefreshList();
RefreshInputs();
await EnsureContextRenewed();
ViewModel.SearchQuery = "";
}

View File

@@ -615,12 +615,16 @@ namespace Elwig.Windows {
}
private void DisposeDocs() {
try {
PrintDocument?.Dispose();
PrintDocument = null;
} catch { }
if (EmailDocuments != null) {
foreach (var (m, docs) in EmailDocuments) {
foreach (var d in docs) {
try {
d.Dispose();
} catch { }
}
}
EmailDocuments = null;

View File

@@ -5,7 +5,7 @@
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Elwig" Height="390" Width="520" ResizeMode="CanMinimize"
Loaded="Window_Loaded" Closing="Window_Closing">
Closing="Window_Closing">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="VerticalAlignment" Value="Top"/>

View File

@@ -37,11 +37,10 @@ namespace Elwig.Windows {
SyncButton.Visibility = App.Config.SyncUrl != null ? Visibility.Visible : Visibility.Hidden;
Menu_Database_Upload.IsEnabled = App.Config.SyncUrl != null;
Menu_Database_Download.IsEnabled = App.Config.SyncUrl != null;
SeasonInput.Value = Utils.CurrentLastSeason;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
SeasonInput.Value = Utils.CurrentLastSeason;
protected override async Task OnInit(AppDbContext ctx) {
if (Utils.HasInternetConnectivity()) {
CheckSync(200);
}

View File

@@ -5,8 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels"
Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150"
Loaded="Window_Loaded">
Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150">
<Window.DataContext>
<vm:MemberAdminViewModel/>
</Window.DataContext>

View File

@@ -82,10 +82,10 @@ namespace Elwig.Windows {
Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null;
Menu_Export_UploadAll.IsEnabled = App.Config.SyncUrl != null;
ViewModel.ShowOnlyActiveMembers = true;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
ViewModel.ShowOnlyActiveMembers = true;
protected override async Task OnInit(AppDbContext ctx) {
UpdateContactInfoVisibility();
LockInputs();
}
@@ -113,8 +113,13 @@ namespace Elwig.Windows {
}
private async Task RefreshList(bool updateSort = false) {
var vm = ViewModel;
var cursor = Mouse.OverrideCursor != null;
if (!cursor) Mouse.OverrideCursor = Cursors.Wait;
var query = (vm.SearchQuery, vm.ShowOnlyActiveMembers);
var (members, totalMemberCount, totalBusinessShares) = await Task.Run(async () => {
using var ctx = new AppDbContext();
var (_, memberQuery, filter) = await ViewModel.GetFilters(ctx);
var (_, memberQuery, filter) = await vm.GetFilters(ctx);
var members = await memberQuery
.Include(m => m.Branch)
.Include(m => m.DefaultWbKg!.AtKg)
@@ -134,25 +139,31 @@ namespace Elwig.Windows {
.ThenBy(a => a.Key.GivenName)
.ThenBy(a => a.Key.MgNr);
var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
members = dict
members = [.. dict
.Where(a => a.Value > threshold)
.Select(a => a.Key)
.ToList();
.Select(a => a.Key)];
} else {
members = members
members = [.. members
.OrderBy(m => m.Name)
.ThenBy(m => m.GivenName)
.ThenBy(m => m.MgNr)
.ToList();
.ThenBy(m => m.MgNr)];
}
var totalMemberCount = await ctx.Members.CountAsync();
var totalBusinessShares = await ctx.Members.SumAsync(m => m.BusinessShares);
return (members, totalMemberCount, totalBusinessShares);
});
if (!cursor) Mouse.OverrideCursor = null;
if (query != (ViewModel.SearchQuery, ViewModel.ShowOnlyActiveMembers)) return;
ControlUtils.RenewItemsSource(MemberList, members,
MemberList_SelectionChanged, ViewModel.TextFilter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
if (updateSort && MemberList.SelectedItem != null)
MemberList.ScrollIntoView(MemberList.SelectedItem);
ViewModel.StatusMembers = $"{members.Count:N0} ({await ctx.Members.CountAsync():N0})";
ViewModel.StatusBusinessShares = $"{members.Sum(m => m.BusinessShares):N0} ({await ctx.Members.SumAsync(m => m.BusinessShares):N0})";
ViewModel.StatusMembers = $"{members.Count:N0} ({totalMemberCount:N0})";
ViewModel.StatusBusinessShares = $"{members.Sum(m => m.BusinessShares):N0} ({totalBusinessShares:N0})";
}
private void RefreshInputs(bool validate = false) {
@@ -436,8 +447,7 @@ namespace Elwig.Windows {
UpdateContactInfoVisibility();
ViewModel.EnableSearchInputs = true;
FinishInputFilling();
await RefreshList();
RefreshInputs();
await EnsureContextRenewed();
ViewModel.SearchQuery = "";
Mouse.OverrideCursor = null;
if (mgnr is int m)

View File

@@ -214,7 +214,7 @@ namespace Elwig.Windows {
App.HintContextChange();
ControlUtils.SelectItemWithPk(WbKgs, k.KgNr);
} catch (Exception exc) {
await HintContextChange();
await ForceContextReload();
var str = "Der Eintrag konnte nicht aus der Datenbank gelöscht werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Katastralgemeinde deaktivieren", MessageBoxButton.OK, MessageBoxImage.Error);

View File

@@ -256,13 +256,13 @@ namespace Elwig.Windows {
await ViewModel.UpdatePaymentVariant(v.Year, v.AvNr);
App.HintContextChange();
} catch (Exception exc) {
await HintContextChange();
await ForceContextReload();
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Auszahlungsvariante aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
await HintContextChange();
await EnsureContextRenewed();
CommentInput_TextChanged(null, null);
DateInput_TextChanged(null, null);
TransferDateInput_TextChanged(null, null);

View File

@@ -13,17 +13,17 @@ About
**Product:** Elwig
**Description:** Electronic Management for Vintners' Cooperatives
**Type:** ERP system
**Version:** 1.0.3.4 ([Changelog](./CHANGELOG.md))
**Version:** 1.0.4.1 ([Changelog](./CHANGELOG.md))
**License:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/
**Source code:** https://git.necronda.net/winzer/elwig
**Developement period:** 20222025
**Developement period:** 20222026
**Technology Stack:**
Language: C#
Framework: Windows Presentation Framework (WPF)
Database: [SQLite](https://sqlite.org/)
PDF creation: [WeasyPrint](https://weasyprint.org/), [RazorLight](https://github.com/toddams/RazorLight), [PdfiumViewer](https://github.com/pvginkel/PdfiumViewer)
PDF creation: [iText](https://itextpdf.com/), [Pdfium](https://github.com/bblanchon/pdfium-binaries)
Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
@@ -33,15 +33,15 @@ Packaging: [WiX Toolset](https://www.firegiant.com/wixtoolset/)
**Produkt:** Elwig
**Beschreibung:** Elektronische Winzergenossenschaftsverwaltung
**Typ:** Warenwirtschaftssystem (ERP-System)
**Version:** 1.0.3.4 ([Änderungsprotokoll](./CHANGELOG.md))
**Version:** 1.0.4.1 ([Änderungsprotokoll](./CHANGELOG.md))
**Lizenz:** [GNU General Public License 3.0 (GPLv3)](./LICENSE)
**Website:** https://elwig.at/
**Quellcode:** https://git.necronda.net/winzer/elwig
**Entwicklungszeitraum:** 20222025
**Entwicklungszeitraum:** 20222026
**Verwendete Technologien:**
Programmiersprache: C#
Framework: Windows Presentation Framework (WPF)
Datenbank: [SQLite](https://sqlite.org/)
PDF-Erstellung: [iText](https://itextpdf.com/), [RazorLight](https://github.com/toddams/RazorLight), [Pdfium](https://github.com/bblanchon/pdfium-binaries)
PDF-Erstellung: [iText](https://itextpdf.com/), [Pdfium](https://github.com/bblanchon/pdfium-binaries)
Paketierung: [WiX Toolset](https://www.firegiant.com/wixtoolset/)

View File

@@ -23,12 +23,12 @@
<PackageReference Include="Appium.WebDriver" Version="4.4.5" />
<PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
<PackageReference Include="NUnit" Version="4.5.1" />
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.12.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="8.0.0">
<PackageReference Include="coverlet.collector" Version="8.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>