Compare commits

...

85 Commits

Author SHA1 Message Date
385cb94427 Bump version to 1.0.0.0 2025-04-24 16:06:23 +02:00
a95edb5bba Add GNU GPLv3 as license 2025-04-24 16:06:23 +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 9930e6173c772fe433ff69397205872ad30f6a08
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
f4fa549130 Windows: Add icons on Buttons and MenuItems
All checks were successful
Test / Run tests (push) Successful in 2m15s
2024-11-10 10:58:44 +01:00
c5453c2fe6 MainWindow: Add 'Flächenbindungen' and 'Liefermenge/Ertrag' button 2024-11-10 10:58:35 +01:00
54deccf021 MainWindow: Add statistics table in Leseabschluss 2024-11-09 09:49:19 +01:00
f5d3a04cb1 Bump version to 0.13.2
All checks were successful
Test / Run tests (push) Successful in 1m50s
Deploy / Build and Deploy (push) Successful in 2m6s
2024-10-13 19:08:20 +02:00
0675c45617 Elwig: Use ExecuteSql/FromSql instead of ExecuteSqlRaw/FromSqlRaw where possible
All checks were successful
Test / Run tests (push) Successful in 1m42s
2024-10-13 18:24:40 +02:00
d1f67dc57d BaseDataWindow: Fix WineAttr/WineCult Id updates in payment variant data
All checks were successful
Test / Run tests (push) Successful in 2m10s
2024-10-13 18:18:31 +02:00
3cbffdbf27 Documents: Add DeliveryDepreciationList
All checks were successful
Test / Run tests (push) Successful in 1m55s
2024-10-13 11:54:18 +02:00
e247925472 Controls: Fix blurry borders when system scaling is enabled
All checks were successful
Test / Run tests (push) Successful in 2m21s
2024-10-12 14:25:24 +02:00
a1b3cff624 [#11] Tests: Add DeliveryServiceTest
All checks were successful
Test / Run tests (push) Successful in 1m56s
2024-10-07 23:43:22 +02:00
65498dd18f App: Fix BranchLocation shortening
All checks were successful
Test / Run tests (push) Successful in 1m46s
2024-10-05 19:31:59 +02:00
27dc4f648f [#11] Tests: Add MemberServiceTest 2024-10-05 19:31:51 +02:00
86f7f693a0 Export/Bki: Format house nr that excel interprets it correctly
All checks were successful
Test / Run tests (push) Successful in 2m10s
2024-10-02 11:08:14 +02:00
8680e51052 Weighing: Fix scale L320 for Baden 2024-10-02 10:45:04 +02:00
1d97f3c422 Bump version to 0.13.1
All checks were successful
Test / Run tests (push) Successful in 1m38s
Deploy / Build and Deploy (push) Successful in 2m12s
2024-09-29 22:25:39 +02:00
b6ae1f5675 Elwig: Update dependencies
All checks were successful
Test / Run tests (push) Successful in 1m37s
2024-09-29 22:24:31 +02:00
c185437b9a DeliveryService: Do not delete own delivery in SplitDeliveryToLsNr()
All checks were successful
Test / Run tests (push) Successful in 2m8s
2024-09-29 22:12:19 +02:00
a2315e84bd Windows: Ask user if they really want to send an email
All checks were successful
Test / Run tests (push) Successful in 1m46s
2024-09-29 09:43:17 +02:00
6ba1973087 MemberAdminWindow: Fix 'Save as PDF' option for DeliveryConfirmation and CreditNote
All checks were successful
Test / Run tests (push) Successful in 2m5s
2024-09-29 09:16:39 +02:00
c62947dacd DeliveryAdminWindow: Add DeliverySplittingDialog
All checks were successful
Test / Run tests (push) Successful in 2m10s
2024-09-28 19:54:50 +02:00
f29a609d3b Bump version to 0.13.0
All checks were successful
Test / Run tests (push) Successful in 1m38s
Deploy / Build and Deploy (push) Successful in 1m58s
2024-09-25 22:56:48 +02:00
8a61747538 DeliveryAdminWindow: Fix extraction error
All checks were successful
Test / Run tests (push) Successful in 1m47s
2024-09-25 22:51:06 +02:00
4a7c95e250 MailWindow: Make more user safe
All checks were successful
Test / Run tests (push) Successful in 1m50s
2024-09-25 22:30:26 +02:00
4fa5b8f6d4 Billing: Include all deliveries when calculating under delivery for area commitments
All checks were successful
Test / Run tests (push) Successful in 1m43s
2024-09-25 15:32:26 +02:00
579ed53487 Windows: Prettify formatting for numbers
All checks were successful
Test / Run tests (push) Successful in 2m5s
2024-09-25 12:19:25 +02:00
1fc736ce16 Bump version to 0.12.0
All checks were successful
Test / Run tests (push) Successful in 1m54s
Deploy / Build and Deploy (push) Successful in 2m9s
2024-09-24 23:10:17 +02:00
ce39797d8b [#56] Windows: Fail gracefully while saving
All checks were successful
Test / Run tests (push) Successful in 2m11s
2024-09-24 20:10:26 +02:00
94a6dd5312 Printing: Replace PDFtoPrinter with PdfiumViewer 2024-09-24 20:10:26 +02:00
b67857ae22 [#56] AppDbContext: Turn off connection pooling 2024-09-24 20:10:26 +02:00
a48ea8e7e2 Printing/Pdf: Small fixes 2024-09-24 20:10:21 +02:00
0f87446906 Bump version to 0.11.4
All checks were successful
Test / Run tests (push) Successful in 1m47s
Deploy / Build and Deploy (push) Successful in 2m7s
2024-09-22 23:33:45 +02:00
e0fcaf1f53 Services: Fix spacing in tool tip grids
All checks were successful
Test / Run tests (push) Successful in 2m3s
2024-09-22 23:22:23 +02:00
26e549caa6 Bump version to 0.11.3
All checks were successful
Test / Run tests (push) Successful in 1m37s
Deploy / Build and Deploy (push) Successful in 2m28s
2024-09-22 21:05:42 +02:00
1d187c25f3 MemberAdminWindow: Revert caching of MembersDeliveries
All checks were successful
Test / Run tests (push) Successful in 1m46s
2024-09-22 21:01:48 +02:00
accbb9df08 Bump version to 0.11.2
All checks were successful
Test / Run tests (push) Successful in 1m36s
Deploy / Build and Deploy (push) Successful in 2m2s
2024-09-22 20:49:14 +02:00
7791d02979 Services: Extract GenerateToolTipData() from GenerateToolTip()
All checks were successful
Test / Run tests (push) Successful in 1m35s
2024-09-22 20:47:06 +02:00
dcec6f03fe DeliveryAdminWindow: Fix 'Collection was modified' error
All checks were successful
Test / Run tests (push) Successful in 2m1s
2024-09-22 12:35:37 +02:00
526e951029 Elwig: Add LogWindow
All checks were successful
Test / Run tests (push) Successful in 1m49s
2024-09-22 12:16:14 +02:00
3cde360aaa DeliveryAncmtAdminWindow: Replace 'Traubenanmeldung' with 'Anmeldung'
All checks were successful
Test / Run tests (push) Successful in 2m4s
2024-09-22 10:29:48 +02:00
66be5a3e2c MemberAdminWindow: Cache MembersDeliveries
All checks were successful
Test / Run tests (push) Successful in 1m46s
2024-09-21 23:29:10 +02:00
5c12dba125 DeliveryService: Fix duplicate LsNr error
All checks were successful
Test / Run tests (push) Successful in 1m49s
2024-09-21 23:04:30 +02:00
165770fa37 DeliveryAncmtList: Add DefaultKg of member 2024-09-21 23:04:30 +02:00
648c406ad2 Services: Make FillInputs synchronous 2024-09-21 23:04:26 +02:00
9fa9d9fbec DeliveryAdminWindow: Minor layout changes 2024-09-21 21:37:57 +02:00
102 changed files with 5820 additions and 1287 deletions

View File

@ -1,7 +1,7 @@
name: Deploy
on:
push:
tags: ["v[0-9]+.[0-9]+.[0-9]+"]
tags: ["v([0-9]+.)?[0-9]+.[0-9]+.[0-9]+"]
jobs:
deploy:
name: Build and Deploy

View File

@ -2,6 +2,282 @@
Changelog
=========
[v1.0.0.0][v1.0.0.0] (2025-XX-YY) {#v1.0.0.0}
---------------------------------------------
### Neue Funktionen {#v1.0.0.0-features}
* Something.
### Behobene Fehler {#v1.0.0.0-bugfixes}
* Something.
### Sonstiges {#v1.0.0.0-misc}
* Something.
[v1.0.0.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.0
[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}
------------------------------------------
### Neue Funktionen {#v0.13.2-features}
* Im Lieferungen-Fenster den Menüpunkt _Abwertungsliste_ (`DeliveryDepreciationList`) hinzugefügt. (3cbffdbf27)
### Behobene Fehler {#v0.13.2-bugfixes}
* Fehler im Waagenprotokoll `Avery-Async` (L320) behoben. (8680e51052)
* 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)
### Sonstiges {#v0.13.2-misc}
* Weitere automatisierte Tests hinzugefügt. ([#11][i11])
* Namenszusätze bei Gemeinden (z.B. an, bei, im, am) genauer angegeben. (65498dd18f)
* Bei einigen Eingabefeldern waren die Ränder unscharf. (e247925472)
* Wo leicht möglich wird `ExecuteSql`/`FromSql` statt `ExecuteSqlRaw`/`FromSqlRaw` verwendet. (0675c45617)
[v0.13.2]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.2
[i11]: https://git.necronda.net/winzer/elwig/issues/11
[v0.13.1][v0.13.1] (2024-09-29) {#v0.13.1}
------------------------------------------
### 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)
### 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)
### Sonstiges {#v0.13.1-misc}
* Abhängigkeiten aktualisiert. (b6ae1f5675)
[v0.13.1]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.1
[v0.13.0][v0.13.0] (2024-09-25) {#v0.13.0}
------------------------------------------
> [!NOTE]
> Ab dieser Version verhält sich die Berechnung der Unterlieferungen bei Flächenbindungen anders.
### 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)
### Sonstiges {#v0.13.0-misc}
* Es werden alle Lieferungen (inkl. `WEI`, `RSW`, `LDW`) zur Berechnung der Unterlieferung bei Flächenbindungen herangezogen. (4fa5b8f6d4)
* In diversen Fenstern die Formatierung von Zahlen verbessert. (579ed53487)
* Das Rundschreiben-Fenster (`MailWindow`) ist nun benutzerfreundlicher/-sicherer. (4a7c95e250)
[v0.13.0]: https://git.necronda.net/winzer/elwig/releases/tag/v0.13.0
[v0.12.0][v0.12.0] (2024-09-24) {#v0.12.0}
------------------------------------------
### Behobene Fehler {#v0.12.0-bugfixes}
* Das Drucken von Dokumenten ist wieder möglich. (94a6dd5312, a48ea8e7e2)
### Sonstiges {#v0.12.0-misc}
* Versuch 1: `Database is locked` Fehler beheben. ([#56][i56])
[v0.12.0]: https://git.necronda.net/winzer/elwig/releases/tag/v0.12.0
[i56]: https://git.necronda.net/winzer/elwig/issues/56
[v0.11.4][v0.11.4] (2024-09-22) {#v0.11.4}
------------------------------------------
> [!WARNING]
> Aufgrund eines Fehlers ist in dieser Version das Drucken von Dokumenten nicht möglich!
>
> Es wird empfohlen die nächste Version zu verwenden.
### Behobene Fehler {#v0.11.4-bugfixes}
* In den Tooltips für Gewicht und Flächenbindungen wurden die Abstände wieder hergestellt. (e0fcaf1f53)
[v0.11.4]: https://git.necronda.net/winzer/elwig/releases/tag/v0.11.4
[v0.11.3][v0.11.3] (2024-09-22) {#v0.11.3}
------------------------------------------
> [!WARNING]
> Aufgrund eines Fehlers ist in dieser Version das Drucken von Dokumenten nicht möglich!
>
> Es wird empfohlen Version 0.12.0 zu verwenden.
### Behobene Fehler {#v0.11.3-bugfixes}
* Im Mitglieder-Fenster (`MemberAdminWindow`) wurde das Berechnen der Mitgliederdaten pro Jahr und Mitglied rückgängig gemacht. (1d187c25f3)
[v0.11.3]: https://git.necronda.net/winzer/elwig/releases/tag/v0.11.3
[v0.11.2][v0.11.2] (2024-09-22) {#v0.11.2}
------------------------------------------
> [!WARNING]
> Aufgrund eines Fehlers ist in dieser Version das Drucken von Dokumenten nicht möglich!
>
> Es wird empfohlen Version 0.12.0 zu verwenden.
### Neue Funktionen {#v0.11.2-features}
* In der Anmeldeliste (`DeliveryAncmtList`) wird die Stamm-KG des Mitgliedes angegeben. (165770fa37)
* Im Haupt-Fenster (`MainWindow`) wurde im Menüpunkt _Hilfe_ ein Fehler-Protokoll-Fenster (`LogWindow`) hinzugefügt. (526e951029)
### Behobene Fehler {#v0.11.2-bugfixes}
* Falls das Übernahme-Fenster (`DeliveryAdminWindow`) geöffnet war und man währenddessen die aktuellste Lieferung gelöscht hat kam es zum Fehler beim Nummerieren des Lieferscheines. (5c12dba125)
* In Seltenen Fällen konnte es vorkommen, dass sich Elwig aufgrund eines internen Fehlers im Übernahme-Fenster (`DeliveryAdminWindow`) geschlossen hat. (dcec6f03fe)
### Sonstiges {#v0.11.2-misc}
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) wurde das Layout der Teillieferungen-Liste angepasst. (9fa9d9fbec)
* Alle Aufrufe von `FillInputs()` in `Services` sind nun synchron. (648c406ad2, 7791d02979)
* Im Mitglieder-Fenster (`MemberAdminWindow`) werden die Lieferinformationen pro Jahr und Mitglied im Voraus berechnet. (66be5a3e2c)
* Im Anmeldungen-Fenster (`DeliveryAncmtAdminWindow`) heißt es nun _Anmeldungen_, nicht mehr _Traubenanmeldungen_. (3cde360aaa)
[v0.11.2]: https://git.necronda.net/winzer/elwig/releases/tag/v0.11.2
[v0.11.1][v0.11.1] (2024-09-19) {#v0.11.1}
------------------------------------------
@ -91,7 +367,7 @@ Changelog
### 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])
* Versuch: Fehler bei automatischer Daten-Erneuerung bei längerer Benutzung. (8c8c0a8c2b)
@ -193,7 +469,7 @@ Changelog
> [!NOTE]
> Mitglieder können ab dieser Version als juristische Person markiert werden.
> Es ist empfohlen, dies bei entsprechenden Mitglieder zu überprüfen und einzusetzen (z.B. Bezirksbauernkammer, Lagerhaus).
> Es wird empfohlen, dies bei entsprechenden Mitglieder zu überprüfen und einzusetzen (z.B. Bezirksbauernkammer, Lagerhaus).
### Neue Funktionen {#v0.10.0-features}
@ -408,7 +684,7 @@ Changelog
### 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}

View File

@ -7,9 +7,6 @@ using Elwig.Helpers;
using Elwig.Helpers.Weighing;
using System.Collections.Generic;
using System.Windows.Threading;
using System.Globalization;
using System.Threading;
using System.Windows.Markup;
using System.Reflection;
using Elwig.Helpers.Printing;
using Elwig.Windows;
@ -29,6 +26,7 @@ namespace Elwig {
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
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 ExePath = @"C:\Program Files\Elwig\";
public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");
@ -59,10 +57,11 @@ namespace Elwig {
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Directory.CreateDirectory(TempPath);
Directory.CreateDirectory(DataPath);
Directory.CreateDirectory(MailsPath);
MainDispatcher = Dispatcher;
Scales = [];
CurrentApp = this;
OverrideCulture();
Utils.OverrideCulture();
var args = Environment.GetCommandLineArgs();
if (args.Length >= 2) {
@ -82,25 +81,8 @@ namespace Elwig {
MainDispatcher.BeginInvoke(HintContextChange);
}
private static void OverrideCulture() {
var locale = new CultureInfo("de-AT", false);
locale.NumberFormat.CurrencyGroupSeparator = Utils.GroupSeparator;
locale.NumberFormat.NumberGroupSeparator = Utils.GroupSeparator;
locale.NumberFormat.PercentGroupSeparator = Utils.GroupSeparator;
CultureInfo.CurrentCulture = locale;
CultureInfo.CurrentUICulture = locale;
Thread.CurrentThread.CurrentCulture = locale;
Thread.CurrentThread.CurrentUICulture = locale;
CultureInfo.DefaultThreadCurrentCulture = locale;
CultureInfo.DefaultThreadCurrentUICulture = locale;
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name))
);
}
protected override async void OnStartup(StartupEventArgs evt) {
Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0");
Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0.0");
try {
await AppDbUpdater.CheckDb();
@ -183,6 +165,9 @@ namespace Elwig {
}
private async void Application_Exit(object sender, ExitEventArgs evt) {
foreach (var s in EventScales) {
s.Dispose();
}
await Pdf.Cleanup();
}
@ -194,7 +179,13 @@ namespace Elwig {
ZwstId = entry.Item1;
BranchName = entry.Item2;
BranchPlz = entry.Item3;
BranchLocation = entry.Item4?.Split(" im ")[0].Split(" an ")[0].Split(" bei ")[0]; // FIXME
BranchLocation = entry.Item4?
.Split(" in ")[0]
.Split(" im ")[0]
.Split(" an ")[0]
.Split(" am ")[0]
.Split(" bei ")[0]
.Split(" beim ")[0];
BranchAddress = entry.Item5;
BranchPhoneNr = entry.Item6;
BranchFaxNr = entry.Item7;
@ -202,6 +193,7 @@ namespace Elwig {
}
public static void HintContextChange() {
if (CurrentApp == null) return;
var ch = CurrentLastWrite;
if (ch > CurrentApp.LastChanged)
CurrentApp.LastChanged = ch;

View File

@ -15,9 +15,8 @@
<ColumnDefinition Width="18"/>
</Grid.ColumnDefinitions>
<Border x:Name="Border" BorderThickness="1,1,0,1"
BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
SnapsToDevicePixels="True" Grid.RowSpan="2">
<Border x:Name="Border" BorderThickness="1,1,0,1" Grid.RowSpan="2"
BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}">
<ScrollViewer x:Name="PART_ContentHost" VerticalAlignment="Center"/>
</Border>
@ -43,6 +42,8 @@
</Setter.Value>
</Setter>
<Setter Property="TextAlignment" Value="Right"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Gray"/>

View File

@ -7,9 +7,8 @@
<ControlTemplate TargetType="ctrl:UnitTextBox">
<Border x:Name="Border"
BorderThickness="{Binding Path=BorderThickness, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
SnapsToDevicePixels="True">
<Grid>
BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}">
<Grid Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}">
<ScrollViewer x:Name="PART_ContentHost" VerticalAlignment="Bottom">
<ScrollViewer.Margin>
<Binding ElementName="UnitBlock" Path="ActualWidth">
@ -32,6 +31,8 @@
</Setter.Value>
</Setter>
<Setter Property="TextAlignment" Value="Right"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Gray"/>

View File

@ -1,56 +0,0 @@
<Window x:Class="Elwig.Dialogs.AbwertenDialog"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Dialogs"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=WeightInput}"
Title="Teillieferung abwerten" Height="190" Width="400">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Padding" Value="2,4,2,4"/>
<Setter Property="Height" Value="25"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="10,10,10,10" Grid.ColumnSpan="2" TextWrapping="Wrap" TextAlignment="Center">
Welche Menge der Teillieferung <Run x:Name="TextLsNr" FontWeight="Bold" Text="20201010A000/1"/><LineBreak/>
von <Run x:Name="TextMember" FontWeight="Bold" Text="Max Mustermann"/><LineBreak/>
mit <Run x:Name="TextWeight" FontWeight="Bold" Text="1&#x202f;000&#x202f;kg"/> soll abgewertet werden?
</TextBlock>
<Label Content="Gewicht:" Margin="10,70,10,10"/>
<Grid Grid.Column="1" Width="70" Height="25" Margin="0,70,10,10" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBox x:Name="WeightInput" TextAlignment="Right" Padding="2,2,17,2"
TextChanged="WeightInput_TextChanged"/>
<Label Content="kg" Margin="0,4,3,0" HorizontalAlignment="Right" FontSize="10" Padding="2,4,2,4"/>
</Grid>
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" Grid.Column="1" IsEnabled="False" IsDefault="True"
Click="ConfirmButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" Grid.Column="1" IsCancel="True"/>
</Grid>
</Window>

View File

@ -1,33 +0,0 @@
using Elwig.Helpers;
using System.Windows;
using System.Windows.Controls;
namespace Elwig.Dialogs {
public partial class AbwertenDialog : Window {
public int Weight;
public AbwertenDialog(string lsnr, string name, int weight) {
Weight = weight;
InitializeComponent();
TextLsNr.Text = lsnr;
TextMember.Text = name;
TextWeight.Text = $"{weight:N0}{Utils.UnitSeparator}kg";
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
DialogResult = true;
Weight = int.Parse(WeightInput.Text);
Close();
}
private void UpdateButtons() {
ConfirmButton.IsEnabled = int.TryParse(WeightInput.Text, out var w) && w > 0 && w <= Weight;
}
private void WeightInput_TextChanged(object sender, TextChangedEventArgs evt) {
Validator.CheckInteger(WeightInput, true, 5);
UpdateButtons();
}
}
}

View File

@ -57,8 +57,8 @@
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
IsChecked="{Binding DeletePaymentData}"/>
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" Grid.Column="1" IsEnabled="False"
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" IsEnabled="False"
Click="ConfirmButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" Grid.Column="1" IsCancel="True"/>
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" IsCancel="True"/>
</Grid>
</Window>

View File

@ -1,57 +0,0 @@
<Window x:Class="Elwig.Dialogs.DeliveryExtractionDialog"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Dialogs"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=WeightInput}"
Title="Teillieferung extrahieren" Height="210" Width="380">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Padding" Value="2,4,2,4"/>
<Setter Property="Height" Value="25"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="10,10,0,10" TextWrapping="Wrap">
Was soll mit der Teillieferung <Run x:Name="TextLsNr" FontWeight="Bold" Text="20201010A000/1"/><LineBreak/>
von <Run x:Name="TextMember" FontWeight="Bold" Text="Max Mustermann"/> geschehen?
</TextBlock>
<RadioButton x:Name="NewDeliveryButton" Content="Neue Lieferung erstellen"
Margin="10,80,0,10" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="Selection_Changed" Unchecked="Selection_Changed"/>
<RadioButton x:Name="AddToDeliveryButton" Content="Zu Lieferung hinzufügen"
Margin="10,100,0,10" HorizontalAlignment="Left" VerticalAlignment="Top"
Checked="Selection_Changed" Unchecked="Selection_Changed"/>
<ListBox x:Name="DeliveryList" Grid.Column="1" Margin="10,10,10,45"
SelectionChanged="DeliveryList_SelectionChanged"/>
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" Grid.ColumnSpan="2" IsEnabled="False" IsDefault="True"
Click="ConfirmButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" Grid.ColumnSpan="2" IsCancel="True"/>
</Grid>
</Window>

View File

@ -1,37 +0,0 @@
using System.Collections.Generic;
using System.Windows;
namespace Elwig.Dialogs {
public partial class DeliveryExtractionDialog : Window {
public string? AddTo;
public DeliveryExtractionDialog(string lsnr, string name, bool single, IEnumerable<string> lsnrs) {
InitializeComponent();
TextLsNr.Text = lsnr;
TextMember.Text = name;
NewDeliveryButton.IsEnabled = !single;
DeliveryList.IsEnabled = false;
DeliveryList.ItemsSource = lsnrs;
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
DialogResult = true;
AddTo = NewDeliveryButton.IsChecked == true ? "new" : DeliveryList.SelectedItem as string;
Close();
}
private void UpdateButtons() {
ConfirmButton.IsEnabled = NewDeliveryButton.IsChecked == true || (AddToDeliveryButton.IsChecked == true && DeliveryList.SelectedItem != null);
DeliveryList.IsEnabled = AddToDeliveryButton.IsChecked == true;
}
private void Selection_Changed(object sender, RoutedEventArgs evt) {
UpdateButtons();
}
private void DeliveryList_SelectionChanged(object sender, RoutedEventArgs evt) {
UpdateButtons();
}
}
}

View File

@ -0,0 +1,104 @@
<local:ContextWindow
x:Class="Elwig.Dialogs.DeliverySplittingDialog"
AutomationProperties.AutomationId="DeliverySplittingDialog"
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"
xmlns:ctrl="clr-namespace:Elwig.Controls"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Lieferung abwerten oder aufteilen" Height="400" Width="600">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<Grid>
<RadioButton x:Name="DepreciateModeInput" GroupName="ModeInput" Content="Abwerten" Margin="15,10,10,10" IsChecked="True"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="ModeInput_Changed"/>
<RadioButton x:Name="MemberModeInput" GroupName="ModeInput" Content="Auf Mitglied übertragen" Margin="15,30,10,10"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="ModeInput_Changed"/>
<RadioButton x:Name="DeliveryModeInput" GroupName="ModeInput" Content="Zu anderer Lieferung hinzufügen" Margin="15,50,10,10"
VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="ModeInput_Changed"/>
<TextBox x:Name="MgNrInput" FontSize="14" Padding="2" Visibility="Hidden"
Width="48" Margin="220,10,0,0" Height="25" TextAlignment="Right"
TextChanged="MgNrInput_TextChanged"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<ComboBox x:Name="MemberInput" FontSize="14" Visibility="Hidden"
Margin="273,10,40,10" IsEditable="True" Height="25"
ItemTemplate="{StaticResource MemberAdminNameTemplate}" TextSearch.TextPath="AdministrativeName"
SelectionChanged="MemberInput_SelectionChanged"
VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<Button x:Name="MemberReferenceButton" Height="25" Width="25" FontFamily="Segoe MDL2 Assets" Content="&#xEE35;" Padding="0"
Margin="10,10,10,10" VerticalAlignment="Top" HorizontalAlignment="Right" ToolTip="Zu Mitglied springen" FontSize="14" Visibility="Hidden"
Click="MemberReferenceButton_Click"/>
<ComboBox x:Name="DeliveryInput" FontSize="14" Visibility="Hidden"
Margin="220,10,10,10" Height="25"
TextSearch.TextPath="LsNr"
SelectionChanged="DeliveryInput_SelectionChanged"
VerticalAlignment="Top" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding LsNr}" Width="100"/>
<TextBlock Text="{Binding Weight, StringFormat='{}{0:N0} kg'}" Width="80" TextAlignment="Right" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Member.AdministrativeName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock x:Name="InfoBlock" Margin="230,45,10,10" FontSize="14" TextAlignment="Right"
VerticalAlignment="Top" HorizontalAlignment="Stretch"
Text="Insgesamt 0 kg / 0 kg ausgewählt."/>
<ListBox x:Name="DeliveryPartList" Margin="10,75,10,40" ItemsSource="{Binding DeliveryParts, Mode=TwoWay}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Part.DPNr}" Width="20" TextAlignment="Right"
VerticalAlignment="Center" Margin="0,0,5,0" FontSize="14"/>
<TextBlock Text="{Binding Part.SortId}" Width="40" TextAlignment="Center"
VerticalAlignment="Center" Margin="0,0,0,0" FontSize="14"/>
<TextBlock Text="{Binding Part.Kmw, StringFormat='{}{0:N1}°'}" Width="40" TextAlignment="Right" Padding="0,0,10,0"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding Part.QualId}" Width="30"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding Part.Weight, StringFormat='{}{0:N0} kg'}" Width="70" TextAlignment="Right"
VerticalAlignment="Center" Margin="0,0,10,0" FontSize="14"/>
<TextBlock Text="{Binding Part.Attribute.Name}" Width="60"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding Part.Cultivation.Name}" Width="50"
VerticalAlignment="Center"/>
<CheckBox IsChecked="{Binding SplitCompletely, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="Vollständig" Tag="{Binding Part.DPNr}"
VerticalAlignment="Center" Margin="20,0,5,0"
Checked="SplitCompletelyInput_Changed" Unchecked="SplitCompletelyInput_Changed"/>
<ctrl:UnitTextBox Text="{Binding SplitWeightString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Unit="kg" Width="70" Height="25" IsEnabled="{Binding SplitWeightEnabled}" Tag="{Binding Part.DPNr}"
VerticalAlignment="Center" Margin="5,0,5,0" FontSize="14" Padding="2" Background="White"
TextChanged="SplitWeightInput_TextChanged"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" IsEnabled="False"
Click="ConfirmButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" IsCancel="True"/>
</Grid>
</local:ContextWindow>

View File

@ -0,0 +1,163 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using Elwig.Windows;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Elwig.Dialogs {
public partial class DeliverySplittingDialog : ContextWindow {
public class Row {
public DeliveryPart Part { get; set; }
public bool SplitCompletely { get; set; }
public string? SplitWeightString { get; set; }
public int? SplitWeight {
get => int.TryParse(SplitWeightString, out var v) ? v : null;
set => SplitWeightString = $"{value}";
}
public bool SplitWeightEnabled { get; set; }
}
private readonly Delivery _delivery;
public int? MgNr { get; set; }
public string? LsNr { get; set; }
public int[]? Weights => DeliveryParts.Select(r => r.SplitCompletely ? r.Part.Weight : r.SplitWeight ?? 0).ToArray();
public ObservableCollection<Row> DeliveryParts { get; set; }
public DeliverySplittingDialog(Delivery d) {
_delivery = d;
DeliveryParts = new(d.Parts.Select(p => new Row {
Part = p,
SplitCompletely = false,
SplitWeight = null,
SplitWeightEnabled = true,
}).ToList());
InitializeComponent();
}
protected override async Task OnRenewContext(AppDbContext ctx) {
ControlUtils.RenewItemsSource(MemberInput, await ctx.Members
.Where(m => m.IsActive)
.OrderBy(m => m.Name)
.ThenBy(m => m.GivenName)
.ToListAsync());
ControlUtils.RenewItemsSource(DeliveryInput, await ctx.Deliveries
.Where(d => d.DateString == $"{_delivery.Date:yyyy-MM-dd}" && d.ZwstId == _delivery.ZwstId)
.OrderBy(d => d.LsNr)
.Include(d => d.Member)
.Include(d => d.Parts)
.ToListAsync());
if (DeliveryInput.SelectedItem == null)
ControlUtils.SelectItem(DeliveryInput, _delivery);
CheckValidity();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
if (DepreciateModeInput.IsChecked == true) {
MgNr = null;
LsNr = null;
} else if (MemberModeInput.IsChecked == true) {
MgNr = ((Member)MemberInput.SelectedItem).MgNr;
LsNr = null;
} else if (DeliveryModeInput.IsChecked == true) {
MgNr = null;
LsNr = ((Delivery)DeliveryInput.SelectedItem).LsNr;
}
DialogResult = true;
Close();
}
private void ModeInput_Changed(object sender, RoutedEventArgs evt) {
if (!IsLoaded) return;
CheckValidity();
if (DepreciateModeInput.IsChecked == true) {
MgNrInput.Visibility = Visibility.Hidden;
MemberInput.Visibility = Visibility.Hidden;
MemberReferenceButton.Visibility = Visibility.Hidden;
DeliveryInput.Visibility = Visibility.Hidden;
} else if (MemberModeInput.IsChecked == true) {
MgNrInput.Visibility = Visibility.Visible;
MemberInput.Visibility = Visibility.Visible;
MemberReferenceButton.Visibility = Visibility.Visible;
DeliveryInput.Visibility = Visibility.Hidden;
} else if (DeliveryModeInput.IsChecked == true) {
MgNrInput.Visibility = Visibility.Hidden;
MemberInput.Visibility = Visibility.Hidden;
MemberReferenceButton.Visibility = Visibility.Hidden;
DeliveryInput.Visibility = Visibility.Visible;
}
}
private void CheckValidity() {
var weight = DeliveryParts.Sum(r => r.SplitCompletely ? r.Part.Weight : r.SplitWeight ?? 0);
var total = DeliveryParts.Sum(r => r.Part.Weight);
InfoBlock.Text = $"Insgesamt {weight:N0} kg / {total:N0} kg ausgewählt.";
ConfirmButton.IsEnabled = DeliveryParts.Any(r => r.SplitCompletely || r.SplitWeight > 0) && (
DepreciateModeInput.IsChecked == true ||
(MemberModeInput.IsChecked == true && MemberInput.SelectedItem != null && !DeliveryParts.All(r => r.SplitCompletely)) ||
(DeliveryModeInput.IsChecked == true && DeliveryInput.SelectedItem != null));
}
private void MgNrInput_TextChanged(object sender, TextChangedEventArgs evt) {
var res = Validator.CheckMgNr((TextBox)sender, true);
var text = MgNrInput.Text;
var caret = MgNrInput.CaretIndex;
ControlUtils.SelectItemWithPk(MemberInput, res.IsValid ? int.Parse(MgNrInput.Text) : null);
MgNrInput.Text = text;
MgNrInput.CaretIndex = caret;
}
private void MemberInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
var m = MemberInput.SelectedItem as Member;
MgNrInput.Text = m?.MgNr.ToString();
CheckValidity();
}
private void MemberReferenceButton_Click(object sender, RoutedEventArgs evt) {
if (MemberInput.SelectedItem is not Member m) return;
App.FocusMember(m.MgNr);
}
private void DeliveryInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
CheckValidity();
}
private void SplitCompletelyInput_Changed(object sender, RoutedEventArgs evt) {
var checkbox = (CheckBox)sender;
var dpnr = int.Parse(checkbox.Tag.ToString()!);
var row = DeliveryParts.First(d => d.Part.DPNr == dpnr);
if (checkbox.IsChecked == true) {
row.SplitWeightEnabled = false;
row.SplitWeight = row.Part.Weight;
} else if (checkbox.IsChecked == false) {
row.SplitWeightEnabled = true;
row.SplitWeight = null;
}
CollectionViewSource.GetDefaultView(DeliveryParts).Refresh();
CheckValidity();
}
private void SplitWeightInput_TextChanged(object sender, TextChangedEventArgs evt) {
var textbox = (TextBox)sender;
Validator.CheckInteger(textbox, false, 6);
var dpnr = int.Parse(textbox.Tag.ToString()!);
var row = DeliveryParts.First(d => d.Part.DPNr == dpnr);
var w = int.TryParse(textbox.Text, out var v) ? v : (int?)null;
if (w >= row.Part.Weight) {
row.SplitCompletely = true;
row.SplitWeightEnabled = false;
row.SplitWeight = row.Part.Weight;
CollectionViewSource.GetDefaultView(DeliveryParts).Refresh();
}
CheckValidity();
}
}
}

View File

@ -81,11 +81,11 @@ namespace Elwig.Documents {
}
private static string FormatRow(
int obligation, int right, int delivery, int? payment = null, int? area = null,
int obligation, int right, int delivery, int? totalDelivery = null, int? payment = null, int? area = null,
bool isGa = false, bool showPayment = false, bool showArea = false
) {
totalDelivery ??= delivery;
payment ??= delivery;
var baseline = showPayment ? payment : delivery;
if (showArea) {
return $"<td>{(area == null ? "" : $"{area:N0}")}</td>" +
@ -95,15 +95,15 @@ namespace Elwig.Documents {
return $"<td>{(obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
$"<td>{(right == 0 ? "-" : $"{right:N0}")}</td>" +
$"<td>{(baseline < obligation ? $"<b>{obligation - baseline:N0}</b>" : "-")}</td>" +
$"<td>{(baseline >= obligation && delivery <= right ? $"{right - delivery:N0}" : "-")}</td>" +
$"<td>{(totalDelivery < obligation ? $"<b>{obligation - totalDelivery:N0}</b>" : "-")}</td>" +
$"<td>{(delivery <= right ? $"{right - delivery:N0}" : "-")}</td>" +
$"<td>{(obligation == 0 && right == 0 ? "-" : (delivery > right ? ((isGa ? "<b>" : "") + $"{delivery - right:N0}" + (isGa ? "</b>" : "")) : "-"))}</td>" +
(showPayment ? $"<td>{(isGa ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}</td>" : "") +
$"<td>{delivery:N0}</td>";
$"<td>{totalDelivery:N0}</td>";
}
private static string FormatRow(MemberBucket bucket, bool isGa = false, bool showPayment = false, bool showArea = false) {
return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.Payment, bucket.Area, isGa, showPayment, showArea);
return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.DeliveryTotal, bucket.Payment, bucket.Area, isGa, showPayment, showArea);
}
public string PrintBucketTable(

View File

@ -9,8 +9,8 @@
<table class="credit">
<colgroup>
<col style="width: 25mm;"/>
<col style="width: 5mm;"/>
<col style="width: 22mm;"/>
<col style="width: 6mm;"/>
<col style="width: 21mm;"/>
<col style="width: 15mm;"/>
<col style="width: 10mm;"/>
<col style="width: 10mm;"/>
@ -51,7 +51,7 @@
<tr class="@(i == 0 ? "first" : "") @(rows == i + 1 ? "last" : "")">
@if (i == 0) {
<td rowspan="@rows">@p.LsNr</td>
<td rowspan="@rows">@p.DPNr</td>
<td rowspan="@rows" class="center narrow">@p.DPNr</td>
<td class="small">@p.Variety</td>
<td class="small">
@p.Attribute@(p.Attribute != null && p.Cultivation != null ? " / " : "")@p.Cultivation

View File

@ -8,10 +8,11 @@
<h2>@Model.Filter</h2>
<table class="announcement-list">
<colgroup>
<col style="width: 18mm;"/>
<col style="width: 15mm;"/>
<col style="width: 12mm;"/>
<col style="width: 70mm;"/>
<col style="width: 40mm;"/>
<col style="width: 50mm;"/>
<col style="width: 25mm;"/>
<col style="width: 38mm;"/>
<col style="width: 11mm;"/>
<col style="width: 14mm;"/>
</colgroup>
@ -20,6 +21,7 @@
<th rowspan="2">Datum</th>
<th rowspan="2">MgNr.</th>
<th rowspan="2" style="text-align: left;">Mitglied</th>
<th rowspan="2" style="text-align: left;">Ort</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="2">Anmldg.</th>
<th>Gewicht</th>
@ -31,9 +33,10 @@
<tbody>
@foreach (var a in Model.Announcements) {
<tr>
<td>@($"{a.Date:dd.MM.yyyy}")</td>
<td class="small">@($"{a.Date:dd.MM.yyyy}")</td>
<td class="number">@a.MgNr</td>
<td>@a.AdministrativeName</td>
<td class="small">@a.DefaultKg</td>
<td>@a.Variety</td>
<td class="small center">@(a.Status ?? "-")</td>
<td class="number">@($"{a.Weight:N0}")</td>
@ -41,7 +44,7 @@
}
<tr class="sum bold">
<td colspan="2">Gesamt:</td>
<td colspan="2">Anmeldungen: @($"{Model.Announcements.Count():N0}")</td>
<td colspan="3">Anmeldungen: @($"{Model.Announcements.Count():N0}")</td>
<td colspan="2" class="number">@($"{Model.Announcements.Sum(a => a.Weight):N0}")</td>
</tr>
</tbody>

View File

@ -9,8 +9,8 @@
<table class="delivery-confirmation">
<colgroup>
<col style="width: 25mm;"/>
<col style="width: 5mm;"/>
<col style="width: 24mm;"/>
<col style="width: 6mm;"/>
<col style="width: 23mm;"/>
<col style="width: 16mm;"/>
<col style="width: 17mm;"/>
<col style="width: 10mm;"/>
@ -60,7 +60,7 @@
<tr class="@(first ? "first" : "") @(p.Variety != lastVariety && lastVariety != "" ? "new": "") @(rows > i + 1 ? "last" : "")">
@if (first) {
<td rowspan="@rows">@p.LsNr</td>
<td rowspan="@rows">@p.DPNr</td>
<td rowspan="@rows" class="center narrow">@p.DPNr</td>
<td class="small">@p.Variety</td>
<td class="small">@p.Attribute@(p.Attribute != null && p.Cultivation != null ? " / " : "")@p.Cultivation</td>
<td class="small">@p.QualityLevel</td>

View File

@ -0,0 +1,22 @@
using Elwig.Models.Dtos;
using System.Collections.Generic;
namespace Elwig.Documents {
public class DeliveryDepreciationList : Document {
public new static string Name => "Abwertungsliste";
public string Filter;
public IEnumerable<DeliveryJournalRow> Deliveries;
public DeliveryDepreciationList(string filter, IEnumerable<DeliveryJournalRow> deliveries) :
base($"{Name} {filter}") {
Filter = filter;
Deliveries = deliveries;
}
public DeliveryDepreciationList(string filter, DeliveryJournalData data) :
this(filter, data.Rows) {
}
}
}

View File

@ -0,0 +1,104 @@
@using RazorLight
@inherits TemplatePage<Elwig.Documents.DeliveryDepreciationList>
@model Elwig.Documents.DeliveryDepreciationList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryDepreciationList.css" />
<main>
<h1>Abwertungsliste</h1>
<h2>@Model.Filter</h2>
<table class="journal">
<colgroup>
<col style="width: 25mm;"/>
<col style="width: 6mm;"/>
<col style="width: 20mm;"/>
<col style="width: 15mm;"/>
<col style="width: 35mm;"/>
<col style="width: 30mm;"/>
<col style="width: 10mm;"/>
<col style="width: 10mm;"/>
<col style="width: 14mm;"/>
</colgroup>
<thead>
<tr>
<th rowspan="2" style="text-align: left;">Lieferschein-Nr.</th>
<th rowspan="2" class="narrow">Pos.</th>
<th rowspan="2">Datum</th>
<th rowspan="2">Zeit</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="2" style="text-align: left;">Attr./Bewirt.</th>
<th colspan="2">Gradation</th>
<th>Gewicht</th>
</tr>
<tr>
<th class="unit">[°Oe]</th>
<th class="unit narrow">[°KMW]</th>
<th class="unit">[kg]</th>
</tr>
</thead>
<tbody>
@{
int? lastMember = null;
}
@foreach (var p in Model.Deliveries) {
if (lastMember != p.MgNr) {
<tr class="subheading @(lastMember != null ? "new" : "")">
@{
var memberDeliveries = Model.Deliveries.Where(d => d.MgNr == p.MgNr).ToList();
var memberKmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(memberDeliveries);
var memberOe = Elwig.Helpers.Utils.KmwToOe(memberKmw);
}
<th colspan="5">
@($"{p.MgNr}, {p.AdministrativeName}")
</th>
<td>Teil-Lfrg.: <span style="float: right;">@($"{memberDeliveries.Count():N0}")</span></td>
<td class="center">@($"{memberOe:N0}")</td>
<td class="center">@($"{memberKmw:N1}")</td>
<td class="number">@($"{memberDeliveries.Sum(p => p.Weight):N0}")</td>
</tr>
}
<tr>
<td>@p.LsNr</td>
<td class="center narrow">@p.Pos</td>
<td>@($"{p.Date:dd.MM.yyyy}")</td>
<td>@($"{p.Time:HH:mm}")</td>
<td>@p.Variety</td>
<td>@p.Attribute@(p.Attribute != null && p.Cultivation != null ? " / " : "")@p.Cultivation</td>
<td class="center">@($"{p.Oe:N0}")</td>
<td class="center">@($"{p.Kmw:N1}")</td>
<td class="number">@($"{p.Weight:N0}")</td>
</tr>
lastMember = p.MgNr;
}
@{
var branches = Model.Deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray();
if (branches.Length > 1) {
foreach (var b in branches) {
<tr class="@(branches[0] == b ? "sum" : "") bold">
@{
var branchDeliveries = Model.Deliveries.Where(d => d.DeliveryBranch == b).ToList();
var branchKmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries);
var branchOe = Elwig.Helpers.Utils.KmwToOe(branchKmw);
}
<td colspan="2">@b:</td>
<td colspan="4">(Teil-)Lieferungen: @($"{branchDeliveries.DistinctBy(p => p.LsNr).Count():N0}") (@($"{branchDeliveries.Count():N0}"))</td>
<td class="center">@($"{branchOe:N0}")</td>
<td class="center">@($"{branchKmw:N1}")</td>
<td class="number">@($"{branchDeliveries.Sum(p => p.Weight):N0}")</td>
</tr>
}
}
}
<tr class="sum bold">
@{
var kmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(Model.Deliveries);
var oe = Elwig.Helpers.Utils.KmwToOe(kmw);
}
<td colspan="2">Gesamt:</td>
<td colspan="4">(Teil-)Lieferungen: @($"{Model.Deliveries.DistinctBy(p => p.LsNr).Count():N0}") (@($"{Model.Deliveries.Count():N0}"))</td>
<td class="center">@($"{oe:N0}")</td>
<td class="center">@($"{kmw:N1}")</td>
<td class="number">@($"{Model.Deliveries.Sum(p => p.Weight):N0}")</td>
</tr>
</tbody>
</table>
</main>

View File

@ -0,0 +1,13 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 10mm;
margin-bottom: 2mm;
}
h2 {
text-align: center;
font-size: 14pt;
margin-top: 2mm;
}

View File

@ -9,7 +9,8 @@ namespace Elwig.Documents {
public string Filter;
public IEnumerable<DeliveryJournalRow> Deliveries;
public DeliveryJournal(string filter, IEnumerable<DeliveryJournalRow> deliveries) : base($"{Name} {filter}") {
public DeliveryJournal(string filter, IEnumerable<DeliveryJournalRow> deliveries) :
base($"{Name} {filter}") {
Filter = filter;
Deliveries = deliveries;
}

View File

@ -9,10 +9,10 @@
<table class="journal">
<colgroup>
<col style="width: 25mm;"/>
<col style="width: 5mm;"/>
<col style="width: 6mm;"/>
<col style="width: 15mm;"/>
<col style="width: 8mm;"/>
<col style="width: 12mm;"/>
<col style="width: 8mm;"/>
<col style="width: 11mm;"/>
<col style="width: 38mm;"/>
<col style="width: 28mm;"/>
<col style="width: 10mm;"/>
@ -41,7 +41,7 @@
@foreach (var p in Model.Deliveries) {
<tr>
<td>@p.LsNr</td>
<td>@p.Pos</td>
<td class="center narrow">@p.Pos</td>
<td class="small">@($"{p.Date:dd.MM.yyyy}")</td>
<td class="small">@($"{p.Time:HH:mm}")</td>
<td class="number">@p.MgNr</td>
@ -52,6 +52,25 @@
<td class="number">@($"{p.Weight:N0}")</td>
</tr>
}
@{
var branches = Model.Deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray();
if (branches.Length > 1) {
foreach (var b in branches) {
<tr class="@(branches[0] == b ? "sum" : "") bold">
@{
var branchDeliveries = Model.Deliveries.Where(d => d.DeliveryBranch == b).ToList();
var branchKmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries);
var branchOe = Elwig.Helpers.Utils.KmwToOe(branchKmw);
}
<td colspan="2">@b:</td>
<td colspan="5">(Teil-)Lieferungen: @($"{branchDeliveries.DistinctBy(p => p.LsNr).Count():N0}") (@($"{branchDeliveries.Count():N0}"))</td>
<td class="center">@($"{branchOe:N0}")</td>
<td class="center">@($"{branchKmw:N1}")</td>
<td class="number">@($"{branchDeliveries.Sum(p => p.Weight):N0}")</td>
</tr>
}
}
}
<tr class="sum bold">
@{
var kmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(Model.Deliveries);

View File

@ -78,6 +78,8 @@ namespace Elwig.Documents {
name = "CreditNote";
} else if (this is DeliveryJournal) {
name = "DeliveryJournal";
} else if (this is DeliveryDepreciationList) {
name = "DeliveryDepreciationList";
} else if (this is Letterhead) {
name = "Letterhead";
} else if (this is DeliveryConfirmation) {
@ -153,7 +155,7 @@ namespace Elwig.Documents {
public async Task Print(int copies = 1) {
if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
await Pdf.Print(PdfPath, copies);
await Pdf.Print(PdfPath, copies, DoublePaged);
}
public void Show() {

View File

@ -7,7 +7,7 @@
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.11.1</Version>
<Version>1.0.0.0</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
@ -25,19 +25,21 @@
</Target>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.1" />
<PackageReference Include="LinqKit" Version="1.3.0" />
<PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.33" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2739.15" />
<PackageReference Include="NJsonSchema" Version="11.0.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="LinqKit" Version="1.3.7" />
<PackageReference Include="MailKit" Version="4.9.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<PackageReference Include="NJsonSchema" Version="11.1.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="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.39" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.53" />
<PackageReference Include="System.IO.Ports" Version="9.0.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.1" />
</ItemGroup>
</Project>

View File

@ -17,7 +17,7 @@ namespace Elwig.Helpers {
public record struct AreaComBucket(int Area, int Obligation, int Right);
public record struct UnderDelivery(int Weight, int Diff);
public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int Payment);
public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int DeliveryTotal, int Payment);
public record struct MemberStat(string Variety, string Discr, int Weight);
public record struct ModifierStat(string ModId, string Name, int Count, decimal? Min, decimal? Max, decimal Sum);
@ -63,10 +63,12 @@ namespace Elwig.Helpers {
public DbSet<PaymentDeliveryPart> PaymentDeliveryParts { get; private set; }
public DbSet<PaymentCustom> CustomPayments { get; private set; }
public DbSet<Credit> Credits { get; private set; }
public DbSet<DeliveryPartBucket> DeliveryPartBuckets { get; private set; }
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; }
public DbSet<MemberAreaComsRowSingle> MemberAreaComsRows { get; private set; }
public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; }
@ -78,7 +80,7 @@ namespace Elwig.Helpers {
public bool HasBackendChanged => SavedLastWriteTime != LastWriteTime;
public static string? ConnectionStringOverride { get; set; } = null;
public static string ConnectionString => ConnectionStringOverride ?? $"Data Source=\"{App.Config.DatabaseFile}\"; Mode=ReadWrite; Foreign Keys=True; Cache=Default";
public static string ConnectionString => ConnectionStringOverride ?? $"Data Source=\"{App.Config.DatabaseFile}\"; Mode=ReadWrite; Foreign Keys=True; Cache=Default; Pooling=False";
private readonly Dictionary<int, Dictionary<int, Dictionary<string, AreaComBucket>>> _memberAreaCommitmentBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = [];
@ -203,10 +205,10 @@ namespace Elwig.Helpers {
return c + 1;
}
public async Task<int> NextLNr(DateOnly date) {
public async Task<int> NextLNr(DateOnly date, string zwstid) {
var dateStr = date.ToString("yyyy-MM-dd");
int c = 0;
(await Deliveries.Where(d => d.DateString == dateStr).Select(d => d.LNr).ToListAsync())
(await Deliveries.Where(d => d.DateString == dateStr && d.ZwstId == zwstid).Select(d => d.LNr).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
@ -442,6 +444,7 @@ namespace Elwig.Helpers {
rightsAndObligations.GetValueOrDefault(id).Right,
deliveryBuckets.GetValueOrDefault(id),
deliveryBucketsStrict.GetValueOrDefault(id),
deliveryBuckets.GetValueOrDefault(id) + deliveryBuckets.GetValueOrDefault(id + "_"),
paymentBuckets.GetValueOrDefault(id)
);
}

View File

@ -9,7 +9,7 @@ namespace Elwig.Helpers {
public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 30;
public static readonly int RequiredSchemaVersion = 31;
private static int VersionOffset = 0;

View File

@ -299,22 +299,30 @@ namespace Elwig.Helpers.Billing {
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);
if (!data.ContainsKey("default")) {
foreach (var (v, ks) in rev1) {
if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
foreach (var k in ks) data.Remove(k);
if ((ks.Count > vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
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;
CollapsePaymentData(data, vaributes, useDefault);
CollapsePaymentData(data, originalData, vaributes, useDefault);
return;
}
}
foreach (var (v, ks) in rev2) {
if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
foreach (var k in ks) data.Remove(k);
if ((ks.Count > vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
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;
CollapsePaymentData(data, vaributes, useDefault);
CollapsePaymentData(data, originalData, vaributes, useDefault);
return;
}
}
@ -330,16 +338,26 @@ namespace Elwig.Helpers.Billing {
var len = vaributes.Count(e => $"{e.AttrId}{(e.CultId != null && e.CultId != "" ? "-" : "")}{e.CultId}" == idx);
foreach (var (v, ks) in rev1) {
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);
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) {
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);
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("/-")) {
data.Remove(k, out var 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);
var keyVaributes = data
.Select(e => e.Key.Split('-')[0])
.Where(e => e.Length > 0 && e != "default")
.Select(e => e.Key)
.Where(e => e.Length > 0 && !e.Contains('-') && e != "default")
.Distinct()
.ToList();
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(qualityWei, vaributes ?? qualityWei.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultQuality);
CollapsePaymentData(payment, payment.DeepClone().AsObject(), vaributes ?? payment.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultPayment);
CollapsePaymentData(qualityWei, qualityWei.DeepClone().AsObject(), vaributes ?? qualityWei.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultQuality);
var data = new JsonObject {
["mode"] = "elwig",

View File

@ -46,14 +46,14 @@ namespace Elwig.Helpers.Billing {
m.mgnr,
v.avnr,
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,
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.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) +
IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
AS modifiers,
lc.modifiers AS prev_modifiers
IIF(lc.amount >= 0, lc.modifiers, 0) AS prev_modifiers
FROM season s
JOIN payment_variant v ON v.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() => 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 NameShort;
public string Name;
@ -67,6 +71,12 @@ namespace Elwig.Helpers {
public string? TextEmailSubject;
public string? TextEmailBody;
public bool MailIncludeNonDeliverers;
public bool MailDoublePaged;
public int MailSendPostal;
public int MailSendEmail;
public int MailOrdering;
public int ExportEbicsVersion;
public int ExportEbicsAddress;
@ -117,7 +127,7 @@ namespace Elwig.Helpers {
case "KMW/5": ModeWineQualityStatistics = 3; 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 "NAME": OrderingMemberList = 1; break;
case "KG": OrderingMemberList = 2; break;
@ -135,6 +145,31 @@ namespace Elwig.Helpers {
TextEmailBody = parameters.GetValueOrDefault("TEXT_EMAIL_BODY");
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;
switch (parameters.GetValueOrDefault("EXPORT_EBICS_ADDRESS", "FULL")?.ToUpper()) {
case "OMIT": ExportEbicsAddress = 0; break;
@ -177,6 +212,25 @@ namespace Elwig.Helpers {
case 1: orderingMemberList = "NAME"; 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";
switch (ExportEbicsAddress) {
case 0: exportEbicsAddress = "OMIT"; break;
@ -212,6 +266,11 @@ namespace Elwig.Helpers {
("TEXT_CREDITNOTE", TextCreditNote),
("TEXT_EMAIL_SUBJECT", TextEmailSubject),
("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_ADDRESS", exportEbicsAddress),
("AUTOADJUST_BUSINESSSHARES", autoAdjust),

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using Brush = System.Windows.Media.Brush;
using Brushes = System.Windows.Media.Brushes;
@ -234,5 +235,21 @@ namespace Elwig.Helpers {
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

@ -22,7 +22,7 @@ namespace Elwig.Helpers.Export {
""";
var c = App.Client;
var (a1, a2) = Utils.SplitAddress(c.Address);
_clientData = $"{c.LfbisNr};{c.NameFull};;{a1};{a2};{c.Plz};{c.Ort}";
_clientData = $"{c.LfbisNr};{c.NameFull};;{a1};\t{a2};{c.Plz};{c.Ort}";
}
public async Task ExportAsync(int year) {
@ -35,7 +35,7 @@ namespace Elwig.Helpers.Export {
WHERE year = {year}
""";
var r = await cmd.ExecuteReaderAsync();
List<Row> rows = new();
List<Row> rows = [];
while (await r.ReadAsync()) {
rows.Add(new(
(r.IsDBNull(0) ? null : r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetInt32(7)),
@ -57,7 +57,7 @@ namespace Elwig.Helpers.Export {
var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName);
var (a1, a2) = Utils.SplitAddress(address);
var memberData = $"{lfBisNr};{n1};{n2};{a1};{a2};{plz};{ort}";
var memberData = $"{lfBisNr};{n1};{n2};{a1};\t{a2};{plz};{ort}";
var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}";
var vollData = $"N;;;{area / 10_000.0}";

View File

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

View File

@ -23,7 +23,7 @@ namespace Elwig.Helpers {
}
public static string? ReadUntil(this StreamReader reader, char delimiter) {
return ReadUntil(reader, new char[] { delimiter });
return ReadUntil(reader, [ delimiter ]);
}
public static string? ReadUntil(this StreamReader reader, char[] delimiter) {
@ -45,7 +45,7 @@ namespace Elwig.Helpers {
}
public static Task<string?> ReadUntilAsync(this StreamReader reader, char delimiter) {
return ReadUntilAsync(reader, new char[] { delimiter });
return ReadUntilAsync(reader, [ delimiter ]);
}
public static async Task<string?> ReadUntilAsync(this StreamReader reader, char[] delimiter) {

View File

@ -8,16 +8,12 @@ using System.Windows;
using System.Text.RegularExpressions;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using PdfiumViewer;
using System.Drawing.Printing;
namespace Elwig.Helpers.Printing {
public static class Pdf {
private static readonly string PdfToPrinter = new string[] { App.ExePath }
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
.Select(x => Path.Combine(x, "PDFtoPrinter.exe"))
.Where(File.Exists)
.FirstOrDefault() ?? throw new FileNotFoundException("PDFtoPrinter executable not found");
private static readonly string WinziPrint = new string[] { App.ExePath }
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
.Select(x => Path.Combine(x, "WinziPrint.exe"))
@ -92,21 +88,24 @@ namespace Elwig.Helpers.Printing {
});
}
public static async Task Print(string path, int copies = 1) {
public static async Task Print(string path, int copies = 1, bool doublePaged = false) {
await Print(path, new() {
Copies = (short)copies,
Collate = true,
Duplex = doublePaged ? Duplex.Vertical : Duplex.Simplex,
});
}
public static Task Print(string path, PrinterSettings settings) {
try {
var p = new Process() { StartInfo = new() {
FileName = PdfToPrinter,
CreateNoWindow = true,
UseShellExecute = false,
} };
p.StartInfo.ArgumentList.Add(path);
p.StartInfo.ArgumentList.Add("/s");
p.StartInfo.ArgumentList.Add($"copies={copies}");
p.Start();
await p.WaitForExitAsync();
using var doc = PdfDocument.Load(path);
using var printDoc = doc.CreatePrintDocument(PdfPrintMode.CutMargin);
printDoc.PrinterSettings = settings;
printDoc.Print();
} catch (Exception e) {
MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken");
MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken", MessageBoxButton.OK, MessageBoxImage.Error);
}
return Task.CompletedTask;
}
}
}

View File

@ -28,6 +28,9 @@ using LinqKit;
using System.Linq.Expressions;
using Elwig.Models;
using Microsoft.Win32;
using System.Globalization;
using System.Threading;
using System.Windows.Markup;
namespace Elwig.Helpers {
public static partial class Utils {
@ -118,6 +121,23 @@ namespace Elwig.Helpers {
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,
];
public static void OverrideCulture() {
var locale = new CultureInfo("de-AT", false);
locale.NumberFormat.CurrencyGroupSeparator = Utils.GroupSeparator;
locale.NumberFormat.NumberGroupSeparator = Utils.GroupSeparator;
locale.NumberFormat.PercentGroupSeparator = Utils.GroupSeparator;
CultureInfo.CurrentCulture = locale;
CultureInfo.CurrentUICulture = locale;
Thread.CurrentThread.CurrentCulture = locale;
Thread.CurrentThread.CurrentUICulture = locale;
CultureInfo.DefaultThreadCurrentCulture = locale;
CultureInfo.DefaultThreadCurrentUICulture = locale;
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name))
);
}
public static SerialPort OpenSerialConnection(string connection) {
var m = SerialRegex.Match(connection);
if (!m.Success)
@ -267,9 +287,9 @@ namespace Elwig.Helpers {
return d.ShowDialog() == true ? (d.Weight, d.Reason) : null;
}
public static int? ShowAbwertenDialog(string lsnr, string name, int weight) {
var d = new AbwertenDialog(lsnr, name, weight);
return d.ShowDialog() == true ? d.Weight : null;
public static (string?, int[])? ShowDeliverySplittingDialog(Delivery delivery) {
var d = new DeliverySplittingDialog(delivery);
return d.ShowDialog() == true ? (d.MgNr?.ToString() ?? d.LsNr, d.Weights ?? []) : null;
}
public static double? ShowLinearPriceIncreaseDialog() {
@ -277,11 +297,6 @@ namespace Elwig.Helpers {
return d.ShowDialog() == true ? d.Price : null;
}
public static string? ShowDeliveryExtractionDialog(string lsnr, string name, bool single, IEnumerable<string> lsnrs) {
var d = new DeliveryExtractionDialog(lsnr, name, single, lsnrs);
return d.ShowDialog() == true ? d.AddTo : null;
}
public static Footer GenerateFooter(string lineBreak, string seperator) {
return new Footer(lineBreak, seperator);
}
@ -539,6 +554,16 @@ namespace Elwig.Helpers {
}
}
public static List<EventLogEntry> GetLogEntries() {
using var log = new EventLog {
Log = "Application",
Source = ".NET Runtime",
};
return log.Entries.Cast<EventLogEntry>()
.Where(e => e.Message.StartsWith("Application: Elwig.exe"))
.ToList();
}
public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
var pk = primaryKey.Select(k => k?.GetHashCode() ?? 0).ToArray();
return ((IStructuralEquatable)pk).GetHashCode(EqualityComparer<int>.Default);
@ -569,5 +594,76 @@ namespace Elwig.Helpers {
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear);
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query, int year) =>
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

@ -58,11 +58,19 @@ namespace Elwig.Helpers.Weighing {
}
protected async Task<WeighingResult?> Receive() {
var line = await Reader.ReadUntilAsync("\r\n");
var line = "";
while (line.Length < 33) {
var ch = Reader.Read();
if (ch == -1) {
return null;
} else if (line.Length > 0 || ch == ' ') {
line += char.ToString((char)ch);
}
}
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
if (line == null || line == "") {
return null;
} else if (line.Length != 35 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
} else if (line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
throw new IOException($"Invalid event from scale: '{line}'");
}

View File

@ -34,7 +34,7 @@ namespace Elwig.Models.Dtos {
}
private static async Task<IEnumerable<AreaComUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaComUnderDeliveryRowSingle> table, int year) {
return await table.FromSqlRaw($"""
return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
@ -45,7 +45,7 @@ namespace Elwig.Models.Dtos {
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
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)
WHERE c.year = {year} AND m.active = 1
WHERE c.year = {year}
ORDER BY m.mgnr, c.bucket
""").ToListAsync();
}

View File

@ -49,7 +49,7 @@ namespace Elwig.Models.Dtos {
}
private static async Task<IEnumerable<CreditNoteRowSingle>> FromDbSet(DbSet<CreditNoteRowSingle> table, int year, int avnr) {
return await table.FromSqlRaw($"""
return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,

View File

@ -15,6 +15,7 @@ namespace Elwig.Models.Dtos {
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("DefaultKg", "Ort", null, 40),
("SortId", "Sorte", null, 10),
("Weight", "Gewicht", "kg", 20),
("CreatedTimestamp", "Angemeldet", null, 35),
@ -43,6 +44,7 @@ namespace Elwig.Models.Dtos {
public string Name1;
public string? Name2;
public string AdministrativeName;
public string? DefaultKg;
public string SortId;
public string Variety;
public DateTime CreatedTimestamp;
@ -60,6 +62,7 @@ namespace Elwig.Models.Dtos {
Name1 = m.AdministrativeName1;
Name2 = m.AdministrativeName2;
AdministrativeName = m.AdministrativeName;
DefaultKg = m.DefaultKg?.Name;
SortId = a.SortId;
Variety = a.Variety.Name;
CreatedTimestamp = a.CreatedTimestamp;

View File

@ -0,0 +1,105 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
class MemberAreaComsData : DataTable<MemberAreaComsRow> {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SortIds", "Sorte", null, 12),
("AttrIds", "Attribut", null, 16),
("Areas", "Fläche", "m²", 22),
("DeliveryObligations", "Lieferpflicht", "kg", 22),
("DeliveryRights", "Lieferrecht", "kg", 22),
];
public MemberAreaComsData(IEnumerable<MemberAreaComsRow> rows, int year) :
base($"Flächenbindungen", $"Flächenbindungen pro Mitglied {year}", rows, FieldNames) {
}
public static async Task<MemberAreaComsData> ForSeason(DbSet<MemberAreaComsRowSingle> table, int year) {
return new MemberAreaComsData(
(await FromDbSet(table, year)).GroupBy(
r => r.MgNr,
(k, g) => new MemberAreaComsRow(g)
), year);
}
private static async Task<IEnumerable<MemberAreaComsRowSingle>> FromDbSet(DbSet<MemberAreaComsRowSingle> table, int year) {
return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address,
c.bucket, c.area, c.min_kg, c.max_kg
FROM v_area_commitment_bucket_strict c
LEFT JOIN member m ON m.mgnr = c.mgnr
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz
WHERE c.year = {year}
ORDER BY m.mgnr, c.bucket
""").ToListAsync();
}
}
public class MemberAreaComsRow {
public int MgNr;
public string Name1;
public string? Name2;
public string Address;
public int Plz;
public string Locality;
public string[] SortIds;
public string[] AttrIds;
public int[] Areas;
public int[] DeliveryObligations;
public int[] DeliveryRights;
public MemberAreaComsRow(IEnumerable<MemberAreaComsRowSingle> rows) {
var f = rows.First();
MgNr = f.MgNr;
Name1 = f.Name1;
Name2 = f.Name2;
Address = f.Address;
Plz = f.Plz;
Locality = f.Locality.Split(",")[0];
SortIds = rows.Select(r => r.VtrgId[..2]).ToArray();
AttrIds = rows.Select(r => r.VtrgId[2..]).ToArray();
Areas = rows.Select(r => r.Area).ToArray();
DeliveryObligations = rows.Select(r => r.MinKg).ToArray();
DeliveryRights = rows.Select(r => r.MaxKg).ToArray();
}
}
[Keyless]
public class MemberAreaComsRowSingle {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]
public int Plz { get; set; }
[Column("ort")]
public required string Locality { get; set; }
[Column("bucket")]
public required string VtrgId { get; set; }
[Column("area")]
public int Area { get; set; }
[Column("min_kg")]
public int MinKg { get; set; }
[Column("max_kg")]
public int MaxKg { get; set; }
}
}

View File

@ -1,12 +1,11 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class MemberDeliveryPerVariantData : DataTable<MemberDeliveryPerVariantRow> {
public class MemberDeliveryPerVarietyData : DataTable<MemberDeliveryPerVariantRow> {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
@ -22,13 +21,12 @@ namespace Elwig.Models.Dtos {
("Yields", "Ertrag", "kg/ha", 22),
];
public MemberDeliveryPerVariantData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) {
}
public static async Task<MemberDeliveryPerVariantData> ForSeason(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) {
return new MemberDeliveryPerVariantData(
public static async Task<MemberDeliveryPerVarietyData> ForSeason(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) {
return new MemberDeliveryPerVarietyData(
(await FromDbSet(table, year)).GroupBy(
r => r.MgNr,
(k, g) => new MemberDeliveryPerVariantRow(g)
@ -36,7 +34,7 @@ namespace Elwig.Models.Dtos {
}
private static async Task<IEnumerable<MemberDeliveryPerVariantRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) {
return await table.FromSqlRaw($"""
return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,

View File

@ -25,7 +25,7 @@ namespace Elwig.Models.Dtos {
}
public static async Task<OverUnderDeliveryData> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) {
var rows = await table.FromSqlRaw($"""
var rows = await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
@ -33,14 +33,13 @@ namespace Elwig.Models.Dtos {
m.business_shares * s.min_kg_per_bs AS min_kg,
m.business_shares * s.max_kg_per_bs AS max_kg,
COALESCE(SUM(d.weight), 0) AS sum
FROM v_delivery d
LEFT JOIN member m ON m.mgnr = d.mgnr
FROM season s, member m
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
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)
GROUP BY d.year, m.mgnr
ORDER BY 100.0 * sum / max_kg, m.mgnr;
GROUP BY s.year, m.mgnr
ORDER BY 100.0 * sum / max_kg, m.mgnr
""").ToListAsync();
return new OverUnderDeliveryData(rows, year);
}

View File

@ -49,7 +49,7 @@ namespace Elwig.Models.Dtos {
}
private static async Task<IEnumerable<PaymentVariantSummaryRow>> FromDbSet(DbSet<PaymentVariantSummaryRow> table, int year, int avnr) {
return await table.FromSqlRaw($"""
return await table.FromSql($"""
SELECT v.type AS type,
v.name AS variety,
a.name AS attribute,

View File

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

@ -0,0 +1,14 @@
-- schema version 30 to 31
PRAGMA writable_schema = ON;
DROP VIEW v_under_delivery_bucket_strict;
CREATE VIEW v_under_delivery_bucket_strict AS
SELECT c.year, c.mgnr, c.bucket, c.min_kg, SUM(COALESCE(p.weight, 0)) AS weight
FROM v_area_commitment_bucket_strict c
LEFT JOIN v_payment_bucket_strict p ON (p.year, p.mgnr, p.bucket) = (c.year, c.mgnr, c.bucket) OR (p.year, p.mgnr, p.bucket) = (c.year, c.mgnr, c.bucket || '_')
GROUP BY c.year, c.mgnr, c.bucket
ORDER BY c.year, c.mgnr, c.bucket;
PRAGMA schema_version = 3001;
PRAGMA writable_schema = OFF;

View File

@ -48,6 +48,7 @@ namespace Elwig.Services {
var filterNotVar = new List<string>();
var filterAttr = new List<string>();
var filterNotAttr = new List<string>();
var filterSeasons = new List<int>();
var filter = vm.TextFilter;
if (filter.Count > 0) {
@ -87,6 +88,10 @@ namespace Elwig.Services {
filter.RemoveAt(i--);
filterNames.Add($"ohne {var[e[1..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 (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));
foreach (var year in filterSeasons) areaComQuery = Utils.ActiveAreaCommitments(areaComQuery, year);
}
return (filterNames, areaComQuery, filter);
@ -162,20 +168,13 @@ namespace Elwig.Services {
AddToolTipCell(grid, max == null ? "" : $"{max:N0} kg", row, 4, 1, bold, true);
}
public static async Task<(string, Grid)> GenerateToolTip(IQueryable<AreaCom> areaComs, int maxKgPerHa) {
var grid = new Grid();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
AddToolTipCell(grid, "Lieferpflicht", 0, 3, 1, false, false, true);
AddToolTipCell(grid, "Lieferrecht", 0, 4, 1, false, false, true);
public static async Task<(string, (string?, string?, int, int?, int?)[])> GenerateToolTipData(IQueryable<AreaCom> areaComs, int maxKgPerHa) {
var grid = new List<(string?, string?, int, int?, int?)>();
var text = "-";
var area = await areaComs.SumAsync(p => p.Area);
text = $"{area:N0} m²";
AddToolTipRow(grid, 1, "Geb. Fläche", null, area, null, null);
grid.Add(("Geb. Fläche", null, area, null, null));
if (await areaComs.AnyAsync()) {
var attrGroups = await areaComs
@ -221,24 +220,38 @@ namespace Elwig.Services {
.ThenBy(g => g.SortId)
.ToListAsync();
int rowNum = 2;
if (noAttr.Count > 0) {
rowNum++;
AddToolTipRow(grid, rowNum++, null, null, noAttr.Sum(g => g.Area), noAttr.Sum(g => g.Min), noAttr.Sum(g => g.Max));
grid.Add((null, null, noAttr.Sum(g => g.Area), noAttr.Sum(g => g.Min), noAttr.Sum(g => g.Max)));
foreach (var g in noAttr) {
AddToolTipRow(grid, rowNum++, null, g.SortId, g.Area, g.Min, g.Max);
grid.Add((null, g.SortId, g.Area, g.Min, g.Max));
}
}
foreach (var attrG in attrGroups) {
rowNum++;
AddToolTipRow(grid, rowNum++, attrG.Attr, null, attrG.Area, attrG.Min, attrG.Max);
grid.Add((attrG.Attr, null, attrG.Area, attrG.Min, attrG.Max));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr).OrderByDescending(g => g.Area).ThenBy(g => g.SortId)) {
AddToolTipRow(grid, rowNum++, null, g.SortId, g.Area, g.Min, g.Max);
grid.Add((null, g.SortId, g.Area, g.Min, g.Max));
}
}
}
return (text, grid);
return (text, grid.ToArray());
}
public static Grid GenerateToolTip((string?, string?, int, int?, int?)[] data) {
var grid = new Grid();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
AddToolTipCell(grid, "Lieferpflicht", 0, 3, 1, false, false, true);
AddToolTipCell(grid, "Lieferrecht", 0, 4, 1, false, false, true);
int rowNum = 1;
foreach (var row in data) {
if (rowNum == 2 || (rowNum != 1 && row.Item1 != null)) rowNum++;
AddToolTipRow(grid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
return grid;
}
}
}

View File

@ -32,7 +32,7 @@ namespace Elwig.Services {
vm.StatusAncmtModified = "-";
}
public static async Task FillInputs(this DeliveryAncmtAdminViewModel vm, DeliveryAncmt a) {
public static void FillInputs(this DeliveryAncmtAdminViewModel vm, DeliveryAncmt a) {
vm.MgNr = a.MgNr;
vm.DeliverySchedule = (DeliverySchedule?)ControlUtils.GetItemFromSourceWithPk(vm.DeliveryScheduleSource, a.Year, a.DsNr);
vm.SortId = a.SortId;
@ -230,7 +230,7 @@ namespace Elwig.Services {
await ctx.SaveChangesAsync();
if (oldDsNr != null && (oldYear != year || oldDsNr != dsnr || oldMgNr != newMgNr || oldSortId != newSortId)) {
await ctx.Database.ExecuteSqlRawAsync($"UPDATE delivery_announcement SET year = {year}, dsnr = {dsnr}, mgnr = {newMgNr}, sortid = '{newSortId}' WHERE (year, dsnr, mgnr, sortid) = ({a.Year}, {a.DsNr}, {a.MgNr}, '{a.SortId}')");
await ctx.Database.ExecuteSqlAsync($"UPDATE delivery_announcement SET year = {year}, dsnr = {dsnr}, mgnr = {newMgNr}, sortid = {newSortId} WHERE (year, dsnr, mgnr, sortid) = ({a.Year}, {a.DsNr}, {a.MgNr}, {a.SortId})");
}
}

View File

@ -21,7 +21,7 @@ namespace Elwig.Services {
public static void ClearInputs(this DeliveryScheduleAdminViewModel vm) {
}
public static async Task FillInputs(this DeliveryScheduleAdminViewModel vm, DeliverySchedule s) {
public static void FillInputs(this DeliveryScheduleAdminViewModel vm, DeliverySchedule s) {
vm.Date = s.Date;
vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, s.ZwstId);
vm.Description = s.Description;

View File

@ -24,7 +24,7 @@ namespace Elwig.Services {
public static class DeliveryService {
public enum ExportSubject {
FromFilters, FromToday, FromSeasonAndBranch, Selected,
FromFilters, FromToday, FromSeason, FromSeasonAndBranch, Selected,
};
public static async Task<Member?> GetMemberAsync(int mgnr) {
@ -430,70 +430,71 @@ namespace Elwig.Services {
}
public static async Task<DeliveryPart> UpdateDeliveryPart(this DeliveryAdminViewModel vm, int? oldYear, int? oldDid, int? oldDpnr, bool dateHasChanged, bool timeHasChanged, bool timeIsDefault) {
using var ctx = new AppDbContext();
DeliveryPart p;
int year = oldYear ?? Utils.CurrentYear;
int did = oldDid ?? await ctx.NextDId(year);
int dpnr = oldDpnr ?? await ctx.NextDPNr(year, did);
using (var ctx = new AppDbContext()) {
int year = oldYear ?? Utils.CurrentYear;
int did = oldDid ?? await ctx.NextDId(year);
int dpnr = oldDpnr ?? await ctx.NextDPNr(year, did);
var oldDelivery = await ctx.Deliveries.FindAsync(year, did);
bool deliveryNew = (oldDid == null);
bool partNew = (oldDpnr == null);
var originalMgNr = oldDelivery?.MgNr;
var originalMemberKgNr = oldDelivery?.Member.DefaultKgNr;
var oldDelivery = await ctx.Deliveries.FindAsync(year, did);
bool deliveryNew = (oldDid == null);
bool partNew = (oldDpnr == null);
var originalMgNr = oldDelivery?.MgNr;
var originalMemberKgNr = oldDelivery?.Member.DefaultKgNr;
var date = DateOnly.ParseExact(vm.Date!, "dd.MM.yyyy");
int? newLnr = (deliveryNew || dateHasChanged) ? await ctx.NextLNr(date) : null;
var date = DateOnly.ParseExact(vm.Date!, "dd.MM.yyyy");
int? newLnr = (deliveryNew || dateHasChanged) ? await ctx.NextLNr(date, vm.Branch!.ZwstId) : null;
string? newLsNr = (newLnr != null) ? Utils.GenerateLsNr(date, vm.Branch!.ZwstId, newLnr.Value) : null;
string? newTimeString = null;
if (partNew && timeIsDefault) {
newTimeString = DateTime.Now.ToString("HH:mm:ss");
} else if (partNew || timeHasChanged) {
newTimeString = string.IsNullOrEmpty(vm.Time) ? null : vm.Time + ":00";
}
string? newTimeString = null;
if (partNew && timeIsDefault) {
newTimeString = DateTime.Now.ToString("HH:mm:ss");
} else if (partNew || timeHasChanged) {
newTimeString = string.IsNullOrEmpty(vm.Time) ? null : vm.Time + ":00";
}
var d = new Delivery {
Year = year,
DId = did,
DateString = $"{date:yyyy-MM-dd}",
TimeString = newTimeString ?? oldDelivery?.TimeString,
LNr = newLnr ?? oldDelivery!.LNr,
ZwstId = vm.Branch!.ZwstId,
LsNr = vm.LsNr!,
MgNr = (int)vm.MgNr!,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
};
var d = new Delivery {
Year = year,
DId = did,
DateString = $"{date:yyyy-MM-dd}",
TimeString = newTimeString ?? oldDelivery?.TimeString,
LNr = newLnr ?? oldDelivery!.LNr,
ZwstId = vm.Branch!.ZwstId,
LsNr = newLsNr ?? vm.LsNr!,
MgNr = vm.MgNr!.Value,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
};
var p = new DeliveryPart {
Year = year,
DId = did,
DPNr = dpnr,
p = new DeliveryPart {
Year = year,
DId = did,
DPNr = dpnr,
SortId = vm.WineVar!.SortId,
AttrId = vm.WineAttr?.AttrId,
CultId = vm.WineCult?.CultId,
Kmw = (double)vm.GradationKmw!,
QualId = vm.WineQualityLevel!.QualId,
HkId = vm.WineOrigin!.HkId,
KgNr = vm.WineKg?.KgNr,
RdNr = vm.WineRd?.RdNr,
SortId = vm.WineVar!.SortId,
AttrId = vm.WineAttr?.AttrId,
CultId = vm.WineCult?.CultId,
Kmw = vm.GradationKmw!.Value,
QualId = vm.WineQualityLevel!.QualId,
HkId = vm.WineOrigin!.HkId,
KgNr = vm.WineKg?.KgNr,
RdNr = vm.WineRd?.RdNr,
IsNetWeight = vm.IsNetWeight,
IsHandPicked = vm.IsHandPicked,
IsLesewagen = vm.IsLesewagen,
IsGebunden = vm.IsGebunden,
Temperature = vm.Temperature,
Acid = vm.Acid,
Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment,
IsNetWeight = vm.IsNetWeight,
IsHandPicked = vm.IsHandPicked,
IsLesewagen = vm.IsLesewagen,
IsGebunden = vm.IsGebunden,
Temperature = vm.Temperature,
Acid = vm.Acid,
Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment,
Weight = (int)vm.Weight!,
IsManualWeighing = vm.IsManualWeighing,
ScaleId = vm.ScaleId,
WeighingData = vm.WeighingData,
WeighingReason = vm.ManualWeighingReason,
};
Weight = vm.Weight!.Value,
IsManualWeighing = vm.IsManualWeighing,
ScaleId = vm.ScaleId,
WeighingData = vm.WeighingData,
WeighingReason = vm.ManualWeighingReason,
};
try {
if (oldDelivery != null && ctx.Entry(oldDelivery) is EntityEntry<Delivery> entry) {
entry.State = EntityState.Detached;
}
@ -523,15 +524,135 @@ namespace Elwig.Services {
}
await ctx.SaveChangesAsync();
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
}
App.HintContextChange();
return p;
}
public static async Task<Delivery> SplitDeliveryToMember(int year, int did, int[] weights, int mgnr) {
Delivery n;
using (var ctx = new AppDbContext()) {
bool anyLeft = false;
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var lnr = await ctx.NextLNr(d.Date, d.ZwstId);
n = new Delivery {
Year = year,
DId = await ctx.NextDId(d.Year),
DateString = d.DateString,
TimeString = d.TimeString,
ZwstId = d.ZwstId,
LNr = lnr,
LsNr = Utils.GenerateLsNr(d.Date, d.ZwstId, lnr),
MgNr = mgnr,
Comment = d.Comment,
};
ctx.Add(n);
await ctx.SaveChangesAsync();
var dpnr = 1;
foreach (var (p, w) in d.Parts.ToList().Zip(weights)) {
if (w <= 0) {
anyLeft = true;
continue;
} else if (w >= p.Weight) {
await ctx.Database.ExecuteSqlAsync($"UPDATE delivery_part SET year = {n.Year}, did = {n.DId}, dpnr = {dpnr++} WHERE (year, did, dpnr) = ({p.Year}, {p.DId}, {p.DPNr})");
} else {
anyLeft = true;
p.Weight -= w;
ctx.Update(p);
var s = ctx.CreateProxy<DeliveryPart>();
var values = ctx.Entry(p).CurrentValues;
ctx.Entry(s).CurrentValues.SetValues(values);
s.Year = n.Year;
s.DId = n.DId;
s.DPNr = dpnr++;
s.Weight = w;
ctx.Add(s);
}
}
await ctx.SaveChangesAsync();
if (!anyLeft)
await ctx.Database.ExecuteSqlAsync($"DELETE FROM delivery WHERE (year, did) = ({d.Year}, {d.DId})");
}
App.HintContextChange();
return n;
}
public static async Task<Delivery> SplitDeliveryToLsNr(int year, int did, int[] weights, string lsnr) {
Delivery n;
using (var ctx = new AppDbContext()) {
var anyLeft = false;
n = (await ctx.Deliveries.FirstAsync(d => d.LsNr == lsnr))!;
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var dpnr = await ctx.NextDPNr(n.Year, n.DId);
foreach (var (p, w) in d.Parts.ToList().Zip(weights)) {
if (w <= 0) {
anyLeft = true;
continue;
} else if (w >= p.Weight) {
await ctx.Database.ExecuteSqlAsync($"UPDATE delivery_part SET year = {n.Year}, did = {n.DId}, dpnr = {dpnr++} WHERE (year, did, dpnr) = ({p.Year}, {p.DId}, {p.DPNr})");
} else {
anyLeft = true;
p.Weight -= w;
ctx.Update(p);
var s = ctx.CreateProxy<DeliveryPart>();
var values = ctx.Entry(p).CurrentValues;
ctx.Entry(s).CurrentValues.SetValues(values);
s.Year = n.Year;
s.DId = n.DId;
s.DPNr = dpnr++;
s.Weight = w;
ctx.Add(s);
}
}
await ctx.SaveChangesAsync();
if (!anyLeft && n.LsNr != d.LsNr)
await ctx.Database.ExecuteSqlAsync($"DELETE FROM delivery WHERE (year, did) = ({d.Year}, {d.DId})");
}
App.HintContextChange();
return n;
}
public static async Task DepreciateDelivery(int year, int did, int[] weights) {
using (var ctx = new AppDbContext()) {
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var dpnr = await ctx.NextDPNr(year, did);
foreach (var (p, w) in d.Parts.ToList().Zip(weights)) {
if (w <= 0) {
continue;
} else if (w >= p.Weight) {
p.QualId = "WEI";
p.HkId = "OEST";
ctx.Update(p);
} else {
p.Weight -= w;
ctx.Update(p);
var n = ctx.CreateProxy<DeliveryPart>();
var values = ctx.Entry(p).CurrentValues;
ctx.Entry(n).CurrentValues.SetValues(values);
n.DPNr = dpnr++;
n.Weight = w;
n.QualId = "WEI";
n.HkId = "OEST";
ctx.Add(n);
}
}
await ctx.SaveChangesAsync();
}
App.HintContextChange();
}
public static async Task GenerateDeliveryNote(int year, int did, ExportMode mode) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
@ -636,7 +757,7 @@ namespace Elwig.Services {
} else {
await ElwigData.Export(path, list, filterNames);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {list.Count} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
@ -688,6 +809,103 @@ namespace Elwig.Services {
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) {
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 if (subject == ExportSubject.FromSeason) {
var year = vm.FilterSeason ?? Utils.CurrentLastSeason;
query = ctx.DeliveryParts
.Where(p => p.Year == year);
filterNames.Add($"{year}");
} else {
throw new ArgumentException("Invalid value for ExportSubject");
}
query = query
.Where(p => p.QualId == "WEI")
.OrderBy(p => p.Delivery.MgNr)
.ThenBy(p => p.Delivery.DateString)
.ThenBy(p => p.Delivery.TimeString)
.ThenBy(p => p.Delivery.LsNr)
.ThenBy(p => p.DPNr);
filterNames.Remove("abgewertet");
if (mode == ExportMode.SaveList) {
var d = new SaveFileDialog() {
FileName = $"{DeliveryDepreciationList.Name}-{vm.FilterSeason ?? Utils.CurrentLastSeason}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{DeliveryDepreciationList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ods = new OdsFile(d.FileName);
var tblTotal = await DeliveryJournalData.FromQuery(query, filterNames);
tblTotal.FullName = DeliveryDepreciationList.Name;
tblTotal.Name = "Gesamt";
await ods.AddTable(tblTotal);
foreach (var branch in await ctx.Branches.OrderBy(b => b.Name).ToListAsync()) {
var tbl = await DeliveryJournalData.FromQuery(query.Where(p => p.Delivery.ZwstId == branch.ZwstId), filterNames);
tbl.FullName = DeliveryDepreciationList.Name;
tbl.Name = branch.Name;
await ods.AddTable(tbl);
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
} else {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryDepreciationList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
var tb = new TextBlock() {
Text = text,
@ -720,36 +938,22 @@ namespace Elwig.Services {
AddToolTipCell(grid, $"{max:N1}°", row, 4, 1, bold, true);
}
public static async Task<(string WeightText, Grid WeightGrid, string GradationText, Grid GradationGrid)> GenerateToolTip(IQueryable<DeliveryPart> deliveryParts) {
var wGrid = new Grid();
wGrid.ColumnDefinitions.Add(new() { Width = new(10) });
wGrid.ColumnDefinitions.Add(new() { Width = new(60) });
wGrid.ColumnDefinitions.Add(new() { Width = new(80) });
wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
public static async Task<(string WeightText, (string?, string?, int, int?, int)[] WeightGrid, string GradationText, (string?, string?, double, double, double)[] GradationGrid)> GenerateToolTipData(IQueryable<DeliveryPart> deliveryParts) {
var wGrid = new List<(string?, string?, int, int?, int)>();
var wText = "-";
var gGrid = new Grid();
gGrid.ColumnDefinitions.Add(new() { Width = new(10) });
gGrid.ColumnDefinitions.Add(new() { Width = new(60) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
AddToolTipCell(gGrid, "Min.", 0, 2, 1, false, false, true);
AddToolTipCell(gGrid, "⌀", 0, 3, 1, false, false, true);
AddToolTipCell(gGrid, "Max.", 0, 4, 1, false, false, true);
var gGrid = new List<(string?, string?, double, double, double)>();
var gText = "-";
var weight = await deliveryParts.SumAsync(p => p.Weight);
wText = $"{weight:N0} kg";
AddWeightToolTipRow(wGrid, 0, "Gewicht", null, weight, null, weight);
wGrid.Add(("Gewicht", null, weight, null, weight));
if (await deliveryParts.AnyAsync()) {
var kmwMin = await deliveryParts.MinAsync(p => p.Kmw);
var kmwAvg = Utils.AggregateDeliveryPartsKmw(deliveryParts);
var kmwMax = await deliveryParts.MaxAsync(p => p.Kmw);
gText = $"{kmwMin:N1}° / {kmwAvg:N1}° / {kmwMax:N1}°";
AddGradationToolTipRow(gGrid, 1, "Gradation", null, kmwMin, kmwAvg, kmwMax);
gGrid.Add(("Gradation", null, kmwMin, kmwAvg, kmwMax));
var attrGroups = await deliveryParts
.GroupBy(p => new { Attr = p.Attribute!.Name, Cult = p.Cultivation!.Name })
@ -798,22 +1002,18 @@ namespace Elwig.Services {
.ThenBy(g => g.SortId)
.ToListAsync();
int rowNum = 1;
foreach (var attrG in attrGroups) {
rowNum++;
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
AddWeightToolTipRow(wGrid, rowNum++, name, null, attrG.Weight, attrG.Weight, weight);
wGrid.Add((name, null, attrG.Weight, attrG.Weight, weight));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
AddWeightToolTipRow(wGrid, rowNum++, null, g.SortId, g.Weight, attrG.Weight, weight);
wGrid.Add((null, g.SortId, g.Weight, attrG.Weight, weight));
}
}
rowNum = 2;
foreach (var attrG in attrGroups) {
rowNum++;
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
AddGradationToolTipRow(gGrid, rowNum++, name, null, attrG.Min, attrG.Avg, attrG.Max);
gGrid.Add((name, null, attrG.Min, attrG.Avg, attrG.Max));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Avg).ThenBy(g => g.SortId)) {
AddGradationToolTipRow(gGrid, rowNum++, null, g.SortId, g.Min, g.Avg, g.Max);
gGrid.Add((null, g.SortId, g.Min, g.Avg, g.Max));
}
}
@ -835,7 +1035,46 @@ namespace Elwig.Services {
}
}
return (wText, wGrid, gText, gGrid);
return (wText, wGrid.ToArray(), gText, gGrid.ToArray());
}
public static (Grid WeightGrid, Grid GradationGrid) GenerateToolTip((string?, string?, int, int?, int)[] weightData, (string?, string?, double, double, double)[] gradationData) {
var wGrid = new Grid();
wGrid.ColumnDefinitions.Add(new() { Width = new(10) });
wGrid.ColumnDefinitions.Add(new() { Width = new(60) });
wGrid.ColumnDefinitions.Add(new() { Width = new(80) });
wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
int rowNum = 0;
foreach (var row in weightData) {
if (rowNum == 1 || (rowNum != 0 && row.Item1 != null)) rowNum++;
AddWeightToolTipRow(wGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
var gGrid = new Grid();
gGrid.ColumnDefinitions.Add(new() { Width = new(10) });
gGrid.ColumnDefinitions.Add(new() { Width = new(60) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
AddToolTipCell(gGrid, "Min.", 0, 2, 1, false, false, true);
AddToolTipCell(gGrid, "⌀", 0, 3, 1, false, false, true);
AddToolTipCell(gGrid, "Max.", 0, 4, 1, false, false, true);
rowNum = 1;
foreach (var row in gradationData) {
if (rowNum == 2 || (rowNum != 1 && row.Item1 != null)) rowNum++;
AddGradationToolTipRow(gGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
return (wGrid, gGrid);
}
public static async Task DeleteDelivery(string lsnr) {
using (var ctx = new AppDbContext()) {
await ctx.Deliveries.Where(d => d.LsNr == lsnr).ExecuteDeleteAsync();
await ctx.SaveChangesAsync();
}
App.HintContextChange();
}
}
}

View File

@ -25,7 +25,7 @@ namespace Elwig.Services {
public static async Task InitInputs(this MemberAdminViewModel vm) {
using var ctx = new AppDbContext();
vm.MgNrString = $"{await ctx.NextMgNr()}";
vm.MgNr = await ctx.NextMgNr();
vm.EntryDate = DateTime.Now.ToString("dd.MM.yyyy");
if (vm.BranchSource.Count() == 1)
vm.Branch = vm.BranchSource.First();
@ -51,10 +51,10 @@ namespace Elwig.Services {
vm.Age = "-";
}
public static async Task FillInputs(this MemberAdminViewModel vm, Member m) {
public static void FillInputs(this MemberAdminViewModel vm, Member m) {
vm.IsMemberSelected = true;
vm.MgNrString = $"{m.MgNr}";
vm.PredecessorMgNrString = $"{m.PredecessorMgNr}";
vm.MgNr = m.MgNr;
vm.PredecessorMgNr = m.PredecessorMgNr;
vm.IsJuridicalPerson = m.IsJuridicalPerson;
vm.EnableMemberReferenceButton = m.PredecessorMgNr != null;
vm.Prefix = m.Prefix;
@ -73,10 +73,10 @@ namespace Elwig.Services {
vm.IsDeceased = m.IsDeceased;
vm.Address = m.Address;
if (m.PostalDest.AtPlz is AT_PlzDest p) {
vm.PlzString = $"{p.Plz}";
vm.Plz = p.Plz;
vm.Ort = ControlUtils.GetItemFromSource(vm.OrtSource, p);
} else {
vm.PlzString = null;
vm.Plz = null;
vm.Ort = null;
}
@ -114,19 +114,19 @@ namespace Elwig.Services {
vm.BillingName = billingAddr.FullName;
vm.BillingAddress = billingAddr.Address;
if (billingAddr.PostalDest.AtPlz is AT_PlzDest b) {
vm.BillingPlzString = $"{b.Plz}";
vm.BillingPlz = b.Plz;
vm.BillingOrt = ControlUtils.GetItemFromSource(vm.BillingOrtSource, b);
}
} else {
vm.BillingName = null;
vm.BillingAddress = null;
vm.BillingPlzString = null;
vm.BillingPlz = null;
vm.BillingOrt = null;
}
vm.EntryDate = (m.EntryDateString != null) ? string.Join(".", m.EntryDateString.Split("-").Reverse()) : null;
vm.ExitDate = (m.ExitDateString != null) ? string.Join(".", m.ExitDateString.Split("-").Reverse()) : null;
vm.BusinessSharesString = $"{m.BusinessShares}";
vm.BusinessShares = m.BusinessShares;
vm.AccountingNr = m.AccountingNr;
vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, m.ZwstId);
vm.DefaultKg = (AT_Kg?)ControlUtils.GetItemFromSourceWithPk(vm.DefaultKgSource, m.DefaultKgNr);
@ -137,19 +137,28 @@ namespace Elwig.Services {
vm.ContactViaPost = m.ContactViaPost;
vm.ContactViaEmail = m.ContactViaEmail;
Dictionary<int, int> deliveries;
using (var ctx = new AppDbContext()) {
vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}";
vm.StatusDeliveriesLastSeason = "...";
vm.StatusDeliveriesLastSeasonToolTip = null;
vm.StatusDeliveriesThisSeasonInfo = $"{Utils.CurrentLastSeason}";
vm.StatusDeliveriesThisSeason = "...";
vm.StatusDeliveriesThisSeasonToolTip = null;
vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}";
vm.StatusAreaCommitment = "...";
vm.StatusAreaCommitmentToolTip = null;
Utils.RunBackground("Mitgliederdaten laden", async () => {
if (App.MainDispatcher == null)
return;
using var ctx = new AppDbContext();
var d1 = ctx.Deliveries.Where(d => d.Year == Utils.CurrentLastSeason - 1 && d.MgNr == m.MgNr);
var (_, d1Grid, _, _) = await DeliveryService.GenerateToolTip(d1.SelectMany(d => d.Parts));
vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}";
vm.StatusDeliveriesLastSeason = $"{await d1.CountAsync():N0} ({await d1.SumAsync(d => d.Parts.Count):N0}), {await d1.SelectMany(d => d.Parts).SumAsync(p => p.Weight):N0} kg";
vm.StatusDeliveriesLastSeasonToolTip = d1Grid;
var (_, d1GridData, _, _) = await DeliveryService.GenerateToolTipData(d1.SelectMany(d => d.Parts));
var textLast = $"{await d1.CountAsync():N0} ({await d1.SumAsync(d => d.Parts.Count):N0}), {await d1.SelectMany(d => d.Parts).SumAsync(p => p.Weight):N0} kg";
var d2 = ctx.Deliveries.Where(d => d.Year == Utils.CurrentLastSeason && d.MgNr == m.MgNr);
var (_, d2Grid, _, _) = await DeliveryService.GenerateToolTip(d2.SelectMany(d => d.Parts));
vm.StatusDeliveriesThisSeasonInfo = $"{Utils.CurrentLastSeason}";
vm.StatusDeliveriesThisSeason = $"{await d2.CountAsync():N0} ({await d2.SumAsync(d => d.Parts.Count):N0}), {await d2.SelectMany(d => d.Parts).SumAsync(p => p.Weight):N0} kg";
vm.StatusDeliveriesThisSeasonToolTip = d2Grid;
var (_, d2GridData, _, _) = await DeliveryService.GenerateToolTipData(d2.SelectMany(d => d.Parts));
var textThis = $"{await d2.CountAsync():N0} ({await d2.SumAsync(d => d.Parts.Count):N0}), {await d2.SelectMany(d => d.Parts).SumAsync(p => p.Weight):N0} kg";
var c = m.ActiveAreaCommitments(ctx, Utils.CurrentLastSeason);
int maxKgPerHa = 10_000;
@ -157,21 +166,36 @@ namespace Elwig.Services {
var s = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year));
if (s != null) maxKgPerHa = s.MaxKgPerHa;
} catch { }
var (text, grid) = await AreaComService.GenerateToolTip(c, maxKgPerHa);
vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}";
vm.StatusAreaCommitment = text;
vm.StatusAreaCommitmentToolTip = grid;
var (text, gridData) = await AreaComService.GenerateToolTipData(c, maxKgPerHa);
deliveries = ctx.Deliveries
.Where(d => d.MgNr == m.MgNr)
.SelectMany(d => d.Parts)
.GroupBy(d => d.Year)
.ToDictionary(g => g.Key, g => g.Sum(d => d.Weight));
}
var deliveries = await ctx.Deliveries
.Where(d => d.MgNr == m.MgNr)
.GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Any());
if (m.MgNr != vm.MgNr)
return;
await App.MainDispatcher.BeginInvoke(() => {
var (d1Grid, _) = DeliveryService.GenerateToolTip(d1GridData, []);
var (d2Grid, _) = DeliveryService.GenerateToolTip(d2GridData, []);
var grid = AreaComService.GenerateToolTip(gridData);
vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}";
vm.StatusDeliveriesLastSeason = textLast;
vm.StatusDeliveriesLastSeasonToolTip = d1Grid;
vm.StatusDeliveriesThisSeasonInfo = $"{Utils.CurrentLastSeason}";
vm.StatusDeliveriesThisSeason = textThis;
vm.StatusDeliveriesThisSeasonToolTip = d2Grid;
vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}";
vm.StatusAreaCommitment = text;
vm.StatusAreaCommitmentToolTip = grid;
vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => deliveries.GetValueOrDefault(i, false)).ToList();
});
});
vm.MemberHasEmail = m.EmailAddresses.Count > 0;
vm.MemberCanSendEmail = App.Config.Smtp != null && m.EmailAddresses.Count > 0;
vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => deliveries.GetValueOrDefault(i, 0) > 0).ToList();
vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => false).ToList();
}
public static async Task<(List<string>, IQueryable<Member>, List<string>)> GetFilters(this MemberAdminViewModel vm, AppDbContext ctx) {
@ -188,6 +212,7 @@ namespace Elwig.Services {
var filterLfbisNr = new List<string>();
var filterUstIdNr = new List<string>();
var filterAreaCom = new List<string>();
var filterNotAreaCom = new List<string>();
var filter = vm.TextFilter;
if (filter.Count > 0) {
@ -199,7 +224,15 @@ namespace Elwig.Services {
for (int i = 0; i < filter.Count; 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);
filter.RemoveAt(i--);
filterNames.Add("Funktionäre");
@ -290,10 +323,22 @@ namespace Elwig.Services {
memberQuery = memberQuery.Where(m => m.TelephoneNumbers.Any(t => t.Number.Replace(" ", "").StartsWith(e)));
filter.RemoveAt(i--);
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())) {
filterAreaCom.Add(e.ToUpper());
filter.RemoveAt(i--);
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)) {
filterLfbisNr.Add(e);
filter.RemoveAt(i--);
@ -313,6 +358,7 @@ namespace Elwig.Services {
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 (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 (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr));
}
@ -477,7 +523,7 @@ namespace Elwig.Services {
} else {
await ElwigData.Export(path, members, areaComs, filterNames);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {members.Count} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
@ -630,5 +676,23 @@ namespace Elwig.Services {
return newMgNr;
}
public static async Task DeleteMember(int mgnr, bool deletePaymentData, bool deleteDeliveries, bool deleteAreaComs) {
using (var ctx = new AppDbContext()) {
var l = (await ctx.Members.FindAsync(mgnr))!;
if (deletePaymentData) {
ctx.RemoveRange(l.Credits);
}
if (deleteDeliveries) {
ctx.RemoveRange(l.Deliveries);
}
if (deleteAreaComs) {
ctx.RemoveRange(l.AreaCommitments);
}
ctx.Remove(l);
await ctx.SaveChangesAsync();
}
App.HintContextChange();
}
}
}

View File

@ -96,10 +96,16 @@ namespace Elwig.ViewModels {
private IEnumerable<object> _wineCultSource = [];
[ObservableProperty]
private string? _gradationOeString;
public double? GradationOe => double.TryParse(GradationOeString, out var oe) ? oe : null;
public double? GradationOe {
get => double.TryParse(GradationOeString, out var oe) ? oe : null;
set => GradationOeString = $"{value:0}";
}
[ObservableProperty]
private string? _gradationKmwString;
public double? GradationKmw => double.TryParse(GradationKmwString, out var kmw) ? kmw : null;
public double? GradationKmw {
get => double.TryParse(GradationKmwString, out var kmw) ? kmw : null;
set => GradationKmwString = $"{value:0.0}";
}
[ObservableProperty]
private WineQualLevel? _wineQualityLevel;
[ObservableProperty]
@ -152,10 +158,16 @@ namespace Elwig.ViewModels {
private string? _partComment;
[ObservableProperty]
private string? _temperatureString;
public double? Temperature => double.TryParse(TemperatureString, out var t) ? t : null;
public double? Temperature {
get => double.TryParse(TemperatureString, out var t) ? t : null;
set => TemperatureString = $"{value:0.0}";
}
[ObservableProperty]
private string? _acidString;
public double? Acid => double.TryParse(AcidString, out var a) ? a : null;
public double? Acid {
get => double.TryParse(AcidString, out var a) ? a : null;
set => AcidString = $"{value:0.0}";
}
[ObservableProperty]
private bool _isLesewagen;
[ObservableProperty]

View File

@ -39,7 +39,7 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private bool _enableSearchInputs = true;
[ObservableProperty]
private IEnumerable<bool> _memberHasDeliveries = [ .. Enumerable.Range(0, 9999).Select(i => false) ];
private IEnumerable<bool> _memberHasDeliveries = [.. Enumerable.Range(0, 9999).Select(i => false)];
[ObservableProperty]
private bool _memberListOrderByMgNr;
@ -50,10 +50,16 @@ namespace Elwig.ViewModels {
[ObservableProperty]
private string? _mgNrString;
public int? MgNr => int.TryParse(MgNrString, out var mgnr) ? mgnr : null;
public int? MgNr {
get => int.TryParse(MgNrString, out var mgnr) ? mgnr : null;
set => MgNrString = $"{value}";
}
[ObservableProperty]
private string? _predecessorMgNrString;
public int? PredecessorMgNr => int.TryParse(PredecessorMgNrString, out var mgnr) ? mgnr : null;
public int? PredecessorMgNr {
get => int.TryParse(PredecessorMgNrString, out var mgnr) ? mgnr : null;
set => PredecessorMgNrString = $"{value}";
}
[ObservableProperty]
private bool _isJuridicalPerson;
[ObservableProperty]
@ -76,7 +82,10 @@ namespace Elwig.ViewModels {
private string? _address;
[ObservableProperty]
private string? _plzString;
public int? Plz => int.TryParse(PlzString, out var plz) ? plz : null;
public int? Plz {
get => int.TryParse(PlzString, out var plz) ? plz : null;
set => PlzString = $"{value}";
}
[ObservableProperty]
private AT_PlzDest? _ort;
[ObservableProperty]
@ -88,7 +97,10 @@ namespace Elwig.ViewModels {
private string? _billingAddress;
[ObservableProperty]
private string? _billingPlzString;
public int? BillingPlz => int.TryParse(BillingPlzString, out var plz) ? plz : null;
public int? BillingPlz {
get => int.TryParse(BillingPlzString, out var plz) ? plz : null;
set => BillingPlzString = $"{value}";
}
[ObservableProperty]
private AT_PlzDest? _billingOrt;
[ObservableProperty]
@ -114,7 +126,10 @@ namespace Elwig.ViewModels {
private string? _exitDate;
[ObservableProperty]
private string? _businessSharesString;
public int? BusinessShares => int.TryParse(BusinessSharesString, out var bs) ? bs : null;
public int? BusinessShares {
get => int.TryParse(BusinessSharesString, out var bs) ? bs : null;
set => BusinessSharesString = $"{value}";
}
[ObservableProperty]
private string? _accountingNr;
[ObservableProperty]
@ -147,6 +162,14 @@ namespace Elwig.ViewModels {
public string? _number = number;
[ObservableProperty]
public string? _comment = comment;
public override bool Equals(object? obj) {
return obj is PhoneNr nr && Type == nr.Type && Number == nr.Number && Comment == nr.Comment;
}
public override int GetHashCode() {
return Type ^ (Number?.GetHashCode() ?? 0) ^ (Comment?.GetHashCode() ?? 0);
}
}
public ObservableCollection<PhoneNr> PhoneNrs { get; private set; } = [new(), new(), new(), new(), new(), new(), new(), new(), new()];

View File

@ -8,7 +8,6 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Input;
namespace Elwig.Windows {
@ -358,22 +357,6 @@ namespace Elwig.Windows {
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) {
return InputTextChanged(input, new ValidationResult(true, null));
}

View File

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

View File

@ -33,7 +33,7 @@ namespace Elwig.Windows {
GstNrInput, AreaInput, AreaComTypeInput, WineCultivationInput
];
InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ActiveAreaCommitmentInput.Content = ((string)ActiveAreaCommitmentInput.Content).Replace("2020", $"{Utils.CurrentLastSeason}");
}
@ -79,9 +79,9 @@ namespace Elwig.Windows {
if (filter.Count == 0) {
ViewModel.StatusAreaCommitments = $"{await areaComQuery.CountAsync():N0}";
var s = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year));
var (text, grid) = await AreaComService.GenerateToolTip(areaComQuery, s?.MaxKgPerHa ?? 10_000);
var (text, gridData) = await AreaComService.GenerateToolTipData(areaComQuery, s?.MaxKgPerHa ?? 10_000);
ViewModel.StatusArea = text;
ViewModel.StatusAreaToolTip = grid;
ViewModel.StatusAreaToolTip = AreaComService.GenerateToolTip(gridData);
} else {
ViewModel.StatusAreaCommitments = $"{areaComs.Count:N0}";
ViewModel.StatusArea = $"{areaComs.Select(a => a.Area).Sum():N0} m²";
@ -226,14 +226,22 @@ namespace Elwig.Windows {
}
private async void AreaCommitmentSaveButton_Click(object? sender, RoutedEventArgs? evt) {
int? fbnr = null;
AreaCommitmentSaveButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
int fbnr;
try {
fbnr = await ViewModel.UpdateAreaCommitment((AreaCommitmentList.SelectedItem as AreaCom)?.FbNr);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Flächenbindung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
AreaCommitmentSaveButton.IsEnabled = true;
return;
} finally {
Mouse.OverrideCursor = null;
}
IsEditing = false;
IsCreating = false;
AreaCommitmentList.IsEnabled = true;

View File

@ -58,6 +58,8 @@ namespace Elwig.Windows {
foreach (var (old, attrid) in _attrs.Where(a => a.Value != null)) {
await ctx.Database.ExecuteSqlAsync($"UPDATE wine_attribute SET attrid = {attrid} WHERE attrid = {old}");
await ctx.Database.ExecuteSqlAsync($"UPDATE area_commitment_type SET vtrgid = (sortid || COALESCE(attrid, '') || COALESCE(disc, '')) WHERE attrid = {attrid}");
await ctx.Database.ExecuteSqlRawAsync($"UPDATE payment_variant SET data = REPLACE(REPLACE(data, '/{old}\"', '/{attrid}\"'), '/{old}-', '/{attrid}-')");
}
await ctx.SaveChangesAsync();

View File

@ -58,6 +58,7 @@ namespace Elwig.Windows {
foreach (var (old, cultid) in _cults.Where(c => c.Value != null)) {
await ctx.Database.ExecuteSqlAsync($"UPDATE wine_cultivation SET cultid = {cultid} WHERE cultid = {old}");
await ctx.Database.ExecuteSqlRawAsync($"UPDATE payment_variant SET data = REPLACE(data, '-{old}\"', '-{cultid}\"')");
}
await ctx.SaveChangesAsync();

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Elwig.Windows {
public partial class BaseDataWindow : AdministrationWindow {
@ -316,12 +317,19 @@ namespace Elwig.Windows {
}
private async void SaveButton_Click(object? sender, RoutedEventArgs? evt) {
SaveButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
await Save();
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Stammdaten aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
SaveButton.IsEnabled = true;
return;
} finally {
Mouse.OverrideCursor = null;
}
IsEditing = false;

View File

@ -10,7 +10,6 @@ using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using ScottPlot.Plottables;
using ScottPlot;
using ScottPlot.Control;
namespace Elwig.Windows {
public partial class ChartWindow : ContextWindow {
@ -226,30 +225,8 @@ namespace Elwig.Windows {
DataPlot.Color = 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.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.IsVisible = false;
@ -661,6 +638,9 @@ namespace Elwig.Windows {
}
private async void SaveButton_Click(object sender, RoutedEventArgs e) {
SaveButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using (var ctx = new AppDbContext()) {
var origData = BillingData.FromJson(PaymentVar.Data);
@ -677,7 +657,12 @@ namespace Elwig.Windows {
var str = "Der Eintrag konnte nicht in der Datenbank gespeichert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Auszahlungsvariante speichern", MessageBoxButton.OK, MessageBoxImage.Error);
SaveButton.IsEnabled = true;
return;
} finally {
Mouse.OverrideCursor = null;
}
LockContext = true;
SetHasChanged(false);
}

View File

@ -67,13 +67,29 @@
<Menu Grid.ColumnSpan="3" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Lieferschein">
<MenuItem x:Name="Menu_DeliveryNote_Show" Header="...anzeigen (PDF)" IsEnabled="False"
Click="Menu_DeliveryNote_Show_Click" InputGestureText="Strg+P"/>
Click="Menu_DeliveryNote_Show_Click" InputGestureText="Strg+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE8FF;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_DeliveryNote_SavePdf" Header="...speichern... (PDF)" IsEnabled="False"
Click="Menu_DeliveryNote_SavePdf_Click"/>
Click="Menu_DeliveryNote_SavePdf_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEA90;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_DeliveryNote_Print" Header="...drucken" IsEnabled="False"
Click="Menu_DeliveryNote_Print_Click" InputGestureText="Strg+Shift+P"/>
Click="Menu_DeliveryNote_Print_Click" InputGestureText="Strg+Shift+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE749;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_DeliveryNote_Email" Header="...per E-Mail schicken" IsEnabled="False"
Click="Menu_DeliveryNote_Email_Click"/>
Click="Menu_DeliveryNote_Email_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE89C;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Lieferjournal">
<MenuItem x:Name="Menu_DeliveryJournal_SaveFilters" Header="...aus Filtern speichern... (Excel)"
@ -94,31 +110,56 @@
<MenuItem x:Name="Menu_DeliveryJournal_PrintToday" Header="...von heute drucken"
Click="Menu_DeliveryJournal_PrintToday_Click" InputGestureText="Strg+J"/>
</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"/>
<MenuItem Header="Abwertungsliste" x:Name="Menu_DeliveryDepreciationList">
<MenuItem x:Name="Menu_DeliveryDepreciationList_SaveFilters" Header="...aus Filtern speichern... (Excel)"
Click="Menu_DeliveryDepreciationList_SaveFilters_Click"/>
<MenuItem x:Name="Menu_DeliveryDepreciationList_ShowFilters" Header="...aus Filtern anzeigen (PDF)"
Click="Menu_DeliveryDepreciationList_ShowFilters_Click"/>
<MenuItem x:Name="Menu_DeliveryDepreciationList_SavePdfFilters" Header="...aus Filtern speichern... (PDF)"
Click="Menu_DeliveryDepreciationList_SavePdfFilters_Click"/>
<MenuItem x:Name="Menu_DeliveryDepreciationList_PrintFilters" Header="...aus Filtern drucken"
Click="Menu_DeliveryDepreciationList_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 x:Name="Menu_DeliveryDepreciationList_SaveSeason" Header="...von Saison speichern... (Excel)"
Click="Menu_DeliveryDepreciationList_SaveSeason_Click"/>
<MenuItem x:Name="Menu_DeliveryDepreciationList_ShowSeason" Header="...von Saison anzeigen (PDF)"
Click="Menu_DeliveryDepreciationList_ShowSeason_Click"/>
<MenuItem x:Name="Menu_DeliveryDepreciationList_SavePdfSeason" Header="...von Saison speichern... (PDF)"
Click="Menu_DeliveryDepreciationList_SavePdfSeason_Click"/>
<MenuItem x:Name="Menu_DeliveryDepreciationList_PrintSeason" Header="...von Saison drucken"
Click="Menu_DeliveryDepreciationList_PrintSeason_Click"/>
</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 x:Name="Menu_Bki_SaveList" Header="Traubentransportscheinliste speichern..."/>
@ -274,10 +315,10 @@
<TextBlock FontWeight="Bold">Alt+Einfg</TextBlock>
</Button.ToolTip>
</Button>
<Button x:Name="AbwertenButton" Content="Abwerten" IsEnabled="False"
ToolTip="Ausgewählte Teillieferung vollständig oder teilweise abwerten"
<Button x:Name="DepreciateButton" Content="Abwert./Aufteil." IsEnabled="False"
ToolTip="Lieferung vollständig oder teilweise abwerten bzw. auf anderes Mitglied aufteilen"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="5,5,2.5,10" Grid.Column="0" Grid.Row="2"
Click="AbwertenButton_Click"/>
Click="DepreciateButton_Click"/>
<Button x:Name="EditDeliveryButton" Content="Bearbeiten" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,2.5,10" Grid.Column="1" Grid.Row="2"
Click="EditDeliveryButton_Click">
@ -356,14 +397,14 @@
</Grid>
</GroupBox>
<GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Margin="5,5,5,5">
<GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Margin="5,5,5,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="LieferscheinNr.:" Margin="10,10,0,0" Grid.Column="0"/>
<Label Content="Lieferschein-Nr.:" Margin="10,10,0,0" Grid.Column="0"/>
<TextBox x:Name="LsNrInput" Text="{Binding LsNr, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="126" Grid.Column="1" HorizontalAlignment="Left" Margin="0,10,0,0"
IsReadOnly="True" IsTabStop="False"
@ -390,6 +431,23 @@
<TextBox x:Name="CommentInput" Text="{Binding Comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1" Margin="0,100,10,10"
TextChanged="TextBox_TextChanged"/>
<ListBox x:Name="DeliveryPartList" Margin="5,135,5,5" Grid.ColumnSpan="2"
SelectionChanged="DeliveryPartList_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DPNr}" Width="13" TextAlignment="Right" Margin="0,0,7,0"/>
<TextBlock Text="{Binding SortId}" Width="30"/>
<TextBlock Text="{Binding Kmw, StringFormat='{}{0:N1}°'}" Width="40" TextAlignment="Right" Padding="0,0,10,0"/>
<TextBlock Text="{Binding QualId}" Width="30"/>
<TextBlock Text="{Binding Weight, StringFormat='{}{0:N0} kg'}" Width="60" TextAlignment="Right" Padding="0,0,10,0"/>
<TextBlock Text="{Binding Attribute.Name}" Width="60"/>
<TextBlock Text="{Binding Cultivation.Name}" Width="50"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</GroupBox>
@ -538,39 +596,6 @@
</Grid>
</GroupBox>
<GroupBox Header="Teillieferungen" Grid.Column="0" Grid.Row="2" Margin="5,5,5,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox x:Name="DeliveryPartList" Margin="5,5,5,38" Grid.ColumnSpan="2"
SelectionChanged="DeliveryPartList_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DPNr}" Width="20"/>
<TextBlock Text="{Binding SortId}" Width="30"/>
<TextBlock Text="{Binding Kmw, StringFormat='{}{0:0.0}°'}" Width="40" TextAlignment="Right" Padding="0,0,10,0"/>
<TextBlock Text="{Binding QualId}" Width="30"/>
<TextBlock Text="{Binding Weight, StringFormat='{}{0:N0} kg'}" Width="60" TextAlignment="Right" Padding="0,0,10,0"/>
<TextBlock Text="{Binding Attribute.Name}" Width="60"/>
<TextBlock Text="{Binding Cultivation.Name}" Width="50"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="ExtractDeliveryPartButton" Content="Extrahieren" IsEnabled="False"
ToolTip="Ausgewählte Teillieferung aus aktueller Lieferung entfernen und entweder anderer oder neuer Lieferung zuordnen"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="5,10,2.5,5" Grid.Column="0" Grid.Row="2"
Click="ExtractDeliveryPartButton_Click"/>
<Button x:Name="DeleteDeliveryPartButton" Content="Löschen" IsEnabled="False"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,10,5,5" Grid.Column="1" Grid.Row="2"
Click="DeleteDeliveryPartButton_Click"/>
</Grid>
</GroupBox>
<GroupBox Header="Herkunft" Grid.Column="0" Grid.Row="3" Margin="5,5,5,10">
<Grid>
<Grid.ColumnDefinitions>

View File

@ -41,7 +41,7 @@ namespace Elwig.Windows {
CommandBindings.Add(new CommandBinding(CtrlP, Menu_DeliveryNote_Show_Click));
CommandBindings.Add(new CommandBinding(CtrlO, Menu_DeliveryJournal_ShowFilters_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(CtrlShiftO, Menu_DeliveryJournal_PrintFilters_Click));
RequiredInputs = [
@ -66,7 +66,7 @@ namespace Elwig.Windows {
SecondsTimer.Tick += new EventHandler(OnSecondPassed);
SecondsTimer.Interval = new TimeSpan(0, 0, 1);
InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason;
@ -97,17 +97,17 @@ namespace Elwig.Windows {
WeighingDButton.Visibility = Visibility.Hidden;
}
Menu_WineQualityStatistics_ModeOe.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw1.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw2.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw5.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw10.IsChecked = false;
Menu_Statistics_WineQuality_ModeOe.IsChecked = false;
Menu_Statistics_WineQuality_ModeKmw1.IsChecked = false;
Menu_Statistics_WineQuality_ModeKmw2.IsChecked = false;
Menu_Statistics_WineQuality_ModeKmw5.IsChecked = false;
Menu_Statistics_WineQuality_ModeKmw10.IsChecked = false;
switch (App.Client.OrderingMemberList) {
case 0: Menu_WineQualityStatistics_ModeOe.IsChecked = true; break;
case 1: Menu_WineQualityStatistics_ModeKmw1.IsChecked = true; break;
case 2: Menu_WineQualityStatistics_ModeKmw2.IsChecked = true; break;
case 3: Menu_WineQualityStatistics_ModeKmw5.IsChecked = true; break;
case 4: Menu_WineQualityStatistics_ModeKmw10.IsChecked = true; break;
case 0: Menu_Statistics_WineQuality_ModeOe.IsChecked = true; break;
case 1: Menu_Statistics_WineQuality_ModeKmw1.IsChecked = true; break;
case 2: Menu_Statistics_WineQuality_ModeKmw2.IsChecked = true; break;
case 3: Menu_Statistics_WineQuality_ModeKmw5.IsChecked = true; break;
case 4: Menu_Statistics_WineQuality_ModeKmw10.IsChecked = true; break;
}
Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null;
@ -155,6 +155,10 @@ namespace Elwig.Windows {
private async void Menu_DeliveryNote_Email_Click(object sender, RoutedEventArgs evt) {
if (DeliveryList.SelectedItem is not Delivery d)
return;
var res = MessageBox.Show("Soll eine E-Mail verschickt werden?", "Lieferschein verschicken",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
if (res != MessageBoxResult.Yes)
return;
await DeliveryService.GenerateDeliveryNote(d.Year, d.DId, ExportMode.Email);
}
@ -168,9 +172,11 @@ namespace Elwig.Windows {
Title = $"Traubentransportscheinliste (BKI) speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.AppStarting;
using var file = new Bki(d.FileName);
await file.ExportAsync(year);
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
using var file = new Bki(d.FileName);
await file.ExportAsync(year);
});
Mouse.OverrideCursor = null;
}
}
@ -212,40 +218,61 @@ namespace Elwig.Windows {
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);
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);
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);
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);
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);
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);
private async void Menu_WineQualityStatistics_Mode_Click(object sender, RoutedEventArgs evt) {
Menu_WineQualityStatistics.IsSubmenuOpen = true;
if (sender == Menu_WineQualityStatistics_ModeOe) {
private async void Menu_Statistics_WineQuality_Mode_Click(object sender, RoutedEventArgs evt) {
Menu_Statistics.IsSubmenuOpen = true;
Menu_Statistics_WineQuality.IsSubmenuOpen = true;
if (sender == Menu_Statistics_WineQuality_ModeOe) {
App.Client.OrderingMemberList = 0;
} else if (sender == Menu_WineQualityStatistics_ModeKmw1) {
} else if (sender == Menu_Statistics_WineQuality_ModeKmw1) {
App.Client.OrderingMemberList = 1;
} else if (sender == Menu_WineQualityStatistics_ModeKmw2) {
} else if (sender == Menu_Statistics_WineQuality_ModeKmw2) {
App.Client.OrderingMemberList = 2;
} else if (sender == Menu_WineQualityStatistics_ModeKmw5) {
} else if (sender == Menu_Statistics_WineQuality_ModeKmw5) {
App.Client.OrderingMemberList = 3;
} else if (sender == Menu_WineQualityStatistics_ModeKmw10) {
} else if (sender == Menu_Statistics_WineQuality_ModeKmw10) {
App.Client.OrderingMemberList = 4;
}
Menu_WineQualityStatistics_ModeOe.IsChecked = App.Client.OrderingMemberList == 0;
Menu_WineQualityStatistics_ModeKmw1.IsChecked = App.Client.OrderingMemberList == 1;
Menu_WineQualityStatistics_ModeKmw2.IsChecked = App.Client.OrderingMemberList == 2;
Menu_WineQualityStatistics_ModeKmw5.IsChecked = App.Client.OrderingMemberList == 3;
Menu_WineQualityStatistics_ModeKmw10.IsChecked = App.Client.OrderingMemberList == 4;
Menu_Statistics_WineQuality_ModeOe.IsChecked = App.Client.OrderingMemberList == 0;
Menu_Statistics_WineQuality_ModeKmw1.IsChecked = App.Client.OrderingMemberList == 1;
Menu_Statistics_WineQuality_ModeKmw2.IsChecked = App.Client.OrderingMemberList == 2;
Menu_Statistics_WineQuality_ModeKmw5.IsChecked = App.Client.OrderingMemberList == 3;
Menu_Statistics_WineQuality_ModeKmw10.IsChecked = App.Client.OrderingMemberList == 4;
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) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromFilters, ExportMode.SaveList);
private async void Menu_DeliveryDepreciationList_ShowFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromFilters, ExportMode.Show);
private async void Menu_DeliveryDepreciationList_SavePdfFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromFilters, ExportMode.SavePdf);
private async void Menu_DeliveryDepreciationList_PrintFilters_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromFilters, ExportMode.Print);
private async void Menu_DeliveryDepreciationList_SaveSeason_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromSeason, ExportMode.SaveList);
private async void Menu_DeliveryDepreciationList_ShowSeason_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromSeason, ExportMode.Show);
private async void Menu_DeliveryDepreciationList_SavePdfSeason_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromSeason, ExportMode.SavePdf);
private async void Menu_DeliveryDepreciationList_PrintSeason_Click(object sender, RoutedEventArgs evt) =>
await ViewModel.GenerateDeliveryDepreciationList(DeliveryService.ExportSubject.FromSeason, ExportMode.Print);
private void Menu_Settings_EnableFreeEditing_Checked(object sender, RoutedEventArgs evt) {
if (IsEditing || IsCreating) {
DateInput.IsReadOnly = false;
@ -283,19 +310,23 @@ namespace Elwig.Windows {
UnsetDefaultValue(GerebeltGewogenInput);
}
if (!App.Client.HasNetWeighing(ViewModel.Branch)) {
if (App.Client.HasBoxWeighing(ViewModel.Branch)) {
LesewagenInput.IsEnabled = false;
SetDefaultValue(LesewagenInput, false);
HandPickedInput.IsThreeState = false;
UnsetDefaultValue(HandPickedInput);
} else {
LesewagenInput.IsEnabled = true;
UnsetDefaultValue(LesewagenInput);
}
if (!App.Client.HasNetWeighing(ViewModel.Branch)) {
HandPickedInput.IsThreeState = false;
UnsetDefaultValue(HandPickedInput);
} else {
HandPickedInput.IsThreeState = true;
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;
SetDefaultValue(GebundenInput, null);
} else {
@ -386,16 +417,18 @@ namespace Elwig.Windows {
await RefreshDeliveryParts();
var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList();
ViewModel.StatusMembers = $"{members.Count}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : "");
ViewModel.StatusDeliveries = $"{deliveries.Count}";
ViewModel.StatusMembers = $"{members.Count:N0}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : "");
ViewModel.StatusDeliveries = $"{deliveries.Count:N0}";
if (filter.Count == 0) {
var deliveryParts = deliveryPartsQuery;
ViewModel.StatusDeliveries = $"{deliveries.Count} ({await deliveryParts.CountAsync()})";
ViewModel.StatusDeliveries = $"{deliveries.Count:N0} ({await deliveryParts.CountAsync():N0})";
var varieties = await deliveryParts.Select(d => d.SortId).Distinct().ToListAsync();
ViewModel.StatusVarieties = $"{varieties.Count}" + (varieties.Count > 0 && varieties.Count <= 10 ? $" ({string.Join(", ", varieties)})" : "");
(ViewModel.StatusWeight, ViewModel.StatusWeightToolTip,
ViewModel.StatusGradation, ViewModel.StatusGradationToolTip) = await DeliveryService.GenerateToolTip(deliveryParts);
ViewModel.StatusVarieties = $"{varieties.Count:N0}" + (varieties.Count > 0 && varieties.Count <= 10 ? $" ({string.Join(", ", varieties)})" : "");
var (wText, wData, gText, gData) = await DeliveryService.GenerateToolTipData(deliveryParts);
ViewModel.StatusWeight = wText;
ViewModel.StatusGradation = gText;
(ViewModel.StatusWeightToolTip, ViewModel.StatusGradationToolTip) = DeliveryService.GenerateToolTip(wData, gData);
} else {
ViewModel.StatusVarieties = "-";
ViewModel.StatusWeight = "-";
@ -636,15 +669,11 @@ namespace Elwig.Windows {
private void DeliveryPartList_SelectionChanged(object? sender, SelectionChangedEventArgs? evt) {
RefreshInputs();
if (DeliveryPartList.SelectedItem is DeliveryPart p) {
AbwertenButton.IsEnabled = p.QualId != "WEI";
DepreciateButton.IsEnabled = true;
EditDeliveryButton.IsEnabled = true;
ExtractDeliveryPartButton.IsEnabled = !IsCreating;
DeleteDeliveryPartButton.IsEnabled = DeliveryList.SelectedItem is Delivery { Parts.Count: > 1 } && !IsCreating;
} else {
AbwertenButton.IsEnabled = false;
DepreciateButton.IsEnabled = false;
EditDeliveryButton.IsEnabled = false;
ExtractDeliveryPartButton.IsEnabled = false;
DeleteDeliveryPartButton.IsEnabled = false;
}
}
@ -692,14 +721,28 @@ namespace Elwig.Windows {
NewDeliveryPartButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
DeliveryPartList.IsEnabled = false;
var p = await ViewModel.UpdateDeliveryPart(
(DeliveryList.SelectedItem as Delivery)?.Year,
(DeliveryList.SelectedItem as Delivery)?.DId,
(DeliveryPartList.SelectedItem as DeliveryPart)?.DPNr,
InputHasChanged(DateInput),
InputHasChanged(TimeInput),
!InputIsNotDefault(TimeInput)
);
DeliveryPart? p;
try {
p = await ViewModel.UpdateDeliveryPart(
(DeliveryList.SelectedItem as Delivery)?.Year,
(DeliveryList.SelectedItem as Delivery)?.DId,
(DeliveryPartList.SelectedItem as DeliveryPart)?.DPNr,
InputHasChanged(DateInput),
InputHasChanged(TimeInput),
!InputIsNotDefault(TimeInput)
);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
FinishButton.IsEnabled = true;
SaveButton.IsEnabled = true;
Mouse.OverrideCursor = null;
DeliveryPartList.IsEnabled = true;
return;
}
EmptyScale();
await RefreshList();
await RefreshDeliveryParts();
@ -716,14 +759,28 @@ namespace Elwig.Windows {
NewDeliveryPartButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
DeliveryPartList.IsEnabled = false;
var p = await ViewModel.UpdateDeliveryPart(
(DeliveryList.SelectedItem as Delivery)?.Year,
(DeliveryList.SelectedItem as Delivery)?.DId,
(DeliveryPartList.SelectedItem as DeliveryPart)?.DPNr,
InputHasChanged(DateInput),
InputHasChanged(TimeInput),
!InputIsNotDefault(TimeInput)
);
DeliveryPart? p;
try {
p = await ViewModel.UpdateDeliveryPart(
(DeliveryList.SelectedItem as Delivery)?.Year,
(DeliveryList.SelectedItem as Delivery)?.DId,
(DeliveryPartList.SelectedItem as DeliveryPart)?.DPNr,
InputHasChanged(DateInput),
InputHasChanged(TimeInput),
!InputIsNotDefault(TimeInput)
);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
FinishButton.IsEnabled = true;
SaveButton.IsEnabled = true;
Mouse.OverrideCursor = null;
DeliveryPartList.IsEnabled = true;
return;
}
EmptyScale();
await RefreshList();
await RefreshDeliveryParts();
@ -741,6 +798,7 @@ namespace Elwig.Windows {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
Mouse.OverrideCursor = null;
DeliveryList.SelectedItem = null;
await RenewContext();
@ -824,43 +882,29 @@ namespace Elwig.Windows {
LockSearchInputs();
}
private async void AbwertenButton_Click(object sender, RoutedEventArgs evt) {
if (DeliveryPartList.SelectedItem is not DeliveryPart p) return;
var res = Utils.ShowAbwertenDialog($"{p.Delivery.LsNr}/{p.DPNr}", p.Delivery.Member.AdministrativeName, p.Weight);
if (res == null || res <= 0)
private async void DepreciateButton_Click(object sender, RoutedEventArgs evt) {
if (DeliveryList.SelectedItem is not Delivery d) return;
var res = Utils.ShowDeliverySplittingDialog(d);
if (res == null)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ctx = new AppDbContext();
ClearOriginalValues();
if (res >= p.Weight) {
ControlUtils.SelectItemWithPk(WineQualityLevelInput, "WEI");
ControlUtils.SelectItemWithPk(WineOriginInput, "OEST");
p.QualId = "WEI";
p.HkId = "OEST";
ctx.Update(p);
var id = res.Value.Item1;
var weights = res.Value.Item2;
if (id == null) {
// abwerten
await DeliveryService.DepreciateDelivery(d.Year, d.DId, weights);
} else if (id.All(char.IsAsciiDigit)) {
// auf Mitglied übertragen
var n = await DeliveryService.SplitDeliveryToMember(d.Year, d.DId, weights, int.Parse(id));
await Task.Delay(500);
ControlUtils.SelectItemWithPk(DeliveryList, n.Year, n.DId);
} else {
var w = p.Weight - res.Value;
ViewModel.Weight = w;
p.Weight = w;
ctx.Update(p);
var d = p.Delivery;
var p2 = ctx.CreateProxy<DeliveryPart>();
var values = ctx.Entry(p).CurrentValues;
ctx.Entry(p2).CurrentValues.SetValues(values);
p2.DPNr = await ctx.NextDPNr(d.Year, d.DId);
p2.Weight = res.Value;
p2.QualId = "WEI";
p2.HkId = "OEST";
ctx.Add(p2);
ctx.UpdateDeliveryPartModifiers(p2, [], p.Modifiers);
// zu existierender Lieferung hinzufügen
var n = await DeliveryService.SplitDeliveryToLsNr(d.Year, d.DId, weights, id);
ControlUtils.SelectItemWithPk(DeliveryList, n.Year, n.DId);
}
await ctx.SaveChangesAsync();
await RefreshDeliveryParts();
FinishInputFilling();
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
@ -898,9 +942,7 @@ namespace Elwig.Windows {
UnlockInputs();
LockSearchInputs();
AbwertenButton.IsEnabled = false;
ExtractDeliveryPartButton.IsEnabled = false;
DeleteDeliveryPartButton.IsEnabled = false;
DepreciateButton.IsEnabled = false;
}
protected override void ShortcutDelete() {
@ -918,12 +960,13 @@ namespace Elwig.Windows {
"Lieferung löschen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (r == MessageBoxResult.OK) {
Mouse.OverrideCursor = Cursors.AppStarting;
using (var ctx = new AppDbContext()) {
ctx.Remove(d);
await ctx.SaveChangesAsync();
try {
await DeliveryService.DeleteDelivery(d.LsNr);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Lieferung löschen", MessageBoxButton.OK, MessageBoxImage.Error);
}
await RefreshList();
await RefreshDeliveryParts();
Mouse.OverrideCursor = null;
}
}
@ -938,22 +981,31 @@ namespace Elwig.Windows {
SaveButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
DeliveryPart? p;
try {
p = await ViewModel.UpdateDeliveryPart(
(DeliveryList.SelectedItem as Delivery)?.Year,
(DeliveryList.SelectedItem as Delivery)?.DId,
(DeliveryPartList.SelectedItem as DeliveryPart)?.DPNr,
InputHasChanged(DateInput),
InputHasChanged(TimeInput),
!InputIsNotDefault(TimeInput)
);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
SaveButton.IsEnabled = true;
return;
} finally {
Mouse.OverrideCursor = null;
}
IsEditing = false;
IsCreating = false;
DeliveryList.IsEnabled = true;
DeliveryPartList.IsEnabled = true;
var p = await ViewModel.UpdateDeliveryPart(
(DeliveryList.SelectedItem as Delivery)?.Year,
(DeliveryList.SelectedItem as Delivery)?.DId,
(DeliveryPartList.SelectedItem as DeliveryPart)?.DPNr,
InputHasChanged(DateInput),
InputHasChanged(TimeInput),
!InputIsNotDefault(TimeInput)
);
Mouse.OverrideCursor = null;
HideSaveResetCancelButtons();
ShowNewEditDeleteButtons();
LockInputs();
@ -963,9 +1015,7 @@ namespace Elwig.Windows {
await RefreshDeliveryParts();
RefreshInputs();
AbwertenButton.IsEnabled = p.QualId != "WEI";
ExtractDeliveryPartButton.IsEnabled = DeliveryPartList.SelectedItem != null && !IsCreating;
DeleteDeliveryPartButton.IsEnabled = DeliveryList.SelectedItem is Delivery { Parts.Count: > 1 } && !IsCreating;
DepreciateButton.IsEnabled = true;
}
protected override void ShortcutReset() {
@ -996,91 +1046,7 @@ namespace Elwig.Windows {
LockInputs();
UnlockSearchInputs();
AbwertenButton.IsEnabled = DeliveryPartList.SelectedItem is DeliveryPart p && p.QualId != "WEI";
ExtractDeliveryPartButton.IsEnabled = DeliveryPartList.SelectedItem != null && !IsCreating;
DeleteDeliveryPartButton.IsEnabled = DeliveryList.SelectedItem is Delivery { Parts.Count: > 1 } && !IsCreating;
}
private async void ExtractDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
if (DeliveryPartList.SelectedItem is not DeliveryPart p)
return;
var delivery = p.Delivery;
var day = delivery.Date;
var count = delivery.Parts.Count;
if (delivery.Time <= new TimeOnly(3, 0))
day = day.AddDays(-1);
string? res;
using (var ctx = new AppDbContext()) {
var lsnrs = await ctx.Deliveries
.Where(d => d.ZwstId == delivery.ZwstId)
.Where(d => (d.DateString == day.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") > 0)) ||
(d.DateString == day.AddDays(1).ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") <= 0)))
.Where(d => d.LsNr != delivery.LsNr)
.OrderBy(d => d.LsNr)
.Select(d => d.LsNr)
.ToListAsync();
res = Utils.ShowDeliveryExtractionDialog($"{delivery.LsNr}/{p.DPNr}", delivery.Member.AdministrativeName, count == 1, lsnrs);
if (res == null)
return;
}
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ctx = new AppDbContext();
if (res == "new") {
var lnr = await ctx.NextLNr(delivery.Date);
ctx.Add(new Delivery {
Year = p.Year,
DId = await ctx.NextDId(p.Year),
LNr = lnr,
DateString = $"{delivery.Date:yyyy-MM-dd}",
TimeString = $"{delivery.Time:HH:mm:ss}",
ZwstId = delivery.ZwstId,
MgNr = delivery.MgNr,
Comment = delivery.Comment,
LsNr = Utils.GenerateLsNr(delivery.Date, delivery.ZwstId, lnr),
});
await ctx.SaveChangesAsync();
}
Delivery? d = await ctx.Deliveries.Where(d => d.LsNr == res).FirstOrDefaultAsync();
if (d == null) return;
await ctx.Database.ExecuteSqlAsync($"UPDATE delivery_part SET year = {d.Year}, did = {d.DId}, dpnr = {await ctx.NextDPNr(d.Year, d.DId)} WHERE (year, did, dpnr) = ({p.Year}, {p.DId}, {p.DPNr})");
if (count == 1) {
await ctx.Database.ExecuteSqlAsync($"DELETE FROM delivery WHERE (year, did) = ({delivery.Year}, {delivery.DId})");
}
await ctx.SaveChangesAsync();
await RefreshList();
ControlUtils.SelectItem(DeliveryList, d);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private async void DeleteDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
if (DeliveryPartList.SelectedItem is not DeliveryPart p)
return;
var r = MessageBox.Show(
$"Soll die Teillieferung Nr. {p.DPNr} wirklich unwiderruflich gelöscht werden?",
"Lieferung löschen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (r == MessageBoxResult.OK) {
Mouse.OverrideCursor = Cursors.AppStarting;
using (var ctx = new AppDbContext()) {
ctx.Remove(p);
await ctx.SaveChangesAsync();
}
await RefreshDeliveryParts();
Mouse.OverrideCursor = null;
}
DepreciateButton.IsEnabled = DeliveryPartList.SelectedItem != null;
}
private void ShowSaveResetCancelButtons() {
@ -1103,22 +1069,22 @@ namespace Elwig.Windows {
private void ShowNewEditDeleteButtons() {
NewDeliveryButton.IsEnabled = ViewModel.IsReceipt;
AbwertenButton.IsEnabled = DeliveryPartList.SelectedItem is DeliveryPart p && p.QualId == "WEI";
DepreciateButton.IsEnabled = DeliveryList.SelectedItem != null;
EditDeliveryButton.IsEnabled = DeliveryPartList.SelectedItem != null;
DeleteDeliveryButton.IsEnabled = DeliveryList.SelectedItem != null;
NewDeliveryButton.Visibility = ViewModel.IsReceipt ? Visibility.Visible : Visibility.Hidden;
AbwertenButton.Visibility = !ViewModel.IsReceipt ? Visibility.Visible : Visibility.Hidden;
DepreciateButton.Visibility = !ViewModel.IsReceipt ? Visibility.Visible : Visibility.Hidden;
EditDeliveryButton.Visibility = Visibility.Visible;
DeleteDeliveryButton.Visibility = Visibility.Visible;
}
private void HideNewEditDeleteButtons() {
NewDeliveryButton.IsEnabled = false;
AbwertenButton.IsEnabled = false;
DepreciateButton.IsEnabled = false;
EditDeliveryButton.IsEnabled = false;
DeleteDeliveryButton.IsEnabled = false;
NewDeliveryButton.Visibility = Visibility.Hidden;
AbwertenButton.Visibility = Visibility.Hidden;
DepreciateButton.Visibility = Visibility.Hidden;
EditDeliveryButton.Visibility = Visibility.Hidden;
DeleteDeliveryButton.Visibility = Visibility.Hidden;
}
@ -1195,7 +1161,7 @@ namespace Elwig.Windows {
var branch = (Branch)BranchInput.SelectedItem;
var date = DateOnly.ParseExact(ViewModel.Date!, "dd.MM.yyyy");
using var ctx = new AppDbContext();
var lnr = await ctx.NextLNr(date);
var lnr = await ctx.NextLNr(date, branch.ZwstId);
ViewModel.LsNr = Utils.GenerateLsNr(date, branch.ZwstId, lnr);
} catch {
ViewModel.LsNr = "";
@ -1309,8 +1275,7 @@ namespace Elwig.Windows {
private void ModifiersInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
if (!IsEditing && !IsCreating) return;
var mod = ModifiersInput.SelectedItems.Cast<Modifier>();
var source = ModifiersInput.ItemsSource.Cast<Modifier>();
var mod = ModifiersInput.SelectedItems.Cast<Modifier>().ToList();
if (App.Client.IsMatzen) {
var kl = mod.Where(m => m.Name.StartsWith("Klasse "));
if (kl.Count() > 1) {
@ -1330,8 +1295,8 @@ namespace Elwig.Windows {
private void LesewagenInput_Changed(object sender, RoutedEventArgs evt) {
if (!IsEditing && !IsCreating) return;
var mod = ModifiersInput.SelectedItems.Cast<Modifier>();
var source = ModifiersInput.ItemsSource.Cast<Modifier>();
var mod = ModifiersInput.SelectedItems.Cast<Modifier>().ToList();
var source = ModifiersInput.ItemsSource.Cast<Modifier>().ToList();
var lw = LesewagenInput.IsChecked == true;
if (App.Client.IsMatzen) {
var kl = mod.Where(m => m.Name.StartsWith("Klasse ")).Select(m => m.ModId).LastOrDefault("A")[0];
@ -1419,7 +1384,7 @@ namespace Elwig.Windows {
}
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;
}
if (!ViewModel.IsReceipt || App.Client.HasNetWeighing(ViewModel.Branch)) {
@ -1429,7 +1394,7 @@ namespace Elwig.Windows {
}
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;
}
CheckBox_Changed(sender, evt);

View File

@ -8,7 +8,7 @@
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:vm="clr-namespace:Elwig.ViewModels"
xmlns:ctrl="clr-namespace:Elwig.Controls"
Title="Traubenanmeldungen - Elwig" Height="700" Width="980" MinWidth="600" MinHeight="400"
Title="Anmeldungen - Elwig" Height="700" Width="980" MinWidth="600" MinHeight="400"
Loaded="Window_Loaded">
<Window.DataContext>
<vm:DeliveryAncmtAdminViewModel/>
@ -69,13 +69,29 @@
<Menu Grid.ColumnSpan="3" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Anmeldeliste">
<MenuItem x:Name="Menu_DeliveryAncmtList_SaveSelected" Header="...von ausgewähltem Leseplan speichern... (Excel)"
Click="Menu_DeliveryAncmtList_SaveSelected_Click"/>
Click="Menu_DeliveryAncmtList_SaveSelected_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_DeliveryAncmtList_ShowSelected" Header="...von ausgewähltem Leseplan anzeigen (PDF)"
Click="Menu_DeliveryAncmtList_ShowSelected_Click" InputGestureText="Strg+P"/>
Click="Menu_DeliveryAncmtList_ShowSelected_Click" InputGestureText="Strg+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE8FF;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_DeliveryAncmtList_SavePdfSelected" Header="...von ausgewähltem Leseplan speichern... (PDF)"
Click="Menu_DeliveryAncmtList_SavePdfSelected_Click"/>
Click="Menu_DeliveryAncmtList_SavePdfSelected_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEA90;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_DeliveryAncmtList_PrintSelected" Header="...von ausgewähltem Leseplan drucken"
Click="Menu_DeliveryAncmtList_PrintSelected_Click" InputGestureText="Strg+Shift+P"/>
Click="Menu_DeliveryAncmtList_PrintSelected_Click" InputGestureText="Strg+Shift+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE749;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
@ -99,7 +115,7 @@
<TextBox.ToolTip>
<TextBlock>
<Bold>Strg+F</Bold><LineBreak/><LineBreak/>
Traubenanmeldungen filtern und durchsuchen. Die Filter sind beliebig kombinierbar.<LineBreak/>
Anmeldungen filtern und durchsuchen. Die Filter sind beliebig kombinierbar.<LineBreak/>
Groß- und Kleinschreibung ist in den meisten Fällen egal.<LineBreak/>
<LineBreak/>
Filtern nach:<LineBreak/>

View File

@ -33,7 +33,7 @@ namespace Elwig.Windows {
DoShowWarningWindows = false;
InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason;
}
@ -144,12 +144,12 @@ namespace Elwig.Windows {
}
}
private async Task RefreshInputs(bool validate = false) {
private void RefreshInputs(bool validate = false) {
ClearInputStates();
if (ViewModel.SelectedDeliveryAncmt is DeliveryAncmt a) {
EditDeliveryAncmtButton.IsEnabled = true;
DeleteDeliveryAncmtButton.IsEnabled = true;
await FillInputs(a);
FillInputs(a);
} else {
EditDeliveryAncmtButton.IsEnabled = false;
DeleteDeliveryAncmtButton.IsEnabled = false;
@ -185,8 +185,8 @@ namespace Elwig.Windows {
await RefreshList();
}
private async void DeliveryAncmtList_SelectionChanged(object sender, RoutedEventArgs evt) {
await RefreshInputs();
private void DeliveryAncmtList_SelectionChanged(object sender, RoutedEventArgs evt) {
RefreshInputs();
}
private async void DeliveryScheduleList_SelectionChanged(object sender, RoutedEventArgs evt) {
@ -314,8 +314,8 @@ namespace Elwig.Windows {
if (ViewModel.SelectedDeliveryAncmt is not DeliveryAncmt a)
return;
var r = MessageBox.Show(
$"Soll die Traubenanmeldung wirklich unwiderruflich gelöscht werden?",
"Traubenanmeldung löschen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
$"Soll die Anmeldung wirklich unwiderruflich gelöscht werden?",
"Anmeldung löschen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (r == MessageBoxResult.OK) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
@ -327,7 +327,7 @@ namespace Elwig.Windows {
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Traubenanmeldung löschen", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(str, "Anmeldung löschen", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
@ -341,6 +341,8 @@ namespace Elwig.Windows {
private async void SaveButton_Click(object? sender, RoutedEventArgs? evt) {
Mouse.OverrideCursor = Cursors.AppStarting;
SaveButton.IsEnabled = false;
int year = -1, dsnr = -1, mgnr = -1;
string? sortid = null;
try {
@ -349,9 +351,13 @@ namespace Elwig.Windows {
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Traubenanmeldung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(str, "Anmeldung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
SaveButton.IsEnabled = true;
return;
} finally {
Mouse.OverrideCursor = null;
}
Mouse.OverrideCursor = null;
IsEditing = false;
IsCreating = false;
DeliveryAncmtList.IsEnabled = true;
@ -361,7 +367,7 @@ namespace Elwig.Windows {
ViewModel.EnableSearchInputs = true;
FinishInputFilling();
await RefreshList();
await RefreshInputs();
RefreshInputs();
ViewModel.SearchQuery = "";
ControlUtils.SelectItemWithPk(DeliveryScheduleList, year, dsnr);
if (sortid != null)
@ -374,9 +380,9 @@ namespace Elwig.Windows {
ResetButton_Click(null, null);
}
private async void ResetButton_Click(object? sender, RoutedEventArgs? evt) {
private void ResetButton_Click(object? sender, RoutedEventArgs? evt) {
if (IsEditing) {
await RefreshInputs();
RefreshInputs();
} else if (IsCreating) {
ClearInputs();
InitInputs();
@ -401,15 +407,15 @@ namespace Elwig.Windows {
HideSaveResetCancelButtons();
ShowNewEditDeleteButtons();
await RefreshInputs();
RefreshInputs();
LockInputs();
ViewModel.EnableSearchInputs = true;
}
private async Task FillInputs(DeliveryAncmt a) {
private void FillInputs(DeliveryAncmt a) {
ClearOriginalValues();
ClearDefaultValues();
await ViewModel.FillInputs(a);
ViewModel.FillInputs(a);
FinishInputFilling();
}

View File

@ -27,7 +27,7 @@ namespace Elwig.Windows {
DateInput, BranchInput, DescriptionInput, MainWineVarietiesInput,
];
InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.FilterSeason = Utils.CurrentLastSeason;
}
@ -72,12 +72,12 @@ namespace Elwig.Windows {
DeliveryScheduleList.ScrollIntoView(DeliveryScheduleList.SelectedItem);
}
private async Task RefreshInputs(bool validate = false) {
private void RefreshInputs(bool validate = false) {
ClearInputStates();
if (ViewModel.SelectedDeliverySchedule is DeliverySchedule s) {
EditDeliveryScheduleButton.IsEnabled = true;
DeleteDeliveryScheduleButton.IsEnabled = true;
await FillInputs(s);
FillInputs(s);
} else {
EditDeliveryScheduleButton.IsEnabled = false;
DeleteDeliveryScheduleButton.IsEnabled = false;
@ -113,8 +113,8 @@ namespace Elwig.Windows {
await RefreshList();
}
private async void DeliveryScheduleList_SelectionChanged(object sender, RoutedEventArgs evt) {
await RefreshInputs();
private void DeliveryScheduleList_SelectionChanged(object sender, RoutedEventArgs evt) {
RefreshInputs();
}
private async void OnlyUpcomingInput_Changed(object sender, RoutedEventArgs evt) {
@ -201,14 +201,20 @@ namespace Elwig.Windows {
private async void SaveButton_Click(object? sender, RoutedEventArgs? evt) {
Mouse.OverrideCursor = Cursors.AppStarting;
SaveButton.IsEnabled = false;
try {
await ViewModel.UpdateDeliverySchedule(ViewModel.SelectedDeliverySchedule?.Year, ViewModel.SelectedDeliverySchedule?.DsNr);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Leseplan aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
SaveButton.IsEnabled = true;
return;
} finally {
Mouse.OverrideCursor = null;
}
Mouse.OverrideCursor = null;
IsEditing = false;
IsCreating = false;
DeliveryScheduleList.IsEnabled = true;
@ -218,7 +224,7 @@ namespace Elwig.Windows {
ViewModel.EnableSearchInputs = true;
FinishInputFilling();
await RefreshList();
await RefreshInputs();
RefreshInputs();
ViewModel.SearchQuery = "";
}
@ -228,9 +234,9 @@ namespace Elwig.Windows {
ResetButton_Click(null, null);
}
private async void ResetButton_Click(object? sender, RoutedEventArgs? evt) {
private void ResetButton_Click(object? sender, RoutedEventArgs? evt) {
if (IsEditing) {
await RefreshInputs();
RefreshInputs();
} else if (IsCreating) {
ClearInputs();
InitInputs();
@ -238,21 +244,21 @@ namespace Elwig.Windows {
UpdateButtons();
}
private async void CancelButton_Click(object? sender, RoutedEventArgs? evt) {
private void CancelButton_Click(object? sender, RoutedEventArgs? evt) {
IsEditing = false;
IsCreating = false;
DeliveryScheduleList.IsEnabled = true;
HideSaveResetCancelButtons();
ShowNewEditDeleteButtons();
await RefreshInputs();
RefreshInputs();
LockInputs();
ViewModel.EnableSearchInputs = true;
}
private async Task FillInputs(DeliverySchedule s) {
private void FillInputs(DeliverySchedule s) {
ClearOriginalValues();
ClearDefaultValues();
await ViewModel.FillInputs(s);
ViewModel.FillInputs(s);
FinishInputFilling();
}

View File

@ -0,0 +1,34 @@
<Window x:Class="Elwig.Windows.LogWindow"
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="Fehler-Protokoll - Elwig" Height="600" Width="1000"
Loaded="Window_Loaded">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid x:Name="EventList" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Single"
CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False"
SelectionChanged="EventList_SelectionChanged"
Margin="10,10,5,10">
<DataGrid.Columns>
<DataGridTextColumn Header="Ebene" Binding="{Binding Event.EntryType}" Width="60"/>
<DataGridTextColumn Header="Zeitpunkt" Binding="{Binding Event.TimeGenerated, StringFormat='{}{0:dd.MM.yyyy HH:mm:ss}'}" Width="120"/>
<DataGridTextColumn Header="Gerät" Binding="{Binding Event.MachineName}" Width="120"/>
<DataGridTextColumn Header="Fehler" Binding="{Binding ExceptionName}" Width="200"/>
<DataGridTextColumn Header="Beschreibung" Binding="{Binding ExceptionMessage}" Width="200"/>
<DataGridTextColumn Header="Herkunft" Binding="{Binding Location}" Width="200"/>
</DataGrid.Columns>
</DataGrid>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<TextBox x:Name="EventData" Grid.Column="2" Margin="5,10,10,10" IsReadOnly="True"
HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible"/>
</Grid>
</Window>

View File

@ -0,0 +1,45 @@
using Elwig.Helpers;
using System.Diagnostics;
using System.Linq;
using System.Windows;
namespace Elwig.Windows {
public partial class LogWindow : Window {
public LogWindow() {
InitializeComponent();
WindowState = WindowState.Maximized;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
var log = Utils.GetLogEntries();
EventList.ItemsSource = log
.Select(e => new {
Event = e,
Lines = e.Message.Split('\n').ToArray(),
})
.Select(e => new {
e.Event,
Exception = e.Lines.FirstOrDefault(l => l.StartsWith("Exception Info: "))?[16..].Trim().Split(':', 2),
Location = e.Lines.FirstOrDefault(l => l.StartsWith(" at Elwig."))?[5..].Trim(),
})
.Select(e => new {
e.Event,
e.Exception,
ExceptionName = e.Exception?[0].Trim(),
ExceptionMessage = e.Exception?.Length >= 2 ? e.Exception?[1].Trim() : null,
e.Location,
})
.OrderByDescending(e => e.Event.TimeGenerated)
.ToList();
EventList.SelectedIndex = 0;
}
private void EventList_SelectionChanged(object sender, RoutedEventArgs evt) {
var item = EventList.SelectedItem;
var t = item.GetType();
var p = t.GetProperty("Event")!;
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,278 +27,334 @@
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
</Window.Resources>
<TabControl x:Name="TabControl" BorderThickness="0" PreviewDragOver="Document_PreviwDragOver" AllowDrop="True" Drop="Document_Drop">
<TabItem Header="Dokumente" Visibility="Collapsed">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="320"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="19"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<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>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="320"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Label Content="Verfügbare Dokumente"
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 Height="200" VerticalAlignment="Top" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<CheckBox x:Name="DocumentNonDeliverersInput" Content="Auch Nicht-Lieferanten miteinbeziehen"
Margin="10,10,10,10" Grid.Column="1"/>
<Label Content="Verfügbare Dokumente"
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"/>
<TextBox x:Name="DeliveryConfirmationFooterInput" Grid.Column="1"
Margin="0,40,10,10" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap"/>
<TextBox x:Name="CreditNoteFooterInput" Grid.Column="1"
Margin="0,10,10,10" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
AcceptsReturn="True" VerticalScrollBarVisibility="Visible" TextWrapping="Wrap"/>
<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>
<TextBox x:Name="PostalLocation" Grid.Column="1"
Margin="10,30,10,10" Width="120" HorizontalAlignment="Left"/>
<Label Content=", am" Margin="130,30,10,10" FontSize="14" Grid.Column="1"/>
<TextBox x:Name="PostalDate" Grid.Column="1" Text="01.01.2020"
Margin="162,30,10,10" Width="78" HorizontalAlignment="Left"
TextChanged="Date_TextChanged" LostFocus="Date_LostFocus"/>
<GroupBox x:Name="DocumentBox" Header="Dokument" Margin="10,170,10,47" HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GroupBox Header="Adressaten" Margin="10,70,10,47" Grid.Column="1">
<Grid>
<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"/>
<CheckBox x:Name="DocumentNonDeliverersInput" Content="Auch Nicht-Lieferanten miteinbeziehen"
Margin="10,10,10,10" Grid.Column="1"/>
<Label Content="Zwst.:" x:Name="MemberBranchLabel" Margin="10,140,0,10"/>
<ctrl:CheckComboBox x:Name="MemberBranchInput" AllItemsSelectedContent="Alle Stammzweigstellen" Delimiter=", " DisplayMemberPath="Name"
Margin="50,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<Label x:Name="DocumentFooterLabel" Content="Fußtext:" Margin="10,40,0,10"/>
<TextBox x:Name="DeliveryConfirmationFooterInput" Grid.Column="1"
Margin="0,40,10,10" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
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"/>
<ctrl:CheckComboBox x:Name="MemberKgInput" AllItemsSelectedContent="Alle Stammgemeinden" Delimiter=", " DisplayMemberPath="Name"
IsSelectAllActive="True" SelectAllContent="Alle Stammgemeinden"
Margin="50,170,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<TextBox x:Name="PostalLocation" Grid.Column="1" TextChanged="PostalLocation_TextChanged"
Margin="10,30,10,10" Width="120" HorizontalAlignment="Left"/>
<Label Content=", am" Margin="130,30,10,10" FontSize="14" Grid.Column="1"/>
<TextBox x:Name="PostalDate" Grid.Column="1" Text="01.01.2020"
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"/>
<RadioButton x:Name="MemberOrganicYesInput" Content="Ja" GroupName="MemberOrganic"
Margin="80,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<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"/>
<GroupBox Header="Adressaten" Margin="10,70,10,47" Grid.Column="1">
<Grid>
<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="Funktionär:" x:Name="MemberFunktionärLabel" Margin="10,230,0,10"/>
<RadioButton x:Name="MemberFunktionärYesInput" Content="Ja" GroupName="MemberFunktionär"
Margin="80,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<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="Zwst.:" x:Name="MemberBranchLabel" Margin="10,140,0,10"/>
<ctrl:CheckComboBox x:Name="MemberBranchInput" AllItemsSelectedContent="Alle Stammzweigstellen" Delimiter=", " DisplayMemberPath="Name"
Margin="50,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<Label Content="Vtrg.:" x:Name="MemberAreaComLabel" Margin="10,260,0,10"/>
<ctrl:CheckComboBox x:Name="MemberAreaComInput" AllItemsSelectedContent="Alle Vertragsarten" Delimiter=", " DisplayMemberPath="VtrgId"
IsSelectAllActive="True" SelectAllContent="Alle Vertragsarten"
Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<Label Content="Gem.:" x:Name="MemberKgLabel" Margin="10,170,0,10"/>
<ctrl:CheckComboBox x:Name="MemberKgInput" AllItemsSelectedContent="Alle Stammgemeinden" Delimiter=", " DisplayMemberPath="Name"
IsSelectAllActive="True" SelectAllContent="Alle Stammgemeinden"
Margin="50,170,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<Label Content="Tag:" x:Name="MemberDeliveryAncmtLabel" Margin="10,260,0,10"/>
<ctrl:CheckComboBox x:Name="MemberDeliveryAncmtInput" AllItemsSelectedContent="Alle Lesepläne" Delimiter=", " DisplayMemberPath="Identifier"
IsSelectAllActive="True" SelectAllContent="Alle Lesepläne"
Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<Label Content="Bio-Betrieb:" x:Name="MemberOrganicLabel" Margin="10,200,0,10"/>
<RadioButton x:Name="MemberOrganicYesInput" Content="Ja" GroupName="MemberOrganic"
Margin="80,205,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<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"
IsSelectAllActive="True" SelectAllContent="Alle Mitglieder"
Margin="10,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
</Grid>
</GroupBox>
<Label Content="Funktionär:" x:Name="MemberFunktionärLabel" Margin="10,230,0,10"/>
<RadioButton x:Name="MemberFunktionärYesInput" Content="Ja" GroupName="MemberFunktionär"
Margin="80,235,10,10" VerticalAlignment="Top" HorizontalAlignment="Left"
Checked="MemberInput_Checked"/>
<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"/>
<Button x:Name="ContinueButton" Content="Weiter" Grid.Column="1"
Margin="10,10,10,10" Height="27" Width="100" Padding="9,3" FontSize="14"
VerticalAlignment="Bottom" HorizontalAlignment="Right"
Click="ContinueButton_Click"/>
</Grid>
</TabItem>
<Label Content="Vtrg.:" x:Name="MemberAreaComLabel" Margin="10,260,0,10"/>
<ctrl:CheckComboBox x:Name="MemberAreaComInput" AllItemsSelectedContent="Alle Vertragsarten" Delimiter=", " DisplayMemberPath="VtrgId"
IsSelectAllActive="True" SelectAllContent="Alle Vertragsarten"
Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<TabItem Header="Absenden" Visibility="Collapsed">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="80"/>
</Grid.RowDefinitions>
<Label Content="Tag:" x:Name="MemberDeliveryAncmtLabel" Margin="10,260,0,10"/>
<ctrl:CheckComboBox x:Name="MemberDeliveryAncmtInput" AllItemsSelectedContent="Alle Lesepläne" Delimiter=", " DisplayMemberPath="Identifier"
IsSelectAllActive="True" SelectAllContent="Alle Lesepläne"
Margin="50,260,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
<GroupBox Header="Post" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,5,10" Grid.Column="0">
<Grid>
<GroupBox Header="Zusenden an..." Margin="10,10,10,10" Height="150" Width="220" VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel>
<RadioButton x:Name="PostalAllInput" Margin="10,10,10,2.5">
<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" 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">
<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)"/>
</StackPanel>
</GroupBox>
<ctrl:CheckComboBox x:Name="MemberCustomInput" AllItemsSelectedContent="Alle Mitglieder" Delimiter=", " DisplayMemberPath="AdministrativeName"
IsSelectAllActive="True" SelectAllContent="Alle Mitglieder"
Margin="10,140,10,10" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="25"
SelectionChanged="MemberInput_SelectionChanged"/>
</Grid>
</GroupBox>
<GroupBox Header="Sortieren nach" Margin="10,180,10,10" Width="180" Height="80" VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel Margin="5,5,0,5">
<RadioButton GroupName="Order" x:Name="OrderMgNrInput" Content="Mitgliedsnummer" IsChecked="True"/>
<RadioButton GroupName="Order" x:Name="OrderNameInput" Content="Name"/>
<RadioButton GroupName="Order" x:Name="OrderPlzInput" Content="PLZ, Ort, Name"/>
</StackPanel>
</GroupBox>
<Button x:Name="ContinueButton" Content="Weiter" Grid.Column="1"
Margin="10,10,10,10" Height="27" Width="100" Padding="9,3" FontSize="14"
VerticalAlignment="Bottom" HorizontalAlignment="Right"
Click="ContinueButton_Click"/>
</Grid>
</TabItem>
<CheckBox x:Name="DoublePagedInput" Margin="20,270,10,10" Content="Doppelseitig drucken"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<TextBox x:Name="PostalSender1" IsEnabled="False"
Margin="10,300,10,10"/>
<TextBox x:Name="PostalSender2"
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">
<TabItem Header="Absenden" Visibility="Collapsed">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="0.4*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="0.6*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="80"/>
</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"/>
<GroupBox Header="Post" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,5,10" Grid.Column="0">
<Grid>
<GroupBox Header="Zusenden an..." Margin="10,10,10,10" Height="150" Width="220" VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel>
<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"
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"/>
<GroupBox Header="Sortieren nach" Margin="10,180,10,10" Width="180" Height="80" VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel Margin="5,5,0,5">
<RadioButton GroupName="Order" x:Name="OrderMgNrInput" Content="Mitgliedsnummer" Click="OrderInput_Changed" IsChecked="True"/>
<RadioButton GroupName="Order" x:Name="OrderNameInput" Content="Name" Click="OrderInput_Changed"/>
<RadioButton GroupName="Order" x:Name="OrderPlzInput" Content="PLZ, Ort, Name" Click="OrderInput_Changed"/>
</StackPanel>
</GroupBox>
<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>
</TabItem>
</TabControl>
<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>
</TabItem>
</TabControl>
<StatusBar Grid.Row="2" BorderThickness="0,1,0,0" BorderBrush="Gray">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<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>

View File

@ -58,6 +58,7 @@ namespace Elwig.Windows {
public IEnumerable<Member> Recipients = [];
protected Document? PrintDocument;
protected Dictionary<Member, List<Document>>? PrintMemberDocuments;
protected Dictionary<Member, List<Document>>? EmailDocuments;
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;
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;
PostalSender2.Text = App.Client.Sender2;
PostalLocation.Text = App.BranchLocation;
@ -200,6 +221,53 @@ namespace Elwig.Windows {
await UpdateRecipients(ctx);
}
private void ResetDocuments() {
DisposeDocs();
if (IsLoaded) {
PreviewButton.IsEnabled = false;
PrintButton.IsEnabled = false;
EmailButton.IsEnabled = false;
ProgressBar.Value = 0;
}
}
private void LockInputs() {
DocumentAddButton.IsEnabled = false;
DocumentRemoveButton.IsEnabled = false;
SelectDocumentButton.IsEnabled = false;
foreach (var tb in ControlUtils.FindAllChildren<TextBox>(this, []))
tb.IsReadOnly = true;
foreach (var cb in ControlUtils.FindAllChildren<ComboBox>(this, []))
cb.IsEnabled = false;
foreach (var cb in ControlUtils.FindAllChildren<CheckBox>(this, []))
cb.IsEnabled = false;
foreach (var lb in ControlUtils.FindAllChildren<ListBox>(this, []))
lb.IsEnabled = false;
foreach (var rb in ControlUtils.FindAllChildren<RadioButton>(this, []))
rb.IsEnabled = false;
}
private void UnlockInputs() {
DocumentAddButton.IsEnabled = true;
DocumentRemoveButton.IsEnabled = true;
SelectDocumentButton.IsEnabled = true;
foreach (var tb in ControlUtils.FindAllChildren<TextBox>(this, []))
tb.IsReadOnly = false;
foreach (var cb in ControlUtils.FindAllChildren<ComboBox>(this, []))
cb.IsEnabled = true;
foreach (var cb in ControlUtils.FindAllChildren<CheckBox>(this, []))
cb.IsEnabled = true;
foreach (var lb in ControlUtils.FindAllChildren<ListBox>(this, []))
lb.IsEnabled = true;
foreach (var rb in ControlUtils.FindAllChildren<RadioButton>(this, []))
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) {
TabControl.SelectedIndex = 1;
TabControl.AllowDrop = false;
@ -210,7 +278,7 @@ namespace Elwig.Windows {
TabControl.AllowDrop = true;
}
private void Document_Drop(object sender, DragEventArgs evt) {
private async void Document_Drop(object sender, DragEventArgs evt) {
if (evt.Data.GetDataPresent(DataFormats.FileDrop)) {
var files = (string[])evt.Data.GetData(DataFormats.FileDrop);
foreach (var file in files) {
@ -218,6 +286,8 @@ namespace Elwig.Windows {
SelectedDocs.Add(new(DocType.Custom, Path.GetFileName(file), file));
}
}
using var ctx = new AppDbContext();
await UpdateRecipients(ctx);
}
}
@ -260,30 +330,33 @@ namespace Elwig.Windows {
}
}
private void DocumentAddButton_Click(object sender, RoutedEventArgs evt) {
private async void DocumentAddButton_Click(object sender, RoutedEventArgs evt) {
var idx = AvaiableDocumentsList.SelectedIndex;
using var ctx = new AppDbContext();
if (AvaiableDocumentsList.SelectedItem is not string s)
return;
if (idx == 0) {
SelectedDocs.Add(new(DocType.MemberDataSheet, s, null));
} else if (idx == 1) {
SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, (Year, DocumentNonDeliverersInput.IsChecked == true)));
SelectedDocs.Add(new(DocType.DeliveryConfirmation, s, Year));
RecipientsDeliveryMembersInput.IsChecked = true;
} else if (idx >= 2) {
using var ctx = new AppDbContext();
var name = s.Split(" ")[^1];
var pv = ctx.PaymentVariants.Single(v => v.Year == Year && v.Name == name)!;
var pv = await ctx.PaymentVariants.SingleAsync(v => v.Year == Year && v.Name == name)!;
SelectedDocs.Add(new(DocType.CreditNote, s, (pv.Year, pv.AvNr)));
RecipientsDeliveryMembersInput.IsChecked = true;
}
SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1;
await UpdateRecipients(ctx);
}
private void DocumentRemoveButton_Click(object sender, RoutedEventArgs evt) {
private async void DocumentRemoveButton_Click(object sender, RoutedEventArgs evt) {
DeleteCommand.Execute(null);
using var ctx = new AppDbContext();
await UpdateRecipients(ctx);
}
private void SelectDocumentButton_Click(object sender, RoutedEventArgs evt) {
private async void SelectDocumentButton_Click(object sender, RoutedEventArgs evt) {
var d = new OpenFileDialog() {
Title = "Dokument auswählen - Elwig",
DefaultExt = "pdf",
@ -296,6 +369,8 @@ namespace Elwig.Windows {
SelectedDocs.Add(new(DocType.Custom, Path.GetFileName(file), file));
}
}
using var ctx = new AppDbContext();
await UpdateRecipients(ctx);
}
}
@ -334,6 +409,7 @@ namespace Elwig.Windows {
private void Date_TextChanged(object sender, RoutedEventArgs evt) {
Validator.CheckDate((TextBox)sender, true);
ResetDocuments();
}
private void Date_LostFocus(object sender, RoutedEventArgs evt) {
@ -375,6 +451,8 @@ namespace Elwig.Windows {
query = query.Where(m => m.Deliveries.Any(d => d.Year == Year));
} else if (RecipientsNonDeliveryMembersInput.IsChecked == true) {
query = query.Where(m => m.IsActive && !m.Deliveries.Any(d => d.Year == Year));
} else if (RecipientsActiveMembersInput.IsChecked == true && SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation || d.Type == DocType.CreditNote)) {
query = query.Where(m => m.IsActive || m.Deliveries.Any(d => d.Year == Year));
} else {
query = query.Where(m => m.IsActive);
}
@ -397,17 +475,32 @@ namespace Elwig.Windows {
}
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);
EmailWishCount = Recipients.Count(m => m.EmailAddresses.Count > 0 && m.ContactViaEmail);
PostalAllCount = Recipients.Count();
PostalWishCount = Recipients.Count(m => m.ContactViaPost);
var m = EmailAllInput.IsChecked == true ? 3 : EmailWishInput.IsChecked == true ? 2 : 1;
PostalNoEmailCount = PostalAllCount - (m == 3 ? EmailAllCount : m == 2 ? EmailWishCount : 0);
var countEmail = (modeEmail == 2 ? EmailAllCount : modeEmail == 1 ? 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();
}
private async Task UpdateTextParameters() {
private async Task UpdateClientParameters() {
var changed = false;
var dcInclude = DocumentNonDeliverersInput.IsChecked ?? false;
if (dcInclude != App.Client.MailIncludeNonDeliverers) {
App.Client.MailIncludeNonDeliverers = dcInclude;
changed = true;
}
var dcText = DeliveryConfirmationFooterInput.Text.Trim();
if (dcText.Length == 0) dcText = null;
if (dcText != App.Client.TextDeliveryConfirmation) {
@ -422,6 +515,53 @@ namespace Elwig.Windows {
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();
if (emailSubject.Length == 0) emailSubject = null;
if (emailSubject != App.Client.TextEmailSubject) {
@ -458,6 +598,7 @@ namespace Elwig.Windows {
}
private async void GenerateButton_Click(object sender, RoutedEventArgs evt) {
LockInputs();
PreviewButton.IsEnabled = false;
PrintButton.IsEnabled = false;
EmailButton.IsEnabled = false;
@ -465,7 +606,7 @@ namespace Elwig.Windows {
GenerateButton.IsEnabled = false;
DisposeDocs();
await UpdateTextParameters();
await UpdateClientParameters();
using var ctx = new AppDbContext();
@ -508,8 +649,7 @@ namespace Elwig.Windows {
Dictionary<(int, int), (IDictionary<int, CreditNoteDeliveryData>, IDictionary<int, PaymentMember>, BillingData)> cnData = [];
foreach (var doc in docs) {
if (doc.Type == DocType.DeliveryConfirmation) {
var details = ((int, bool))doc.Details!;
var year = details.Item1;
var year = (int)doc.Details!;
try {
var b = new Billing(year);
@ -520,6 +660,7 @@ namespace Elwig.Windows {
dcData[year] = await DeliveryConfirmationDeliveryData.ForSeason(ctx.DeliveryParts, year);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs();
GenerateButton.IsEnabled = true;
Mouse.OverrideCursor = null;
return;
@ -536,6 +677,7 @@ namespace Elwig.Windows {
);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs();
GenerateButton.IsEnabled = true;
Mouse.OverrideCursor = null;
return;
@ -554,13 +696,11 @@ namespace Elwig.Windows {
} else if (doc.Type == DocType.MemberDataSheet) {
return [new GeneratedDoc(new MemberDataSheet(m, ctx) { Date = postalDate })];
} else if (doc.Type == DocType.DeliveryConfirmation) {
var details = ((int, bool))doc.Details!;
var year = details.Item1;
var include = details.Item2;
var year = (int)doc.Details!;
DeliveryConfirmationDeliveryData data;
if (dcData[year].TryGetValue(m.MgNr, out var d)) {
data = d;
} else if (include) {
} else if (App.Client.MailIncludeNonDeliverers) {
data = DeliveryConfirmationDeliveryData.CreateEmpty(year, m);
} else {
return [];
@ -626,6 +766,7 @@ namespace Elwig.Windows {
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs();
GenerateButton.IsEnabled = true;
Mouse.OverrideCursor = null;
return;
@ -634,11 +775,13 @@ namespace Elwig.Windows {
EmailDocuments = email;
}
var printDocs = memberDocs
var printMemberDocs = memberDocs
.Where(d =>
printMode == 3 ||
(printMode == 2 && d.Member.ContactViaPost) ||
(printMode == 1 && !emailRecipients.Contains(d.Member.MgNr)))
.ToList();
var printDocs = printMemberDocs
.SelectMany(m => {
var docs = m.Docs.Select(d => d.Doc).ToList();
if (docs.Count == 0 || m.Docs[0].Type == DocType.Custom) {
@ -663,15 +806,21 @@ namespace Elwig.Windows {
ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum;
}));
PrintDocument = print;
PrintMemberDocuments = printMemberDocs.ToDictionary(m => m.Member, m => m.Docs.Select(d => d.Doc).ToList());
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
UnlockInputs();
GenerateButton.IsEnabled = true;
Mouse.OverrideCursor = null;
return;
}
} else {
PrintDocument = null;
PrintMemberDocuments = null;
}
ProgressBar.Value = 100.0;
UnlockInputs();
GenerateButton.IsEnabled = true;
Mouse.OverrideCursor = null;
PreviewButton.IsEnabled = true;
@ -704,10 +853,13 @@ namespace Elwig.Windows {
}
private async void PrintButton_Click(object sender, RoutedEventArgs evt) {
if (PrintDocument == null) return;
if (PrintDocument == null || PrintMemberDocuments == null) return;
PrintButton.IsEnabled = false;
var res = MessageBox.Show($"Sollen {PrintDocument.Pages} Blätter ({PrintDocument.TotalPages} Seiten) gedruckt werden?\n" +
$"Sind die \"Duplex-Einstellungen\" des Standarddruckers entsprechend eingestellt (doppelseitig bzw. einseitig)?",
GenerateButton.IsEnabled = false;
LockInputs();
var res = MessageBox.Show($"Sollen {PrintDocument.Pages} Blätter ({PrintDocument.TotalPages} Seiten) gedruckt werden?",
"Rundschreiben drucken", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (res == MessageBoxResult.Yes) {
Mouse.OverrideCursor = Cursors.AppStarting;
@ -715,23 +867,37 @@ namespace Elwig.Windows {
PrintDocument.Show();
} else {
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;
}
PrintButton.IsEnabled = true;
GenerateButton.IsEnabled = true;
UnlockInputs();
}
private async void EmailButton_Click(object sender, RoutedEventArgs evt) {
if (App.Config.Smtp == null || EmailDocuments == null) return;
EmailButton.IsEnabled = false;
GenerateButton.IsEnabled = false;
LockInputs();
SmtpClient? client = null;
try {
Mouse.OverrideCursor = Cursors.AppStarting;
client = await Utils.GetSmtpClient();
Mouse.OverrideCursor = null;
var res = MessageBox.Show($"Sollen {EmailDocuments.Count} E-Mails verschickt werden?",
var res = MessageBox.Show($"Sollen {EmailDocuments.Count:N0} E-Mails verschickt werden?",
"Rundschreiben verschicken", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (res != MessageBoxResult.Yes) {
return;
@ -740,6 +906,7 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = Cursors.AppStarting;
var subject = EmailSubjectInput.Text;
var text = EmailBodyInput.Text;
await Utils.AddSentMailBody(subject, text, EmailDocuments.Count);
foreach (var (m, docs) in EmailDocuments) {
using var msg = new MimeMessage();
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
@ -754,7 +921,15 @@ namespace Elwig.Windows {
}
msg.Body = body;
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);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} finally {
@ -762,11 +937,14 @@ namespace Elwig.Windows {
await client.DisconnectAsync(true);
client?.Dispose();
EmailButton.IsEnabled = true;
GenerateButton.IsEnabled = true;
UnlockInputs();
Mouse.OverrideCursor = null;
}
}
public void AddDeliveryConfirmation() {
if (!GenerateButton.IsEnabled) return;
AvaiableDocumentsList.SelectedIndex = 1;
if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.DeliveryConfirmation))
return;
@ -776,6 +954,7 @@ namespace Elwig.Windows {
}
public void AddCreditNote(int index) {
if (!GenerateButton.IsEnabled) return;
AvaiableDocumentsList.SelectedIndex = 2 + index;
if (AvaiableDocumentsList.SelectedItem is not string s || SelectedDocs.Any(d => d.Type == DocType.CreditNote))
return;
@ -786,5 +965,29 @@ namespace Elwig.Windows {
SelectedDocumentsList.SelectedIndex = SelectedDocs.Count - 1;
RecipientsDeliveryMembersInput.IsChecked = true;
}
private void DocumentInput_TextChanged(object sender, TextChangedEventArgs evt) {
ResetDocuments();
}
private void PostalLocation_TextChanged(object sender, TextChangedEventArgs evt) {
ResetDocuments();
}
private void PostalInput_Changed(object sender, RoutedEventArgs evt) {
UpdatePostalEmailRecipients();
}
private void OrderInput_Changed(object sender, RoutedEventArgs evt) {
ResetDocuments();
}
private void DoublePagedInput_Changed(object sender, RoutedEventArgs evt) {
ResetDocuments();
}
private void PostalSender_TextChanged(object sender, TextChangedEventArgs evt) {
ResetDocuments();
}
}
}

View File

@ -10,6 +10,7 @@
<Style TargetType="Button">
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="9,3"/>
<Setter Property="Height" Value="35"/>
@ -19,22 +20,67 @@
<Grid>
<Menu BorderThickness="0,0,0,1" VerticalAlignment="Top" Height="19" BorderBrush="LightGray" Background="White">
<MenuItem Header="Datenbank">
<MenuItem Header="Daten exportieren..." Click="Menu_Database_Export_Click" IsEnabled="False"/>
<MenuItem Header="Daten importieren..." Click="Menu_Database_Import_Click"/>
<MenuItem Header="Daten exportieren..." Click="Menu_Database_Export_Click" IsEnabled="False">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEDE1;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Daten importieren..." Click="Menu_Database_Import_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE8B5;"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click"/>
<MenuItem Header="Speicherort öffnen..." Click="Menu_Database_Open_Click"/>
<MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE756;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Speicherort öffnen..." Click="Menu_Database_Open_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xED25;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Waage">
<MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click"/>
<MenuItem Header="Datum und Uhrzeit setzen" Click="Menu_Scale_SetDateTime_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEC92;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem x:Name="HelpMenu" Header="Hilfe">
<MenuItem Header="Über"/>
<MenuItem x:Name="Menu_Help_Update" Header="Nach Updates suchen" Click="Menu_Help_Update_Click"/>
<MenuItem x:Name="Menu_Help_Smtp" Header="E-Mail-Einstellungen testen" Click="Menu_Help_Smtp_Click"/>
<MenuItem Header="Über">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE946;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Help_Update" Header="Nach Updates suchen" Click="Menu_Help_Update_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE895;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Help_Smtp" Header="E-Mail-Einstellungen testen" Click="Menu_Help_Smtp_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE715;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Help_Log" Header="Fehler-Protokoll anzeigen" Click="Menu_Help_Log_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF168;"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem x:Name="Menu_Help_Config" Header="Konfigurationsdatei öffnen..." Click="Menu_Help_Config_Click"/>
<MenuItem x:Name="Menu_Help_Directory" Header="Konfigurationsspeicherort öffnen..." Click="Menu_Help_Directory_Click"/>
<MenuItem x:Name="Menu_Help_Config" Header="Konfigurationsdatei öffnen..." Click="Menu_Help_Config_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEC7A;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Help_Directory" Header="Konfigurationsspeicherort öffnen..." Click="Menu_Help_Directory_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xED25;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
@ -57,26 +103,64 @@
</TextBlock>
</Grid>
<Button x:Name="MemberAdminButton" Content="Mitglieder" Click="MemberAdminButton_Click"
Margin="0,170,205,0"/>
<Button x:Name="MailButton" Content="Rundschreiben" Click="MailButton_Click"
Margin="205,170,0,0"/>
<Button x:Name="DeliveryAdminButton" Content="Lieferungen" Click="DeliveryAdminButton_Click"
Margin="0,210,205,0"/>
<Button x:Name="ReceiptButton" Content="Übernahme" Click="ReceiptButton_Click"
Margin="205,210,0,0"/>
<Button x:Name="BaseDataButton" Content="Stammdaten" Click="BaseDataButton_Click"
Margin="0,250,205,0"/>
<Button x:Name="DeliveryAncmtButton" Content="Anmeldungen" Click="DeliveryAncmtButton_Click"
Margin="205,250,0,0"/>
<Button x:Name="MemberAdminButton" Click="MemberAdminButton_Click"
Margin="0,170,205,0">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE77B;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,1.5,0,0"/>
<TextBlock TextAlignment="Center">Mitglieder</TextBlock>
</Grid>
</Button>
<Button x:Name="MailButton" Click="MailButton_Click"
Margin="205,170,0,0">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE715;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,1.5,0,0"/>
<TextBlock TextAlignment="Center">Rundschreiben</TextBlock>
</Grid>
</Button>
<Button x:Name="DeliveryAdminButton" Click="DeliveryAdminButton_Click"
Margin="0,210,205,0">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE736;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,1.5,0,0"/>
<TextBlock TextAlignment="Center">Lieferungen</TextBlock>
</Grid>
</Button>
<Button x:Name="ReceiptButton" Click="ReceiptButton_Click"
Margin="205,210,0,0">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEC5B;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,1.5,0,0"/>
<TextBlock TextAlignment="Center">Übernahme</TextBlock>
</Grid>
</Button>
<Button x:Name="BaseDataButton" Click="BaseDataButton_Click"
Margin="0,250,205,0">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE713;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,1.5,0,0"/>
<TextBlock TextAlignment="Center">Stammdaten</TextBlock>
</Grid>
</Button>
<Button x:Name="DeliveryAncmtButton" Click="DeliveryAncmtButton_Click"
Margin="205,250,0,0">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xF0E4;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,1.5,0,0"/>
<TextBlock TextAlignment="Center">Anmeldungen</TextBlock>
</Grid>
</Button>
<Button x:Name="DownloadButton" Click="DownloadButton_Click"
Margin="310,135,0,0" Padding="1.5,0,0,0" Height="30" Width="30"
Margin="310,135,0,0" Padding="0.375,0.5,0,0" Height="30" Width="30"
Content="&#xE896;" FontFamily="Segoe MDL2 Assets" FontSize="16"
HorizontalContentAlignment="Center"
ToolTip="Lieferungen und Mitgliederdaten anderer Zweigstellen herunterladen"/>
<Button x:Name="UploadButton" Click="UploadButton_Click"
Margin="375,135,0,0" Padding="1.5,0,0,0" Height="30" Width="30"
Margin="375,135,0,0" Padding="1.0,0.5,0,0" Height="30" Width="30"
Content="&#xE898;" FontFamily="Segoe MDL2 Assets" FontSize="16"
HorizontalContentAlignment="Center"
ToolTip="Lieferungen dieser Zweigstelle hochladen"/>
<Expander x:Name="SeasonFinish" Header="Leseabschluss" SnapsToDevicePixels="True"
@ -89,21 +173,104 @@
Margin="0,13,0,0" VerticalAlignment="Top" HorizontalAlignment="Center"
TextChanged="SeasonInput_TextChanged"/>
<Button x:Name="OverUnderDeliveryButton" Content="Über-/Unterlieferungen"
Click="OverUnderDeliveryButton_Click"
Margin="0,50,195,10" Width="190"/>
<Button x:Name="DeliveryConfirmationButton" Content="Anlieferungsbestätigung"
<Button x:Name="DeliveryConfirmationButton"
Click="DeliveryConfirmationButton_Click"
Margin="195,50,0,10" Width="190"/>
Margin="0,50,195,10" Width="190">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE715;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,0.5,0,0"/>
<TextBlock FontSize="12" Margin="18,1,0,0" TextAlignment="Center">Anlieferungsbestätigung</TextBlock>
</Grid>
</Button>
<Button x:Name="BreakdownButton" Content="Sorten-/Qual.aufteilung"
Click="BreakdownButton_Click"
Margin="0,90,195,10" Width="190"/>
<Button x:Name="PaymentButton" Content="Auszahlung"
<Button x:Name="PaymentButton"
Click="PaymentButton_Click"
Margin="195,90,0,10" Width="190"/>
Margin="195,50,0,10" Width="190">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE8EF;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0.5,1.5,0,0"/>
<TextBlock TextAlignment="Center">Auszahlung</TextBlock>
</Grid>
</Button>
<Button x:Name="OverUnderDeliveryButton"
Click="OverUnderDeliveryButton_Click"
Margin="0,90,195,10" Width="190" Padding="3,5,5,5"
ToolTip="Über-/Unterlieferungen laut gezeichneten Geschäftsanteilen und Unterlieferungen nach Flächenbindungen">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="6.5,0.5,0,0"/>
<TextBlock FontSize="12" Margin="18,1,0,0" TextAlignment="Center">Über-/Unterlieferungen</TextBlock>
</Grid>
</Button>
<Button x:Name="BreakdownButton"
Click="BreakdownButton_Click"
Margin="195,90,0,10" Width="190" Padding="3,5,5,5"
ToolTip="Aufschlüsselung des Gewichts nach Zweigstelle, Mitglied, Sorte, Attribut/Bewirt., Qualitätsstufe, gebunden/ungebunden">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="6.5,0.5,0,0"/>
<TextBlock FontSize="12" Margin="18,1,0,0" TextAlignment="Center">Sorten-/Qual.aufschlüssel.</TextBlock>
</Grid>
</Button>
<Button x:Name="AreaCommitmentsButton"
Click="AreaCommitmentsButton_Click"
Margin="0,130,195,10" Width="190" Padding="3,5,5,5"
ToolTip="Aktive Flächenbindungen der Saison pro Mitglied und Sorte/Attribut">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="6.5,1.5,0,0"/>
<TextBlock Margin="18,0,0,0" TextAlignment="Center">Flächenbindungen</TextBlock>
</Grid>
</Button>
<Button x:Name="BreakdownMemberVarietyButton"
Click="BreakdownMemberVarietyButton_Click"
Margin="195,130,0,10" Width="190" Padding="3,5,5,5"
ToolTip="Liefermengen und Ertrag (kg/ha) pro Mitglied">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="6.5,1.5,0,0"/>
<TextBlock Margin="18,0,0,0" TextAlignment="Center">Liefermengen/Ertrag</TextBlock>
</Grid>
</Button>
<Grid VerticalAlignment="Bottom" Margin="50,175,50,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" TextAlignment="Right"><Bold>Mitglieder</Bold></TextBlock>
<TextBlock Grid.Row="0" Grid.Column="2" TextAlignment="Right"><Bold>Gewicht</Bold></TextBlock>
<TextBlock Grid.Row="0" Grid.Column="3" TextAlignment="Right"><Bold>Fläche</Bold></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0">Gesamt:</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0">Gebunden:</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2">Ungebunden:</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="1" TextAlignment="Right" x:Name="SeasonStatMembersTotal">-</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1" TextAlignment="Right" x:Name="SeasonStatMembersGeb">-</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="1" TextAlignment="Right">-</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="2" TextAlignment="Right" x:Name="SeasonStatWeightTotal">-</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="2" TextAlignment="Right" x:Name="SeasonStatWeightGeb">-</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="2" TextAlignment="Right" x:Name="SeasonStatWeightUngeb">-</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="3" TextAlignment="Right">-</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="3" TextAlignment="Right" x:Name="SeasonStatArea">-</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="3" TextAlignment="Right">-</TextBlock>
</Grid>
</Grid>
</Expander>
</Grid>

View File

@ -25,7 +25,7 @@ namespace Elwig.Windows {
public MainWindow() {
InitializeComponent();
var v = Assembly.GetExecutingAssembly().GetName().Version;
VersionField.Text = "Version: " + (v == null ? "?" : $"{v.Major}.{v.Minor}.{v.Build}") + $" {App.BranchName}";
VersionField.Text = $"Version: {v?.ToString() ?? "?"} {App.BranchName}";
if (App.Client.Client == null) VersionField.Text += " (Unbekannt)";
Menu_Help_Update.IsEnabled = App.Config.UpdateUrl != null;
Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null;
@ -90,6 +90,11 @@ namespace Elwig.Windows {
} catch { }
}
private void Menu_Help_Log_Click(object sender, RoutedEventArgs evt) {
var w = new LogWindow();
w.Show();
}
private async void Menu_Scale_SetDateTime_Click(object sender, RoutedEventArgs evt) {
if (App.CommandScales.Count == 0) {
MessageBox.Show("Es sind keine geeigneten Waagen verfügbar!", "Datum und Uhrzeit setzen", MessageBoxButton.OK, MessageBoxImage.Error);
@ -201,7 +206,7 @@ namespace Elwig.Windows {
} else {
await ElwigData.Export(path, deliveries, [$"{Utils.CurrentLastSeason}", $"Zweigstelle {App.BranchName}"]);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Hochladen von {deliveries.Count} Lieferungen erfolgreich!", "Lieferungen hochladen",
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
@ -246,7 +251,7 @@ namespace Elwig.Windows {
}
private void SeasonFinish_Expanded(object sender, RoutedEventArgs evt) {
Height = 530;
Height = 660;
}
private void SeasonFinish_Collapsed(object sender, RoutedEventArgs evt) {
@ -255,12 +260,34 @@ namespace Elwig.Windows {
private async void SeasonInput_TextChanged(object? sender, TextChangedEventArgs? evt) {
using var ctx = new AppDbContext();
var s0 = await ctx.Seasons.FindAsync(SeasonInput.Value);
var year = SeasonInput.Value;
var s0 = await ctx.Seasons.FindAsync(year);
var valid = (s0 != null);
DeliveryConfirmationButton.IsEnabled = valid;
OverUnderDeliveryButton.IsEnabled = valid;
PaymentButton.IsEnabled = valid;
OverUnderDeliveryButton.IsEnabled = valid;
BreakdownButton.IsEnabled = valid;
AreaCommitmentsButton.IsEnabled = valid;
BreakdownMemberVarietyButton.IsEnabled = valid;
if (valid) {
var areaComs = Utils.ActiveAreaCommitments(ctx.AreaCommitments, year!.Value);
var weightTotal = await ctx.DeliveryParts.Where(p => p.Year == year).SumAsync(p => p.Weight);
var gebWeight = await ctx.DeliveryPartBuckets.Where(b => b.Year == year && b.Discr != "_").SumAsync(b => b.Value);
SeasonStatMembersTotal.Text = $"{await ctx.Deliveries.Where(d => d.Year == year).Select(d => d.Member).Distinct().CountAsync():N0}";
SeasonStatMembersGeb.Text = $"{await areaComs.Select(c => c.Member).Distinct().CountAsync():N0}";
SeasonStatWeightTotal.Text = $"{weightTotal:N0} kg";
SeasonStatWeightGeb.Text = $"{gebWeight:N0} kg";
SeasonStatWeightUngeb.Text = $"{weightTotal - gebWeight:N0} kg";
SeasonStatArea.Text = $"{await areaComs.SumAsync(c => c.Area):N0} m²";
} else {
SeasonStatMembersTotal.Text = "-";
SeasonStatMembersGeb.Text = "-";
SeasonStatWeightTotal.Text = "-";
SeasonStatWeightGeb.Text = "-";
SeasonStatWeightUngeb.Text = "-";
SeasonStatArea.Text = "-";
}
}
private void DeliveryConfirmationButton_Click(object sender, RoutedEventArgs evt) {
@ -270,6 +297,12 @@ namespace Elwig.Windows {
w.AddDeliveryConfirmation();
}
private void PaymentButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
App.FocusPaymentVariants(year);
}
private async void OverUnderDeliveryButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
@ -292,23 +325,15 @@ namespace Elwig.Windows {
using var ctx = new AppDbContext();
var tbl1 = await OverUnderDeliveryData.ForSeason(ctx.OverUnderDeliveryRows, year);
var tbl2 = await AreaComUnderDeliveryData.ForSeason(ctx.AreaComUnderDeliveryRows, year);
var tbl3 = await MemberDeliveryPerVariantData.ForSeason(ctx.MemberDeliveryPerVariantRows, year);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl1);
await ods.AddTable(tbl2);
await ods.AddTable(tbl3);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private void PaymentButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
App.FocusPaymentVariants(year);
}
private async void BreakdownButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
@ -341,5 +366,63 @@ namespace Elwig.Windows {
}
Mouse.OverrideCursor = null;
}
private async void AreaCommitmentsButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
var d = new SaveFileDialog() {
FileName = $"Flächenbindungen-{year}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Flächenbindungen {year} speichern unter - Elwig"
};
if (d.ShowDialog() == false)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
App.HintContextChange();
using var ctx = new AppDbContext();
var tbl = await MemberAreaComsData.ForSeason(ctx.MemberAreaComsRows, year);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private async void BreakdownMemberVarietyButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
var d = new SaveFileDialog() {
FileName = $"Liefermengen-Ertrag-{year}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Liefermengen/Ertrag {year} speichern unter - Elwig"
};
if (d.ShowDialog() == false)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
App.HintContextChange();
using var ctx = new AppDbContext();
var tbl = await MemberDeliveryPerVarietyData.ForSeason(ctx.MemberDeliveryPerVariantRows, year);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
}

View File

@ -52,19 +52,43 @@
<Menu Grid.ColumnSpan="3" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Kontaktieren">
<MenuItem x:Name="Menu_Contact_Email" Header="E-Mail senden..." IsEnabled="{Binding MemberHasEmail}"
Click="Menu_Contact_Email_Click"/>
Click="Menu_Contact_Email_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE715;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_Contact_Letterhead" Header="Briefkopf drucken" IsEnabled="{Binding IsMemberSelected}"
Click="Menu_Contact_Letterhead_Click"/>
Click="Menu_Contact_Letterhead_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE749;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Stammdatenblatt">
<MenuItem x:Name="Menu_MemberDataSheet_Show" Header="...anzeigen (PDF)" IsEnabled="{Binding IsMemberSelected}"
Click="Menu_MemberDataSheet_Show_Click" InputGestureText="Strg+P"/>
Click="Menu_MemberDataSheet_Show_Click" InputGestureText="Strg+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE8FF;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_MemberDataSheet_SavePdf" Header="...speichern... (PDF)" IsEnabled="{Binding IsMemberSelected}"
Click="Menu_MemberDataSheet_SavePdf_Click"/>
Click="Menu_MemberDataSheet_SavePdf_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEA90;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_MemberDataSheet_Print" Header="...drucken" IsEnabled="{Binding IsMemberSelected}"
Click="Menu_MemberDataSheet_Print_Click" InputGestureText="Strg+Shift+P"/>
Click="Menu_MemberDataSheet_Print_Click" InputGestureText="Strg+Shift+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE749;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_MemberDataSheet_Email" Header="...per E-Mail schicken" IsEnabled="{Binding MemberCanSendEmail}"
Click="Menu_MemberDataSheet_Email_Click"/>
Click="Menu_MemberDataSheet_Email_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE89C;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Anlieferungsbestätigung" x:Name="Menu_DeliveryConfirmation">
<MenuItem x:Name="Menu_DeliveryConfirmation_Show" Header="...anzeigen (PDF)" IsEnabled="False"
@ -170,6 +194,7 @@
<Bold>Stammgemeinde</Bold>: z.B. matzen, Wolkersdorf, ...<LineBreak/>
<Bold>UID</Bold>: z.B. ATU12345678, ...<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>Buchführend</Bold>: buchf[ührend], Pauschal[iert], !buchf[ührend]<LineBreak/>
<Bold>Volllieferant</Bold>: voll[lieferant], !Voll[lieferant] (nicht-Volllieferant)<LineBreak/>
@ -177,7 +202,7 @@
<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>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")
</TextBlock>
</TextBox.ToolTip>

View File

@ -68,7 +68,7 @@ namespace Elwig.Windows {
(PhoneNr9TypeInput, PhoneNr9Input, PhoneNr9CommentInput),
];
InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
ControlUtils.InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
SearchInput.TextChanged -= SearchInput_TextChanged;
ViewModel.MemberListOrderByMgNr = false;
@ -155,12 +155,12 @@ namespace Elwig.Windows {
ViewModel.StatusBusinessShares = $"{members.Sum(m => m.BusinessShares):N0} ({await ctx.Members.SumAsync(m => m.BusinessShares):N0})";
}
private async Task RefreshInputs(bool validate = false) {
private void RefreshInputs(bool validate = false) {
ClearInputStates();
if (ViewModel.SelectedMember is Member m) {
EditMemberButton.IsEnabled = true;
DeleteMemberButton.IsEnabled = true;
await FillInputs(m);
FillInputs(m);
} else {
EditMemberButton.IsEnabled = false;
DeleteMemberButton.IsEnabled = false;
@ -187,49 +187,80 @@ namespace Elwig.Windows {
ControlUtils.RenewItemsSource(BranchInput, await ctx.Branches.OrderBy(b => b.Name).ToListAsync());
ControlUtils.RenewItemsSource(DefaultKgInput, await ctx.WbKgs.Select(k => k.AtKg).OrderBy(k => k.Name).ToListAsync());
var font = new System.Windows.Media.FontFamily("Segoe MDL2 Assets");
MenuItem? temp = null;
var seasons = await ctx.Seasons.OrderByDescending(s => s.Year).ToListAsync();
Menu_DeliveryConfirmation.Items.Clear();
foreach (var s in seasons) {
var i = new MenuItem { Header = $"Saison {s.Year}...", Tag = s.Year };
var i = new MenuItem {
Header = $"Saison {s.Year}...",
Tag = s.Year,
Icon = s.Year == seasons[0].Year ? new TextBlock { FontSize = 16, Text = "\uE734", FontFamily = font } : null,
};
i.SetBinding(IsEnabledProperty, new Binding() { Path = new("IsMemberSelected") });
var show = new MenuItem { Header = "...anzeigen (PDF)" };
var show = new MenuItem { Header = "...anzeigen (PDF)", Icon = new TextBlock { FontSize = 16, Text = "\uE8FF", FontFamily = font } };
show.Click += Menu_DeliveryConfirmation_Show_Click;
i.Items.Add(show);
var pdf = new MenuItem { Header = "...speichern... (PDF)" };
pdf.Click += Menu_DeliveryConfirmation_Email_Click;
var pdf = new MenuItem { Header = "...speichern... (PDF)", Icon = new TextBlock { FontSize = 16, Text = "\uEA90", FontFamily = font } };
pdf.Click += Menu_DeliveryConfirmation_SavePdf_Click;
i.Items.Add(pdf);
var print = new MenuItem { Header = "...drucken" };
var print = new MenuItem { Header = "...drucken", Icon = new TextBlock { FontSize = 16, Text = "\uE749", FontFamily = font } };
print.Click += Menu_DeliveryConfirmation_Print_Click;
i.Items.Add(print);
var email = new MenuItem { Header = "...per E-Mail schicken" };
var email = new MenuItem { Header = "...per E-Mail schicken", Icon = new TextBlock { FontSize = 16, Text = "\uE89C", FontFamily = font } };
email.Click += Menu_DeliveryConfirmation_Email_Click;
email.SetBinding(IsEnabledProperty, new Binding() { Path = new("MemberCanSendEmail") });
i.Items.Add(email);
Menu_DeliveryConfirmation.Items.Add(i);
var decade = s.Year / 10;
if (seasons[0].Year / 10 != decade) {
if (temp == null || !temp.Header.ToString()!.Contains($"{decade}0er")) {
temp = new MenuItem { Header = $"Saisons {decade}0er..." };
Menu_DeliveryConfirmation.Items.Add(temp);
}
temp?.Items.Add(i);
} else {
Menu_DeliveryConfirmation.Items.Add(i);
}
}
temp = null;
Menu_CreditNote.Items.Clear();
foreach (var s in seasons) {
var i1 = new MenuItem { Header = $"Saison {s.Year}...", Tag = s.Year, IsEnabled = MemberList.SelectedItem != null };
var i1 = new MenuItem {
Header = $"Saison {s.Year}...",
Tag = s.Year,
IsEnabled = MemberList.SelectedItem != null,
Icon = s.Year == seasons[0].Year ? new TextBlock { FontSize = 16, Text = "\uE734", FontFamily = font } : null,
};
i1.SetBinding(IsEnabledProperty, new Binding() { Path = new($"MemberHasDeliveries[{s.Year}]") });
foreach (var v in s.PaymentVariants.OrderByDescending(v => v.AvNr)) {
var i2 = new MenuItem { Header = $"...{v.Name}...", Tag = v.AvNr };
var show = new MenuItem { Header = "...anzeigen (PDF)" };
var show = new MenuItem { Header = "...anzeigen (PDF)", Icon = new TextBlock { FontSize = 16, Text = "\uE8FF", FontFamily = font } };
show.Click += Menu_CreditNote_Show_Click;
i2.Items.Add(show);
var pdf = new MenuItem { Header = "...speichern... (PDF)" };
pdf.Click += Menu_CreditNote_Email_Click;
var pdf = new MenuItem { Header = "...speichern... (PDF)", Icon = new TextBlock { FontSize = 16, Text = "\uEA90", FontFamily = font } };
pdf.Click += Menu_CreditNote_SavePdf_Click;
i2.Items.Add(pdf);
var print = new MenuItem { Header = "...drucken" };
var print = new MenuItem { Header = "...drucken", Icon = new TextBlock { FontSize = 16, Text = "\uE749", FontFamily = font } };
print.Click += Menu_CreditNote_Print_Click;
i2.Items.Add(print);
var email = new MenuItem { Header = "...per E-Mail schicken" };
var email = new MenuItem { Header = "...per E-Mail schicken", Icon = new TextBlock { FontSize = 16, Text = "\uE89C", FontFamily = font } };
email.SetBinding(IsEnabledProperty, new Binding { Path = new("MemberCanSendEmail") });
email.Click += Menu_CreditNote_Email_Click;
i2.Items.Add(email);
i1.Items.Add(i2);
}
Menu_CreditNote.Items.Add(i1);
var decade = s.Year / 10;
if (seasons[0].Year / 10 != decade) {
if (temp == null || !temp.Header.ToString()!.Contains($"{decade}0er")) {
temp = new MenuItem { Header = $"Saisons {decade}0er..." };
Menu_CreditNote.Items.Add(temp);
}
temp?.Items.Add(i1);
} else {
Menu_CreditNote.Items.Add(i1);
}
}
temp = null;
await RefreshList();
@ -283,8 +314,8 @@ namespace Elwig.Windows {
inputs.Address.Visibility = vis;
}
private async void MemberList_SelectionChanged(object sender, RoutedEventArgs evt) {
await RefreshInputs();
private void MemberList_SelectionChanged(object sender, RoutedEventArgs evt) {
RefreshInputs();
if (MemberList.SelectedItem is Member m) {
Menu_Export_ExportSelected.IsEnabled = !IsEditing && !IsCreating;
Menu_Export_UploadSelected.IsEnabled = !IsEditing && !IsCreating && App.Config.SyncUrl != null;
@ -360,21 +391,7 @@ namespace Elwig.Windows {
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using (var ctx = new AppDbContext()) {
var l = (await ctx.Members.FindAsync(m.MgNr))!;
if (d.DeletePaymentData) {
ctx.RemoveRange(l.Credits);
}
if (d.DeleteDeliveries) {
ctx.RemoveRange(l.Deliveries);
}
if (d.DeleteAreaComs) {
ctx.RemoveRange(l.AreaCommitments);
}
ctx.Remove(l);
await ctx.SaveChangesAsync();
}
App.HintContextChange();
await MemberService.DeleteMember(m.MgNr, d.DeletePaymentData, d.DeleteDeliveries, d.DeleteAreaComs);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
@ -391,14 +408,22 @@ namespace Elwig.Windows {
}
private async void SaveButton_Click(object? sender, RoutedEventArgs? evt) {
int? mgnr = null;
Mouse.OverrideCursor = Cursors.AppStarting;
SaveButton.IsEnabled = false;
int mgnr;
try {
mgnr = await ViewModel.UpdateMember(ViewModel.SelectedMember?.MgNr);
} catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Mitglied aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
SaveButton.IsEnabled = true;
return;
} finally {
Mouse.OverrideCursor = null;
}
IsEditing = false;
IsCreating = false;
MemberList.IsEnabled = true;
@ -409,7 +434,7 @@ namespace Elwig.Windows {
ViewModel.EnableSearchInputs = true;
FinishInputFilling();
await RefreshList();
await RefreshInputs();
RefreshInputs();
ViewModel.SearchQuery = "";
if (mgnr is int m)
FocusMember(m);
@ -425,7 +450,7 @@ namespace Elwig.Windows {
ViewModel.TransferPredecessorAreaComs = null;
ViewModel.CancelAreaComs = null;
if (IsEditing) {
await RefreshInputs();
RefreshInputs();
} else if (IsCreating) {
ClearInputs();
await InitInputs();
@ -433,7 +458,7 @@ namespace Elwig.Windows {
UpdateButtons();
}
private async void CancelButton_Click(object sender, RoutedEventArgs evt) {
private void CancelButton_Click(object sender, RoutedEventArgs evt) {
IsEditing = false;
IsCreating = false;
MemberList.IsEnabled = true;
@ -441,7 +466,7 @@ namespace Elwig.Windows {
ViewModel.CancelAreaComs = null;
HideSaveResetCancelButtons();
ShowNewEditDeleteButtons();
await RefreshInputs();
RefreshInputs();
LockInputs();
UpdateContactInfoVisibility();
ViewModel.EnableSearchInputs = true;
@ -498,6 +523,10 @@ namespace Elwig.Windows {
private async void Menu_MemberDataSheet_Email_Click(object sender, RoutedEventArgs evt) {
if (ViewModel.SelectedMember is not Member m) return;
var res = MessageBox.Show("Soll eine E-Mail verschickt werden?", "Stammdatenblatt verschicken",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
if (res != MessageBoxResult.Yes)
return;
await MemberService.GenerateMemberDataSheet(m, ExportMode.Email);
}
@ -526,6 +555,10 @@ namespace Elwig.Windows {
var year = (int?)((sender as MenuItem)?.Parent as MenuItem)?.Tag;
if (ViewModel.SelectedMember is not Member m || year == null)
return;
var res = MessageBox.Show("Soll eine E-Mail verschickt werden?", "Anlieferungsbestätigung verschicken",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
if (res != MessageBoxResult.Yes)
return;
await MemberService.GenerateDeliveryConfirmation(m, (int)year, ExportMode.Email);
}
@ -558,6 +591,10 @@ namespace Elwig.Windows {
var avnr = (int?)((sender as MenuItem)?.Parent as MenuItem)?.Tag;
if (ViewModel.SelectedMember is not Member m || year == null || avnr == null)
return;
var res = MessageBox.Show("Soll eine E-Mail verschickt werden?", "Traubengutschrift verschicken",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
if (res != MessageBoxResult.Yes)
return;
await MemberService.GenerateCreditNote(m, (int)year, (int)avnr, ExportMode.Email);
}
@ -686,10 +723,10 @@ namespace Elwig.Windows {
}
}
private async Task FillInputs(Member m) {
private void FillInputs(Member m) {
ClearOriginalValues();
ClearDefaultValues();
await ViewModel.FillInputs(m);
ViewModel.FillInputs(m);
UpdateContactInfoVisibility(IsEditing || IsCreating);
FinishInputFilling();
}

View File

@ -56,21 +56,45 @@
<Menu Grid.ColumnSpan="2" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="White">
<MenuItem Header="Variantendaten">
<MenuItem x:Name="Menu_SummaryShow" Header="...anzeigen (PDF)" IsEnabled="False"
Click="Menu_SummaryShow_Click" InputGestureText="Strg+P"/>
Click="Menu_SummaryShow_Click" InputGestureText="Strg+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE8FF;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_SummarySave" Header="...speichern... (PDF)" IsEnabled="False"
Click="Menu_SummarySave_Click"/>
Click="Menu_SummarySave_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xEA90;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_SummaryExport" Header="...speichern... (Excel)" IsEnabled="False"
Click="Menu_SummaryExport_Click"/>
Click="Menu_SummaryExport_Click">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="Menu_SummaryPrint" Header="...drucken" IsEnabled="False"
Click="Menu_SummaryPrint_Click" InputGestureText="Strg+Shift+P"/>
Click="Menu_SummaryPrint_Click" InputGestureText="Strg+Shift+P">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE749;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Buchungsliste">
<MenuItem x:Name="Menu_ExportSave" Header="...speichern... (Excel)" IsEnabled="False"
Click="Menu_ExportSave_Click" InputGestureText="Strg+L"/>
Click="Menu_ExportSave_Click" InputGestureText="Strg+L">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE9F9;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Überweisungsdaten">
<MenuItem x:Name="Menu_EbicsSave" Header="...exportieren... (EBICS)" IsEnabled="False"
Click="Menu_EbicsSave_Click" InputGestureText="Strg+Ü"/>
Click="Menu_EbicsSave_Click" InputGestureText="Strg+Ü">
<MenuItem.Icon>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE792;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
@ -116,8 +140,9 @@
<TextBox x:Name="CommentInput" Grid.Column="1" HorizontalAlignment="Stretch" Margin="0,40,10,0"
TextChanged="CommentInput_TextChanged"/>
<Label Content="Erstellt am:" 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"/>
<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"
TextChanged="DateInput_TextChanged"/>
<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"
@ -215,10 +240,16 @@
Click="SaveButton_Click"/>
</Grid>
<Button x:Name="MailButton" Content="Traubengutschriften"
FontSize="14" Width="160" Margin="10,10,10,10" Height="27" IsEnabled="False"
<Button x:Name="MailButton"
FontSize="14" Width="180" Margin="10,10,10,10" Height="30" IsEnabled="False"
Click="MailButton_Click"
VerticalAlignment="Bottom" HorizontalAlignment="Right" Grid.Column="1"/>
VerticalAlignment="Bottom" HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Grid.Column="1">
<Grid>
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text="&#xE715;"
TextAlignment="Left" HorizontalAlignment="Left" Padding="0,1.5,0,0" Margin="0,0,10,0"/>
<TextBlock Margin="18,0,0,0" TextAlignment="Center">Traubengutschriften</TextBlock>
</Grid>
</Button>
</Grid>
<StatusBar Grid.Row="2" Grid.ColumnSpan="2" BorderThickness="0,1,0,0" BorderBrush="Gray">

View File

@ -19,7 +19,7 @@ namespace Elwig.Windows {
public readonly int Year;
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 bool WeightModifierChanged = false;
@ -175,6 +175,7 @@ namespace Elwig.Windows {
private void UpdateSaveButton() {
SaveButton.IsEnabled = PaymentVariantList.SelectedItem != null &&
((DataChanged && DataValid) || NameChanged || CommentChanged ||
(DateChanged && DateValid) ||
(TransferDateChanged && TransferDateValid) ||
(ConsiderModifiersInput.IsChecked != BillingData?.ConsiderDelieryModifiers) ||
(ConsiderPenaltiesInput.IsChecked != BillingData?.ConsiderContractPenalties) ||
@ -433,10 +434,16 @@ namespace Elwig.Windows {
var withoutIban = v.Credits.Count(c => c.Member.Iban == null);
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);
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() {
FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}",
@ -489,6 +496,7 @@ namespace Elwig.Windows {
try {
v.Name = NameInput.Text;
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;
var d = App.Config.Debug ? BillingData.FromJson(DataInput.Text) : BillingData;
d.ConsiderDelieryModifiers = ConsiderModifiersInput.IsChecked ?? false;
@ -557,6 +565,27 @@ namespace Elwig.Windows {
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) {
if (PaymentVariantList.SelectedItem is not PaymentVar v) {
ControlUtils.ClearInputState(TransferDateInput);

View File

@ -1,5 +1,4 @@
::mkdir "C:\Program Files\Elwig"
::curl --fail -s -L "https://github.com/emendelson/pdftoprinter/raw/main/PDFtoPrinter.exe" -z "C:\Program Files\Elwig\PDFtoPrinter.exe" -o "C:\Program Files\Elwig\PDFtoPrinter.exe"
mkdir "C:\ProgramData\Elwig\resources"
copy /b /y Documents\*.css "C:\ProgramData\Elwig\resources"
copy /b /y Documents\*.cshtml "C:\ProgramData\Elwig\resources"

View File

@ -1,4 +1,4 @@
<Project Sdk="WixToolset.Sdk/5.0.0">
<Project Sdk="WixToolset.Sdk/6">
<PropertyGroup>
<HarvestFileSuppressUniqueIds>false</HarvestFileSuppressUniqueIds>
<HarvestFileGenerateGuidsNow>true</HarvestFileGenerateGuidsNow>
@ -25,7 +25,6 @@
</Task>
</UsingTask>
<Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
<Exec Command="curl --fail -s -L &quot;https://github.com/emendelson/pdftoprinter/raw/main/PDFtoPrinter.exe&quot; -z &quot;$(ProjectDir)\Files\PDFtoPrinter.exe&quot; -o &quot;$(ProjectDir)\Files\PDFtoPrinter.exe&quot;" />
<Exec Command="dotnet publish &quot;$(ProjectDir)\..\Elwig\Elwig.csproj&quot; &quot;/p:PublishProfile=$(ProjectDir)\..\Elwig\Properties\PublishProfiles\FolderProfile.pubxml&quot;" />
<GetFileVersion AssemblyPath="..\Elwig\bin\Publish\Elwig.exe">
<Output TaskParameter="Version" PropertyName="ElwigFileVersion" />
@ -36,7 +35,6 @@
</Target>
<ItemGroup>
<None Include="Files\config.ini" />
<None Include="Files\PDFtoPrinter.exe" />
<None Include="Files\WinziPrint.exe" />
</ItemGroup>
</Project>
</Project>

View File

@ -10,9 +10,6 @@
<Component Directory="InstallFolder">
<File Source="$(ProjectDir)\Files\WinziPrint.exe" Id="WinziPrint.exe"/>
</Component>
<Component Directory="InstallFolder">
<File Source="$(ProjectDir)\Files\PDFtoPrinter.exe" Id="PDFtoPrinter.exe"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -2,3 +2,6 @@
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"
IconSourceFile="$(var.ElwigProjectDir)\Resources\Images\Elwig.ico">
<BootstrapperApplication>

View File

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

View File

@ -30,6 +30,11 @@ namespace Tests {
await BillingData.Init();
}
[OneTimeSetUp]
public void Setup_4_Culture() {
Utils.OverrideCulture();
}
[OneTimeTearDown]
public async Task TeardownDatabase() {
AppDbContext.ConnectionStringOverride = null;

View File

@ -29,9 +29,10 @@ namespace Tests.DocumentTests {
Assert.That(text, Contains.Substring("AT81 1234 5678 9012 3457"));
Assert.That(text, Contains.Substring("""
20201001X001 1 Grüner Veltliner 73 15,0 ungeb.: 3 219 0,5000 - - 1 609,50
20201001X003 1 Grüner Veltliner 75 15,4 ungeb.: 2 561 - -
20201001X003 2 Grüner Veltliner Kabinett 87 17,6 ungeb.: 3 129 - -
20201001X003 3 Grüner Veltliner 79 16,1 ungeb.: 1 280 - -
20201001X003 1 Grüner Veltliner abgew. 75 15,4 ungeb.: 2 561 - -
20201001X003 2 Grüner Veltliner Kabinett / abgew.
87 17,6 ungeb.: 3 129 - -
20201001X003 3 Grüner Veltliner abgew. 79 16,1 ungeb.: 1 280 - -
20201001X005 1 Welschriesling 84 17,0 ungeb.: 3 192 - -
20201001X005 2 Welschriesling 84 17,1 ungeb.: 2 190 - -
"""));

View File

@ -18,11 +18,11 @@ namespace Tests.DocumentTests {
Assert.That(text, Contains.Substring("Anmeldeliste"));
Assert.That(text, Contains.Substring("01.10.2020 Matzen GV Kabinettaktion"));
Assert.That(table, Is.EqualTo(new string[][] {
["01.10.2020", "101 MUSTERMANN Max", "Grüner Veltliner", "-", "5 000"],
["01.10.2020", "102 WEINBAUER Wernhardt", "Grüner Veltliner", "-", "10 000"],
["01.10.2020", "103 MUSTERBAUER Matthäus", "Grüner Veltliner", "-", "8 000"],
["01.10.2020", "104 WINZER Waltraud", "Grüner Veltliner", "-", "2 000"],
["Gesamt:", "Anmeldungen: 4", "25 000"],
["01.10.2020", "101 MUSTERMANN Max", "Hohenruppersdorf", "Grüner Veltliner", "-", "5 000"],
["01.10.2020", "102 WEINBAUER Wernhardt", "Hohenruppersdorf", "Grüner Veltliner", "-", "10 000"],
["01.10.2020", "103 MUSTERBAUER Matthäus", "Wolkersdorf", "Grüner Veltliner", "-", "8 000"],
["01.10.2020", "104 WINZER Waltraud", "Wolkersdorf", "Grüner Veltliner", "-", "2 000"],
["Gesamt:", "Anmeldungen: 4", "25 000"],
}));
});
}

View File

@ -24,10 +24,10 @@ namespace Tests.DocumentTests {
Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}"));
Assert.That(text, Contains.Substring("Anlieferungsbestätigung 2020"));
Assert.That(text, Contains.Substring("""
20201001X003 2 Grüner Veltliner Kabinett Kabinett 87 17,6 ungeb.: 3 129 3 129
20201001X003 3 Grüner Veltliner Qualitätswein 79 16,1 ungeb.: 1 280 1 280
20201001X003 1 Grüner Veltliner Qualitätswein 75 15,4 ungeb.: 2 561 2 561
20201001X001 1 Grüner Veltliner Qualitätswein 73 15,0 ungeb.: 3 219 3 219
20201001X003 2 Grüner Veltliner Kabinett Wein 87 17,6 ungeb.: 3 129 3 129
20201001X003 3 Grüner Veltliner Wein 79 16,1 ungeb.: 1 280 1 280
20201001X003 1 Grüner Veltliner Wein 75 15,4 ungeb.: 2 561 2 561
20201001X005 2 Welschriesling Kabinett 84 17,1 ungeb.: 2 190 2 190
20201001X005 1 Welschriesling Kabinett 84 17,0 ungeb.: 3 192 3 192
"""));

View File

@ -0,0 +1,32 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
[TestFixture]
public class DeliveryDepreciationListTest {
[Test]
public async Task Test_01_DepreciatedDeliveries2020() {
using var ctx = new AppDbContext();
var data = await DeliveryJournalData.FromQuery(ctx.Deliveries.Where(d => d.Year == 2020).SelectMany(d => d.Parts).Where(d => d.QualId == "WEI"), ["Saison 2020"]);
using var doc = new DeliveryDepreciationList("Saison 2020", data);
var text = await Utils.GeneratePdfText(doc, true);
var table = Utils.ExtractTable(text);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("Abwertungsliste"));
Assert.That(text, Contains.Substring("Saison 2020"));
Assert.That(table, Is.EqualTo(new string[][] {
["101, MUSTERMANN Max", "Teil-Lfrg.:", "3", "81", "16,5", "6 970"],
["20201001X003 1 01.10.2020 10:24", "Grüner Veltliner", "75", "15,4", "2 561"],
["20201001X003 2 01.10.2020 10:24", "Grüner Veltliner", "Kabinett", "87", "17,6", "3 129"],
["20201001X003 3 01.10.2020 10:24", "Grüner Veltliner", "79", "16,1", "1 280"],
["103, MUSTERBAUER Matthäus", "Teil-Lfrg.:", "2", "79", "16,2", "6 099"],
["20201002X001 1 02.10.2020 09:13", "Grüner Veltliner", "Bio", "80", "16,3", "3 198"],
["20201002X002 1 02.10.2020 09:28", "Grüner Veltliner", "Bio", "78", "16,0", "2 901"],
["Gesamt:", "(Teil-)Lieferungen: 3 (5)", "80", "16,3", "13 069"],
}));
});
}
}
}

View File

@ -84,20 +84,20 @@ namespace Tests.DocumentTests {
Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201001X003"));
Assert.That(text, Contains.Substring("Das Mitglied erklärt, dass die gelieferte Ware dem österreichischen Weingesetz entspricht"));
Assert.That(text, Contains.Substring("""
1 Grüner Veltliner Qualitätswein 75 15,4 2 561
Herkunft: Österreich / Weinland / Niederösterreich
1 Grüner Veltliner Wein 75 15,4 2 561
Herkunft: Österreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("""
2 Grüner Veltliner Kabinett Kabinett 87 17,6 3 129
Herkunft: Österreich / Weinland / Niederösterreich
2 Grüner Veltliner Kabinett Wein 87 17,6 3 129
Herkunft: Österreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("""
3 Grüner Veltliner Qualitätswein 79 16,1 1 280
Herkunft: Österreich / Weinland / Niederösterreich
3 Grüner Veltliner Wein 79 16,1 1 280
Herkunft: Österreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (gerebelt gewogen)
"""));
@ -123,9 +123,9 @@ namespace Tests.DocumentTests {
Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201002X001"));
Assert.That(text, Contains.Substring("Das Mitglied erklärt, dass die gelieferte Ware dem österreichischen Weingesetz entspricht"));
Assert.That(text, Contains.Substring("""
1 Grüner Veltliner Qualitätswein 80 16,3 3 198
1 Grüner Veltliner Wein 80 16,3 3 198
Bewirtschaftung: Bio (AT-BIO-302)
Herkunft: Österreich / Weinland / Niederösterreich
Herkunft: Österreich
/ Wolkersdorfer Hochleithen / Wolkersdorf im Weinviertel / KG Wolkersdorf
Waage: ?, ID: ? (gerebelt gewogen)
"""));

View File

@ -16,14 +16,19 @@ namespace Tests.DocumentTests {
Assert.That(text, Contains.Substring("Qualitätsstatistik"));
Assert.That(text, Contains.Substring("Saison 2020"));
Assert.That(text, Contains.Substring("""
Qualitätswein
73 2 4 431
74 2 4 203
75 3 5 176
77 1 842
Wein
75 1 2 561
78 1 2 901
79 1 1 280
80 1 3 198
87 1 3 129
"""));
Assert.That(text, Contains.Substring("""
Qualitätswein
73 2 4 431
74 2 4 203
75 2 2 615
77 1 842
82 1 4 002
"""));
Assert.That(text, Contains.Substring("""
@ -31,14 +36,14 @@ namespace Tests.DocumentTests {
84 3 8 960
85 3 11 181
86 1 2 987
87 2 5 002
87 1 1 873
89 2 4 723
"""));
Assert.That(text, Contains.Substring(
"80 5 " + "13 069 " +
"- 0 0 " +
"- 0 0 " +
"77 12 " + "26 033 " +
"86 11 " + "32 853"));
"77 5 " + "11 568 " +
"85 6 " + "17 561"));
});
}
}

View File

@ -15,7 +15,7 @@ namespace Tests.E2ETests {
[OneTimeSetUp]
public static async Task SetupDatabase() {
if (File.Exists(Utils.TestDatabasePath)) File.Delete(Utils.TestDatabasePath);
using var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{Utils.TestDatabasePath}\"; Mode=ReadWriteCreate; Foreign Keys=True; Cache=Default");
using var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{Utils.TestDatabasePath}\"; Mode=ReadWriteCreate; Foreign Keys=True; Cache=Default; Pooling=False");
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
await AppDbContext.ExecuteEmbeddedScript(cnx, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.E2EInsert.sql");

View File

@ -1,6 +1,5 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using System.Text.Json;
namespace Tests.HelperTests {
@ -452,7 +451,7 @@ namespace Tests.HelperTests {
[73] = 0.5m,
[83] = 1.0m
}, null), GetSelection(["GV/-"]))
];
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
@ -476,14 +475,14 @@ namespace Tests.HelperTests {
[Test]
public void TestWrite_04_Simple() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GV/-", "ZW/-"])),
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GV/-", "ZW/-"])),
new GraphEntry(10, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.75m,
}, null), GetSelection(["WR/-"]))
];
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
@ -510,18 +509,18 @@ namespace Tests.HelperTests {
[Test]
public void TestWrite_05_Attribute() {
List<GraphEntry> entries = [
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GV/K-", "ZW/K-"])),
new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.5m,
[84] = 1.0m
}, null), GetSelection(["GV/K-", "ZW/K-"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[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() {
[73] = 0.65m,
[84] = 1.2m
}, null), GetSelection(["BP/-", "SA/-"]))
];
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
@ -564,12 +563,12 @@ namespace Tests.HelperTests {
}, null), GetSelection(["GV/-B", "ZW/-B"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[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() {
[73] = 0.65m,
[84] = 1.2m
}, null), GetSelection(["BP/-", "SA/-"]))
];
];
var data = BillingData.FromGraphEntries(entries);
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() {
[73] = 0.8m,
}, null), GetSelection(["BP/-", "BP/-B"])),
];
];
var data = BillingData.FromGraphEntries(entries);
Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
{
@ -653,8 +652,8 @@ namespace Tests.HelperTests {
}, null), GetSelection(["BP/-B", "ZW/-B", "FV/-B"])),
new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
[73] = 0.8m,
}, null), GetSelection(["BP/-", "ZW/-", "FV/-", "WR/-", "BL/-"])),
];
}, null), GetSelection(["BP/-", "ZW/-", "FV/-", "WR/-", "BL/-", "RR/-"])),
];
var data = BillingData.FromGraphEntries(entries);
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

@ -43,17 +43,17 @@ INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw,
(2020, 1, 1, 'GV', NULL, NULL, 3219, 15.0, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, '1', '{"id":"321","nr":321,"gross_weight":3219,"tare_weight":0,"net_weight":3219,"date":"2020-10-01","time":"09:02:46"}', NULL),
(2020, 2, 1, 'GV', 'K', NULL, 2987, 17.5, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 2, 2, 'GV', 'K', NULL, 1873, 17.7, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 1, 'GV', NULL, NULL, 2561, 15.4, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 2, 'GV', 'K', NULL, 3129, 17.6, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 3, 'GV', NULL, NULL, 1280, 16.1, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 1, 'GV', NULL, NULL, 2561, 15.4, 'WEI', 'OEST', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 2, 'GV', 'K', NULL, 3129, 17.6, 'WEI', 'OEST', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 3, 'GV', NULL, NULL, 1280, 16.1, 'WEI', 'OEST', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 4, 1, 'GV', NULL, NULL, 4002, 16.7, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 4, 2, 'GV', NULL, NULL, 481, 15.3, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 5, 1, 'WR', NULL, NULL, 3192, 17.0, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 5, 2, 'WR', NULL, NULL, 2190, 17.1, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 6, 1, 'GV', NULL, 'B', 1732, 15.2, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 7, 1, 'GV', NULL, 'B', 3198, 16.3, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 7, 1, 'GV', NULL, 'B', 3198, 16.3, 'WEI', 'OEST', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 7, 2, 'GV', NULL, 'B', 2134, 15.4, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 8, 1, 'GV', NULL, 'B', 2901, 16.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 8, 1, 'GV', NULL, 'B', 2901, 16.0, 'WEI', 'OEST', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 8, 2, 'GV', NULL, 'B', 3321, 17.3, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 9, 1, 'WR', NULL, 'B', 3998, 17.2, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 10, 1, 'ZW', NULL, NULL, 1212, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),

View File

@ -70,7 +70,7 @@ INSERT INTO member (mgnr, given_name, name, zwstid, volllieferant, buchführend,
(101, 'Max', 'Mustermann', 'X', FALSE, FALSE, 40, 222303524, 'Winzerstraße 1', 06109, 'AT811234567890123457', '0123463', NULL ),
(102, 'Wernhardt', 'Weinbauer', 'X', FALSE, FALSE, 40, 222303524, 'Winzerstraße 2', 06109, 'AT541234567890123458', '0123471', 'ATU12345684'),
(103, 'Matthäus', 'Musterbauer', 'X', FALSE, FALSE, 40, 212005138, 'Brünner Straße 10', 15224, 'AT271234567890123459', '0123480', NULL ),
(104, 'Waltraud', 'Winzer', 'X', FALSE, FALSE, 40, 212005138, 'Wiener Straße 15', 15224, 'AT971234567890123460', '0123498', 'ATU12345693');
(104, 'Waltraud', 'Winzer', 'X', FALSE, TRUE , 40, 212005138, 'Wiener Straße 15', 15224, 'AT971234567890123460', '0123498', 'ATU12345693');
INSERT INTO member_billing_address (mgnr, name, country, postal_dest, address) VALUES
(102, 'W&B Weinbauer GesbR', 40, 222303524, 'Winzerstraße 2'),

View File

@ -0,0 +1,11 @@
-- deletes for ServiceTests
DELETE FROM credit;
DELETE FROM payment_variant;
DELETE FROM delivery;
DELETE FROM season;
DELETE FROM area_commitment;
DELETE FROM area_commitment_type;
DELETE FROM member WHERE mgnr >= 200;
DELETE FROM wine_cultivation;
DELETE FROM wine_attribute;

View File

@ -0,0 +1,135 @@
-- inserts for ServiceTests
INSERT INTO wine_cultivation (cultid, name, description) VALUES
('B', 'Bio', 'AT-BIO-302');
INSERT INTO wine_attribute (attrid, name, active, max_kg_per_ha, strict, fill_lower) VALUES
('K', 'Kabinett', TRUE, NULL, FALSE, 0);
INSERT INTO member (mgnr, given_name, name, zwstid, volllieferant, buchführend, country, postal_dest, address, default_kgnr, iban, lfbis_nr, ustid_nr) VALUES
(201, 'Theodor', 'Testbauer', 'X', FALSE, FALSE, 40, 222303524, 'Teststraße 1', 06109, 'AT811234567890123457', '0123463', NULL ),
(202, 'Thomas', 'Testmann', 'X', FALSE, FALSE, 40, 222303524, 'Teststraße 2', 06109, 'AT541234567890123458', '0123471', 'ATU12345684'),
(203, 'Tina', 'Testwinzer', 'X', FALSE, FALSE, 40, 212005138, 'Teststraße 3', 15224, 'AT271234567890123459', '0123480', NULL ),
(204, 'Trude', 'Testerin', 'X', FALSE, TRUE , 40, 212005138, 'Teststraße 4', 15224, 'AT971234567890123460', '0123498', 'ATU12345693');
INSERT INTO member_billing_address (mgnr, name, country, postal_dest, address) VALUES
(202, 'T&T Testmann GesbR', 40, 222303524, 'Teststraße 11'),
(203, 'Testwinzer GesbR', 40, 212005138, 'Teststraße 3' ),
(204, 'Trauben Trude GmbH', 40, 212205137, 'Teststraße 12');
INSERT INTO member_telephone_number (mgnr, nr, type, number, comment) VALUES
(203, 1, 'mobile', '+43 664 987654321', NULL),
(204, 1, 'mobile', '+43 664 123456789', NULL);
INSERT INTO member_email_address (mgnr, nr, address, comment) VALUES
(203, 1, 'tina@winzer.com', NULL),
(204, 1, 'trude@trauben.at', NULL);
INSERT INTO area_commitment_type (vtrgid, sortid, attrid, disc, min_kg_per_ha, penalty_per_kg, penalty_amount, penalty_none) VALUES
('GV', 'GV', NULL, NULL, 5000, 500, NULL, NULL);
INSERT INTO area_commitment (fbnr, mgnr, vtrgid, cultid, area, kgnr, gstnr, rdnr, year_from, year_to) VALUES
( 1, 203, 'GV', NULL, 10000, 15224, '321/9', NULL, NULL, NULL),
( 2, 204, 'GV', NULL, 10000, 15224, '123/1', NULL, 2000, 2019),
( 3, 204, 'GV', NULL, 10000, 15224, '123/2', NULL, 2025, 2030),
( 4, 204, 'GV', NULL, 10000, 15224, '123/3', NULL, 2021, 2031);
INSERT INTO season (year, currency, min_kg_per_bs, max_kg_per_bs, penalty_per_kg, penalty_amount, penalty_none, start_date, end_date) VALUES
(2021, 'EUR', 2000, 4000, NULL, NULL, NULL, NULL, NULL),
(2022, 'EUR', 2000, 4000, NULL, NULL, NULL, NULL, NULL),
(2023, 'EUR', 2000, 4000, NULL, NULL, NULL, NULL, NULL);
INSERT INTO modifier (year, modid, ordering, name, abs, rel, active) VALUES
(2021, 'S', 0, 'Geschädigte Trauben', NULL, -0.1, TRUE),
(2022, 'S', 0, 'Geschädigte Trauben', NULL, -0.1, TRUE);
INSERT INTO delivery (mgnr, year, did, date, time, zwstid, lnr) VALUES
(203, 2021, 1, '2021-10-01', NULL, 'X', 1),
(204, 2021, 2, '2021-10-01', NULL, 'X', 2),
(101, 2022, 1, '2022-09-29', NULL, 'X', 1),
(101, 2022, 2, '2022-09-30', NULL, 'X', 1),
(101, 2023, 1, '2023-10-01', NULL, 'X', 1),
(101, 2023, 2, '2023-10-01', NULL, 'X', 2),
(101, 2023, 3, '2023-10-01', NULL, 'X', 3),
(101, 2023, 4, '2023-10-01', NULL, 'X', 4),
(101, 2023, 5, '2023-10-02', NULL, 'X', 1),
(101, 2023, 6, '2023-10-03', NULL, 'X', 1),
(101, 2023, 7, '2023-10-04', NULL, 'X', 1),
(101, 2023, 8, '2023-10-05', NULL, 'X', 1),
(101, 2023, 9, '2023-10-06', NULL, 'X', 1),
(101, 2023, 10, '2023-10-06', NULL, 'X', 2),
(101, 2023, 11, '2023-10-07', NULL, 'X', 1),
(101, 2023, 12, '2023-10-07', NULL, 'X', 2),
(101, 2023, 13, '2023-10-08', NULL, 'X', 1),
(101, 2023, 14, '2023-10-08', NULL, 'X', 2),
(101, 2023, 15, '2023-10-09', NULL, 'X', 1),
(101, 2023, 16, '2023-10-09', NULL, 'X', 2),
(101, 2023, 17, '2023-10-10', NULL, 'X', 1),
(101, 2023, 18, '2023-10-10', NULL, 'X', 2),
(101, 2023, 19, '2023-10-10', NULL, 'X', 3);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_id, weighing_data, weighing_reason) VALUES
(2021, 1, 1, 'GV', NULL, NULL, 3500, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2021, 2, 1, 'GV', NULL, NULL, 4000, 17.0, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2021, 2, 2, 'GV', NULL, NULL, 4000, 16.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2022, 1, 1, 'GV', NULL, NULL, 3700, 16.5, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2022, 1, 2, 'GV', NULL, NULL, 3700, 16.5, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 1, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 1, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 1, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 2, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 2, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 2, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 3, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 3, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 3, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 4, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 4, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 4, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 5, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 5, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 5, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 6, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 6, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 6, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 7, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 7, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 7, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 8, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 8, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 8, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 9, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 9, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 9, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 10, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 11, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 11, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 11, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 12, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 13, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 13, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 13, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 14, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 15, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 15, 2, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 15, 3, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 16, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 17, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 18, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2023, 19, 1, 'GV', NULL, NULL, 1000, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL);
INSERT INTO delivery_part_modifier (year, did, dpnr, modid) VALUES
(2021, 1, 1, 'S'),
(2021, 2, 2, 'S'),
(2022, 1, 2, 'S');
INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, comment, data) VALUES
(2021, 1, 'Probevariante', '2022-01-15', '2022-01-15', TRUE, NULL, NULL, '{"mode":"elwig","version":1,"payment":0.5,"curves":[]}');
INSERT INTO payment_member (year, avnr, mgnr, net_amount) VALUES
(2021, 1, 203, 10000000),
(2021, 1, 204, 10000000);
INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers) VALUES
(2021, 1, 203, 1, 10000000, 0, 0.1, 0, 0),
(2021, 2, 204, 1, 10000000, 0, 0.1, 0, 0);

View File

@ -0,0 +1,847 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using Elwig.Services;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
namespace Tests.ServiceTests {
[TestFixture]
public class DeliveryServiceTest {
private static async Task InitViewModel(DeliveryAdminViewModel vm) {
using var ctx = new AppDbContext();
vm.MemberSource = await ctx.Members.ToListAsync();
vm.BranchSource = await ctx.Branches.ToListAsync();
vm.WineVarSource = await ctx.WineVarieties.ToListAsync();
List<object> attrs = (await ctx.WineAttributes.ToListAsync()).Cast<object>().ToList();
attrs.Insert(0, new NullItem());
vm.WineAttrSource = attrs;
List<object> cults = (await ctx.WineCultivations.ToListAsync()).Cast<object>().ToList();
cults.Insert(0, new NullItem());
vm.WineCultSource = cults;
vm.WineQualityLevelSource = await ctx.WineQualityLevels.ToListAsync();
vm.WineOriginSource = await ctx.WineOrigins.ToListAsync();
vm.WineKgSource = await ctx.Katastralgemeinden.ToListAsync();
vm.ModifiersSource = await ctx.Modifiers.Where(m => m.Year == 2022).ToListAsync();
}
private static async Task<Delivery?> GetDelivery(string lsnr) {
using var ctx = new AppDbContext();
return await ctx.Deliveries
.Where(d => d.LsNr == lsnr)
.Include(d => d.Parts)
.ThenInclude(p => p.PartModifiers)
.ThenInclude(m => m.Modifier)
.AsSplitQuery()
.FirstOrDefaultAsync();
}
private static DeliveryPart[] GetParts(Delivery d) {
return [.. d.Parts.OrderBy(p => p.DPNr)];
}
[Test]
public async Task TestCreate_01_Minimal() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
vm.Date = "01.10.2022";
vm.Branch = vm.BranchSource.First();
vm.MgNr = 101;
vm.Member = vm.MemberSource.First(m => m.MgNr == vm.MgNr);
vm.SortId = "GV";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.GradationKmw = 15.0;
vm.GradationOe = 73;
vm.Weight = 1234;
vm.IsManualWeighing = true;
vm.IsNetWeight = false;
vm.WineQualityLevel = vm.WineQualityLevelSource.First(l => l.QualId == "QUW");
vm.WineOrigin = vm.WineOriginSource.First(o => o.HkId == "WLNO");
var p1 = await vm.UpdateDeliveryPart(2022, null, null, false, false, true);
Assert.That(p1.Delivery.LsNr, Is.EqualTo("20221001X001"));
var d = await GetDelivery("20221001X001");
Assert.That(d, Is.Not.Null);
Assert.That(d.Parts, Has.Count.EqualTo(1));
var p = d.Parts.First();
Assert.That(p, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(d.LsNr, Is.EqualTo("20221001X001"));
Assert.That(d.Date, Is.EqualTo(new DateOnly(2022, 10, 1)));
Assert.That(d.ZwstId, Is.EqualTo("X"));
Assert.That(d.MgNr, Is.EqualTo(101));
Assert.That(p.SortId, Is.EqualTo("GV"));
Assert.That(p.AttrId, Is.Null);
Assert.That(p.CultId, Is.Null);
Assert.That(p.Kmw, Is.EqualTo(15.0));
Assert.That(p.Oe, Is.EqualTo(73));
Assert.That(p.QualId, Is.EqualTo("QUW"));
Assert.That(p.Weight, Is.EqualTo(1234));
Assert.That(p.IsNetWeight, Is.False);
Assert.That(p.IsManualWeighing, Is.True);
Assert.That(p.HkId, Is.EqualTo("WLNO"));
});
vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
Assert.DoesNotThrow(() => {
vm.FillInputs(d);
vm.FillInputs(p);
});
Assert.Multiple(() => {
Assert.That(vm.LsNr, Is.EqualTo("20221001X001"));
Assert.That(vm.Date, Is.EqualTo("01.10.2022"));
Assert.That(vm.Branch?.ZwstId, Is.EqualTo("X"));
Assert.That(vm.MgNr, Is.EqualTo(101));
Assert.That(vm.SortId, Is.EqualTo("GV"));
Assert.That(vm.WineAttr?.AttrId, Is.Null);
Assert.That(vm.WineCult?.CultId, Is.Null);
Assert.That(vm.GradationKmw, Is.EqualTo(15.0));
Assert.That(vm.WineQualityLevel?.QualId, Is.EqualTo("QUW"));
Assert.That(vm.Weight, Is.EqualTo(1234));
Assert.That(vm.IsNetWeight, Is.False);
Assert.That(vm.IsManualWeighing, Is.True);
Assert.That(vm.WineOrigin?.HkId, Is.EqualTo("WLNO"));
});
}
[Test]
public async Task TestCreate_02_Advanced() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
vm.Date = "02.10.2022";
vm.Branch = vm.BranchSource.First();
vm.MgNr = 102;
vm.Member = vm.MemberSource.First(m => m.MgNr == vm.MgNr);
vm.SortId = "ZW";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.WineAttr = vm.WineAttrSource.Skip(1).First() as WineAttr;
vm.WineCult = vm.WineCultSource.Skip(1).First() as WineCult;
vm.GradationKmw = 15.9;
vm.GradationOe = 78;
vm.Weight = 3456;
vm.IsManualWeighing = false;
vm.IsNetWeight = true;
vm.WeighingData = "{}";
vm.WineQualityLevel = vm.WineQualityLevelSource.First(l => l.QualId == "QUW");
vm.WineOrigin = vm.WineOriginSource.First(o => o.HkId == "WLNO");
vm.Modifiers.Add(vm.ModifiersSource.First());
var p1 = await vm.UpdateDeliveryPart(2022, null, null, false, false, true);
Assert.That(p1.Delivery.LsNr, Is.EqualTo("20221002X001"));
var d = await GetDelivery("20221002X001");
Assert.That(d, Is.Not.Null);
Assert.That(d.Parts, Has.Count.EqualTo(1));
var p = d.Parts.First();
Assert.That(p, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(d.LsNr, Is.EqualTo("20221002X001"));
Assert.That(d.Date, Is.EqualTo(new DateOnly(2022, 10, 2)));
Assert.That(d.ZwstId, Is.EqualTo("X"));
Assert.That(d.MgNr, Is.EqualTo(102));
Assert.That(p.SortId, Is.EqualTo("ZW"));
Assert.That(p.AttrId, Is.Not.Null);
Assert.That(p.CultId, Is.Not.Null);
Assert.That(p.Kmw, Is.EqualTo(15.9));
Assert.That(p.Oe, Is.EqualTo(78));
Assert.That(p.QualId, Is.EqualTo("QUW"));
Assert.That(p.Weight, Is.EqualTo(3456));
Assert.That(p.IsNetWeight, Is.True);
Assert.That(p.IsManualWeighing, Is.False);
Assert.That(p.WeighingData, Is.EqualTo("{}"));
Assert.That(p.HkId, Is.EqualTo("WLNO"));
Assert.That(p.Modifiers.Count(), Is.EqualTo(1));
});
vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
Assert.DoesNotThrow(() => {
vm.FillInputs(d);
vm.FillInputs(p);
});
Assert.Multiple(() => {
Assert.That(vm.LsNr, Is.EqualTo("20221002X001"));
Assert.That(vm.Date, Is.EqualTo("02.10.2022"));
Assert.That(vm.Branch?.ZwstId, Is.EqualTo("X"));
Assert.That(vm.MgNr, Is.EqualTo(102));
Assert.That(vm.SortId, Is.EqualTo("ZW"));
Assert.That(vm.WineAttr?.AttrId, Is.Not.Null);
Assert.That(vm.WineCult?.CultId, Is.Not.Null);
Assert.That(vm.GradationKmw, Is.EqualTo(15.9));
Assert.That(vm.WineQualityLevel?.QualId, Is.EqualTo("QUW"));
Assert.That(vm.Weight, Is.EqualTo(3456));
Assert.That(vm.IsNetWeight, Is.True);
Assert.That(vm.IsManualWeighing, Is.False);
Assert.That(vm.WeighingData, Is.EqualTo("{}"));
Assert.That(vm.WineOrigin?.HkId, Is.EqualTo("WLNO"));
Assert.That(vm.Modifiers, Has.Count.EqualTo(1));
});
}
[Test]
public async Task TestCreate_03_TwoParts() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
vm.Date = "03.10.2022";
vm.Branch = vm.BranchSource.First();
vm.MgNr = 101;
vm.Member = vm.MemberSource.First(m => m.MgNr == vm.MgNr);
vm.SortId = "GV";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.GradationKmw = 15.0;
vm.GradationOe = 73;
vm.WineQualityLevel = vm.WineQualityLevelSource.First(l => l.QualId == "QUW");
vm.Weight = 1234;
vm.IsManualWeighing = true;
vm.IsNetWeight = true;
vm.WineOrigin = vm.WineOriginSource.First(o => o.HkId == "WLNO");
var p1 = await vm.UpdateDeliveryPart(2022, null, null, false, false, true);
Assert.That(p1.Delivery.LsNr, Is.EqualTo("20221003X001"));
vm.FillInputs(p1.Delivery);
vm.SortId = "WR";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.GradationKmw = 18.0;
vm.GradationOe = 89;
vm.WineQualityLevel = vm.WineQualityLevelSource.First(l => l.QualId == "KAB");
vm.Weight = 2345;
var p2 = await vm.UpdateDeliveryPart(p1.Year, p1.DId, null, false, false, true);
Assert.That(p2.Delivery.LsNr, Is.EqualTo("20221003X001"));
var d = await GetDelivery("20221003X001");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(d.Parts, Has.Count.EqualTo(2));
Assert.That(d.MgNr, Is.EqualTo(101));
});
var p = d.Parts.First();
Assert.That(p, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(p.DPNr, Is.EqualTo(1));
Assert.That(p.SortId, Is.EqualTo("GV"));
});
p = d.Parts.Skip(1).First();
Assert.That(p, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(p.DPNr, Is.EqualTo(2));
Assert.That(p.SortId, Is.EqualTo("WR"));
});
}
[Test]
public async Task TestCreate_04_ChangeMember() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
vm.Date = "04.10.2022";
vm.Branch = vm.BranchSource.First();
vm.MgNr = 101;
vm.Member = vm.MemberSource.First(m => m.MgNr == vm.MgNr);
vm.SortId = "GV";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.GradationKmw = 15.0;
vm.GradationOe = 73;
vm.WineQualityLevel = vm.WineQualityLevelSource.First(l => l.QualId == "QUW");
vm.Weight = 1234;
vm.IsManualWeighing = true;
vm.IsNetWeight = true;
vm.WineOrigin = vm.WineOriginSource.First(o => o.HkId == "WLNO");
var p1 = await vm.UpdateDeliveryPart(2022, null, null, false, false, true);
Assert.That(p1.Delivery.LsNr, Is.EqualTo("20221004X001"));
vm.FillInputs(p1.Delivery);
vm.MgNr = 102;
vm.Member = vm.MemberSource.First(m => m.MgNr == vm.MgNr);
vm.SortId = "GV";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.GradationKmw = 15.0;
vm.GradationOe = 73;
vm.WineQualityLevel = vm.WineQualityLevelSource.First(l => l.QualId == "QUW");
vm.Weight = 1234;
vm.IsManualWeighing = true;
vm.IsNetWeight = true;
vm.WineOrigin = vm.WineOriginSource.First(o => o.HkId == "WLNO");
var p2 = await vm.UpdateDeliveryPart(p1.Year, p1.DId, null, false, false, true);
Assert.That(p2.Delivery.LsNr, Is.EqualTo("20221004X001"));
var d = await GetDelivery("20221004X001");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(d.Parts, Has.Count.EqualTo(2));
Assert.That(d.MgNr, Is.EqualTo(102));
});
}
[Test]
public async Task TestCreate_05_LNrChanged() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
vm.Date = "29.09.2022";
vm.Branch = vm.BranchSource.First();
vm.LsNr = "20220929X001"; // "old" LNr, simulated change in DB
vm.MgNr = 101;
vm.Member = vm.MemberSource.First(m => m.MgNr == vm.MgNr);
vm.SortId = "GV";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.GradationKmw = 15.0;
vm.GradationOe = 73;
vm.WineQualityLevel = vm.WineQualityLevelSource.First(l => l.QualId == "QUW");
vm.Weight = 1234;
vm.IsManualWeighing = true;
vm.IsNetWeight = true;
vm.WineOrigin = vm.WineOriginSource.First(o => o.HkId == "WLNO");
var p1 = await vm.UpdateDeliveryPart(2022, null, null, false, false, true);
Assert.That(p1.Delivery.LsNr, Is.EqualTo("20220929X002"));
var d = GetDelivery("20220929X002");
Assert.That(d, Is.Not.Null);
}
[Test]
public async Task TestUpdate_01_Simple() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
var d = await GetDelivery("20231010X001");
Assert.That(d, Is.Not.Null);
var p = d.Parts.First();
Assert.That(p, Is.Not.Null);
Assert.DoesNotThrow(() => {
vm.FillInputs(d);
vm.FillInputs(p);
});
vm.SortId = "WR";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.GradationKmw = 15.9;
vm.GradationOe = 79;
Assert.DoesNotThrowAsync(async () => await DeliveryService.UpdateDeliveryPart(vm, p.Year, p.DId, p.DPNr, false, false, true));
d = await GetDelivery("20231010X001");
Assert.That(d, Is.Not.Null);
p = d.Parts.First();
Assert.That(p, Is.Not.Null);
vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
Assert.DoesNotThrow(() => {
vm.FillInputs(d);
vm.FillInputs(p);
});
Assert.Multiple(() => {
Assert.That(vm.SortId, Is.EqualTo("WR"));
Assert.That(vm.GradationKmw, Is.EqualTo(15.9));
});
}
[Test]
public async Task TestUpdate_02_Member() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
var d = await GetDelivery("20231010X002");
Assert.That(d, Is.Not.Null);
var p = d.Parts.First();
Assert.That(p, Is.Not.Null);
Assert.DoesNotThrow(() => {
vm.FillInputs(d);
vm.FillInputs(p);
});
Assert.That(d.MgNr, Is.EqualTo(101));
vm.SortId = "GV";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.MgNr = 102;
vm.Member = vm.MemberSource.First(m => m.MgNr == vm.MgNr);
Assert.DoesNotThrowAsync(async () => await DeliveryService.UpdateDeliveryPart(vm, p.Year, p.DId, p.DPNr, false, false, true));
d = await GetDelivery("20231010X002");
Assert.That(d, Is.Not.Null);
Assert.That(d.MgNr, Is.EqualTo(102));
}
[Test]
public async Task TestUpdate_03_DateAndTime() {
var vm = new DeliveryAdminViewModel();
await InitViewModel(vm);
var d = await GetDelivery("20231015X001");
Assert.That(d, Is.Null);
d = await GetDelivery("20231010X003");
Assert.That(d, Is.Not.Null);
var p = d.Parts.First();
Assert.That(p, Is.Not.Null);
Assert.DoesNotThrow(() => {
vm.FillInputs(d);
vm.FillInputs(p);
});
Assert.That(d.MgNr, Is.EqualTo(101));
vm.SortId = "GV";
vm.WineVar = vm.WineVarSource.First(v => v.SortId == vm.SortId);
vm.Date = "15.10.2023";
vm.Time = "12:00";
Assert.DoesNotThrowAsync(async () => await DeliveryService.UpdateDeliveryPart(vm, p.Year, p.DId, p.DPNr, true, true, false));
d = await GetDelivery("20231010X003");
Assert.That(d, Is.Null);
d = await GetDelivery("20231015X001");
Assert.That(d, Is.Not.Null);
}
[Test]
public async Task TestSplit_01_Depreciate_One() {
var d = await GetDelivery("20231001X001");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(3));
Assert.That(ps[0].Weight, Is.EqualTo(1000));
Assert.That(ps[0].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[1].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[2].QualId, Is.Not.EqualTo("WEI"));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.DepreciateDelivery(d.Year, d.DId, [1000, 0, 0]));
d = await GetDelivery("20231001X001");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(3));
Assert.That(ps[0].Weight, Is.EqualTo(1000));
Assert.That(ps[0].QualId, Is.EqualTo("WEI"));
Assert.That(ps[1].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[2].QualId, Is.Not.EqualTo("WEI"));
});
}
[Test]
public async Task TestSplit_02_Depreciate_Partial() {
var d = await GetDelivery("20231001X002");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(3));
Assert.That(ps[0].Weight, Is.EqualTo(1000));
Assert.That(ps[0].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[1].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[2].QualId, Is.Not.EqualTo("WEI"));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.DepreciateDelivery(d.Year, d.DId, [600, 0, 0]));
d = await GetDelivery("20231001X002");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(4));
Assert.That(ps[0].Weight, Is.EqualTo(400));
Assert.That(ps[0].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[1].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[2].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[3].Weight, Is.EqualTo(600));
Assert.That(ps[3].QualId, Is.EqualTo("WEI"));
});
}
[Test]
public async Task TestSplit_03_Depreciate_Mixed() {
var d = await GetDelivery("20231001X003");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(3));
Assert.That(ps[0].Weight, Is.EqualTo(1000));
Assert.That(ps[0].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[1].Weight, Is.EqualTo(1000));
Assert.That(ps[1].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[2].QualId, Is.Not.EqualTo("WEI"));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.DepreciateDelivery(d.Year, d.DId, [1000, 700, -5]));
d = await GetDelivery("20231001X003");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(4));
Assert.That(ps[0].Weight, Is.EqualTo(1000));
Assert.That(ps[0].QualId, Is.EqualTo("WEI"));
Assert.That(ps[1].Weight, Is.EqualTo(300));
Assert.That(ps[1].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[2].Weight, Is.EqualTo(1000));
Assert.That(ps[2].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[3].Weight, Is.EqualTo(700));
Assert.That(ps[3].QualId, Is.EqualTo("WEI"));
});
}
[Test]
public async Task TestSplit_04_Depreciate_Complete() {
var d = await GetDelivery("20231001X004");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(3));
Assert.That(ps[0].Weight, Is.EqualTo(1000));
Assert.That(ps[0].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[1].Weight, Is.EqualTo(1000));
Assert.That(ps[1].QualId, Is.Not.EqualTo("WEI"));
Assert.That(ps[2].Weight, Is.EqualTo(1000));
Assert.That(ps[2].QualId, Is.Not.EqualTo("WEI"));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.DepreciateDelivery(d.Year, d.DId, [1000, 1100, int.MaxValue]));
d = await GetDelivery("20231001X004");
Assert.That(d, Is.Not.Null);
Assert.Multiple(() => {
var ps = GetParts(d);
Assert.That(ps, Has.Length.EqualTo(3));
Assert.That(ps[0].Weight, Is.EqualTo(1000));
Assert.That(ps[0].QualId, Is.EqualTo("WEI"));
Assert.That(ps[1].Weight, Is.EqualTo(1000));
Assert.That(ps[1].QualId, Is.EqualTo("WEI"));
Assert.That(ps[2].Weight, Is.EqualTo(1000));
Assert.That(ps[2].QualId, Is.EqualTo("WEI"));
});
}
[Test]
public async Task TestSplit_05_OtherMember_One() {
var d1 = await GetDelivery("20231002X001");
var d2 = await GetDelivery("20231002X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
Assert.That(d1.MgNr, Is.EqualTo(101));
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToMember(d1.Year, d1.DId, [1000, 0, 0], 102));
d1 = await GetDelivery("20231002X001");
d2 = await GetDelivery("20231002X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(d1.MgNr, Is.EqualTo(101));
Assert.That(d2.MgNr, Is.EqualTo(102));
Assert.That(ps1, Has.Length.EqualTo(2));
Assert.That(ps1[0].DPNr, Is.EqualTo(2));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].DPNr, Is.EqualTo(3));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(1));
Assert.That(ps2[0].DPNr, Is.EqualTo(1));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
});
}
[Test]
public async Task TestSplit_06_OtherMember_Partial() {
var d1 = await GetDelivery("20231003X001");
var d2 = await GetDelivery("20231003X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
Assert.That(d1.MgNr, Is.EqualTo(101));
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToMember(d1.Year, d1.DId, [400, -1, -2], 102));
d1 = await GetDelivery("20231003X001");
d2 = await GetDelivery("20231003X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(d1.MgNr, Is.EqualTo(101));
Assert.That(d2.MgNr, Is.EqualTo(102));
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(600));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(1));
Assert.That(ps2[0].Weight, Is.EqualTo(400));
});
}
[Test]
public async Task TestSplit_07_OtherMember_Mixed() {
var d1 = await GetDelivery("20231004X001");
var d2 = await GetDelivery("20231004X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
Assert.That(d1.MgNr, Is.EqualTo(101));
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToMember(d1.Year, d1.DId, [200, 1000, int.MinValue], 102));
d1 = await GetDelivery("20231004X001");
d2 = await GetDelivery("20231004X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(d1.MgNr, Is.EqualTo(101));
Assert.That(d2.MgNr, Is.EqualTo(102));
Assert.That(ps1, Has.Length.EqualTo(2));
Assert.That(ps1[0].DPNr, Is.EqualTo(1));
Assert.That(ps1[0].Weight, Is.EqualTo(800));
Assert.That(ps1[1].DPNr, Is.EqualTo(3));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(2));
Assert.That(ps2[0].DPNr, Is.EqualTo(1));
Assert.That(ps2[0].Weight, Is.EqualTo(200));
Assert.That(ps2[1].DPNr, Is.EqualTo(2));
Assert.That(ps2[1].Weight, Is.EqualTo(1000));
});
}
[Test]
public async Task TestSplit_08_OtherMember_Complete() {
var d1 = await GetDelivery("20231005X001");
var d2 = await GetDelivery("20231005X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
Assert.That(d1.MgNr, Is.EqualTo(101));
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToMember(d1.Year, d1.DId, [1000, int.MaxValue, 1100], 102));
d1 = await GetDelivery("20231005X001");
d2 = await GetDelivery("20231005X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps2 = GetParts(d2);
Assert.That(d2.MgNr, Is.EqualTo(102));
Assert.That(ps2, Has.Length.EqualTo(3));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
Assert.That(ps2[1].Weight, Is.EqualTo(1000));
Assert.That(ps2[2].Weight, Is.EqualTo(1000));
});
}
[Test]
public async Task TestSplit_09_OtherDelivery_One() {
var d1 = await GetDelivery("20231006X001");
var d2 = await GetDelivery("20231006X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(1));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToLsNr(d1.Year, d1.DId, [0, 1000, -4], d2.LsNr));
d1 = await GetDelivery("20231006X001");
d2 = await GetDelivery("20231006X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(ps1, Has.Length.EqualTo(2));
Assert.That(ps1[0].DPNr, Is.EqualTo(1));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].DPNr, Is.EqualTo(3));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(2));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
Assert.That(ps2[1].Weight, Is.EqualTo(1000));
});
}
[Test]
public async Task TestSplit_10_OtherDelivery_Partial() {
var d1 = await GetDelivery("20231007X001");
var d2 = await GetDelivery("20231007X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(1));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToLsNr(d1.Year, d1.DId, [0, 300, int.MinValue], d2.LsNr));
d1 = await GetDelivery("20231007X001");
d2 = await GetDelivery("20231007X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(700));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(2));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
Assert.That(ps2[1].Weight, Is.EqualTo(300));
});
}
[Test]
public async Task TestSplit_11_OtherDelivery_Mixed() {
var d1 = await GetDelivery("20231008X001");
var d2 = await GetDelivery("20231008X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(1));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToLsNr(d1.Year, d1.DId, [850, 1000, -4], d2.LsNr));
d1 = await GetDelivery("20231008X001");
d2 = await GetDelivery("20231008X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(ps1, Has.Length.EqualTo(2));
Assert.That(ps1[0].DPNr, Is.EqualTo(1));
Assert.That(ps1[0].Weight, Is.EqualTo(150));
Assert.That(ps1[1].DPNr, Is.EqualTo(3));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(3));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
Assert.That(ps2[1].Weight, Is.EqualTo(850));
Assert.That(ps2[2].Weight, Is.EqualTo(1000));
});
}
[Test]
public async Task TestSplit_12_OtherDelivery_Complete() {
var d1 = await GetDelivery("20231009X001");
var d2 = await GetDelivery("20231009X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Not.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps1 = GetParts(d1);
var ps2 = GetParts(d2);
Assert.That(ps1, Has.Length.EqualTo(3));
Assert.That(ps1[0].Weight, Is.EqualTo(1000));
Assert.That(ps1[1].Weight, Is.EqualTo(1000));
Assert.That(ps1[2].Weight, Is.EqualTo(1000));
Assert.That(ps2, Has.Length.EqualTo(1));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
});
Assert.DoesNotThrowAsync(async () => await DeliveryService.SplitDeliveryToLsNr(d1.Year, d1.DId, [1200, int.MaxValue, 1000], d2.LsNr));
d1 = await GetDelivery("20231009X001");
d2 = await GetDelivery("20231009X002");
Assert.Multiple(() => {
Assert.That(d1, Is.Null);
Assert.That(d2, Is.Not.Null);
});
Assert.Multiple(() => {
var ps2 = GetParts(d2);
Assert.That(ps2, Has.Length.EqualTo(4));
Assert.That(ps2[0].Weight, Is.EqualTo(1000));
Assert.That(ps2[1].Weight, Is.EqualTo(1000));
Assert.That(ps2[2].Weight, Is.EqualTo(1000));
Assert.That(ps2[3].Weight, Is.EqualTo(1000));
});
}
[Test]
public async Task TestDelete_01_Normal() {
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Deliveries.FindAsync(2022, 2), Is.Not.Null);
}
Assert.DoesNotThrowAsync(async () => await DeliveryService.DeleteDelivery("20220930X001"));
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Deliveries.FindAsync(2022, 2), Is.Null);
}
}
}
}

View File

@ -0,0 +1,361 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using Elwig.Services;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
namespace Tests.ServiceTests {
[TestFixture]
public class MemberServiceTest {
private static async Task InitViewModel(MemberAdminViewModel vm) {
using var ctx = new AppDbContext();
vm.BranchSource = await ctx.Branches.ToListAsync();
vm.DefaultKgSource = await ctx.Katastralgemeinden.ToListAsync();
vm.OrtSource = await ctx.PlzDestinations.Include(p => p.Ort).ToListAsync();
vm.BillingOrtSource = await ctx.PlzDestinations.Include(p => p.Ort).ToListAsync();
}
[Test]
public async Task TestCreate_01_Minimal() {
var vm = new MemberAdminViewModel();
await InitViewModel(vm);
await vm.InitInputs();
vm.Name = "Neuling";
vm.GivenName = "Nadine";
vm.Address = "Neubaugasse 1";
vm.Plz = 2120;
vm.Ort = vm.OrtSource.First(d => d.Ort.Name == "Wolkersdorf im Weinviertel");
vm.BusinessShares = 1;
vm.DefaultKg = vm.DefaultKgSource.First(k => k.Name == "Wolkersdorf");
Assert.That(vm.MgNr, Is.EqualTo(205));
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Members.FindAsync(205), Is.Null);
}
Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(null));
Member? m;
using (var ctx = new AppDbContext()) {
m = await ctx.Members
.Where(m => m.MgNr == vm.MgNr)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.AsSplitQuery()
.FirstOrDefaultAsync();
}
Assert.That(m, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(m.MgNr, Is.EqualTo(205));
Assert.That(m.Name, Is.EqualTo("Neuling"));
Assert.That(m.GivenName, Is.EqualTo("Nadine"));
Assert.That(m.Address, Is.EqualTo("Neubaugasse 1"));
Assert.That(m.PostalDest.AtPlz?.Plz, Is.EqualTo(2120));
Assert.That(m.PostalDest.AtPlz?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
Assert.That(m.BusinessShares, Is.EqualTo(1));
Assert.That(m.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
});
vm = new MemberAdminViewModel();
await InitViewModel(vm);
Assert.DoesNotThrow(() => vm.FillInputs(m));
Assert.Multiple(() => {
Assert.That(vm.MgNr, Is.EqualTo(205));
Assert.That(vm.Name, Is.EqualTo("Neuling"));
Assert.That(vm.GivenName, Is.EqualTo("Nadine"));
Assert.That(vm.Address, Is.EqualTo("Neubaugasse 1"));
Assert.That(vm.Plz, Is.EqualTo(2120));
Assert.That(vm.Ort?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
Assert.That(vm.BusinessShares, Is.EqualTo(1));
Assert.That(vm.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
});
}
[Test]
public async Task TestCreate_02_Full() {
var vm = new MemberAdminViewModel();
await InitViewModel(vm);
await vm.InitInputs();
vm.MgNr = 999;
vm.IsJuridicalPerson = true;
vm.Name = "Neue GmbH";
vm.ForTheAttentionOf = "Norbert Neuling";
vm.Address = "Neuegasse 2";
vm.Plz = 2120;
vm.Ort = vm.OrtSource.First(d => d.Ort.Name == "Wolkersdorf im Weinviertel");
vm.EmailAddresses[0] = "neue.gmbh@mail.com";
vm.EmailAddresses[1] = "norbert.neuling@mail.com";
vm.PhoneNrs[0] = new(0, "+43 2245 9876", "Büro");
vm.PhoneNrs[1] = new(1, "+43 664 123456789", "Hr. Neuling");
vm.PhoneNrs[2] = new(2, "+43 2245 9876-2", null);
vm.Iban = "AT97 1234 5678 9012 3460";
vm.Bic = "RLNWATWWWDF";
vm.UstIdNr = "ATU12345693";
vm.LfbisNr = "0123498";
vm.IsBuchführend = true;
vm.IsOrganic = true;
vm.BillingName = "Neue Holding AG";
vm.BillingAddress = "Neuegasse 3";
vm.BillingPlz = 2120;
vm.BillingOrt = vm.BillingOrtSource.First(d => d.Ort.Name == "Wolkersdorf im Weinviertel");
vm.BusinessShares = 10;
vm.AccountingNr = "330999";
vm.DefaultKg = vm.DefaultKgSource.First(k => k.Name == "Wolkersdorf");
vm.Comment = "Ich bin eine Anmerkung";
vm.ContactViaPost = true;
vm.ContactViaEmail = true;
vm.IsVollLieferant = true;
vm.IsFunktionär = true;
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Members.FindAsync(999), Is.Null);
}
Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(null));
Member? m;
using (var ctx = new AppDbContext()) {
m = await ctx.Members
.Where(m => m.MgNr == vm.MgNr)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.AsSplitQuery()
.FirstOrDefaultAsync();
}
Assert.That(m, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(m.MgNr, Is.EqualTo(999));
Assert.That(m.IsJuridicalPerson, Is.True);
Assert.That(m.Name, Is.EqualTo("Neue GmbH"));
Assert.That(m.ForTheAttentionOf, Is.EqualTo("Norbert Neuling"));
Assert.That(m.Address, Is.EqualTo("Neuegasse 2"));
Assert.That(m.PostalDest.AtPlz?.Plz, Is.EqualTo(2120));
Assert.That(m.PostalDest.AtPlz?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
Assert.That(m.EmailAddresses.Select(a => (a.Nr, a.Address)), Is.EquivalentTo(new (int, string)[] {
(1, "neue.gmbh@mail.com"),
(2, "norbert.neuling@mail.com"),
}));
Assert.That(m.TelephoneNumbers.Select(n => (n.Nr, n.Type, n.Number, n.Comment)), Is.EquivalentTo(new (int, string, string, string?)[] {
(1, "landline", "+43 2245 9876", "Büro"),
(2, "mobile", "+43 664 123456789", "Hr. Neuling"),
(3, "fax", "+43 2245 9876-2", null),
}));
Assert.That(m.Iban, Is.EqualTo("AT971234567890123460"));
Assert.That(m.Bic, Is.EqualTo("RLNWATWWWDF"));
Assert.That(m.UstIdNr, Is.EqualTo("ATU12345693"));
Assert.That(m.LfbisNr, Is.EqualTo("0123498"));
Assert.That(m.IsBuchführend, Is.True);
Assert.That(m.IsOrganic, Is.True);
Assert.That(m.BillingAddress, Is.Not.Null);
Assert.That(m.BillingAddress?.FullName, Is.EqualTo("Neue Holding AG"));
Assert.That(m.BillingAddress?.Address, Is.EqualTo("Neuegasse 3"));
Assert.That(m.BillingAddress?.PostalDest.AtPlz?.Plz, Is.EqualTo(2120));
Assert.That(m.BillingAddress?.PostalDest.AtPlz?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
Assert.That(m.BusinessShares, Is.EqualTo(10));
Assert.That(m.AccountingNr, Is.EqualTo("330999"));
Assert.That(m.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
Assert.That(m.Comment, Is.EqualTo("Ich bin eine Anmerkung"));
Assert.That(m.ContactViaPost, Is.True);
Assert.That(m.ContactViaEmail, Is.True);
Assert.That(m.IsVollLieferant, Is.True);
Assert.That(m.IsFunktionär, Is.True);
});
vm = new MemberAdminViewModel();
await InitViewModel(vm);
Assert.DoesNotThrow(() => vm.FillInputs(m));
Assert.Multiple(() => {
Assert.That(vm.MgNr, Is.EqualTo(999));
Assert.That(vm.IsJuridicalPerson, Is.True);
Assert.That(vm.Name, Is.EqualTo("Neue GmbH"));
Assert.That(vm.ForTheAttentionOf, Is.EqualTo("Norbert Neuling"));
Assert.That(vm.Address, Is.EqualTo("Neuegasse 2"));
Assert.That(vm.Plz, Is.EqualTo(2120));
Assert.That(vm.Ort?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
Assert.That(vm.EmailAddresses, Is.EquivalentTo(new string?[] {
"neue.gmbh@mail.com",
"norbert.neuling@mail.com",
null, null, null, null, null, null, null
}));
Assert.That(vm.PhoneNrs, Is.EquivalentTo(new MemberAdminViewModel.PhoneNr?[] {
new(0, "+43 2245 9876", "Büro"),
new(1, "+43 664 123456789", "Hr. Neuling"),
new(2, "+43 2245 9876-2", null),
new(), new(), new(), new(), new(), new()
}));
Assert.That(vm.Iban, Is.EqualTo("AT971234567890123460"));
Assert.That(vm.Bic, Is.EqualTo("RLNWATWWWDF"));
Assert.That(vm.UstIdNr, Is.EqualTo("ATU12345693"));
Assert.That(vm.LfbisNr, Is.EqualTo("0123498"));
Assert.That(vm.IsBuchführend, Is.True);
Assert.That(vm.IsOrganic, Is.True);
Assert.That(vm.BillingName, Is.EqualTo("Neue Holding AG"));
Assert.That(vm.BillingAddress, Is.EqualTo("Neuegasse 3"));
Assert.That(vm.BillingPlz, Is.EqualTo(2120));
Assert.That(vm.BillingOrt?.Ort.Name, Is.EqualTo("Wolkersdorf im Weinviertel"));
Assert.That(vm.BusinessShares, Is.EqualTo(10));
Assert.That(vm.AccountingNr, Is.EqualTo("330999"));
Assert.That(vm.DefaultKg?.Name, Is.EqualTo("Wolkersdorf"));
Assert.That(vm.Comment, Is.EqualTo("Ich bin eine Anmerkung"));
Assert.That(vm.ContactViaPost, Is.True);
Assert.That(vm.ContactViaEmail, Is.True);
Assert.That(vm.IsVollLieferant, Is.True);
Assert.That(vm.IsFunktionär, Is.True);
});
}
[Test]
public async Task TestUpdate_01_Inactive() {
var vm = new MemberAdminViewModel();
await InitViewModel(vm);
using (var ctx = new AppDbContext()) {
vm.FillInputs(await ctx.Members
.Where(m => m.MgNr == 202)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.AsSplitQuery()
.FirstAsync());
}
Assert.That(vm.IsActive, Is.True);
var exitDate = DateTime.Now;
vm.IsActive = false;
vm.ExitDate = $"{exitDate:dd.MM.yyyy}";
Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(202));
Member? m;
using (var ctx = new AppDbContext()) {
m = await ctx.Members
.Where(m => m.MgNr == 202)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.AsSplitQuery()
.FirstOrDefaultAsync();
}
Assert.That(m, Is.Not.Null);
Assert.Multiple(() => {
Assert.That(m.IsActive, Is.False);
Assert.That(m.ExitDateString, Is.EqualTo($"{exitDate:yyyy-MM-dd}"));
});
vm = new MemberAdminViewModel();
await InitViewModel(vm);
Assert.DoesNotThrow(() => vm.FillInputs(m));
Assert.Multiple(() => {
Assert.That(vm.IsActive, Is.False);
Assert.That(vm.ExitDate, Is.EqualTo($"{exitDate:dd.MM.yyyy}"));
});
}
[Test]
public async Task TestUpdate_02_MgNr() {
var vm = new MemberAdminViewModel();
await InitViewModel(vm);
using (var ctx = new AppDbContext()) {
vm.FillInputs(await ctx.Members
.Where(m => m.MgNr == 203)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.AsSplitQuery()
.FirstAsync());
}
Assert.Multiple(() => {
Assert.That(vm.MgNr, Is.EqualTo(203));
Assert.That(vm.EmailAddresses[0], Is.Not.Null);
Assert.That(vm.PhoneNrs[0], Is.Not.Null);
Assert.That(vm.BillingName, Is.Not.Null);
});
vm.MgNr = 210;
Assert.DoesNotThrowAsync(async () => await vm.UpdateMember(203));
Member? m;
using (var ctx = new AppDbContext()) {
m = await ctx.Members
.Where(m => m.MgNr == 210)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.AsSplitQuery()
.FirstOrDefaultAsync();
}
Assert.That(m, Is.Not.Null);
vm = new MemberAdminViewModel();
await InitViewModel(vm);
Assert.DoesNotThrow(() => vm.FillInputs(m));
Assert.Multiple(() => {
Assert.That(vm.MgNr, Is.EqualTo(210));
Assert.That(vm.EmailAddresses[0], Is.Not.Null);
Assert.That(vm.PhoneNrs[0], Is.Not.Null);
Assert.That(vm.BillingName, Is.Not.Null);
});
}
[Test]
public async Task TestDelete_01_NoReferences() {
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Members.FindAsync(201), Is.Not.Null);
}
Assert.DoesNotThrowAsync(async () => await MemberService.DeleteMember(201, false, false, false));
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Members.FindAsync(201), Is.Null);
}
}
[Test]
public async Task TestDelete_02_AllReferences() {
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Members.FindAsync(204), Is.Not.Null);
}
for (int i = 0; i < 7; i++) {
Assert.ThrowsAsync<DbUpdateException>(async () => await MemberService.DeleteMember(204, (i & 1) != 0, (i & 2) != 0, (i & 4) != 0));
using var ctx = new AppDbContext();
Assert.That(await ctx.Members.FindAsync(204), Is.Not.Null);
}
Assert.DoesNotThrowAsync(async () => await MemberService.DeleteMember(204, true, true, true));
using (var ctx = new AppDbContext()) {
Assert.That(await ctx.Members.FindAsync(204), Is.Null);
}
}
}
}

View File

@ -0,0 +1,25 @@
using Elwig.Helpers;
using System.Reflection;
using Microsoft.Data.Sqlite;
namespace Tests.ServiceTests {
[SetUpFixture]
public class Setup {
private SqliteConnection? Connection;
[OneTimeSetUp]
public async Task SetupDatabase() {
Connection = await AppDbContext.ConnectAsync();
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceInsert.sql");
}
[OneTimeTearDown]
public async Task TeardownDatabase() {
if (Connection == null) return;
await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.ServiceDelete.sql");
await Connection.DisposeAsync();
Connection = null;
}
}
}

View File

@ -19,16 +19,16 @@
</Target>
<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="NReco.PdfRenderer" Version="1.5.4" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
<PackageReference Include="NReco.PdfRenderer" Version="1.6.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

Some files were not shown because too many files have changed in this diff Show More