Compare commits

..

43 Commits

Author SHA1 Message Date
318fb5dc7b Bump version to 0.13.9
All checks were successful
Test / Run tests (push) Successful in 2m7s
Deploy / Build and Deploy (push) Successful in 1m55s
2025-05-05 10:41:18 +02:00
41c5288fc5 Elwig: Update dependencies
Some checks failed
Test / Run tests (push) Failing after 14s
2025-05-05 10:36:30 +02:00
e50e7337e6 Helpers/Utils: Automatically change URL to sync.elwig.at when applicable 2025-05-05 10:36:30 +02:00
ffe85d471c README: Add text 2025-04-24 16:05:41 +02:00
d1c07ee92a Installer/Setup: Update to WiX 6
All checks were successful
Test / Run tests (push) Successful in 2m43s
2025-04-24 14:35:40 +02:00
4af2fa256e Tests: Update dependencies 2025-04-24 14:31:36 +02:00
bf0db37872 Elwig: Update dependencies 2025-04-24 14:31:27 +02:00
3161351a30 Bump version to 0.13.8
All checks were successful
Test / Run tests (push) Successful in 2m19s
Deploy / Build and Deploy (push) Successful in 2m1s
2025-02-21 12:05:34 +01:00
aa98909c0a DeliveryAdminWindow: Fix Handlese/Gerebelt Gewogen input behaviour
All checks were successful
Test / Run tests (push) Successful in 1m51s
2025-02-20 17:06:40 +01:00
775bb08e95 Elwig: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 2m29s
2025-02-20 16:08:52 +01:00
fe0a7dab2a Tests: Update dependencies 2025-02-20 16:08:42 +01:00
138dae715e DeliveryAdminWindow: Allow Gr.Inzersdorf to select LesewagenInput 2025-02-20 15:56:26 +01:00
28af7f8dd3 CHANGELOG: Fix unser anchor names 2025-01-22 16:19:24 +01:00
7cc2f75e7c Bump version to 0.13.7
All checks were successful
Test / Run tests (push) Successful in 2m15s
Deploy / Build and Deploy (push) Successful in 2m23s
2025-01-21 11:59:54 +01:00
c7a2f2241d Billing: Handle negative credit amount in following credits
Some checks failed
Test / Run tests (push) Has been cancelled
2025-01-21 11:58:08 +01:00
bd4ebb8c35 PaymentVariantsWindow: Allow user to change date
All checks were successful
Test / Run tests (push) Successful in 2m35s
2025-01-21 11:03:54 +01:00
6d88c5645c PaymentVariantsWindow: Warn user about negative credit exports 2025-01-21 11:01:56 +01:00
5d017cc8ea MailLogWindow: Fix crash when opening
All checks were successful
Test / Run tests (push) Successful in 2m52s
2025-01-19 17:00:31 +01:00
0b8a1b321f BillingData: Fix collapsing tested with permutations
All checks were successful
Test / Run tests (push) Successful in 3m7s
2025-01-18 12:19:33 +01:00
95927c3f1a ChartWindow: Remove old commented-out code
All checks were successful
Test / Run tests (push) Successful in 2m42s
2025-01-16 09:09:28 +01:00
f7297d313a Bump version to 0.13.6
All checks were successful
Deploy / Build and Deploy (push) Successful in 2m20s
2025-01-14 23:43:20 +01:00
80fec4473a Elwig: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 2m3s
2025-01-14 22:43:53 +01:00
95ccb2627c Tests: Update dependencies 2025-01-14 22:43:04 +01:00
20e3e2a76b BillingData: Fix collapsing with cultivations/defaults
All checks were successful
Test / Run tests (push) Successful in 1m58s
2025-01-14 22:05:48 +01:00
b83df45e8f Bump version to 0.13.5
All checks were successful
Test / Run tests (push) Successful in 2m9s
Deploy / Build and Deploy (push) Successful in 2m22s
2025-01-02 17:41:51 +01:00
c24b1ca2b9 DeliveryAdminWindow: Add WineLocalityStatistics
All checks were successful
Test / Run tests (push) Successful in 2m10s
2025-01-02 17:25:46 +01:00
5e53d864b1 MemberAdminWindow: Add filters for active and non-active members
All checks were successful
Test / Run tests (push) Successful in 2m20s
2025-01-02 14:08:46 +01:00
633b560a67 Tests: Update dependencies 2025-01-02 14:04:50 +01:00
c9e483ba9d Elwig: Update dependencies 2025-01-02 14:04:40 +01:00
c484d27520 Bump version to 0.13.4
All checks were successful
Test / Run tests (push) Successful in 1m54s
Deploy / Build and Deploy (push) Successful in 2m10s
2024-11-25 19:23:20 +01:00
6c7f10cb26 Tests: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 1m55s
2024-11-25 19:19:31 +01:00
4a10e94d71 Elwig: Update dependencies 2024-11-25 19:19:28 +01:00
338f9fe092 AreaComUnderDeliveryData: Also include inactive members with active area commitments
All checks were successful
Test / Run tests (push) Successful in 2m9s
2024-11-25 12:38:04 +01:00
3b97c2243a AreaComUnderDeliveryData: Fix file name 2024-11-25 12:34:43 +01:00
6ba2aa7143 OverUnderDeliveryData: Fix absence of non-deliverers in list
Bug was introduced by commit 9930e6173c
and shipped with v0.10.6 (2024-08-30)
2024-11-25 12:17:23 +01:00
a99a23fd08 Tests: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 2m52s
2024-11-17 16:58:09 +01:00
70e01849be Setup: Update dependencies 2024-11-17 16:57:53 +01:00
c3a2f983d5 Elwig: Update dependencies 2024-11-17 16:57:40 +01:00
ebf0c20a90 Bump version to 0.13.3
All checks were successful
Test / Run tests (push) Successful in 2m26s
Deploy / Build and Deploy (push) Successful in 2m21s
2024-11-13 23:35:29 +01:00
2ee0d56dcc Windows: Add MailLogWindow
All checks were successful
Test / Run tests (push) Successful in 1m54s
2024-11-13 18:37:50 +01:00
0a9731af09 MailWindow: Fix small bugs and persist all settings
All checks were successful
Test / Run tests (push) Successful in 1m54s
2024-11-13 18:07:03 +01:00
6718ad4c8d AreaComAdminWindow: Add filters for season
All checks were successful
Test / Run tests (push) Successful in 2m26s
2024-11-10 16:35:44 +01:00
a1d84dd988 MemberAdminWindow: Add more filters for AreaComs 2024-11-10 16:35:03 +01:00
40 changed files with 1509 additions and 456 deletions

View File

@ -3,20 +3,143 @@ Changelog
========= =========
[v0.13.9][v0.13.9] (2025-05-05) {#v0.13.9}
------------------------------------------
### Sonstiges {#v0.13.9-misc}
* Abhängigkeiten aktualisiert. (bf0db37872, 4af2fa256e, d1c07ee92a, 41c5288fc5)
* Automatisches Aktualisieren der Synchroisations-URL (`https://elwig.at/clients/` -> `https://sync.elwig.at/`). (e50e7337e6)
[v0.13.9]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.9
[v0.13.8][v0.13.8] (2025-02-21) {#v0.13.8}
------------------------------------------
### Behobene Fehler {#v0.13.8-bugfixes}
* Details im Lieferungen-Fenster (`DeliveryAdminWindow`) für die WG Weinland richtiggestellt (Lesweagen, Verhalten bei _Gerebelt gewogen_). (138dae715e, aa98909c0a)
### Sonstiges {#v0.13.8-misc}
* Abhängigkeiten aktualisiert. (fe0a7dab2a, 775bb08e95)
[v0.13.8]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.8
[v0.13.7][v0.13.7] (2025-01-21) {#v0.13.7}
------------------------------------------
### Behobene Fehler {#v0.13.7-bugfixes}
* In seltenen Fällen konnten im Auszahlungsvariante-Fenster (`ChartWindow`) manche (Sorten-/Attribut-/Bewirtschaftungsart-)Zuordnungen zu Kurven nicht richtig gespeichert werden. (0b8a1b321f)
* Beim Öffnen des Ausgangs-Protokoll-Fensters (`MailLogWindow`) kam es zu einem Absturz. (5d017cc8ea)
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) war es nicht möglich die Überweisungsdaten zu exportieren, sofern mindestens eine Traubengutschrift einen negativen Betrag aufwies.
Jetzt wird der Benutzer nur gewarnt und es ist möglich alle anderen Gutschriften zu exportieren. (6d88c5645c, c7a2f2241d)
### Sonstiges {#v0.13.7-misc}
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) ist es nun möglich das Datum einer Auszahlungsvariante zu ändern. (bd4ebb8c35)
[v0.13.7]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.7
[v0.13.6][v0.13.6] (2025-01-14) {#v0.13.6}
------------------------------------------
### Behobene Fehler {#v0.13.6-bugfixes}
* In seltenen Fällen konnten im Auszahlungsvariante-Fenster (`ChartWindow`) manche (Sorten-/Attribut-/Bewirtschaftungsart-)Zuordnungen zu Kurven nicht richtig gespeichert werden.
Berechnungen basierend auf diesen (evtl. falschen) Zuordnungen wurden immer richtig ausgeführt, eine nachträgliche Überprüfung ist daher möglich. (20e3e2a76b)
### Sonstiges {#v0.13.6-misc}
* Abhängigkeiten aktualisiert. (95ccb2627c, 80fec4473a)
[v0.13.6]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.6
[v0.13.5][v0.13.5] (2025-01-02) {#v0.13.5}
------------------------------------------
### Neue Funktionen {#v0.13.5-features}
* Im Mitglieder-Fenster (`MemberAdminWindow`) Filter `aktiv` und `!aktiv` hinzugefügt. (5e53d864b1)
* Im Lieferungen Fenster (`DeliveryAdminWindow`) Menüpunkt _Statistik_ mitsamt _Qualitätsstatistik_ und _Lieferstatistik pro Ort_ hinzugefügt. (c24b1ca2b9)
### Sonstiges {#v0.13.5-misc}
* Abhängigkeiten aktualisiert. (c9e483ba9d, 633b560a67)
[v0.13.5]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.5
[v0.13.4][v0.13.4] (2024-11-25) {#v0.13.4}
------------------------------------------
### Behobene Fehler {#v0.13.4-bugfixes}
* Bei _Unter-/Überlieferungen lt. gez. GA_ waren seit [v0.10.6](#v0.10.6) (2023-08-30) Nicht-Lieferanten nicht aufgeführt. (6ba2aa7143)
* Bei _Unterlieferungen laut Flächenbindungen_ wurden nur Mitglieder berücksichtigt, die zum Zeitpunkt des Exports aktiv waren. (3b97c2243a, 338f9fe092)
### Sonstiges {#v0.13.4-misc}
* Abhängigkeiten aktualisiert. (c3a2f983d5, 70e01849be, a99a23fd08, 4a10e94d71, 6c7f10cb26)
[v0.13.4]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.4
[v0.13.3][v0.13.3] (2024-11-13) {#v0.13.3}
------------------------------------------
### Neue Funktionen {#v0.13.3-features}
* Im Haupt-Fenster (`MainWindow`) unter _Leseabschluss_ eine Statistik-Tabelle (Gebunden/Ungeb., Mitglieder/Gewicht/Fläche) hinzugefügt. (54deccf021)
* Im Haupt-Fenster (`MainWindow`) unter _Leseabschluss_ zwei Exportmöglichkeiten (_Flächenbindungen_, _Liefermenge/Ertrag_) hinzugefügt. (c5453c2fe6)
* Ausgangs-Protokoll-Fenster (`MailLogWindow`) zum Ansehen aller ausgehenden E-Mails oder ausgedruckten Rundschreiben hinzugefügt (_Rundschreiben_ -> _Hilfe_). (2ee0d56dcc)
### Behobene Fehler {#v0.13.3-bugfixes}
* Im Rundschreiben-Fenster (`MailWindow`) kleinere Fehler behoben und alle Einstellungen werden nun gespeichert. (0a9731af09)
### Sonstiges {#v0.13.3-misc}
* In allen Fenstern an passenden Stellen Symbole/Icons hinzugefügt. (f4fa549130)
* Im Mitglieder-Fenster (`MemberAdminWindow`) Filter `Flächenbindung` (Mitglieder mit irgendeiner aktiven Flächenbindung) hinzugefügt. (a1d84dd988)
* Im Flächenbindungen-Fenster (`AreaComAdminWindow`) Filter für explizite Saisons hinzugefügt. (6718ad4c8d)
[v0.13.3]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.3
[v0.13.2][v0.13.2] (2024-10-13) {#v0.13.2} [v0.13.2][v0.13.2] (2024-10-13) {#v0.13.2}
------------------------------------------ ------------------------------------------
### Neue Funktionen {#0.13.2-features} ### Neue Funktionen {#v0.13.2-features}
* Im Lieferungen-Fenster den Menüpunkt _Abwerteliste_ (`DeliveryDepreciationList`) hinzugefügt. (3cbffdbf27) * Im Lieferungen-Fenster den Menüpunkt _Abwertungsliste_ (`DeliveryDepreciationList`) hinzugefügt. (3cbffdbf27)
### Behobene Fehler {#0.13.2-bugfixes} ### Behobene Fehler {#v0.13.2-bugfixes}
* Fehler im Waagenprotokoll `Avery-Async` (L320) behoben. (8680e51052) * Fehler im Waagenprotokoll `Avery-Async` (L320) behoben. (8680e51052)
* Hausnummern in der BKI Traubentransportscheinliste werden in Excel richtig angezeigt. (86f7f693a0) * Hausnummern in der BKI Traubentransportscheinliste werden in Excel richtig angezeigt. (86f7f693a0)
* Beim Ändern der Identifikatoren von Attributen/Bewirtschaftungsarten wurden diese in Auszahlungsvarianten nicht aktualisiert. (d1f67dc57d) * Beim Ändern der Identifikatoren von Attributen/Bewirtschaftungsarten wurden diese in Auszahlungsvarianten nicht aktualisiert. (d1f67dc57d)
### Sonstiges {#0.13.2-misc} ### Sonstiges {#v0.13.2-misc}
* Weitere automatisierte Tests hinzugefügt. ([#11][i11]) * Weitere automatisierte Tests hinzugefügt. ([#11][i11])
* Namenszusätze bei Gemeinden (z.B. an, bei, im, am) genauer angegeben. (65498dd18f) * Namenszusätze bei Gemeinden (z.B. an, bei, im, am) genauer angegeben. (65498dd18f)
@ -32,15 +155,15 @@ Changelog
[v0.13.1][v0.13.1] (2024-09-29) {#v0.13.1} [v0.13.1][v0.13.1] (2024-09-29) {#v0.13.1}
------------------------------------------ ------------------------------------------
### Neue Funktionen {#0.13.1-features} ### Neue Funktionen {#v0.13.1-features}
* Das Extrahieren/Abwerten/Aufteilen von (Teil-)Lieferungen wurde grundlegend überarbeitet und funktioniert ab jetzt in einem einzigen, übersichtlicheren Dialog. (c62947dacd, c185437b9a) * Das Extrahieren/Abwerten/Aufteilen von (Teil-)Lieferungen wurde grundlegend überarbeitet und funktioniert ab jetzt in einem einzigen, übersichtlicheren Dialog. (c62947dacd, c185437b9a)
### Behobene Fehler {#0.13.1-bugfixes} ### Behobene Fehler {#v0.13.1-bugfixes}
* Im Mitglieder-Fenster (`MemberAdminWinodw`) wurden bei `Anlieferungsbestätigung -> speichern (PDF)` und `Traubengutschrift -> speichern (PDF)` E-Mails verschickt, anstatt ein PDF gespeichert. (6ba1973087, a2315e84bd) * Im Mitglieder-Fenster (`MemberAdminWinodw`) wurden bei `Anlieferungsbestätigung -> speichern (PDF)` und `Traubengutschrift -> speichern (PDF)` E-Mails verschickt, anstatt ein PDF gespeichert. (6ba1973087, a2315e84bd)
### Sonstiges {#0.13.1-misc} ### Sonstiges {#v0.13.1-misc}
* Abhängigkeiten aktualisiert. (b6ae1f5675) * Abhängigkeiten aktualisiert. (b6ae1f5675)
@ -55,7 +178,7 @@ Changelog
> [!NOTE] > [!NOTE]
> Ab dieser Version verhält sich die Berechnung der Unterlieferungen bei Flächenbindungen anders. > Ab dieser Version verhält sich die Berechnung der Unterlieferungen bei Flächenbindungen anders.
### Behobene Fehler {#0.13.0-bugfixes} ### Behobene Fehler {#v0.13.0-bugfixes}
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) war das Extrahieren in eine neue Lieferung seit ca. 6 Monaten (98688168b8) nicht mehr möglich. (8a61747538) * Im Lieferungen-Fenster (`DeliveryAdminWindow`) war das Extrahieren in eine neue Lieferung seit ca. 6 Monaten (98688168b8) nicht mehr möglich. (8a61747538)
@ -132,7 +255,7 @@ Changelog
### Neue Funktionen {#v0.11.2-features} ### Neue Funktionen {#v0.11.2-features}
* In der Anmeldeliste (`DeliveryAncmtList`) wird die Stamm-KG des Mitgliedes angegeben. (165770fa37) * In der Anmeldeliste (`DeliveryAncmtList`) wird die Stamm-KG des Mitgliedes angegeben. (165770fa37)
* Im Hauptmenü wurde im Menüpunkt _Hilfe_ ein Fehler-Protokoll-Fenster (`LogWindow`) hinzugefügt. (526e951029) * Im Haupt-Fenster (`MainWindow`) wurde im Menüpunkt _Hilfe_ ein Fehler-Protokoll-Fenster (`LogWindow`) hinzugefügt. (526e951029)
### Behobene Fehler {#v0.11.2-bugfixes} ### Behobene Fehler {#v0.11.2-bugfixes}
@ -239,7 +362,7 @@ Changelog
### Behobene Fehler {#v0.10.6-bugfixes} ### Behobene Fehler {#v0.10.6-bugfixes}
* Der Titel des Flächenbindungs-Fensters (`AreaComAdminWindow`) ist jetzt _Flächenbindungen_, nicht mehr _Lieferungen_. (ee1315929c) * Der Titel des Flächenbindungen-Fensters (`AreaComAdminWindow`) ist jetzt _Flächenbindungen_, nicht mehr _Lieferungen_. (ee1315929c)
* Im Auszahlungsvariante-Fenster (`ChartWindow`) einen Skalierungs-Fehler behoben. ([#33][i33]) * Im Auszahlungsvariante-Fenster (`ChartWindow`) einen Skalierungs-Fehler behoben. ([#33][i33])
* Versuch: Fehler bei automatischer Daten-Erneuerung bei längerer Benutzung. (8c8c0a8c2b) * Versuch: Fehler bei automatischer Daten-Erneuerung bei längerer Benutzung. (8c8c0a8c2b)
@ -556,7 +679,7 @@ Changelog
### Behobene Fehler {#v0.8.4-bugfixes} ### Behobene Fehler {#v0.8.4-bugfixes}
* Falls beim Schließen des Hauptmenüs ein anderes Fenster im Bearbeiten-/Erstellen-Modus war führte das zu einem Absturz. (70f8276808) * Falls beim Schließen des Haupt-Fensters (`MainWindow`) ein anderes Fenster im Bearbeiten-/Erstellen-Modus war führte das zu einem Absturz. (70f8276808)
### Sonstiges {#v0.8.4-misc} ### Sonstiges {#v0.8.4-misc}

View File

@ -26,6 +26,7 @@ namespace Elwig {
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) }; private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
public static readonly string DataPath = @"C:\ProgramData\Elwig\"; public static readonly string DataPath = @"C:\ProgramData\Elwig\";
public static readonly string MailsPath = Path.Combine(DataPath, "mails");
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini"); public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
public static readonly string ExePath = @"C:\Program Files\Elwig\"; public static readonly string ExePath = @"C:\Program Files\Elwig\";
public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig"); public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");
@ -56,6 +57,7 @@ namespace Elwig {
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Directory.CreateDirectory(TempPath); Directory.CreateDirectory(TempPath);
Directory.CreateDirectory(DataPath); Directory.CreateDirectory(DataPath);
Directory.CreateDirectory(MailsPath);
MainDispatcher = Dispatcher; MainDispatcher = Dispatcher;
Scales = []; Scales = [];
CurrentApp = this; CurrentApp = this;

View File

@ -7,7 +7,7 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon> <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.13.2</Version> <Version>0.13.9</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages> <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
@ -25,21 +25,21 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="LinqKit" Version="1.3.0" /> <PackageReference Include="LinqKit" Version="1.3.8" />
<PackageReference Include="MailKit" Version="4.8.0" /> <PackageReference Include="MailKit" Version="4.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.33" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.4" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2792.45" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<PackageReference Include="NJsonSchema" Version="11.0.2" /> <PackageReference Include="NJsonSchema" Version="11.3.2" />
<PackageReference Include="PdfiumViewer" Version="2.13.0" /> <PackageReference Include="PdfiumViewer" Version="2.13.0" />
<PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" /> <PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
<PackageReference Include="RazorLight" Version="2.3.1" /> <PackageReference Include="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.39" /> <PackageReference Include="ScottPlot.WPF" Version="5.0.55" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" /> <PackageReference Include="System.IO.Ports" Version="9.0.4" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -299,22 +299,30 @@ namespace Elwig.Helpers.Billing {
return (rev1, rev2); return (rev1, rev2);
} }
protected static void CollapsePaymentData(JsonObject data, IEnumerable<RawVaribute> vaributes, bool useDefault = true) { protected static void CollapsePaymentData(JsonObject data, JsonObject originalData, IEnumerable<RawVaribute> vaributes, bool useDefault = true) {
var (rev1, rev2) = GetReverseKeys(data); var (rev1, rev2) = GetReverseKeys(data);
if (!data.ContainsKey("default")) { if (!data.ContainsKey("default")) {
foreach (var (v, ks) in rev1) { foreach (var (v, ks) in rev1) {
if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) { if ((ks.Count > vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
foreach (var k in ks) data.Remove(k); foreach (var k in ks) {
if (!(originalData[$"{k[..2]}/"]?.AsValue().TryGetValue<string>(out var o) ?? false) || o == v)
if (!(originalData[k.Split('-')[0]]?.AsValue().TryGetValue<string>(out var o2) ?? false) || o2 == v)
data.Remove(k);
}
data["default"] = v; data["default"] = v;
CollapsePaymentData(data, vaributes, useDefault); CollapsePaymentData(data, originalData, vaributes, useDefault);
return; return;
} }
} }
foreach (var (v, ks) in rev2) { foreach (var (v, ks) in rev2) {
if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) { if ((ks.Count > vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
foreach (var k in ks) data.Remove(k); foreach (var k in ks) {
if (!(originalData[$"{k[..2]}/"]?.AsValue().TryGetValue<decimal>(out var o) ?? false) || o == v)
if (!(originalData[k.Split('-')[0]]?.AsValue().TryGetValue<decimal>(out var o2) ?? false) || o2 == v)
data.Remove(k);
}
data["default"] = v; data["default"] = v;
CollapsePaymentData(data, vaributes, useDefault); CollapsePaymentData(data, originalData, vaributes, useDefault);
return; return;
} }
} }
@ -330,16 +338,26 @@ namespace Elwig.Helpers.Billing {
var len = vaributes.Count(e => $"{e.AttrId}{(e.CultId != null && e.CultId != "" ? "-" : "")}{e.CultId}" == idx); var len = vaributes.Count(e => $"{e.AttrId}{(e.CultId != null && e.CultId != "" ? "-" : "")}{e.CultId}" == idx);
foreach (var (v, ks) in rev1) { foreach (var (v, ks) in rev1) {
var myKs = ks.Where(k => k.EndsWith($"/{idx}")).ToList(); var myKs = ks.Where(k => k.EndsWith($"/{idx}")).ToList();
if (myKs.Count > 1 && ((myKs.Count >= len * 0.5 && useDefault) || myKs.Count == len)) { if (myKs.Count > 1 && ((myKs.Count > len * 0.5 && useDefault) || myKs.Count == len)) {
foreach (var k in myKs) data.Remove(k); foreach (var k in myKs) data.Remove(k);
data[(idx.StartsWith('-') && !useDefault ? "" : "/") + idx] = v; var discr = (idx.StartsWith('-') && !useDefault ? "" : "/") + idx;
data[discr] = v;
foreach (var (k, o) in originalData) {
if (o!.AsValue().TryGetValue<string>(out var o2) && o2 != v && k.Contains(discr))
data[k] = o2;
}
} }
} }
foreach (var (v, ks) in rev2) { foreach (var (v, ks) in rev2) {
var myKs = ks.Where(k => k.EndsWith($"/{idx}")).ToList(); var myKs = ks.Where(k => k.EndsWith($"/{idx}")).ToList();
if (myKs.Count > 1 && ((myKs.Count >= len * 0.5 && useDefault) || myKs.Count == len)) { if (myKs.Count > 1 && ((myKs.Count > len * 0.5 && useDefault) || myKs.Count == len)) {
foreach (var k in myKs) data.Remove(k); foreach (var k in myKs) data.Remove(k);
data[(idx.StartsWith('-') && !useDefault ? "" : "/") + idx] = v; var discr = (idx.StartsWith('-') && !useDefault ? "" : "/") + idx;
data[discr] = v;
foreach (var (k, o) in originalData) {
if (o!.AsValue().TryGetValue<decimal>(out var o2) && o2 != v && k.Contains(discr))
data[k] = o2;
}
} }
} }
} }
@ -355,13 +373,22 @@ namespace Elwig.Helpers.Billing {
} else if (k.Contains("/-")) { } else if (k.Contains("/-")) {
data.Remove(k, out var val); data.Remove(k, out var val);
data.Add(k.Replace("/-", "-"), val); data.Add(k.Replace("/-", "-"), val);
if (k[0] == '/' || k.Contains('-')) {
foreach (var (k2, o) in originalData) {
if (!data.ContainsKey(k2) && k2.Contains('-') && k2.Contains("-" + k.Split('-')[1]) && !k2.Contains("/-")
&& (!k2.Contains('/') || k2.Length <= 4 || !data.ContainsKey(k2[2..])))
{
data[k2] = o?.DeepClone();
}
}
}
} }
} }
(rev1, rev2) = GetReverseKeys(data, false); (rev1, rev2) = GetReverseKeys(data, false);
var keyVaributes = data var keyVaributes = data
.Select(e => e.Key.Split('-')[0]) .Select(e => e.Key)
.Where(e => e.Length > 0 && e != "default") .Where(e => e.Length > 0 && !e.Contains('-') && e != "default")
.Distinct() .Distinct()
.ToList(); .ToList();
foreach (var idx in keyVaributes) { foreach (var idx in keyVaributes) {
@ -419,8 +446,8 @@ namespace Elwig.Helpers.Billing {
} }
} }
CollapsePaymentData(payment, vaributes ?? payment.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultPayment); CollapsePaymentData(payment, payment.DeepClone().AsObject(), vaributes ?? payment.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultPayment);
CollapsePaymentData(qualityWei, vaributes ?? qualityWei.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultQuality); CollapsePaymentData(qualityWei, qualityWei.DeepClone().AsObject(), vaributes ?? qualityWei.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultQuality);
var data = new JsonObject { var data = new JsonObject {
["mode"] = "elwig", ["mode"] = "elwig",

View File

@ -46,14 +46,14 @@ namespace Elwig.Helpers.Billing {
m.mgnr, m.mgnr,
v.avnr, v.avnr,
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount, ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
ROUND(lp.amount / POW(10, s.precision - 2)) AS prev_amount, IIF(lc.amount >= 0, ROUND(lp.amount / POW(10, s.precision - 2)), 0) AS prev_net_amount,
IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat, IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) + ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) + ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) + ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) +
IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0) IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
AS modifiers, AS modifiers,
lc.modifiers AS prev_modifiers IIF(lc.amount >= 0, lc.modifiers, 0) AS prev_modifiers
FROM season s FROM season s
JOIN payment_variant v ON v.year = s.year JOIN payment_variant v ON v.year = s.year
LEFT JOIN payment_variant l ON l.year = s.year LEFT JOIN payment_variant l ON l.year = s.year

View File

@ -23,6 +23,10 @@ namespace Elwig.Helpers {
public bool HasNetWeighing(Branch? b) => HasNetWeighing(b?.ZwstId); public bool HasNetWeighing(Branch? b) => HasNetWeighing(b?.ZwstId);
public bool HasNetWeighing() => HasNetWeighing(App.ZwstId); public bool HasNetWeighing() => HasNetWeighing(App.ZwstId);
public bool HasBoxWeighing(string? zwstId) => IsWinzerkeller && (zwstId != "W");
public bool HasBoxWeighing(Branch? b) => HasBoxWeighing(b?.ZwstId);
public bool HasBoxWeighing() => HasBoxWeighing(App.ZwstId);
public string NameToken; public string NameToken;
public string NameShort; public string NameShort;
public string Name; public string Name;
@ -67,6 +71,12 @@ namespace Elwig.Helpers {
public string? TextEmailSubject; public string? TextEmailSubject;
public string? TextEmailBody; public string? TextEmailBody;
public bool MailIncludeNonDeliverers;
public bool MailDoublePaged;
public int MailSendPostal;
public int MailSendEmail;
public int MailOrdering;
public int ExportEbicsVersion; public int ExportEbicsVersion;
public int ExportEbicsAddress; public int ExportEbicsAddress;
@ -117,7 +127,7 @@ namespace Elwig.Helpers {
case "KMW/5": ModeWineQualityStatistics = 3; break; case "KMW/5": ModeWineQualityStatistics = 3; break;
case "KMW/10": ModeWineQualityStatistics = 4; break; case "KMW/10": ModeWineQualityStatistics = 4; break;
} }
switch (parameters.GetValueOrDefault("ORDERING_MEMBERLIST", "")?.ToUpper()) { switch (parameters.GetValueOrDefault("ORDERING_MEMBERLIST", "MGNR")?.ToUpper()) {
case "MGNR": OrderingMemberList = 0; break; case "MGNR": OrderingMemberList = 0; break;
case "NAME": OrderingMemberList = 1; break; case "NAME": OrderingMemberList = 1; break;
case "KG": OrderingMemberList = 2; break; case "KG": OrderingMemberList = 2; break;
@ -135,6 +145,31 @@ namespace Elwig.Helpers {
TextEmailBody = parameters.GetValueOrDefault("TEXT_EMAIL_BODY"); TextEmailBody = parameters.GetValueOrDefault("TEXT_EMAIL_BODY");
if (TextEmailBody == "") TextEmailBody = null; if (TextEmailBody == "") TextEmailBody = null;
MailIncludeNonDeliverers = (parameters.GetValueOrDefault("MAIL_INCLUDE_NON_DELIVERERS")?.ToUpper()) switch {
"1" or "TRUE" or "YES" or "JA" => true,
_ => false,
};
MailDoublePaged = (parameters.GetValueOrDefault("MAIL_DOUBLE_PAGED")?.ToUpper()) switch {
"1" or "TRUE" or "YES" or "JA" => true,
_ => false,
};
switch (parameters.GetValueOrDefault("MAIL_SEND_POSTAL", "WISH")?.ToUpper()) {
case "ALL": MailSendPostal = 3; break;
case "WISH": MailSendPostal = 2; break;
case "NO_EMAIL": MailSendPostal = 1; break;
case "NONE": MailSendPostal = 0; break;
}
switch (parameters.GetValueOrDefault("MAIL_SEND_EMAIL", "WISH")?.ToUpper()) {
case "ALL": MailSendEmail = 2; break;
case "WISH": MailSendEmail = 1; break;
case "NONE": MailSendEmail = 0; break;
}
switch (parameters.GetValueOrDefault("MAIL_ORDERING", "MGNR")?.ToUpper()) {
case "MGNR": MailOrdering = 0; break;
case "NAME": MailOrdering = 1; break;
case "PLZ": MailOrdering = 2; break;
}
ExportEbicsVersion = int.TryParse(parameters.GetValueOrDefault("EXPORT_EBICS_VERSION"), out var v) ? v : 9; ExportEbicsVersion = int.TryParse(parameters.GetValueOrDefault("EXPORT_EBICS_VERSION"), out var v) ? v : 9;
switch (parameters.GetValueOrDefault("EXPORT_EBICS_ADDRESS", "FULL")?.ToUpper()) { switch (parameters.GetValueOrDefault("EXPORT_EBICS_ADDRESS", "FULL")?.ToUpper()) {
case "OMIT": ExportEbicsAddress = 0; break; case "OMIT": ExportEbicsAddress = 0; break;
@ -177,6 +212,25 @@ namespace Elwig.Helpers {
case 1: orderingMemberList = "NAME"; break; case 1: orderingMemberList = "NAME"; break;
case 2: orderingMemberList = "KG"; break; case 2: orderingMemberList = "KG"; break;
} }
string mailSendPostal = "MGNR";
switch (MailOrdering) {
case 0: mailSendPostal = "NONE"; break;
case 1: mailSendPostal = "NO_EMAIL"; break;
case 2: mailSendPostal = "WISH"; break;
case 3: mailSendPostal = "ALL"; break;
}
string mailSendEmail = "MGNR";
switch (MailOrdering) {
case 0: mailSendEmail = "NONE"; break;
case 1: mailSendEmail = "WISH"; break;
case 2: mailSendEmail = "ALL"; break;
}
string mailOrdering = "MGNR";
switch (MailOrdering) {
case 0: mailOrdering = "MGNR"; break;
case 1: mailOrdering = "NAME"; break;
case 2: mailOrdering = "PLZ"; break;
}
string exportEbicsAddress = "FULL"; string exportEbicsAddress = "FULL";
switch (ExportEbicsAddress) { switch (ExportEbicsAddress) {
case 0: exportEbicsAddress = "OMIT"; break; case 0: exportEbicsAddress = "OMIT"; break;
@ -212,6 +266,11 @@ namespace Elwig.Helpers {
("TEXT_CREDITNOTE", TextCreditNote), ("TEXT_CREDITNOTE", TextCreditNote),
("TEXT_EMAIL_SUBJECT", TextEmailSubject), ("TEXT_EMAIL_SUBJECT", TextEmailSubject),
("TEXT_EMAIL_BODY", TextEmailBody), ("TEXT_EMAIL_BODY", TextEmailBody),
("MAIL_INCLUDE_NON_DELIVERERS", MailIncludeNonDeliverers ? "YES" : "NO"),
("MAIL_DOUBLE_PAGED", MailDoublePaged ? "YES" : "NO"),
("MAIL_SEND_POSTAL", mailSendPostal),
("MAIL_SEND_EMAIL", mailSendEmail),
("MAIL_ORDERING", mailOrdering),
("EXPORT_EBICS_VERSION", ExportEbicsVersion.ToString()), ("EXPORT_EBICS_VERSION", ExportEbicsVersion.ToString()),
("EXPORT_EBICS_ADDRESS", exportEbicsAddress), ("EXPORT_EBICS_ADDRESS", exportEbicsAddress),
("AUTOADJUST_BUSINESSSHARES", autoAdjust), ("AUTOADJUST_BUSINESSSHARES", autoAdjust),

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using Brush = System.Windows.Media.Brush; using Brush = System.Windows.Media.Brush;
using Brushes = System.Windows.Media.Brushes; using Brushes = System.Windows.Media.Brushes;
@ -234,5 +235,21 @@ namespace Elwig.Helpers {
return null; return null;
} }
} }
public static void InitializeDelayTimer(TextBox tb, Action<object, TextChangedEventArgs> handler) {
var timer = new DispatcherTimer {
Interval = TimeSpan.FromMilliseconds(250)
};
timer.Tick += (object? sender, EventArgs evt) => {
timer.Stop();
var (oSender, oEvent) = ((object, TextChangedEventArgs))timer.Tag;
handler(oSender, oEvent);
};
tb.TextChanged += (object sender, TextChangedEventArgs evt) => {
timer.Stop();
timer.Tag = (sender, evt);
timer.Start();
};
}
} }
} }

View File

@ -301,6 +301,7 @@ namespace Elwig.Helpers.Export {
if (units != null && units.Length > 0) { if (units != null && units.Length > 0) {
int n = -1; int n = -1;
switch (units[0]) { switch (units[0]) {
case "#": n = 0; data = $"{v:N0}"; break;
case "%": n = 1; data = $"{v:N1}"; break; case "%": n = 1; data = $"{v:N1}"; break;
case "€": n = 2; data = $"{v:N2}"; break; case "€": n = 2; data = $"{v:N2}"; break;
case "€/kg": n = 4; data = $"{v:N4}"; break; case "€/kg": n = 4; data = $"{v:N4}"; break;

View File

@ -434,8 +434,7 @@ namespace Elwig.Helpers {
if (accept != null) if (accept != null)
client.DefaultRequestHeaders.Accept.Add(new(accept)); client.DefaultRequestHeaders.Accept.Add(new(accept));
if (username != null || password != null) if (username != null || password != null)
client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String( client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(Utils.UTF8.GetBytes($"{username}:{password}")));
Utils.UTF8.GetBytes($"{username}:{password}")));
return client; return client;
} }
@ -454,6 +453,8 @@ namespace Elwig.Helpers {
} }
public static async Task UploadExportData(string zip, string url, string username, string password) { public static async Task UploadExportData(string zip, string url, string username, string password) {
if (url.StartsWith("https://elwig.at/clients/"))
url = "https://sync.elwig.at/" + url[25..];
if (!url.EndsWith('/')) url += "/"; if (!url.EndsWith('/')) url += "/";
using var client = GetHttpClient(username, password, accept: "application/json"); using var client = GetHttpClient(username, password, accept: "application/json");
var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read)); var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
@ -463,6 +464,8 @@ namespace Elwig.Helpers {
} }
public static async Task<JsonArray> GetExportMetaData(string url, string username, string password) { public static async Task<JsonArray> GetExportMetaData(string url, string username, string password) {
if (url.StartsWith("https://elwig.at/clients/"))
url = "https://sync.elwig.at/" + url[25..];
using var client = GetHttpClient(username, password, accept: "application/json"); using var client = GetHttpClient(username, password, accept: "application/json");
using var res = await client.GetAsync(url); using var res = await client.GetAsync(url);
res.EnsureSuccessStatusCode(); res.EnsureSuccessStatusCode();
@ -594,5 +597,76 @@ namespace Elwig.Helpers {
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear); public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear);
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query, int year) => public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query, int year) =>
query.Where(c => ActiveAreaCommitments(year).Invoke(c)); query.Where(c => ActiveAreaCommitments(year).Invoke(c));
public static async Task<(DateTime DateTime, string Type, int MgNr, string Name, string[] Addresses, string Subject, string[] Attachments)[]> GetSentMails(DateOnly? fromDate = null, DateOnly? toDate = null) {
var f = $"{fromDate:yyyy-MM-dd}";
var t = $"{toDate:yyyy-MM-dd}";
try {
var rows = new List<(DateTime, string, int, string, string[], string, string[])>();
var filenames = Directory.GetFiles(App.MailsPath, "????.csv")
.Where(n => fromDate == null || Path.GetFileName(n).CompareTo($"{fromDate.Value.Year}.csv") >= 0)
.Where(n => toDate == null || Path.GetFileName(n).CompareTo($"{toDate.Value.Year}.csv") <= 0)
.Order();
foreach (var filename in filenames) {
using var reader = new StreamReader(filename, Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
try {
if (line.Length < 20 || line[10] != ';' || line[19] != ';')
continue;
var date = line[..10];
if (fromDate != null && date.CompareTo(f) < 0) {
continue;
} else if (toDate != null && date.CompareTo(t) > 0) {
break;
}
var p = line.Split(';');
rows.Add((
DateOnly.ParseExact(p[0], "yyyy-MM-dd").ToDateTime(TimeOnly.ParseExact(p[1], "HH:mm:ss")),
p[2],
int.Parse(p[3]),
p[4],
p[5].Split(',').Select(a => a.Replace(" | ", "\n")).ToArray(),
p[6],
p[7].Split(',')
));
} catch {
continue;
}
}
}
return [.. rows];
} catch {
return [];
}
}
public static async Task AddSentMails(IEnumerable<(string Type, int MgNr, string Name, string[] Addresses, string Subject, string[] Attachments)> data) {
var now = DateTime.Now;
var filename = Path.Combine(App.MailsPath, $"{now.Year}.csv");
await File.AppendAllLinesAsync(filename, data.Select(d =>
$"{now:yyyy-MM-dd;HH:mm:ss};{d.Type};" +
$"{d.MgNr};{d.Name.Replace(';', ' ')};" +
$"{string.Join(',', d.Addresses.Select(a => a.Replace(';', ' ').Replace(',', ' ').Replace("\n", " | ")))};" +
$"{d.Subject.Replace(';', ' ')};" +
$"{string.Join(',', d.Attachments.Select(a => a.Replace(';', ' ').Replace(',', ' ')))}"
), Utils.UTF8);
}
public static async Task<string?> FindSentMailBody(DateTime target) {
var dt = $"{target:yyyy-MM-dd_HH-mm-ss}_";
var filename = Directory.GetFiles(App.MailsPath, "????-??-??_??-??-??_*.txt")
.Where(n => Path.GetFileName(n).CompareTo(dt) <= 0)
.Order()
.LastOrDefault();
if (filename == null)
return null;
return await File.ReadAllTextAsync(filename, Utils.UTF8);
}
public static async Task AddSentMailBody(string subject, string body, int recipients) {
var filename = Path.Combine(App.MailsPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{NormalizeFileName(subject)}.txt");
await File.WriteAllTextAsync(filename, $"# {subject}\r\n# Vorgesehene Empfänger: {recipients}\r\n\r\n" + body, Utils.UTF8);
}
} }
} }

View File

@ -45,7 +45,7 @@ namespace Elwig.Models.Dtos {
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz LEFT JOIN AT_ort o ON o.okz = p.okz
JOIN v_under_delivery u ON (u.mgnr, u.bucket, u.year) = (m.mgnr, c.bucket, c.year) JOIN v_under_delivery u ON (u.mgnr, u.bucket, u.year) = (m.mgnr, c.bucket, c.year)
WHERE c.year = {year} AND m.active = 1 WHERE c.year = {year}
ORDER BY m.mgnr, c.bucket ORDER BY m.mgnr, c.bucket
""").ToListAsync(); """).ToListAsync();
} }

View File

@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;

View File

@ -33,14 +33,13 @@ namespace Elwig.Models.Dtos {
m.business_shares * s.min_kg_per_bs AS min_kg, m.business_shares * s.min_kg_per_bs AS min_kg,
m.business_shares * s.max_kg_per_bs AS max_kg, m.business_shares * s.max_kg_per_bs AS max_kg,
COALESCE(SUM(d.weight), 0) AS sum COALESCE(SUM(d.weight), 0) AS sum
FROM v_delivery d FROM season s, member m
LEFT JOIN member m ON m.mgnr = d.mgnr
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz LEFT JOIN AT_ort o ON o.okz = p.okz
LEFT JOIN season s ON s.year = d.year LEFT JOIN v_delivery d ON (d.year, d.mgnr) = (s.year, m.mgnr)
WHERE s.year = {year} AND (m.active = TRUE OR d.weight > 0) WHERE s.year = {year} AND (m.active = TRUE OR d.weight > 0)
GROUP BY d.year, m.mgnr GROUP BY s.year, m.mgnr
ORDER BY 100.0 * sum / max_kg, m.mgnr; ORDER BY 100.0 * sum / max_kg, m.mgnr
""").ToListAsync(); """).ToListAsync();
return new OverUnderDeliveryData(rows, year); return new OverUnderDeliveryData(rows, year);
} }

View File

@ -15,7 +15,7 @@ namespace Elwig.Models.Dtos {
public static IEnumerable<Transaction> FromPaymentVariant(PaymentVar variant) { public static IEnumerable<Transaction> FromPaymentVariant(PaymentVar variant) {
return variant.Credits return variant.Credits
.Where(c => c.Member.Iban != null) .Where(c => c.Member.Iban != null && c.Amount > 0)
.OrderBy(c => c.TgNr) .OrderBy(c => c.TgNr)
.Select(c => new Transaction(c)) .Select(c => new Transaction(c))
.ToList(); .ToList();

View File

@ -0,0 +1,60 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static Elwig.Models.Dtos.WineLocalityStatisticsData;
namespace Elwig.Models.Dtos {
class WineLocalityStatisticsData : DataTable<StatisticsRow> {
private static readonly (string, string, string?, int?)[] FieldNames = [
("Branch", "Zwst.", null, 30),
("KgNr", "KgNr.", null, 15),
("Name", "Katastralgemeinde", null, 50),
("Members", "Mitgl.", "#", 15),
("Deliveries", "Lfrg.", "#", 15),
("Parts", "Teill.", "#", 15),
("Weight", "Gewicht", "kg", 20),
("Gradation", "Gradation", "°Oe|°KMW", 30),
];
public record struct StatisticsRow(
string Branch,
int? KgNr,
string? Name,
int Members,
int Deliveries,
int Parts,
int Weight,
(double Oe, double Kmw) Gradation
);
public WineLocalityStatisticsData(IEnumerable<StatisticsRow> rows, List<string> filterNames) :
base("Lieferstatistik pro Ort", "Lieferstatistik pro Ort", string.Join(" / ", filterNames), rows, FieldNames) {
}
public static async Task<WineLocalityStatisticsData> FromQuery(IQueryable<DeliveryPart> query, List<string> filterNames) {
return new((await query
.GroupBy(p => new {
Branch = p.Delivery.Branch.Name,
p.Kg!.KgNr,
Kg = p.Kg!.AtKg.Name,
}, (k, g) => new {
k.Branch,
KgNr = (int?)k.KgNr,
Kg = (string?)k.Kg,
Members = g.Select(p => p.Delivery.Member).Distinct().Count(),
Deliveries = g.Select(p => p.Delivery).Distinct().Count(),
Parts = g.Count(),
Weight = g.Sum(p => p.Weight),
Kmw = g.Sum(p => p.Kmw * p.Weight) / g.Sum(p => p.Weight),
})
.OrderByDescending(g => g.Weight)
.ThenBy(g => g.KgNr)
.ToListAsync()).Select(g => new StatisticsRow(g.Branch, g.KgNr, g.Kg, g.Members, g.Deliveries, g.Parts, g.Weight, (Utils.KmwToOe(g.Kmw), Math.Round(g.Kmw, 1)))), filterNames);
}
}
}

View File

@ -48,6 +48,7 @@ namespace Elwig.Services {
var filterNotVar = new List<string>(); var filterNotVar = new List<string>();
var filterAttr = new List<string>(); var filterAttr = new List<string>();
var filterNotAttr = new List<string>(); var filterNotAttr = new List<string>();
var filterSeasons = new List<int>();
var filter = vm.TextFilter; var filter = vm.TextFilter;
if (filter.Count > 0) { if (filter.Count > 0) {
@ -87,6 +88,10 @@ namespace Elwig.Services {
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"ohne {var[e[1..3].ToUpper()].Name}"); filterNames.Add($"ohne {var[e[1..3].ToUpper()].Name}");
filterNames.Add($"ohne Attribut {attrId[e[3..].ToUpper()].Name}"); filterNames.Add($"ohne Attribut {attrId[e[3..].ToUpper()].Name}");
} else if (e.Length == 4 && int.TryParse(e, out var year)) {
filterSeasons.Add(year);
filter.RemoveAt(i--);
filterNames.Add($"laufend {e}");
} }
} }
@ -94,6 +99,7 @@ namespace Elwig.Services {
if (filterNotVar.Count > 0) areaComQuery = areaComQuery.Where(a => !filterNotVar.Contains(a.AreaComType.WineVar.SortId)); if (filterNotVar.Count > 0) areaComQuery = areaComQuery.Where(a => !filterNotVar.Contains(a.AreaComType.WineVar.SortId));
if (filterAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId != null && filterAttr.Contains(a.AreaComType.WineAttr.AttrId)); if (filterAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId != null && filterAttr.Contains(a.AreaComType.WineAttr.AttrId));
if (filterNotAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId == null || !filterNotAttr.Contains(a.AreaComType.WineAttr.AttrId)); if (filterNotAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId == null || !filterNotAttr.Contains(a.AreaComType.WineAttr.AttrId));
foreach (var year in filterSeasons) areaComQuery = Utils.ActiveAreaCommitments(areaComQuery, year);
} }
return (filterNames, areaComQuery, filter); return (filterNames, areaComQuery, filter);

View File

@ -809,6 +809,38 @@ namespace Elwig.Services {
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
public static async Task GenerateLocalityStatistics(this DeliveryAdminViewModel vm, ExportSubject subject) {
using var ctx = new AppDbContext();
IQueryable<DeliveryPart> query;
List<string> filterNames = [];
if (subject == ExportSubject.FromFilters) {
var (f, _, q, _, _) = await vm.GetFilters(ctx);
query = q;
filterNames.AddRange(f);
} else {
throw new ArgumentException("Invalid value for ExportSubject");
}
var d = new SaveFileDialog() {
FileName = $"Lieferstatistik-{vm.FilterSeason ?? Utils.CurrentLastSeason}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Lieferstatistik pro Ort speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ods = new OdsFile(d.FileName);
var tbl = await WineLocalityStatisticsData.FromQuery(query, filterNames);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
public static async Task GenerateDeliveryDepreciationList(this DeliveryAdminViewModel vm, ExportSubject subject, ExportMode mode) { public static async Task GenerateDeliveryDepreciationList(this DeliveryAdminViewModel vm, ExportSubject subject, ExportMode mode) {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
IQueryable<DeliveryPart> query; IQueryable<DeliveryPart> query;

View File

@ -212,6 +212,7 @@ namespace Elwig.Services {
var filterLfbisNr = new List<string>(); var filterLfbisNr = new List<string>();
var filterUstIdNr = new List<string>(); var filterUstIdNr = new List<string>();
var filterAreaCom = new List<string>(); var filterAreaCom = new List<string>();
var filterNotAreaCom = new List<string>();
var filter = vm.TextFilter; var filter = vm.TextFilter;
if (filter.Count > 0) { if (filter.Count > 0) {
@ -223,7 +224,15 @@ namespace Elwig.Services {
for (int i = 0; i < filter.Count; i++) { for (int i = 0; i < filter.Count; i++) {
var e = filter[i]; var e = filter[i];
if (e.Length >= 5 && e.Length <= 10 && "funktionär".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { if (e.Equals("aktiv", StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsActive);
filter.RemoveAt(i--);
filterNames.Add("aktive Mitglieder");
} else if (e.Equals("!aktiv", StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsActive);
filter.RemoveAt(i--);
filterNames.Add("inaktive Mitglieder");
} else if (e.Length >= 5 && e.Length <= 10 && "funktionär".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsFunktionär); memberQuery = memberQuery.Where(m => m.IsFunktionär);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Funktionäre"); filterNames.Add("Funktionäre");
@ -314,10 +323,22 @@ namespace Elwig.Services {
memberQuery = memberQuery.Where(m => m.TelephoneNumbers.Any(t => t.Number.Replace(" ", "").StartsWith(e))); memberQuery = memberQuery.Where(m => m.TelephoneNumbers.Any(t => t.Number.Replace(" ", "").StartsWith(e)));
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"Tel.-Nr. {e}"); filterNames.Add($"Tel.-Nr. {e}");
} else if (e.Length >= 5 && e.Length <= 14 && "flächenbindung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
filterAreaCom.AddRange(areaComs.Keys);
filter.RemoveAt(i--);
filterNames.Add($"Flächenbindung");
} else if (e.Length >= 6 && e.Length <= 15 && "!flächenbindung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
filterNotAreaCom.AddRange(areaComs.Keys);
filter.RemoveAt(i--);
filterNames.Add($"keine Flächenbindung");
} else if (areaComs.ContainsKey(e.ToUpper())) { } else if (areaComs.ContainsKey(e.ToUpper())) {
filterAreaCom.Add(e.ToUpper()); filterAreaCom.Add(e.ToUpper());
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"Flächenbindung {e.ToUpper()}"); filterNames.Add($"Flächenbindung {e.ToUpper()}");
} else if (e.Length >= 3 && e[0] == '!' && areaComs.ContainsKey(e[1..].ToUpper())) {
filterNotAreaCom.Add(e[1..].ToUpper());
filter.RemoveAt(i--);
filterNames.Add($"ohne Flächenbindung {e[1..].ToUpper()}");
} else if (Validator.CheckLfbisNr(e)) { } else if (Validator.CheckLfbisNr(e)) {
filterLfbisNr.Add(e); filterLfbisNr.Add(e);
filter.RemoveAt(i--); filter.RemoveAt(i--);
@ -337,6 +358,7 @@ namespace Elwig.Services {
if (filterKgNr.Count > 0) memberQuery = memberQuery.Where(m => m.DefaultKgNr != null && filterKgNr.Contains((int)m.DefaultKgNr)); if (filterKgNr.Count > 0) memberQuery = memberQuery.Where(m => m.DefaultKgNr != null && filterKgNr.Contains((int)m.DefaultKgNr));
if (filterZwst.Count > 0) memberQuery = memberQuery.Where(m => m.ZwstId != null && filterZwst.Contains(m.ZwstId)); if (filterZwst.Count > 0) memberQuery = memberQuery.Where(m => m.ZwstId != null && filterZwst.Contains(m.ZwstId));
if (filterAreaCom.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments()).Any(c => filterAreaCom.Contains(c.VtrgId))); if (filterAreaCom.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments()).Any(c => filterAreaCom.Contains(c.VtrgId)));
if (filterNotAreaCom.Count > 0) memberQuery = memberQuery.Where(m => !m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments()).All(c => filterNotAreaCom.Contains(c.VtrgId)));
if (filterLfbisNr.Count > 0) memberQuery = memberQuery.Where(m => m.LfbisNr != null && filterLfbisNr.Contains(m.LfbisNr)); if (filterLfbisNr.Count > 0) memberQuery = memberQuery.Where(m => m.LfbisNr != null && filterLfbisNr.Contains(m.LfbisNr));
if (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr)); if (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr));
} }

View File

@ -8,7 +8,6 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Input; using System.Windows.Input;
namespace Elwig.Windows { namespace Elwig.Windows {
@ -358,22 +357,6 @@ namespace Elwig.Windows {
UpdateComboBox(ortInput); UpdateComboBox(ortInput);
} }
protected static void InitializeDelayTimer(TextBox tb, Action<object, TextChangedEventArgs> handler) {
var timer = new DispatcherTimer {
Interval = TimeSpan.FromMilliseconds(250)
};
timer.Tick += (object? sender, EventArgs evt) => {
timer.Stop();
var (oSender, oEvent) = ((object, TextChangedEventArgs))timer.Tag;
handler(oSender, oEvent);
};
tb.TextChanged += (object sender, TextChangedEventArgs evt) => {
timer.Stop();
timer.Tag = (sender, evt);
timer.Start();
};
}
protected bool InputTextChanged(TextBox input) { protected bool InputTextChanged(TextBox input) {
return InputTextChanged(input, new ValidationResult(true, null)); return InputTextChanged(input, new ValidationResult(true, null));
} }

View File

@ -88,8 +88,9 @@
<LineBreak/> <LineBreak/>
Filtern nach:<LineBreak/> Filtern nach:<LineBreak/>
<Bold>Sorte</Bold>: z.B. GV, zw, RR, ...<LineBreak/> <Bold>Sorte</Bold>: z.B. GV, zw, RR, ...<LineBreak/>
<Bold>Attribut</Bold>: z.B. Kabinett, dac, ... <LineBreak/> <Bold>Attribut</Bold>: z.B. Kabinett, dac, ...<LineBreak/>
<Bold>Flächenbindung</Bold>: z.B. GVK, GVD, ... <Bold>Flächenbindung</Bold>: z.B. GVK, GVD, ...<LineBreak/>
<Bold>Saison</Bold>: z.B. 2020, 2019... (in dieser Saison aktiv)
</TextBlock> </TextBlock>
</TextBox.ToolTip> </TextBox.ToolTip>
</TextBox> </TextBox>

View File

@ -33,7 +33,7 @@ namespace Elwig.Windows {
GstNrInput, AreaInput, AreaComTypeInput, WineCultivationInput GstNrInput, AreaInput, AreaComTypeInput, WineCultivationInput
]; ];
InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ActiveAreaCommitmentInput.Content = ((string)ActiveAreaCommitmentInput.Content).Replace("2020", $"{Utils.CurrentLastSeason}"); ActiveAreaCommitmentInput.Content = ((string)ActiveAreaCommitmentInput.Content).Replace("2020", $"{Utils.CurrentLastSeason}");
} }

View File

@ -10,7 +10,6 @@ using Elwig.Helpers.Billing;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using ScottPlot.Plottables; using ScottPlot.Plottables;
using ScottPlot; using ScottPlot;
using ScottPlot.Control;
namespace Elwig.Windows { namespace Elwig.Windows {
public partial class ChartWindow : ContextWindow { public partial class ChartWindow : ContextWindow {
@ -226,30 +225,8 @@ namespace Elwig.Windows {
DataPlot.Color = ColorUngebunden; DataPlot.Color = ColorUngebunden;
DataPlot.MarkerStyle = new MarkerStyle(MarkerShape.FilledCircle, 9, ColorUngebunden); DataPlot.MarkerStyle = new MarkerStyle(MarkerShape.FilledCircle, 9, ColorUngebunden);
OechslePricePlot.Interaction.Enable(new PlotActions() {
ZoomIn = StandardActions.ZoomIn,
ZoomOut = StandardActions.ZoomOut,
PanUp = StandardActions.PanUp,
PanDown = StandardActions.PanDown,
PanLeft = StandardActions.PanLeft,
PanRight = StandardActions.PanRight,
DragPan = StandardActions.DragPan,
DragZoom = StandardActions.DragZoom,
DragZoomRectangle = StandardActions.DragZoomRectangle,
ZoomRectangleClear = StandardActions.ZoomRectangleClear,
ZoomRectangleApply = StandardActions.ZoomRectangleApply,
AutoScale = StandardActions.AutoScale,
});
//OechslePricePlot.Plot.XAxis.ManualTickSpacing(1);
//OechslePricePlot.Plot.YAxis.ManualTickSpacing(0.1);
OechslePricePlot.Plot.Axes.SetLimits(Math.Min(GraphEntry.MinX, GraphEntry.MinXGeb) - 1, GraphEntry.MaxX + 1, -0.1, 1.5); OechslePricePlot.Plot.Axes.SetLimits(Math.Min(GraphEntry.MinX, GraphEntry.MinXGeb) - 1, GraphEntry.MaxX + 1, -0.1, 1.5);
//OechslePricePlot.Plot.Layout(padding: 0);
//OechslePricePlot.Plot.XAxis2.Layout(padding: 0);
//OechslePricePlot.Plot.YAxis.Layout(padding: 0);
//OechslePricePlot.Plot.YAxis2.Layout(padding: 0);
HighlightedPointPlot = OechslePricePlot.Plot.Add.Marker(0, 0, MarkerShape.OpenCircle, 10, Colors.Red); HighlightedPointPlot = OechslePricePlot.Plot.Add.Marker(0, 0, MarkerShape.OpenCircle, 10, Colors.Red);
HighlightedPointPlot.IsVisible = false; HighlightedPointPlot.IsVisible = false;

View File

@ -110,32 +110,6 @@
<MenuItem x:Name="Menu_DeliveryJournal_PrintToday" Header="...von heute drucken" <MenuItem x:Name="Menu_DeliveryJournal_PrintToday" Header="...von heute drucken"
Click="Menu_DeliveryJournal_PrintToday_Click" InputGestureText="Strg+J"/> Click="Menu_DeliveryJournal_PrintToday_Click" InputGestureText="Strg+J"/>
</MenuItem> </MenuItem>
<MenuItem Header="Qualitätsstatistik" x:Name="Menu_WineQualityStatistics">
<MenuItem x:Name="Menu_WineQualityStatistics_ShowFilters" Header="...aus Filtern anzeigen (PDF)"
Click="Menu_WineQualityStatistics_ShowFilters_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_SavePdfFilters" Header="...aus Filtern speichern... (PDF)"
Click="Menu_WineQualityStatistics_SavePdfFilters_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_PrintFilters" Header="...aus Filtern drucken"
Click="Menu_WineQualityStatistics_PrintFilters_Click"/>
<Separator/>
<MenuItem x:Name="Menu_WineQualityStatistics_ShowToday" Header="...von heute anzeigen (PDF)"
Click="Menu_WineQualityStatistics_ShowToday_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_SavePdfToday" Header="...von heute speichern... (PDF)"
Click="Menu_WineQualityStatistics_SavePdfToday_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_PrintToday" Header="...von heute drucken"
Click="Menu_WineQualityStatistics_PrintToday_Click" InputGestureText="Strg+Q"/>
<Separator/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeOe" Header="...nach °Oe aufschlüsseln" IsCheckable="True" IsChecked="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw10" Header="...nach °KMW aufschlüsseln (&#x2152;)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw5" Header="...nach °KMW aufschlüsseln (&#x2155;)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw2" Header="...nach °KMW aufschlüsseln (&#x00BD;)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw1" Header="...nach °KMW aufschlüsseln (ganze)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
</MenuItem>
<MenuItem Header="Abwertungsliste" x:Name="Menu_DeliveryDepreciationList"> <MenuItem Header="Abwertungsliste" x:Name="Menu_DeliveryDepreciationList">
<MenuItem x:Name="Menu_DeliveryDepreciationList_SaveFilters" Header="...aus Filtern speichern... (Excel)" <MenuItem x:Name="Menu_DeliveryDepreciationList_SaveFilters" Header="...aus Filtern speichern... (Excel)"
Click="Menu_DeliveryDepreciationList_SaveFilters_Click"/> Click="Menu_DeliveryDepreciationList_SaveFilters_Click"/>
@ -155,6 +129,38 @@
<MenuItem x:Name="Menu_DeliveryDepreciationList_PrintSeason" Header="...von Saison drucken" <MenuItem x:Name="Menu_DeliveryDepreciationList_PrintSeason" Header="...von Saison drucken"
Click="Menu_DeliveryDepreciationList_PrintSeason_Click"/> Click="Menu_DeliveryDepreciationList_PrintSeason_Click"/>
</MenuItem> </MenuItem>
<MenuItem Header="Statistik" x:Name="Menu_Statistics">
<MenuItem Header="Qualitätsstatistik..." x:Name="Menu_Statistics_WineQuality">
<MenuItem x:Name="Menu_Statistics_WineQuality_ShowFilters" Header="...aus Filtern anzeigen (PDF)"
Click="Menu_Statistics_WineQuality_ShowFilters_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_SavePdfFilters" Header="...aus Filtern speichern... (PDF)"
Click="Menu_Statistics_WineQuality_SavePdfFilters_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_PrintFilters" Header="...aus Filtern drucken"
Click="Menu_Statistics_WineQuality_PrintFilters_Click"/>
<Separator/>
<MenuItem x:Name="Menu_Statistics_WineQuality_ShowToday" Header="...von heute anzeigen (PDF)"
Click="Menu_Statistics_WineQuality_ShowToday_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_SavePdfToday" Header="...von heute speichern... (PDF)"
Click="Menu_Statistics_WineQuality_SavePdfToday_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_PrintToday" Header="...von heute drucken"
Click="Menu_Statistics_WineQuality_PrintToday_Click" InputGestureText="Strg+Q"/>
<Separator/>
<MenuItem x:Name="Menu_Statistics_WineQuality_ModeOe" Header="...nach °Oe aufschlüsseln" IsCheckable="True" IsChecked="True"
Click="Menu_Statistics_WineQuality_Mode_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_ModeKmw10" Header="...nach °KMW aufschlüsseln (&#x2152;)" IsCheckable="True"
Click="Menu_Statistics_WineQuality_Mode_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_ModeKmw5" Header="...nach °KMW aufschlüsseln (&#x2155;)" IsCheckable="True"
Click="Menu_Statistics_WineQuality_Mode_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_ModeKmw2" Header="...nach °KMW aufschlüsseln (&#x00BD;)" IsCheckable="True"
Click="Menu_Statistics_WineQuality_Mode_Click"/>
<MenuItem x:Name="Menu_Statistics_WineQuality_ModeKmw1" Header="...nach °KMW aufschlüsseln (ganze)" IsCheckable="True"
Click="Menu_Statistics_WineQuality_Mode_Click"/>
</MenuItem>
<MenuItem x:Name="Menu_Statistics_Locality" Header="Lieferstatistik pro Ort...">
<MenuItem x:Name="Menu_Statistic_Locality_SaveFilters" Header="...aus Filtern speichern... (Excel)"
Click="Menu_Statistic_Locality_SaveFilters_Click"/>
</MenuItem>
</MenuItem>
<MenuItem Header="BKI"> <MenuItem Header="BKI">
<MenuItem x:Name="Menu_Bki_SaveList" Header="Traubentransportscheinliste speichern..."/> <MenuItem x:Name="Menu_Bki_SaveList" Header="Traubentransportscheinliste speichern..."/>
</MenuItem> </MenuItem>

View File

@ -41,7 +41,7 @@ namespace Elwig.Windows {
CommandBindings.Add(new CommandBinding(CtrlP, Menu_DeliveryNote_Show_Click)); CommandBindings.Add(new CommandBinding(CtrlP, Menu_DeliveryNote_Show_Click));
CommandBindings.Add(new CommandBinding(CtrlO, Menu_DeliveryJournal_ShowFilters_Click)); CommandBindings.Add(new CommandBinding(CtrlO, Menu_DeliveryJournal_ShowFilters_Click));
CommandBindings.Add(new CommandBinding(CtrlJ, Menu_DeliveryJournal_PrintToday_Click)); CommandBindings.Add(new CommandBinding(CtrlJ, Menu_DeliveryJournal_PrintToday_Click));
CommandBindings.Add(new CommandBinding(CtrlQ, Menu_WineQualityStatistics_PrintToday_Click)); CommandBindings.Add(new CommandBinding(CtrlQ, Menu_Statistics_WineQuality_PrintToday_Click));
CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_DeliveryNote_Print_Click)); CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_DeliveryNote_Print_Click));
CommandBindings.Add(new CommandBinding(CtrlShiftO, Menu_DeliveryJournal_PrintFilters_Click)); CommandBindings.Add(new CommandBinding(CtrlShiftO, Menu_DeliveryJournal_PrintFilters_Click));
RequiredInputs = [ RequiredInputs = [
@ -66,7 +66,7 @@ namespace Elwig.Windows {
SecondsTimer.Tick += new EventHandler(OnSecondPassed); SecondsTimer.Tick += new EventHandler(OnSecondPassed);
SecondsTimer.Interval = new TimeSpan(0, 0, 1); SecondsTimer.Interval = new TimeSpan(0, 0, 1);
InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason; ViewModel.FilterSeason = Utils.CurrentLastSeason;
@ -97,17 +97,17 @@ namespace Elwig.Windows {
WeighingDButton.Visibility = Visibility.Hidden; WeighingDButton.Visibility = Visibility.Hidden;
} }
Menu_WineQualityStatistics_ModeOe.IsChecked = false; Menu_Statistics_WineQuality_ModeOe.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw1.IsChecked = false; Menu_Statistics_WineQuality_ModeKmw1.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw2.IsChecked = false; Menu_Statistics_WineQuality_ModeKmw2.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw5.IsChecked = false; Menu_Statistics_WineQuality_ModeKmw5.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw10.IsChecked = false; Menu_Statistics_WineQuality_ModeKmw10.IsChecked = false;
switch (App.Client.OrderingMemberList) { switch (App.Client.OrderingMemberList) {
case 0: Menu_WineQualityStatistics_ModeOe.IsChecked = true; break; case 0: Menu_Statistics_WineQuality_ModeOe.IsChecked = true; break;
case 1: Menu_WineQualityStatistics_ModeKmw1.IsChecked = true; break; case 1: Menu_Statistics_WineQuality_ModeKmw1.IsChecked = true; break;
case 2: Menu_WineQualityStatistics_ModeKmw2.IsChecked = true; break; case 2: Menu_Statistics_WineQuality_ModeKmw2.IsChecked = true; break;
case 3: Menu_WineQualityStatistics_ModeKmw5.IsChecked = true; break; case 3: Menu_Statistics_WineQuality_ModeKmw5.IsChecked = true; break;
case 4: Menu_WineQualityStatistics_ModeKmw10.IsChecked = true; break; case 4: Menu_Statistics_WineQuality_ModeKmw10.IsChecked = true; break;
} }
Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null; Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null;
@ -218,40 +218,44 @@ namespace Elwig.Windows {
await ViewModel.GenerateDeliveryJournal(DeliveryService.ExportSubject.FromSeasonAndBranch, ExportMode.Upload); await ViewModel.GenerateDeliveryJournal(DeliveryService.ExportSubject.FromSeasonAndBranch, ExportMode.Upload);
} }
private async void Menu_WineQualityStatistics_ShowToday_Click(object sender, RoutedEventArgs evt) => private async void Menu_Statistics_WineQuality_ShowToday_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromToday, ExportMode.Show); await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromToday, ExportMode.Show);
private async void Menu_WineQualityStatistics_SavePdfToday_Click(object sender, RoutedEventArgs evt) => private async void Menu_Statistics_WineQuality_SavePdfToday_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromToday, ExportMode.SavePdf); await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromToday, ExportMode.SavePdf);
private async void Menu_WineQualityStatistics_PrintToday_Click(object sender, RoutedEventArgs evt) => private async void Menu_Statistics_WineQuality_PrintToday_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromToday, ExportMode.Print); await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromToday, ExportMode.Print);
private async void Menu_WineQualityStatistics_ShowFilters_Click(object sender, RoutedEventArgs evt) => private async void Menu_Statistics_WineQuality_ShowFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromFilters, ExportMode.Show); await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromFilters, ExportMode.Show);
private async void Menu_WineQualityStatistics_SavePdfFilters_Click(object sender, RoutedEventArgs evt) => private async void Menu_Statistics_WineQuality_SavePdfFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromFilters, ExportMode.SavePdf); await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromFilters, ExportMode.SavePdf);
private async void Menu_WineQualityStatistics_PrintFilters_Click(object sender, RoutedEventArgs evt) => private async void Menu_Statistics_WineQuality_PrintFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromFilters, ExportMode.Print); await ViewModel.GenerateWineQualityStatistics(DeliveryService.ExportSubject.FromFilters, ExportMode.Print);
private async void Menu_WineQualityStatistics_Mode_Click(object sender, RoutedEventArgs evt) { private async void Menu_Statistics_WineQuality_Mode_Click(object sender, RoutedEventArgs evt) {
Menu_WineQualityStatistics.IsSubmenuOpen = true; Menu_Statistics.IsSubmenuOpen = true;
if (sender == Menu_WineQualityStatistics_ModeOe) { Menu_Statistics_WineQuality.IsSubmenuOpen = true;
if (sender == Menu_Statistics_WineQuality_ModeOe) {
App.Client.OrderingMemberList = 0; App.Client.OrderingMemberList = 0;
} else if (sender == Menu_WineQualityStatistics_ModeKmw1) { } else if (sender == Menu_Statistics_WineQuality_ModeKmw1) {
App.Client.OrderingMemberList = 1; App.Client.OrderingMemberList = 1;
} else if (sender == Menu_WineQualityStatistics_ModeKmw2) { } else if (sender == Menu_Statistics_WineQuality_ModeKmw2) {
App.Client.OrderingMemberList = 2; App.Client.OrderingMemberList = 2;
} else if (sender == Menu_WineQualityStatistics_ModeKmw5) { } else if (sender == Menu_Statistics_WineQuality_ModeKmw5) {
App.Client.OrderingMemberList = 3; App.Client.OrderingMemberList = 3;
} else if (sender == Menu_WineQualityStatistics_ModeKmw10) { } else if (sender == Menu_Statistics_WineQuality_ModeKmw10) {
App.Client.OrderingMemberList = 4; App.Client.OrderingMemberList = 4;
} }
Menu_WineQualityStatistics_ModeOe.IsChecked = App.Client.OrderingMemberList == 0; Menu_Statistics_WineQuality_ModeOe.IsChecked = App.Client.OrderingMemberList == 0;
Menu_WineQualityStatistics_ModeKmw1.IsChecked = App.Client.OrderingMemberList == 1; Menu_Statistics_WineQuality_ModeKmw1.IsChecked = App.Client.OrderingMemberList == 1;
Menu_WineQualityStatistics_ModeKmw2.IsChecked = App.Client.OrderingMemberList == 2; Menu_Statistics_WineQuality_ModeKmw2.IsChecked = App.Client.OrderingMemberList == 2;
Menu_WineQualityStatistics_ModeKmw5.IsChecked = App.Client.OrderingMemberList == 3; Menu_Statistics_WineQuality_ModeKmw5.IsChecked = App.Client.OrderingMemberList == 3;
Menu_WineQualityStatistics_ModeKmw10.IsChecked = App.Client.OrderingMemberList == 4; Menu_Statistics_WineQuality_ModeKmw10.IsChecked = App.Client.OrderingMemberList == 4;
await App.Client.UpdateValues(); await App.Client.UpdateValues();
} }
private async void Menu_Statistic_Locality_SaveFilters_Click(object sender, RoutedEventArgs evt)=>
await ViewModel.GenerateLocalityStatistics(DeliveryService.ExportSubject.FromFilters);
private async void Menu_DeliveryDepreciationList_SaveFilters_Click(object sender, RoutedEventArgs evt) => private async void Menu_DeliveryDepreciationList_SaveFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromFilters, ExportMode.SaveList); await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromFilters, ExportMode.SaveList);
private async void Menu_DeliveryDepreciationList_ShowFilters_Click(object sender, RoutedEventArgs evt) => private async void Menu_DeliveryDepreciationList_ShowFilters_Click(object sender, RoutedEventArgs evt) =>
@ -306,19 +310,23 @@ namespace Elwig.Windows {
UnsetDefaultValue(GerebeltGewogenInput); UnsetDefaultValue(GerebeltGewogenInput);
} }
if (!App.Client.HasNetWeighing(ViewModel.Branch)) { if (App.Client.HasBoxWeighing(ViewModel.Branch)) {
LesewagenInput.IsEnabled = false; LesewagenInput.IsEnabled = false;
SetDefaultValue(LesewagenInput, false); SetDefaultValue(LesewagenInput, false);
HandPickedInput.IsThreeState = false;
UnsetDefaultValue(HandPickedInput);
} else { } else {
LesewagenInput.IsEnabled = true; LesewagenInput.IsEnabled = true;
UnsetDefaultValue(LesewagenInput); UnsetDefaultValue(LesewagenInput);
}
if (!App.Client.HasNetWeighing(ViewModel.Branch)) {
HandPickedInput.IsThreeState = false;
UnsetDefaultValue(HandPickedInput);
} else {
HandPickedInput.IsThreeState = true; HandPickedInput.IsThreeState = true;
SetDefaultValue(HandPickedInput, null); SetDefaultValue(HandPickedInput, null);
} }
if (App.Client.IsMatzen || App.Client.IsWinzerkeller) { if (App.Client.IsMatzen || App.Client.IsWinzerkeller || App.Client.IsBaden || App.Client.IsWeinland) {
GebundenInput.IsEnabled = false; GebundenInput.IsEnabled = false;
SetDefaultValue(GebundenInput, null); SetDefaultValue(GebundenInput, null);
} else { } else {
@ -1376,7 +1384,7 @@ namespace Elwig.Windows {
} }
private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) { private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
if (!App.Client.HasNetWeighing(ViewModel.Branch)) { if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked; HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
} }
if (!ViewModel.IsReceipt || App.Client.HasNetWeighing(ViewModel.Branch)) { if (!ViewModel.IsReceipt || App.Client.HasNetWeighing(ViewModel.Branch)) {
@ -1386,7 +1394,7 @@ namespace Elwig.Windows {
} }
private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) { private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
if (!App.Client.HasNetWeighing(ViewModel.Branch)) { if ((IsEditing || IsCreating) && !App.Client.HasNetWeighing(ViewModel.Branch)) {
GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked; GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
} }
CheckBox_Changed(sender, evt); CheckBox_Changed(sender, evt);

View File

@ -33,7 +33,7 @@ namespace Elwig.Windows {
DoShowWarningWindows = false; DoShowWarningWindows = false;
InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason; ViewModel.FilterSeason = Utils.CurrentLastSeason;
} }

View File

@ -27,7 +27,7 @@ namespace Elwig.Windows {
DateInput, BranchInput, DescriptionInput, MainWineVarietiesInput, DateInput, BranchInput, DescriptionInput, MainWineVarietiesInput,
]; ];
InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason; ViewModel.FilterSeason = Utils.CurrentLastSeason;
} }

View File

@ -36,9 +36,10 @@ namespace Elwig.Windows {
} }
private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) { private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) {
var t = EventList.SelectedItem.GetType(); var item = EventList.SelectedItem;
var t = item.GetType();
var p = t.GetProperty("Event")!; var p = t.GetProperty("Event")!;
EventData.Text = ((EventLogEntry)p.GetValue(EventList.SelectedItem)!).Message; EventData.Text = ((EventLogEntry)p.GetValue(item)!).Message;
} }
} }
} }

View File

@ -0,0 +1,54 @@
<Window x:Class="Elwig.Windows.MailLogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Elwig.Windows"
Title="Ausgangs-Protokoll - Rundschreiben - Elwig" Height="600" Width="1000">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="FilterInput" Margin="10,10,300,10" Height="25" FontSize="14" Padding="2" TextWrapping="NoWrap"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
TextChanged="FilterInput_TextChanged"/>
<ComboBox x:Name="TypeInput" Margin="10,10,165,10" Width="130" Height="25" FontSize="14"
HorizontalAlignment="Right" VerticalAlignment="Top"
SelectionChanged="TypeInput_SelectionChanged">
<ComboBoxItem Content="Post &amp; E-Mail" IsSelected="True"/>
<ComboBoxItem Content="Nur Post"/>
<ComboBoxItem Content="Nur E-Mail"/>
</ComboBox>
<ComboBox x:Name="TimeSpanInput" Margin="10,10,10,10" Width="150" Height="25" FontSize="14"
HorizontalAlignment="Right" VerticalAlignment="Top"
SelectionChanged="TimeSpanInput_SelectionChanged">
<ComboBoxItem Content="letzten 7 Tage" IsSelected="True"/>
<ComboBoxItem Content="letzten 6 Monate"/>
<ComboBoxItem Content="Immer"/>
</ComboBox>
<DataGrid x:Name="MailList" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Single"
CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False"
SelectionChanged="MailList_SelectionChanged"
Margin="10,40,5,10">
<DataGrid.Columns>
<DataGridTextColumn Header="Zeitpunkt" Binding="{Binding DateTime, StringFormat='{}{0:dd.MM.yyyy HH:mm:ss}'}" Width="120"/>
<DataGridTextColumn Header="Typ" Binding="{Binding Type}" Width="50"/>
<DataGridTextColumn Header="MgNr." Binding="{Binding MgNr}" Width="50"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="200"/>
<DataGridTextColumn Header="Adressen" Binding="{Binding Addresses}" Width="200"/>
<DataGridTextColumn Header="Betreff" Binding="{Binding Subject}" Width="250"/>
<DataGridTextColumn Header="Anhänge" Binding="{Binding Attachments}" Width="200"/>
</DataGrid.Columns>
</DataGrid>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<TextBox x:Name="MailData" Grid.Column="2" Margin="5,10,10,10" IsReadOnly="True"
HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible"/>
</Grid>
</Window>

View File

@ -0,0 +1,85 @@
using Elwig.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace Elwig.Windows {
public partial class MailLogWindow : Window {
record struct Row (DateTime DateTime, string Type, int MgNr, string Name, string Addresses, string Subject, string Attachments);
private List<Row> Data = [];
public MailLogWindow() {
InitializeComponent();
WindowState = WindowState.Maximized;
ControlUtils.InitializeDelayTimer(FilterInput, FilterInput_TextChanged);
FilterInput.TextChanged -= FilterInput_TextChanged;
}
private async void TimeSpanInput_SelectionChanged(object sender, RoutedEventArgs evt) {
if (!IsLoaded) return;
DateTime? fromDate = DateTime.Now;
if (TimeSpanInput.SelectedIndex == 0) {
fromDate = fromDate.Value.AddDays(-7);
} else if (TimeSpanInput.SelectedIndex == 1) {
fromDate = fromDate.Value.AddMonths(-6);
} else {
fromDate = null;
}
var mails = await Utils.GetSentMails(fromDate: fromDate == null ? null : DateOnly.FromDateTime(fromDate.Value));
Data = mails.Reverse().Select(m => new Row(
m.DateTime,
m.Type == "email" ? "E-Mail" : m.Type == "postal" ? "Post" : "?",
m.MgNr,
m.Name,
string.Join("\n", m.Addresses),
m.Subject,
string.Join("\n", m.Attachments)
)).ToList();
MailList.ItemsSource = Data;
ApplyFilters();
}
private void FilterInput_TextChanged(object sender, RoutedEventArgs evt) {
ApplyFilters();
}
private void TypeInput_SelectionChanged(object sender, RoutedEventArgs evt) {
ApplyFilters();
}
private void ApplyFilters() {
if (!IsLoaded) return;
var filters = FilterInput.Text.Split(' ');
IEnumerable<Row> data = Data;
switch (TypeInput.SelectedIndex) {
case 1: data = data.Where(d => d.Type == "Post"); break;
case 2: data = data.Where(d => d.Type == "E-Mail"); break;
}
foreach (var filter in filters) {
if (int.TryParse(filter, out var mgnr)) {
data = data.Where(d => d.MgNr == mgnr);
} else {
var f = filter.ToLower();
data = data.Where(d => d.Name.Contains(f, StringComparison.CurrentCultureIgnoreCase) ||
d.Addresses.Contains(f, StringComparison.CurrentCultureIgnoreCase) ||
d.Subject.Contains(f, StringComparison.CurrentCultureIgnoreCase) ||
d.Attachments.Contains(f, StringComparison.CurrentCultureIgnoreCase));
}
}
if (IsLoaded)
MailList.ItemsSource = data.ToList();
}
private async void MailList_SelectionChanged(object sender, RoutedEventArgs evt) {
if (MailList.SelectedItem is not Row item) return;
if (item.Type == "E-Mail") {
MailData.Text = await Utils.FindSentMailBody(item.DateTime);
} else {
MailData.Text = "";
}
}
}
}

View File

@ -27,281 +27,334 @@
<Setter Property="TextWrapping" Value="NoWrap"/> <Setter Property="TextWrapping" Value="NoWrap"/>
</Style> </Style>
</Window.Resources> </Window.Resources>
<TabControl x:Name="TabControl" BorderThickness="0" PreviewDragOver="Document_PreviwDragOver" AllowDrop="True" Drop="Document_Drop"> <Grid>
<TabItem Header="Dokumente" Visibility="Collapsed"> <Grid.RowDefinitions>
<Grid> <RowDefinition Height="19"/>
<Grid.ColumnDefinitions> <RowDefinition Height="1*"/>
<ColumnDefinition Width="*"/> <RowDefinition Height="24"/>
<ColumnDefinition Width="320"/> </Grid.RowDefinitions>
</Grid.ColumnDefinitions>
<Grid Height="200" VerticalAlignment="Top" HorizontalAlignment="Stretch"> <Menu BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Hilfe">
<MenuItem x:Name="Menu_Help_Log" Header="Ausgangs-Protokoll anzeigen"
Click="Menu_Help_Log_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF168;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
<TabControl x:Name="TabControl" BorderThickness="0" Grid.Row="1"
PreviewDragOver="Document_PreviwDragOver" AllowDrop="True" Drop="Document_Drop">
<TabItem Header="Dokumente" Visibility="Collapsed">
<Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/> <ColumnDefinition Width="320"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Label Content="Verfügbare Dokumente" <Grid Height="200" VerticalAlignment="Top" HorizontalAlignment="Stretch">
Grid.Column="0" Margin="10,8,10,10"/>
<ListBox x:Name="AvaiableDocumentsList"
Grid.Column="0" Margin="10,30,10,10"
SelectionChanged="AvaiableDocumentsList_SelectionChanged"/>
<Button x:Name="DocumentAddButton" Content="&#xF0AF;" FontFamily="Segoe MDL2 Assets" FontSize="14"
Grid.Column="1" Margin="0,0,0,30" VerticalAlignment="Center" Height="25" IsEnabled="False"
Click="DocumentAddButton_Click"/>
<Button x:Name="DocumentRemoveButton" Content="&#xE74D;" FontFamily="Segoe MDL2 Assets" FontSize="16" Padding="1.5,0,0,0"
Grid.Column="1" Margin="0,30,0,0" VerticalAlignment="Center" Height="25" IsEnabled="False"
Click="DocumentRemoveButton_Click"/>
<Label Content="Ausgewählte Dokumente"
Grid.Column="2" Margin="10,8,10,10"/>
<ListBox x:Name="SelectedDocumentsList" DisplayMemberPath="Name"
Grid.Column="2" Margin="10,30,10,37"
SelectionChanged="SelectedDocumentsList_SelectionChanged">
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding Path=DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>
</ListBox.InputBindings>
</ListBox>
<Button x:Name="SelectDocumentButton" Content="Durchsuchen..."
Grid.Column="2" VerticalAlignment="Bottom" Margin="10,10,10,10" Height="22"
Click="SelectDocumentButton_Click"/>
</Grid>
<GroupBox x:Name="DocumentBox" Header="Dokument" Margin="10,170,10,47" HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<CheckBox x:Name="DocumentNonDeliverersInput" Content="Auch Nicht-Lieferanten miteinbeziehen" <Label Content="Verfügbare Dokumente"
Margin="10,10,10,10" Grid.Column="1"/> Grid.Column="0" Margin="10,8,10,10"/>
<ListBox x:Name="AvaiableDocumentsList"
Grid.Column="0" Margin="10,30,10,10"
SelectionChanged="AvaiableDocumentsList_SelectionChanged"/>
<Label x:Name="DocumentFooterLabel" Content="Fußtext:" Margin="10,40,0,10"/> <Button x:Name="DocumentAddButton" Content="&#xF0AF;" FontFamily="Segoe MDL2 Assets" FontSize="14"
<TextBox x:Name="DeliveryConfirmationFooterInput" Grid.Column="1" Grid.Column="1" Margin="0,0,0,30" VerticalAlignment="Center" Height="25" IsEnabled="False"
Margin="0,40,10,10" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Click="DocumentAddButton_Click"/>
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" <Button x:Name="DocumentRemoveButton" Content="&#xE74D;" FontFamily="Segoe MDL2 Assets" FontSize="16" Padding="1.5,0,0,0"
TextChanged="DocumentInput_TextChanged"/> Grid.Column="1" Margin="0,30,0,0" VerticalAlignment="Center" Height="25" IsEnabled="False"
<TextBox x:Name="CreditNoteFooterInput" Grid.Column="1" Click="DocumentRemoveButton_Click"/>
Margin="0,10,10,10" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap" <Label Content="Ausgewählte Dokumente"
TextChanged="DocumentInput_TextChanged"/> Grid.Column="2" Margin="10,8,10,10"/>
<ListBox x:Name="SelectedDocumentsList" DisplayMemberPath="Name"
Grid.Column="2" Margin="10,30,10,37"
SelectionChanged="SelectedDocumentsList_SelectionChanged">
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding Path=DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>
</ListBox.InputBindings>
</ListBox>
<Button x:Name="SelectDocumentButton" Content="Durchsuchen..."
Grid.Column="2" VerticalAlignment="Bottom" Margin="10,10,10,10" Height="22"
Click="SelectDocumentButton_Click"/>
</Grid> </Grid>
</GroupBox>
<TextBox x:Name="PostalLocation" Grid.Column="1" TextChanged="PostalLocation_TextChanged" <GroupBox x:Name="DocumentBox" Header="Dokument" Margin="10,170,10,47" HorizontalAlignment="Stretch">
Margin="10,30,10,10" Width="120" HorizontalAlignment="Left"/> <Grid>
<Label Content=", am" Margin="130,30,10,10" FontSize="14" Grid.Column="1"/> <Grid.ColumnDefinitions>
<TextBox x:Name="PostalDate" Grid.Column="1" Text="01.01.2020" <ColumnDefinition Width="70"/>
Margin="162,30,10,10" Width="78" HorizontalAlignment="Left" <ColumnDefinition Width="*"/>
TextChanged="Date_TextChanged" LostFocus="Date_LostFocus"/> </Grid.ColumnDefinitions>
<GroupBox Header="Adressaten" Margin="10,70,10,47" Grid.Column="1"> <CheckBox x:Name="DocumentNonDeliverersInput" Content="Auch Nicht-Lieferanten miteinbeziehen"
<Grid> Margin="10,10,10,10" Grid.Column="1"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsActiveMembersInput" Content="aktive Mitglieder"
Margin="10,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsAreaComMembersInput" Content="Mitglieder mit Flächenbindung"
Margin="10,30,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsDeliveryAncmtMembersInput" Content="Mitglieder mit Anmeldung"
Margin="10,50,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsDeliveryMembersInput" Content="Lieferanten der Saison"
Margin="10,70,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsNonDeliveryMembersInput" Content="Nicht-Lieferanten der Saison"
Margin="10,90,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsCustomInput" Content="Benutzerdefiniert"
Margin="10,110,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<Label Content="Zwst.:" x:Name="MemberBranchLabel" Margin="10,140,0,10"/> <Label x:Name="DocumentFooterLabel" Content="Fußtext:" Margin="10,40,0,10"/>
<ctrl:CheckComboBox x:Name="MemberBranchInput" AllItemsSelectedContent="Alle Stammzweigstellen" Delimiter=", " DisplayMemberPath="Name" <TextBox x:Name="DeliveryConfirmationFooterInput" Grid.Column="1"
Margin="50,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25" Margin="0,40,10,10" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
SelectionChanged="MemberInput_SelectionChanged"/> AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap"
TextChanged="DocumentInput_TextChanged"/>
<TextBox x:Name="CreditNoteFooterInput" Grid.Column="1"
Margin="0,10,10,10" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap"
TextChanged="DocumentInput_TextChanged"/>
</Grid>
</GroupBox>
<Label Content="Gem.:" x:Name="MemberKgLabel" Margin="10,170,0,10"/> <TextBox x:Name="PostalLocation" Grid.Column="1" TextChanged="PostalLocation_TextChanged"
<ctrl:CheckComboBox x:Name="MemberKgInput" AllItemsSelectedContent="Alle Stammgemeinden" Delimiter=", " DisplayMemberPath="Name" Margin="10,30,10,10" Width="120" HorizontalAlignment="Left"/>
IsSelectAllActive="True" SelectAllContent="Alle Stammgemeinden" <Label Content=", am" Margin="130,30,10,10" FontSize="14" Grid.Column="1"/>
Margin="50,170,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25" <TextBox x:Name="PostalDate" Grid.Column="1" Text="01.01.2020"
SelectionChanged="MemberInput_SelectionChanged"/> Margin="162,30,10,10" Width="78" HorizontalAlignment="Left"
TextChanged="Date_TextChanged" LostFocus="Date_LostFocus"/>
<Label Content="Bio-Betrieb:" x:Name="MemberOrganicLabel" Margin="10,200,0,10"/> <GroupBox Header="Adressaten" Margin="10,70,10,47" Grid.Column="1">
<RadioButton x:Name="MemberOrganicYesInput" Content="Ja" GroupName="MemberOrganic" <Grid>
Margin="80,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" <RadioButton GroupName="Recipients" x:Name="RecipientsActiveMembersInput" Content="aktive Mitglieder"
Checked="MemberInput_Checked"/> Margin="10,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
<RadioButton x:Name="MemberOrganicNoInput" Content="Nein" GroupName="MemberOrganic" Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
Margin="125,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" <RadioButton GroupName="Recipients" x:Name="RecipientsAreaComMembersInput" Content="Mitglieder mit Flächenbindung"
Checked="MemberInput_Checked"/> Margin="10,30,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
<RadioButton x:Name="MemberOrganicIndifferentInput" Content="Egal" GroupName="MemberOrganic" Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
Margin="180,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" <RadioButton GroupName="Recipients" x:Name="RecipientsDeliveryAncmtMembersInput" Content="Mitglieder mit Anmeldung"
Checked="MemberInput_Checked"/> Margin="10,50,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsDeliveryMembersInput" Content="Lieferanten der Saison"
Margin="10,70,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsNonDeliveryMembersInput" Content="Nicht-Lieferanten der Saison"
Margin="10,90,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<RadioButton GroupName="Recipients" x:Name="RecipientsCustomInput" Content="Benutzerdefiniert"
Margin="10,110,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="RecipientsInput_Changed" Unchecked="RecipientsInput_Changed"/>
<Label Content="Funktionär:" x:Name="MemberFunktionärLabel" Margin="10,230,0,10"/> <Label Content="Zwst.:" x:Name="MemberBranchLabel" Margin="10,140,0,10"/>
<RadioButton x:Name="MemberFunktionärYesInput" Content="Ja" GroupName="MemberFunktionär" <ctrl:CheckComboBox x:Name="MemberBranchInput" AllItemsSelectedContent="Alle Stammzweigstellen" Delimiter=", " DisplayMemberPath="Name"
Margin="80,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="50,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
Checked="MemberInput_Checked"/> SelectionChanged="MemberInput_SelectionChanged"/>
<RadioButton x:Name="MemberFunktionärNoInput" Content="Nein" GroupName="MemberFunktionär"
Margin="125,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<RadioButton x:Name="MemberFunktionärIndifferentInput" Content="Egal" GroupName="MemberFunktionär"
Margin="180,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<Label Content="Vtrg.:" x:Name="MemberAreaComLabel" Margin="10,260,0,10"/> <Label Content="Gem.:" x:Name="MemberKgLabel" Margin="10,170,0,10"/>
<ctrl:CheckComboBox x:Name="MemberAreaComInput" AllItemsSelectedContent="Alle Vertragsarten" Delimiter=", " DisplayMemberPath="VtrgId" <ctrl:CheckComboBox x:Name="MemberKgInput" AllItemsSelectedContent="Alle Stammgemeinden" Delimiter=", " DisplayMemberPath="Name"
IsSelectAllActive="True" SelectAllContent="Alle Vertragsarten" IsSelectAllActive="True" SelectAllContent="Alle Stammgemeinden"
Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25" Margin="50,170,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/> SelectionChanged="MemberInput_SelectionChanged"/>
<Label Content="Tag:" x:Name="MemberDeliveryAncmtLabel" Margin="10,260,0,10"/> <Label Content="Bio-Betrieb:" x:Name="MemberOrganicLabel" Margin="10,200,0,10"/>
<ctrl:CheckComboBox x:Name="MemberDeliveryAncmtInput" AllItemsSelectedContent="Alle Lesepläne" Delimiter=", " DisplayMemberPath="Identifier" <RadioButton x:Name="MemberOrganicYesInput" Content="Ja" GroupName="MemberOrganic"
IsSelectAllActive="True" SelectAllContent="Alle Lesepläne" Margin="80,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25" Checked="MemberInput_Checked"/>
SelectionChanged="MemberInput_SelectionChanged"/> <RadioButton x:Name="MemberOrganicNoInput" Content="Nein" GroupName="MemberOrganic"
Margin="125,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<RadioButton x:Name="MemberOrganicIndifferentInput" Content="Egal" GroupName="MemberOrganic"
Margin="180,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<ctrl:CheckComboBox x:Name="MemberCustomInput" AllItemsSelectedContent="Alle Mitglieder" Delimiter=", " DisplayMemberPath="AdministrativeName" <Label Content="Funktionär:" x:Name="MemberFunktionärLabel" Margin="10,230,0,10"/>
IsSelectAllActive="True" SelectAllContent="Alle Mitglieder" <RadioButton x:Name="MemberFunktionärYesInput" Content="Ja" GroupName="MemberFunktionär"
Margin="10,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25" Margin="80,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
SelectionChanged="MemberInput_SelectionChanged"/> Checked="MemberInput_Checked"/>
</Grid> <RadioButton x:Name="MemberFunktionärNoInput" Content="Nein" GroupName="MemberFunktionär"
</GroupBox> Margin="125,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<RadioButton x:Name="MemberFunktionärIndifferentInput" Content="Egal" GroupName="MemberFunktionär"
Margin="180,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<Button x:Name="ContinueButton" Content="Weiter" Grid.Column="1" <Label Content="Vtrg.:" x:Name="MemberAreaComLabel" Margin="10,260,0,10"/>
Margin="10,10,10,10" Height="27" Width="100" Padding="9,3" FontSize="14" <ctrl:CheckComboBox x:Name="MemberAreaComInput" AllItemsSelectedContent="Alle Vertragsarten" Delimiter=", " DisplayMemberPath="VtrgId"
VerticalAlignment="Bottom" HorizontalAlignment="Right" IsSelectAllActive="True" SelectAllContent="Alle Vertragsarten"
Click="ContinueButton_Click"/> Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
</Grid> SelectionChanged="MemberInput_SelectionChanged"/>
</TabItem>
<TabItem Header="Absenden" Visibility="Collapsed"> <Label Content="Tag:" x:Name="MemberDeliveryAncmtLabel" Margin="10,260,0,10"/>
<Grid> <ctrl:CheckComboBox x:Name="MemberDeliveryAncmtInput" AllItemsSelectedContent="Alle Lesepläne" Delimiter=", " DisplayMemberPath="Identifier"
<Grid.ColumnDefinitions> IsSelectAllActive="True" SelectAllContent="Alle Lesepläne"
<ColumnDefinition Width="*"/> Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
<ColumnDefinition Width="1.5*"/> SelectionChanged="MemberInput_SelectionChanged"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<GroupBox Header="Post" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,5,10" Grid.Column="0"> <ctrl:CheckComboBox x:Name="MemberCustomInput" AllItemsSelectedContent="Alle Mitglieder" Delimiter=", " DisplayMemberPath="AdministrativeName"
<Grid> IsSelectAllActive="True" SelectAllContent="Alle Mitglieder"
<GroupBox Header="Zusenden an..." Margin="10,10,10,10" Height="150" Width="220" VerticalAlignment="Top" HorizontalAlignment="Left"> Margin="10,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
<StackPanel> SelectionChanged="MemberInput_SelectionChanged"/>
<RadioButton x:Name="PostalAllInput" Margin="10,10,10,2.5" Click="PostalInput_Changed"> </Grid>
<TextBlock> </GroupBox>
...alle (<Run Text="{Binding Path=PostalAllCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="PostalWishInput" Margin="10,2.5,10,2.5" Click="PostalInput_Changed" IsChecked="True">
<TextBlock>
...Mitglieder, die Zusendung<LineBreak/>
per Post wünschen (<Run Text="{Binding Path=PostalWishCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="PostalNoEmailInput" Margin="10,2.5,10,2.5" Click="PostalInput_Changed">
<TextBlock>
...Mitglieder, die keine<LineBreak/>
E-Mail erhalten würden (<Run Text="{Binding Path=PostalNoEmailCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="PostalNobodyInput" Margin="10,2.5,10,10" Content="...niemanden (0)" Click="PostalInput_Changed"/>
</StackPanel>
</GroupBox>
<GroupBox Header="Sortieren nach" Margin="10,180,10,10" Width="180" Height="80" VerticalAlignment="Top" HorizontalAlignment="Left"> <Button x:Name="ContinueButton" Content="Weiter" Grid.Column="1"
<StackPanel Margin="5,5,0,5"> Margin="10,10,10,10" Height="27" Width="100" Padding="9,3" FontSize="14"
<RadioButton GroupName="Order" x:Name="OrderMgNrInput" Content="Mitgliedsnummer" Click="OrderInput_Changed" IsChecked="True"/> VerticalAlignment="Bottom" HorizontalAlignment="Right"
<RadioButton GroupName="Order" x:Name="OrderNameInput" Content="Name" Click="OrderInput_Changed"/> Click="ContinueButton_Click"/>
<RadioButton GroupName="Order" x:Name="OrderPlzInput" Content="PLZ, Ort, Name" Click="OrderInput_Changed"/> </Grid>
</StackPanel> </TabItem>
</GroupBox>
<CheckBox x:Name="DoublePagedInput" Margin="20,270,10,10" Content="Doppelseitig drucken" <TabItem Header="Absenden" Visibility="Collapsed">
VerticalAlignment="Top" HorizontalAlignment="Left" <Grid>
Checked="DoublePagedInput_Changed" Unchecked="DoublePagedInput_Changed"/>
<TextBox x:Name="PostalSender1" TextChanged="PostalSender_TextChanged" IsEnabled="False"
Margin="10,300,10,10"/>
<TextBox x:Name="PostalSender2" TextChanged="PostalSender_TextChanged"
Margin="10,330,10,10"/>
</Grid>
</GroupBox>
<GroupBox Header="E-Mail" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5,10,10,10" Grid.Column="1">
<Grid>
<GroupBox Header="Zusenden an..." Margin="80,10,10,10" Width="220" Height="110" VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel>
<RadioButton x:Name="EmailAllInput" Margin="10,10,10,2.5" Checked="EmailInput_Changed">
<TextBlock>
...alle mit E-Mail-Adressen (<Run Text="{Binding Path=EmailAllCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="EmailWishInput" Margin="10,2.5,10,2.5" IsChecked="True" Checked="EmailInput_Changed">
<TextBlock>
...Mitglieder, die Zusendung<LineBreak/>
per E-Mail wünschen (<Run Text="{Binding Path=EmailWishCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="EmailNobodyInput" Margin="10,2.5,10,10" Content="...niemanden (0)" Checked="EmailInput_Changed"/>
</StackPanel>
</GroupBox>
<Label Content="Betreff:" Margin="10,130,10,10"/>
<TextBox x:Name="EmailSubjectInput" Margin="80,130,10,10"/>
<Label Content="Nachricht:" Margin="10,160,10,10"/>
<TextBox x:Name="EmailBodyInput"
Margin="80,160,10,10" VerticalAlignment="Stretch" Height="Auto"
TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"/>
</Grid>
</GroupBox>
<Grid Grid.Row="1" Grid.ColumnSpan="2" Width="400" Height="59">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/> <ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="0.4*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="0.6*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="5"/> <RowDefinition Height="80"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Button x:Name="GenerateButton" Content="Generieren" <GroupBox Header="Post" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,5,10" Grid.Column="0">
Grid.Row="0" Grid.Column="0" FontSize="14" <Grid>
Click="GenerateButton_Click"/> <GroupBox Header="Zusenden an..." Margin="10,10,10,10" Height="150" Width="220" VerticalAlignment="Top" HorizontalAlignment="Left">
<ProgressBar x:Name="ProgressBar" <StackPanel>
Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True"/> <RadioButton x:Name="PostalAllInput" Margin="10,10,10,2.5" Click="PostalInput_Changed">
<TextBlock>
...alle (<Run Text="{Binding Path=PostalAllCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="PostalWishInput" Margin="10,2.5,10,2.5" Click="PostalInput_Changed" IsChecked="True">
<TextBlock>
...Mitglieder, die Zusendung<LineBreak/>
per Post wünschen (<Run Text="{Binding Path=PostalWishCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="PostalNoEmailInput" Margin="10,2.5,10,2.5" Click="PostalInput_Changed">
<TextBlock>
...Mitglieder, die keine<LineBreak/>
E-Mail erhalten würden (<Run Text="{Binding Path=PostalNoEmailCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="PostalNobodyInput" Margin="10,2.5,10,10" Content="...niemanden (0)" Click="PostalInput_Changed"/>
</StackPanel>
</GroupBox>
<Button x:Name="PreviewButton" Content="Vorschau" IsEnabled="False" <GroupBox Header="Sortieren nach" Margin="10,180,10,10" Width="180" Height="80" VerticalAlignment="Top" HorizontalAlignment="Left">
Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" FontSize="14" <StackPanel Margin="5,5,0,5">
Click="PreviewButton_Click"/> <RadioButton GroupName="Order" x:Name="OrderMgNrInput" Content="Mitgliedsnummer" Click="OrderInput_Changed" IsChecked="True"/>
<Button x:Name="PrintButton" Content="Drucken" IsEnabled="False" <RadioButton GroupName="Order" x:Name="OrderNameInput" Content="Name" Click="OrderInput_Changed"/>
Grid.Row="2" Grid.Column="2" FontSize="14" <RadioButton GroupName="Order" x:Name="OrderPlzInput" Content="PLZ, Ort, Name" Click="OrderInput_Changed"/>
Click="PrintButton_Click"/> </StackPanel>
<Button x:Name="EmailButton" Content="E-Mails verschicken" IsEnabled="False" </GroupBox>
Grid.Row="2" Grid.Column="4" FontSize="14"
Click="EmailButton_Click"/> <CheckBox x:Name="DoublePagedInput" Margin="20,270,10,10" Content="Doppelseitig drucken"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="DoublePagedInput_Changed" Unchecked="DoublePagedInput_Changed"/>
<TextBox x:Name="PostalSender1" TextChanged="PostalSender_TextChanged" IsEnabled="False"
Margin="10,300,10,10"/>
<TextBox x:Name="PostalSender2" TextChanged="PostalSender_TextChanged"
Margin="10,330,10,10"/>
</Grid>
</GroupBox>
<GroupBox Header="E-Mail" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5,10,10,10" Grid.Column="1">
<Grid>
<GroupBox Header="Zusenden an..." Margin="80,10,10,10" Width="220" Height="110" VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel>
<RadioButton x:Name="EmailAllInput" Margin="10,10,10,2.5" Checked="EmailInput_Changed">
<TextBlock>
...alle mit E-Mail-Adressen (<Run Text="{Binding Path=EmailAllCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="EmailWishInput" Margin="10,2.5,10,2.5" IsChecked="True" Checked="EmailInput_Changed">
<TextBlock>
...Mitglieder, die Zusendung<LineBreak/>
per E-Mail wünschen (<Run Text="{Binding Path=EmailWishCount, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MailWindow}}}"/>)
</TextBlock>
</RadioButton>
<RadioButton x:Name="EmailNobodyInput" Margin="10,2.5,10,10" Content="...niemanden (0)" Checked="EmailInput_Changed"/>
</StackPanel>
</GroupBox>
<Label Content="Betreff:" Margin="10,130,10,10"/>
<TextBox x:Name="EmailSubjectInput" Margin="80,130,10,10"/>
<Label Content="Nachricht:" Margin="10,160,10,10"/>
<TextBox x:Name="EmailBodyInput"
Margin="80,160,10,10" VerticalAlignment="Stretch" Height="Auto"
TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"/>
</Grid>
</GroupBox>
<Grid Grid.Row="1" Grid.ColumnSpan="2" Width="400" Height="59">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="0.4*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="0.6*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="GenerateButton" Content="Generieren"
Grid.Row="0" Grid.Column="0" FontSize="14"
Click="GenerateButton_Click"/>
<ProgressBar x:Name="ProgressBar"
Grid.Row="2" Grid.Column="0" SnapsToDevicePixels="True"/>
<Button x:Name="PreviewButton" Content="Vorschau" IsEnabled="False"
Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" FontSize="14"
Click="PreviewButton_Click"/>
<Button x:Name="PrintButton" Content="Drucken" IsEnabled="False"
Grid.Row="2" Grid.Column="2" FontSize="14"
Click="PrintButton_Click"/>
<Button x:Name="EmailButton" Content="E-Mails verschicken" IsEnabled="False"
Grid.Row="2" Grid.Column="4" FontSize="14"
Click="EmailButton_Click"/>
</Grid>
<Button x:Name="BackButton" Content="Zurück" Grid.Row="1"
Margin="10,10,10,10" Height="27" Width="100" Padding="9,3" FontSize="14"
VerticalAlignment="Bottom" HorizontalAlignment="Left"
Click="BackButton_Click"/>
</Grid> </Grid>
</TabItem>
</TabControl>
<Button x:Name="BackButton" Content="Zurück" Grid.Row="1" <StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray">
Margin="10,10,10,10" Height="27" Width="100" Padding="9,3" FontSize="14" <StatusBar.ItemsPanel>
VerticalAlignment="Bottom" HorizontalAlignment="Left" <ItemsPanelTemplate>
Click="BackButton_Click"/> <Grid>
</Grid> <Grid.ColumnDefinitions>
</TabItem> <ColumnDefinition Width="1*"/>
</TabControl> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock>
Adressaten: <Run x:Name="StatusRecipients" Text="0"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="1"/>
<StatusBarItem Grid.Column="2">
<TextBlock>
Adressaten (Post): <Run x:Name="StatusPostalRecipients" Text="0"/>
</TextBlock>
</StatusBarItem>
<Separator Grid.Column="3"/>
<StatusBarItem Grid.Column="4">
<TextBlock>
Adressaten (E-Mail): <Run x:Name="StatusEmailRecipients" Text="0"/>
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</local:ContextWindow> </local:ContextWindow>

View File

@ -58,6 +58,7 @@ namespace Elwig.Windows {
public IEnumerable<Member> Recipients = []; public IEnumerable<Member> Recipients = [];
protected Document? PrintDocument; protected Document? PrintDocument;
protected Dictionary<Member, List<Document>>? PrintMemberDocuments;
protected Dictionary<Member, List<Document>>? EmailDocuments; protected Dictionary<Member, List<Document>>? EmailDocuments;
public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow)); public static readonly DependencyProperty PostalAllCountProperty = DependencyProperty.Register(nameof(PostalAllCount), typeof(int), typeof(MailWindow));
@ -122,6 +123,26 @@ namespace Elwig.Windows {
DeliveryConfirmationFooterInput.Text = App.Client.TextDeliveryConfirmation; DeliveryConfirmationFooterInput.Text = App.Client.TextDeliveryConfirmation;
CreditNoteFooterInput.Text = App.Client.TextCreditNote; CreditNoteFooterInput.Text = App.Client.TextCreditNote;
DocumentNonDeliverersInput.IsChecked = App.Client.MailIncludeNonDeliverers;
DoublePagedInput.IsChecked = App.Client.MailDoublePaged;
switch (App.Client.MailSendPostal) {
case 0: PostalNobodyInput.IsChecked = true; break;
case 1: PostalNoEmailInput.IsChecked = true; break;
case 2: PostalWishInput.IsChecked = true; break;
case 3: PostalAllInput.IsChecked = true; break;
}
switch (App.Client.MailSendEmail) {
case 0: EmailNobodyInput.IsChecked = true; break;
case 1: EmailWishInput.IsChecked = true; break;
case 2: EmailAllInput.IsChecked = true; break;
}
switch (App.Client.MailOrdering) {
case 0: OrderMgNrInput.IsChecked = true; break;
case 1: OrderNameInput.IsChecked = true; break;
case 2: OrderPlzInput.IsChecked = true; break;
}
PostalSender1.Text = App.Client.Sender1; PostalSender1.Text = App.Client.Sender1;
PostalSender2.Text = App.Client.Sender2; PostalSender2.Text = App.Client.Sender2;
PostalLocation.Text = App.BranchLocation; PostalLocation.Text = App.BranchLocation;
@ -242,6 +263,11 @@ namespace Elwig.Windows {
rb.IsEnabled = true; rb.IsEnabled = true;
} }
private void Menu_Help_Log_Click(object sender, RoutedEventArgs evt) {
var w = new MailLogWindow();
w.Show();
}
private void ContinueButton_Click(object sender, RoutedEventArgs evt) { private void ContinueButton_Click(object sender, RoutedEventArgs evt) {
TabControl.SelectedIndex = 1; TabControl.SelectedIndex = 1;
TabControl.AllowDrop = false; TabControl.AllowDrop = false;
@ -312,7 +338,7 @@ namespace Elwig.Windows {
if (idx == 0) { if (idx == 0) {
SelectedDocs.Add(new(DocType.MemberDataSheet, s, null)); SelectedDocs.Add(new(DocType.MemberDataSheet, s, null));
} else if (idx == 1) { } else if (idx == 1) {
SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, (Year, DocumentNonDeliverersInput.IsChecked == true))); SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, Year));
RecipientsDeliveryMembersInput.IsChecked = true; RecipientsDeliveryMembersInput.IsChecked = true;
} else if (idx >= 2) { } else if (idx >= 2) {
var name = s.Split(" ")[^1]; var name = s.Split(" ")[^1];
@ -449,18 +475,32 @@ namespace Elwig.Windows {
} }
private void UpdatePostalEmailRecipients() { private void UpdatePostalEmailRecipients() {
var modeEmail = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0;
var modePostal = PostalAllInput.IsChecked == true ? 3 : PostalWishInput.IsChecked == true ? 2 : PostalNoEmailInput.IsChecked == true ? 1 : 0;
EmailAllCount = Recipients.Count(m => m.EmailAddresses.Count > 0); EmailAllCount = Recipients.Count(m => m.EmailAddresses.Count > 0);
EmailWishCount = Recipients.Count(m => m.EmailAddresses.Count > 0 && m.ContactViaEmail); EmailWishCount = Recipients.Count(m => m.EmailAddresses.Count > 0 && m.ContactViaEmail);
PostalAllCount = Recipients.Count(); PostalAllCount = Recipients.Count();
PostalWishCount = Recipients.Count(m => m.ContactViaPost); PostalWishCount = Recipients.Count(m => m.ContactViaPost);
var m = EmailAllInput.IsChecked == true ? 3 : EmailWishInput.IsChecked == true ? 2 : 1; var countEmail = (modeEmail == 2 ? EmailAllCount : modeEmail == 1 ? EmailWishCount : 0);
PostalNoEmailCount = PostalAllCount - (m == 3 ? EmailAllCount : m == 2 ? EmailWishCount : 0); PostalNoEmailCount = PostalAllCount - countEmail;
var countPostal = (modePostal == 3 ? PostalAllCount : modePostal == 2 ? PostalWishCount : modePostal == 1 ? PostalNoEmailCount : 0);
if (IsLoaded) {
StatusRecipients.Text = $"{Recipients.Count():N0}";
StatusPostalRecipients.Text = $"{countPostal:N0}";
StatusEmailRecipients.Text = $"{countEmail:N0}";
}
ResetDocuments(); ResetDocuments();
} }
private async Task UpdateTextParameters() { private async Task UpdateClientParameters() {
var changed = false; var changed = false;
var dcInclude = DocumentNonDeliverersInput.IsChecked ?? false;
if (dcInclude != App.Client.MailIncludeNonDeliverers) {
App.Client.MailIncludeNonDeliverers = dcInclude;
changed = true;
}
var dcText = DeliveryConfirmationFooterInput.Text.Trim(); var dcText = DeliveryConfirmationFooterInput.Text.Trim();
if (dcText.Length == 0) dcText = null; if (dcText.Length == 0) dcText = null;
if (dcText != App.Client.TextDeliveryConfirmation) { if (dcText != App.Client.TextDeliveryConfirmation) {
@ -475,6 +515,53 @@ namespace Elwig.Windows {
changed = true; changed = true;
} }
var sendPostal = 0;
if (PostalAllInput.IsChecked ?? false) {
sendPostal = 3;
} else if (PostalWishInput.IsChecked ?? false) {
sendPostal = 2;
} else if (PostalNoEmailInput.IsChecked ?? false) {
sendPostal = 1;
}
if (sendPostal != App.Client.MailSendPostal) {
App.Client.MailSendPostal = sendPostal;
changed = true;
}
var sendEmail = 0;
if (EmailAllInput.IsChecked ?? false) {
sendEmail = 2;
} else if (EmailWishInput.IsChecked ?? false) {
sendEmail = 1;
}
if (sendEmail != App.Client.MailSendEmail) {
App.Client.MailSendEmail = sendEmail;
changed = true;
}
var ordering = 0;
if (OrderNameInput.IsChecked ?? false) {
ordering = 1;
} else if (OrderPlzInput.IsChecked ?? false) {
ordering = 2;
}
if (ordering != App.Client.MailOrdering) {
App.Client.MailOrdering = ordering;
changed = true;
}
var mailDoublePaged = DoublePagedInput.IsChecked ?? false;
if (mailDoublePaged != App.Client.MailDoublePaged) {
App.Client.MailDoublePaged = mailDoublePaged;
changed = true;
}
var sender2 = PostalSender2.Text.Trim();
if (sender2 != App.Client.Sender2) {
App.Client.Sender2 = sender2;
changed = true;
}
var emailSubject = EmailSubjectInput.Text.Trim(); var emailSubject = EmailSubjectInput.Text.Trim();
if (emailSubject.Length == 0) emailSubject = null; if (emailSubject.Length == 0) emailSubject = null;
if (emailSubject != App.Client.TextEmailSubject) { if (emailSubject != App.Client.TextEmailSubject) {
@ -519,7 +606,7 @@ namespace Elwig.Windows {
GenerateButton.IsEnabled = false; GenerateButton.IsEnabled = false;
DisposeDocs(); DisposeDocs();
await UpdateTextParameters(); await UpdateClientParameters();
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
@ -562,8 +649,7 @@ namespace Elwig.Windows {
Dictionary<(int, int), (IDictionary<int, CreditNoteDeliveryData>, IDictionary<int, PaymentMember>, BillingData)> cnData = []; Dictionary<(int, int), (IDictionary<int, CreditNoteDeliveryData>, IDictionary<int, PaymentMember>, BillingData)> cnData = [];
foreach (var doc in docs) { foreach (var doc in docs) {
if (doc.Type == DocType.DeliveryConfirmation) { if (doc.Type == DocType.DeliveryConfirmation) {
var details = ((int, bool))doc.Details!; var year = (int)doc.Details!;
var year = details.Item1;
try { try {
var b = new Billing(year); var b = new Billing(year);
@ -610,13 +696,11 @@ namespace Elwig.Windows {
} else if (doc.Type == DocType.MemberDataSheet) { } else if (doc.Type == DocType.MemberDataSheet) {
return [new GeneratedDoc(new MemberDataSheet(m, ctx) { Date = postalDate })]; return [new GeneratedDoc(new MemberDataSheet(m, ctx) { Date = postalDate })];
} else if (doc.Type == DocType.DeliveryConfirmation) { } else if (doc.Type == DocType.DeliveryConfirmation) {
var details = ((int, bool))doc.Details!; var year = (int)doc.Details!;
var year = details.Item1;
var include = details.Item2;
DeliveryConfirmationDeliveryData data; DeliveryConfirmationDeliveryData data;
if (dcData[year].TryGetValue(m.MgNr, out var d)) { if (dcData[year].TryGetValue(m.MgNr, out var d)) {
data = d; data = d;
} else if (include) { } else if (App.Client.MailIncludeNonDeliverers) {
data = DeliveryConfirmationDeliveryData.CreateEmpty(year, m); data = DeliveryConfirmationDeliveryData.CreateEmpty(year, m);
} else { } else {
return []; return [];
@ -691,11 +775,13 @@ namespace Elwig.Windows {
EmailDocuments = email; EmailDocuments = email;
} }
var printDocs = memberDocs var printMemberDocs = memberDocs
.Where(d => .Where(d =>
printMode == 3 || printMode == 3 ||
(printMode == 2 && d.Member.ContactViaPost) || (printMode == 2 && d.Member.ContactViaPost) ||
(printMode == 1 && !emailRecipients.Contains(d.Member.MgNr))) (printMode == 1 && !emailRecipients.Contains(d.Member.MgNr)))
.ToList();
var printDocs = printMemberDocs
.SelectMany(m => { .SelectMany(m => {
var docs = m.Docs.Select(d => d.Doc).ToList(); var docs = m.Docs.Select(d => d.Doc).ToList();
if (docs.Count == 0 || m.Docs[0].Type == DocType.Custom) { if (docs.Count == 0 || m.Docs[0].Type == DocType.Custom) {
@ -720,6 +806,7 @@ namespace Elwig.Windows {
ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum; ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum;
})); }));
PrintDocument = print; PrintDocument = print;
PrintMemberDocuments = printMemberDocs.ToDictionary(m => m.Member, m => m.Docs.Select(d => d.Doc).ToList());
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs(); UnlockInputs();
@ -727,6 +814,9 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
return; return;
} }
} else {
PrintDocument = null;
PrintMemberDocuments = null;
} }
ProgressBar.Value = 100.0; ProgressBar.Value = 100.0;
@ -763,7 +853,7 @@ namespace Elwig.Windows {
} }
private async void PrintButton_Click(object sender, RoutedEventArgs evt) { private async void PrintButton_Click(object sender, RoutedEventArgs evt) {
if (PrintDocument == null) return; if (PrintDocument == null || PrintMemberDocuments == null) return;
PrintButton.IsEnabled = false; PrintButton.IsEnabled = false;
GenerateButton.IsEnabled = false; GenerateButton.IsEnabled = false;
@ -777,6 +867,14 @@ namespace Elwig.Windows {
PrintDocument.Show(); PrintDocument.Show();
} else { } else {
await PrintDocument.Print(); await PrintDocument.Print();
await Utils.AddSentMails(
PrintMemberDocuments.Select(d => (
"postal", d.Key.MgNr, d.Key.AdministrativeName,
new string[] { d.Value.Select(d => (d as BusinessDocument)?.Address).FirstOrDefault(a => a != null) ?? d.Key.FullAddress },
d.Value.Select(d => d.Title).FirstOrDefault("Briefkopf"),
d.Value.Select(d => d.Title).ToArray()
))
);
} }
Mouse.OverrideCursor = null; Mouse.OverrideCursor = null;
} }
@ -808,6 +906,7 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = Cursors.AppStarting; Mouse.OverrideCursor = Cursors.AppStarting;
var subject = EmailSubjectInput.Text; var subject = EmailSubjectInput.Text;
var text = EmailBodyInput.Text; var text = EmailBodyInput.Text;
await Utils.AddSentMailBody(subject, text, EmailDocuments.Count);
foreach (var (m, docs) in EmailDocuments) { foreach (var (m, docs) in EmailDocuments) {
using var msg = new MimeMessage(); using var msg = new MimeMessage();
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From)); msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
@ -822,6 +921,12 @@ namespace Elwig.Windows {
} }
msg.Body = body; msg.Body = body;
await client!.SendAsync(msg); await client!.SendAsync(msg);
await Utils.AddSentMails([(
"email", m.MgNr, m.AdministrativeName,
m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
subject,
docs.Select(d => d.Title).ToArray()
)]);
} }
MessageBox.Show("Erfolgreich alle E-Mails verschickt!", "Rundschreiben verschicken", MessageBoxButton.OK, MessageBoxImage.Information); MessageBox.Show("Erfolgreich alle E-Mails verschickt!", "Rundschreiben verschicken", MessageBoxButton.OK, MessageBoxImage.Information);
@ -870,7 +975,7 @@ namespace Elwig.Windows {
} }
private void PostalInput_Changed(object sender, RoutedEventArgs evt) { private void PostalInput_Changed(object sender, RoutedEventArgs evt) {
ResetDocuments(); UpdatePostalEmailRecipients();
} }
private void OrderInput_Changed(object sender, RoutedEventArgs evt) { private void OrderInput_Changed(object sender, RoutedEventArgs evt) {

View File

@ -194,6 +194,7 @@
<Bold>Stammgemeinde</Bold>: z.B. matzen, Wolkersdorf, ...<LineBreak/> <Bold>Stammgemeinde</Bold>: z.B. matzen, Wolkersdorf, ...<LineBreak/>
<Bold>UID</Bold>: z.B. ATU12345678, ...<LineBreak/> <Bold>UID</Bold>: z.B. ATU12345678, ...<LineBreak/>
<Bold>Betriebs-Nr.</Bold>: z.B. 0123456, ...<LineBreak/> <Bold>Betriebs-Nr.</Bold>: z.B. 0123456, ...<LineBreak/>
<Bold>Aktiv</Bold>: aktiv, !Aktiv (nicht-aktiv)<LineBreak/>
<Bold>Bio-Betrieb</Bold>: BIO, !bio (ausgenommen Bio)<LineBreak/> <Bold>Bio-Betrieb</Bold>: BIO, !bio (ausgenommen Bio)<LineBreak/>
<Bold>Buchführend</Bold>: buchf[ührend], Pauschal[iert], !buchf[ührend]<LineBreak/> <Bold>Buchführend</Bold>: buchf[ührend], Pauschal[iert], !buchf[ührend]<LineBreak/>
<Bold>Volllieferant</Bold>: voll[lieferant], !Voll[lieferant] (nicht-Volllieferant)<LineBreak/> <Bold>Volllieferant</Bold>: voll[lieferant], !Voll[lieferant] (nicht-Volllieferant)<LineBreak/>
@ -201,7 +202,7 @@
<Bold>Telefon-Nr.</Bold>: z.B. +436641234, ....<LineBreak/> <Bold>Telefon-Nr.</Bold>: z.B. +436641234, ....<LineBreak/>
<Bold>Kontaktdaten</Bold>: email (mind. 1 E-Mail-Adr.), telnr (mind. 1 Tel.-Nr.), !email (keine E-Mail-Adr.), !telnr (keine Tel.-Nr.)<LineBreak/> <Bold>Kontaktdaten</Bold>: email (mind. 1 E-Mail-Adr.), telnr (mind. 1 Tel.-Nr.), !email (keine E-Mail-Adr.), !telnr (keine Tel.-Nr.)<LineBreak/>
<Bold>Kontaktart</Bold>: kontakt:email, kontakt:post, !kontakt:email, !kontakt:post<LineBreak/> <Bold>Kontaktart</Bold>: kontakt:email, kontakt:post, !kontakt:email, !kontakt:post<LineBreak/>
<Bold>Flächenbindungen</Bold>: z.B. zw, GVK, WRB, ... (Mitglieder mit aktiven Flächenbindungen)<LineBreak/> <Bold>Flächenbindungen</Bold>: z.B. zw, GVK, WRB, Fläch[enbindung], !GVK, ... (Mitglieder mit aktiven Flächenbindungen)<LineBreak/>
<Bold>Freitext</Bold>: z.B. Rechnungsaddresse, Anmerkung, "matzen" (sucht nach dem Text "matzen") <Bold>Freitext</Bold>: z.B. Rechnungsaddresse, Anmerkung, "matzen" (sucht nach dem Text "matzen")
</TextBlock> </TextBlock>
</TextBox.ToolTip> </TextBox.ToolTip>

View File

@ -68,7 +68,7 @@ namespace Elwig.Windows {
(PhoneNr9TypeInput, PhoneNr9Input, PhoneNr9CommentInput), (PhoneNr9TypeInput, PhoneNr9Input, PhoneNr9CommentInput),
]; ];
InitializeDelayTimer(SearchInput, SearchInput_TextChanged); ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged; SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.MemberListOrderByMgNr = false; ViewModel.MemberListOrderByMgNr = false;

View File

@ -140,8 +140,9 @@
<TextBox x:Name="CommentInput" Grid.Column="1" HorizontalAlignment="Stretch" Margin="0,40,10,0" <TextBox x:Name="CommentInput" Grid.Column="1" HorizontalAlignment="Stretch" Margin="0,40,10,0"
TextChanged="CommentInput_TextChanged"/> TextChanged="CommentInput_TextChanged"/>
<Label Content="Erstellt am:" Margin="10,70,0,0" Grid.Column="0"/> <Label Content="Datum:" Margin="10,70,0,0" Grid.Column="0"/>
<TextBox x:Name="DateInput" Grid.Column="1" Width="77" HorizontalAlignment="Left" Margin="0,70,10,0" IsReadOnly="True"/> <TextBox x:Name="DateInput" Grid.Column="1" Width="77" HorizontalAlignment="Left" Margin="0,70,10,0"
TextChanged="DateInput_TextChanged"/>
<Label Content="Überwiesen am:" Margin="10,100,0,0" Grid.Column="0"/> <Label Content="Überwiesen am:" Margin="10,100,0,0" Grid.Column="0"/>
<TextBox x:Name="TransferDateInput" Grid.Column="1" Width="77" HorizontalAlignment="Left" Margin="0,100,10,0" <TextBox x:Name="TransferDateInput" Grid.Column="1" Width="77" HorizontalAlignment="Left" Margin="0,100,10,0"

View File

@ -19,7 +19,7 @@ namespace Elwig.Windows {
public readonly int Year; public readonly int Year;
public readonly bool SeasonLocked; public readonly bool SeasonLocked;
private bool DataValid, DataChanged, NameChanged, CommentChanged, TransferDateValid, TransferDateChanged; private bool DataValid, DataChanged, NameChanged, CommentChanged, DateValid, DateChanged, TransferDateValid, TransferDateChanged;
private BillingData? BillingData; private BillingData? BillingData;
private bool WeightModifierChanged = false; private bool WeightModifierChanged = false;
@ -175,6 +175,7 @@ namespace Elwig.Windows {
private void UpdateSaveButton() { private void UpdateSaveButton() {
SaveButton.IsEnabled = PaymentVariantList.SelectedItem != null && SaveButton.IsEnabled = PaymentVariantList.SelectedItem != null &&
((DataChanged && DataValid) || NameChanged || CommentChanged || ((DataChanged && DataValid) || NameChanged || CommentChanged ||
(DateChanged && DateValid) ||
(TransferDateChanged && TransferDateValid) || (TransferDateChanged && TransferDateValid) ||
(ConsiderModifiersInput.IsChecked != BillingData?.ConsiderDelieryModifiers) || (ConsiderModifiersInput.IsChecked != BillingData?.ConsiderDelieryModifiers) ||
(ConsiderPenaltiesInput.IsChecked != BillingData?.ConsiderContractPenalties) || (ConsiderPenaltiesInput.IsChecked != BillingData?.ConsiderContractPenalties) ||
@ -433,10 +434,16 @@ namespace Elwig.Windows {
var withoutIban = v.Credits.Count(c => c.Member.Iban == null); var withoutIban = v.Credits.Count(c => c.Member.Iban == null);
if (withoutIban > 0) { if (withoutIban > 0) {
var r = MessageBox.Show($"Achtung: Für {withoutIban} Mitglieder ist kein IBAN hinterlegt.\nDiese werden NICHT exportiert.", var r = MessageBox.Show($"Achtung: Für {withoutIban:N0} Mitglieder ist kein IBAN hinterlegt.\n\nDiese werden NICHT exportiert.",
"Mitglieder ohne IBAN", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); "Mitglieder ohne IBAN", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (r != MessageBoxResult.OK) return; if (r != MessageBoxResult.OK) return;
} }
var withNegAmount = v.Credits.Count(c => c.Amount <= 0);
if (withNegAmount > 0) {
var r = MessageBox.Show($"Achtung: Es gibt {withNegAmount:N0} Traubengutschriften mit negativem Betrag.\n\nDiese werden NICHT exportiert.",
"Traubengutschriften mit negativem Betrag", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.OK);
if (r != MessageBoxResult.OK) return;
}
var d = new SaveFileDialog() { var d = new SaveFileDialog() {
FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}", FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}",
@ -489,6 +496,7 @@ namespace Elwig.Windows {
try { try {
v.Name = NameInput.Text; v.Name = NameInput.Text;
v.Comment = (CommentInput.Text != "") ? CommentInput.Text : null; v.Comment = (CommentInput.Text != "") ? CommentInput.Text : null;
v.DateString = string.Join("-", DateInput.Text.Split(".").Reverse());
v.TransferDateString = (TransferDateInput.Text != "") ? string.Join("-", TransferDateInput.Text.Split(".").Reverse()) : null; v.TransferDateString = (TransferDateInput.Text != "") ? string.Join("-", TransferDateInput.Text.Split(".").Reverse()) : null;
var d = App.Config.Debug ? BillingData.FromJson(DataInput.Text) : BillingData; var d = App.Config.Debug ? BillingData.FromJson(DataInput.Text) : BillingData;
d.ConsiderDelieryModifiers = ConsiderModifiersInput.IsChecked ?? false; d.ConsiderDelieryModifiers = ConsiderModifiersInput.IsChecked ?? false;
@ -557,6 +565,27 @@ namespace Elwig.Windows {
UpdateSaveButton(); UpdateSaveButton();
} }
private void DateInput_TextChanged(object sender, TextChangedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) {
ControlUtils.ClearInputState(DateInput);
return;
}
var res = Validator.CheckDate(DateInput, true);
if (!res.IsValid) {
ControlUtils.SetInputInvalid(DateInput);
DateValid = false;
} else if (DateInput.Text != $"{v.Date:dd.MM.yyyy}") {
ControlUtils.SetInputChanged(DateInput);
DateValid = true;
DateChanged = true;
} else {
ControlUtils.ClearInputState(DateInput);
DateValid = true;
DateChanged = false;
}
UpdateSaveButton();
}
private void TransferDateInput_TextChanged(object sender, TextChangedEventArgs evt) { private void TransferDateInput_TextChanged(object sender, TextChangedEventArgs evt) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) { if (PaymentVariantList.SelectedItem is not PaymentVar v) {
ControlUtils.ClearInputState(TransferDateInput); ControlUtils.ClearInputState(TransferDateInput);

View File

@ -1,4 +1,4 @@
<Project Sdk="WixToolset.Sdk/5.0.0"> <Project Sdk="WixToolset.Sdk/6">
<PropertyGroup> <PropertyGroup>
<HarvestFileSuppressUniqueIds>false</HarvestFileSuppressUniqueIds> <HarvestFileSuppressUniqueIds>false</HarvestFileSuppressUniqueIds>
<HarvestFileGenerateGuidsNow>true</HarvestFileGenerateGuidsNow> <HarvestFileGenerateGuidsNow>true</HarvestFileGenerateGuidsNow>

View File

@ -2,3 +2,6 @@
Elwig Elwig
===== =====
**El**ektronische **Wi**nzer**g**enossenschaftsverwaltung (Electronic Management for Vintners' Cooperatives).
Further information may be found on [the website](https://elwig.at).

View File

@ -1,4 +1,6 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"> <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal"
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
<Bundle Name="Elwig" Manufacturer="Elwig" Version="!(bind.packageVersion.ElwigMsi)" UpgradeCode="f3c8fcab-c37c-43aa-9ab8-e42f4bb518b7" <Bundle Name="Elwig" Manufacturer="Elwig" Version="!(bind.packageVersion.ElwigMsi)" UpgradeCode="f3c8fcab-c37c-43aa-9ab8-e42f4bb518b7"
IconSourceFile="$(var.ElwigProjectDir)\Resources\Images\Elwig.ico"> IconSourceFile="$(var.ElwigProjectDir)\Resources\Images\Elwig.ico">
<BootstrapperApplication> <BootstrapperApplication>

View File

@ -1,4 +1,4 @@
<Project Sdk="WixToolset.Sdk/5.0.0"> <Project Sdk="WixToolset.Sdk/6">
<PropertyGroup> <PropertyGroup>
<OutputType>Bundle</OutputType> <OutputType>Bundle</OutputType>
<OutputName>Elwig</OutputName> <OutputName>Elwig</OutputName>
@ -13,7 +13,7 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Installer\Installer.wixproj" /> <ProjectReference Include="..\Installer\Installer.wixproj" />
<PackageReference Include="WixToolset.Bal.wixext" Version="5.0.1" /> <PackageReference Include="WixToolset.Bal.wixext" Version="6.0.0" />
<PackageReference Include="WixToolset.Util.wixext" Version="5.0.1" /> <PackageReference Include="WixToolset.Util.wixext" Version="6.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,6 +1,5 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Helpers.Billing; using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using System.Text.Json; using System.Text.Json;
namespace Tests.HelperTests { namespace Tests.HelperTests {
@ -452,7 +451,7 @@ namespace Tests.HelperTests {
[73] = 0.5m, [73] = 0.5m,
[83] = 1.0m [83] = 1.0m
}, null), GetSelection(["GV/-"])) }, null), GetSelection(["GV/-"]))
]; ];
var data = BillingData.FromGraphEntries(entries); var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{ {
@ -476,14 +475,14 @@ namespace Tests.HelperTests {
[Test] [Test]
public void TestWrite_04_Simple() { public void TestWrite_04_Simple() {
List<GraphEntry> entries = [ List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m, [73] = 0.5m,
[84] = 1.0m [84] = 1.0m
}, null), GetSelection(["GV/-", "ZW/-"])), }, null), GetSelection(["GV/-", "ZW/-"])),
new GraphEntry(10, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(10, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m, [73] = 0.75m,
}, null), GetSelection(["WR/-"])) }, null), GetSelection(["WR/-"]))
]; ];
var data = BillingData.FromGraphEntries(entries); var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{ {
@ -510,18 +509,18 @@ namespace Tests.HelperTests {
[Test] [Test]
public void TestWrite_05_Attribute() { public void TestWrite_05_Attribute() {
List<GraphEntry> entries = [ List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m, [73] = 0.5m,
[84] = 1.0m [84] = 1.0m
}, null), GetSelection(["GV/K-", "ZW/K-"])), }, null), GetSelection(["GV/K-", "ZW/K-"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m, [73] = 0.75m,
}, null), GetSelection(["WR/-", "BL/-", "RR/-", "FV/-"])), }, null), GetSelection(["WR/-", "BL/-", "RR/-", "FV/-", "GV/-"])),
new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.65m, [73] = 0.65m,
[84] = 1.2m [84] = 1.2m
}, null), GetSelection(["BP/-", "SA/-"])) }, null), GetSelection(["BP/-", "SA/-"]))
]; ];
var data = BillingData.FromGraphEntries(entries); var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{ {
@ -564,12 +563,12 @@ namespace Tests.HelperTests {
}, null), GetSelection(["GV/-B", "ZW/-B"])), }, null), GetSelection(["GV/-B", "ZW/-B"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m, [73] = 0.75m,
}, null), GetSelection(["WR/-", "BL/-", "RR/-", "FV/-"])), }, null), GetSelection(["WR/-", "BL/-", "RR/-", "FV/-", "GV/-"])),
new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.65m, [73] = 0.65m,
[84] = 1.2m [84] = 1.2m
}, null), GetSelection(["BP/-", "SA/-"])) }, null), GetSelection(["BP/-", "SA/-"]))
]; ];
var data = BillingData.FromGraphEntries(entries); var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{ {
@ -616,7 +615,7 @@ namespace Tests.HelperTests {
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.8m, [73] = 0.8m,
}, null), GetSelection(["BP/-", "BP/-B"])), }, null), GetSelection(["BP/-", "BP/-B"])),
]; ];
var data = BillingData.FromGraphEntries(entries); var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{ {
@ -653,8 +652,8 @@ namespace Tests.HelperTests {
}, null), GetSelection(["BP/-B", "ZW/-B", "FV/-B"])), }, null), GetSelection(["BP/-B", "ZW/-B", "FV/-B"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.8m, [73] = 0.8m,
}, null), GetSelection(["BP/-", "ZW/-", "FV/-", "WR/-", "BL/-"])), }, null), GetSelection(["BP/-", "ZW/-", "FV/-", "WR/-", "BL/-", "RR/-"])),
]; ];
var data = BillingData.FromGraphEntries(entries); var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{ {
@ -711,5 +710,329 @@ namespace Tests.HelperTests {
} }
""")); """));
} }
[Test]
public void TestWrite_10_QualityLevel() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["ZW/-"]))
];
entries[0].Abgewertet = true;
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": 0.2,
"quality": {
"WEI": 0.1
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_11_MixedCultivation_1() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "WR/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["GV/-B"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.1,
"GV-B": 0.2
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_12_MixedCultivation_2() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "ZW/-", "WR/-", "FV/-", "RR/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["GV/-B", "FV/-B"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.3m,
}, null), GetSelection(["ZW/-B", "WR/-B"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.1,
"GV-B": 0.2,
"FV-B": 0.2,
"ZW-B": 0.3,
"WR-B": 0.3
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_13_DefaultCultivation_1() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "GV/-B"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["WR/-B"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.1,
"WR-B": 0.2
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_14_DefaultCultivation_2() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "GV/-B", "ZW/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["WR/-B", "ZW/-B"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.1,
"-B": 0.2,
"GV-B": 0.1
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_15_DefaultCultivation_3() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "GV/-B", "ZW/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["GV/S-", "GV/S-B", "ZW/S-"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.3m,
}, null), GetSelection(["WR/S-B", "ZW/S-B"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"GV/S-B": 0.2,
"/S": 0.2,
"/S-B": 0.3,
"GV": 0.1,
"ZW": 0.1
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_16_DefaultCultivation_4() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "RR/-B", "ZW/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["RR/-"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.1,
"RR-B": 0.1,
"RR": 0.2
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_17_DefaultCultivation_5() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "RR/-B", "ZW/-", "SW/-", "SO/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["GV/K-", "RR/K-B", "ZW/K-"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.3m,
}, null), GetSelection(["RR/K-"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"RR/K-B": 0.2,
"RR/K": 0.3,
"default": 0.1,
"/K": 0.2
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_18_DefaultAttribute_1() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "GV/S-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["WR/S-"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"WR/S": 0.2,
"default": 0.1
},
"curves": []
}
"""));
}
[Test]
public void TestWrite_19_DefaultAttribute_2() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.1m,
}, null), GetSelection(["GV/-", "GV/S-", "ZW/-"])),
new GraphEntry(1, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.2m,
}, null), GetSelection(["WR/S-", "ZW/S-"]))
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
"mode": "elwig",
"version": 1,
"payment": {
"default": 0.1,
"/S": 0.2,
"GV/S": 0.1
},
"curves": []
}
"""));
}
private static IEnumerable<List<T>> GetPermutations<T>(T[] input) {
if (input.Length <= 1)
yield return [..input];
for (int i = 0; i < input.Length; i++) {
yield return [input[i]];
foreach (var p in GetPermutations(input.Where((_, j) => j != i).ToArray())) {
p.Insert(0, input[i]);
yield return p;
}
}
}
private static IEnumerable<List<HashSet<RawVaribute>>> GetCurves(RawVaribute[] vaributes, int minNumCurves = 1, int maxNumCurves = 10) {
foreach (var p in GetPermutations(vaributes)) {
for (int nCurves = minNumCurves; nCurves <= Math.Min(maxNumCurves, p.Count); nCurves++) {
yield return Enumerable.Range(0, nCurves)
.Select(n => p.Where((v, idx) => idx % nCurves == n).ToHashSet())
.ToList();
}
}
}
private readonly HashSet<string> TestedCurves = [];
private void TestCollapse(List<HashSet<RawVaribute>> curves) {
var str = string.Join("\n", curves.Select(c => string.Join(" ", c.Select(v => v.ToString().PadRight(6)).Order())).Order().Select((c, n) => $"{n + 1}: [ " + c + " ]"));
if (!TestedCurves.Add(str))
return;
var vaributes = curves.SelectMany(v => v).ToList();
List<GraphEntry> entries = curves
.Select((l, n) => new GraphEntry(n, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = n + 1,
}, null), GetSelection(l.Select(v => v.ToString()))))
.ToList();
var data = BillingData.FromGraphEntries(entries);
var test = PaymentBillingData.FromJson(data.ToJsonString(), vaributes);
for (int i = 0; i < curves.Count; i++) {
foreach (var v in curves[i]) {
var val = test.CalculatePrice(v.SortId!, v.AttrId, v.CultId, "QUW", false, 73.0, 15.0);
var actualCurve = (int)val;
Assert.That(actualCurve, Is.EqualTo(i + 1), $"Invalid: {v} (Curve {i + 1} -> {actualCurve})\n\n{str}\n\n{data.ToJsonString(JsonOpts)}\n");
}
}
}
[Test]
public void TestCollapse_01_Permutations() {
RawVaribute[][] configurations = [
[new("GV/-"), new("WR/-"), new("ZW/-"), new("GV/K-"), new("WR/K-"), new("ZW/K-")],
[new("GV/-"), new("WR/-"), new("GV/K-"), new("WR/K-"), new("GV/S-"), new("WR/S-")],
[new("GV/-"), new("WR/-"), new("ZW/-"), new("GV/-B"), new("WR/-B"), new("ZW/-B")],
[new("GV/-"), new("WR/-"), new("GV/-B"), new("WR/-B"), new("GV/-KIP"), new("WR/-KIP")],
[new("GV/-"), new("GV/K-"), new("ZW/-"), new("ZW/K-"), new("GV/-B"), new("GV/K-B"), new("ZW/-B"), new("ZW/K-B")],
];
Assert.Multiple(() => {
foreach (var config in configurations) {
foreach (var c in GetCurves(config))
TestCollapse(c);
}
});
}
} }
} }

View File

@ -19,16 +19,16 @@
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Appium.WebDriver" Version="4.4.5" /> <PackageReference Include="Appium.WebDriver" Version="4.4.5" />
<PackageReference Include="NReco.PdfRenderer" Version="1.5.4" /> <PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
<PackageReference Include="NUnit" Version="4.2.2" /> <PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" /> <PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0"> <PackageReference Include="NUnit.Analyzers" Version="4.7.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2"> <PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>