Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 149f455256 | |||
| f4fddd111f | |||
| af73226c90 | |||
| 7e6a6138e2 | |||
| 8054a024f4 | |||
| d8c967b2f2 | |||
| 1108427023 | |||
| b58dee6d3f | |||
| 0e2b004b0d | |||
| 19c3322ef2 | |||
| 6f081811c4 | |||
| 432c511b85 | |||
| 7e22759c33 | |||
| a47904cf0b | |||
| 6818491ae3 | |||
| 23db4de1ee | |||
| 9e5f709d42 | |||
| 4cd7ef85a1 | |||
| 2c0b000073 | |||
| f1737af2a2 | |||
| 8f423e3e92 | |||
| c8911c0acb | |||
| baf598b76c | |||
| 7adc9f89e4 | |||
| dde808df6a | |||
| e63de0d867 | |||
| e9e90b1e98 | |||
| 640dbf705e | |||
| 42121fe7da | |||
| d45c3f867f | |||
| a90be2644d | |||
| 01739ba42e | |||
| 7bea4d9ee0 | |||
| b6497fd422 | |||
| f141485537 | |||
| b31603554a | |||
| 90def81cc5 | |||
| 87903227b5 | |||
| 6d6776f0f9 | |||
| bf3cc2ea1e | |||
| c1697dc4f3 | |||
| 3b335a568e | |||
| beacdab54f | |||
| da05a49e10 | |||
| 5cec5b3556 | |||
| 9af498287d | |||
| 452f246f24 | |||
| e97c29db43 | |||
| 3419113dec | |||
| bf6297f63b | |||
| f228ba3019 | |||
| 495aa8a691 | |||
| 811916a8b9 | |||
| 3b6333a6a2 | |||
| 9b37330362 | |||
| 889a17b21c | |||
| ac6d559e5d | |||
| 6d80cca241 | |||
| 9dc225d3e4 | |||
| 0d513f7bff | |||
| b10c744bf9 | |||
| e6367da286 | |||
| 01f4480a08 | |||
| e9722c790c | |||
| af98c32026 | |||
| 7300b30cf5 | |||
| 428cd6ddc2 | |||
| 2de8af878b | |||
| 34d95eab9d | |||
| 548aeb2ce9 | |||
| 7edd888aa2 | |||
| a0d4f19f30 | |||
| 67ba342c28 | |||
| 1b69fcb16a | |||
| c8a95422af | |||
| 9d02f18bac | |||
| f9ee2cb120 | |||
| b27b89f599 | |||
| bfbd0a6a22 | |||
| e2de7a1f1c | |||
| 3f769eb7d7 | |||
| 8bc053053c | |||
| a0dcaf7b4f | |||
| 844fc5217a | |||
| 542afa5892 | |||
| 9e02b15ff1 | |||
| 463769b549 | |||
| 2c383d0c55 | |||
| 5ba74c8368 | |||
| 3c9b3c2db1 | |||
| 98f8907817 | |||
| 44dcc5e19f | |||
| d7012ebfa1 | |||
| a9b5317e79 | |||
| f02598760f | |||
| 4b8cd2a0d7 | |||
| 104798d4f2 | |||
| 4653a4f129 | |||
| 07f9a0f522 | |||
| 7e21d9e2e4 | |||
| 4e2eca295d | |||
| 1f165055c1 | |||
| 7b953fa73e | |||
| d3157e4d48 | |||
| 73fe4531cc | |||
| b6c03892b1 |
317
CHANGELOG.md
317
CHANGELOG.md
@@ -2,6 +2,317 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
[v1.0.4.0][v1.0.4.0] (2026-03-16) {#v1.0.4.0}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.4.0-features}
|
||||
|
||||
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) gibt es nun über die Menü-Leiste die Möglichkeit _Massenaktionen_ für mehrere Lieferungen durchzuführen (z.B. Attribut oder Zu-/Abschlag setzen). ([#78][i78])
|
||||
|
||||
### Behobene Fehler {#v1.0.4.0-bugfixes}
|
||||
|
||||
* Waagen mit Netzwerkschnittstelle versuchen die Verbindung nun wieder neu aufzubauen, sollte diese unterbrochen werden. ([#74][i74])
|
||||
|
||||
### Sonstiges {#v1.0.4.0-misc}
|
||||
|
||||
* Die bisher verwendeten Programm-Bibliotheken zum Erstellen von PDF-Dokumente wurden vollständig durch [iText](https://itextpdf.com/) ersetzt.
|
||||
Das ermöglicht ein schnelleres, effizienteres, und stabileres Erstellen der PDF-Dokumente.
|
||||
Außerdem konnte so die Größe der Installations-Datei von ~138 MB auf ~98 MB um ca. 30 % reduziert werden. (d8c967b2f2, 8054a024f4)
|
||||
* Abhängigkeiten aktualisiert. (af73226c90, f4fddd111f)
|
||||
|
||||
[v1.0.4.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.4.0
|
||||
[i74]: https://git.necronda.net/winzer/elwig/issues/74
|
||||
[i78]: https://git.necronda.net/winzer/elwig/issues/78
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.3.4][v1.0.3.4] (2026-02-19) {#v1.0.3.4}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.3.4-features}
|
||||
|
||||
* Bei Anlieferungsbestätigungen (`DeliveryConfirmation`) wird nun auch die Summe aller abgewerteten Lieferungen angeführt. (9e5f709d42)
|
||||
* Bei der Auszahlung werden Traubengutschriften (`CreditNote`) nur noch für Mitglieder erstellt, die einen positiven Betrag ausgezahlt bekommen. (432c511b85)
|
||||
|
||||
### Behobene Fehler {#v1.0.3.4-bugfixes}
|
||||
|
||||
* Im Haupt-Fenster (`MainWindow`) wurde der Tooltip für den Knopf _Sorten-/Qual.aufschlüssel_ korrigiert. (2c0b000073)
|
||||
* Falsch gesetzte `xtime` und `mtime` bei Lieferungen korrigiert. (23db4de1ee)
|
||||
* Beim Import von Lieferungen ohne `kgnr` tritt nun kein Fehler auf. (6818491ae3)
|
||||
* Beim Abfragen von neuen Daten zum Synchronisieren werden alle Fehler abgefangen. (a47904cf0b)
|
||||
|
||||
### Sonstiges {#v1.0.3.4-misc}
|
||||
|
||||
* Pdfium wird nun direkt importiert (anstatt über PdfiumViewer). (4cd7ef85a1)
|
||||
* Auszahlungsvarianten von vergangenen Saisons sind nun nicht mehr für eine Bearbeitung gesperrt. (7e22759c33)
|
||||
* Im Rundschreiben-Fenster (`MailWindow`) ist es nun möglich Dokumente durch Doppelklick an-/abzuwählen. (6f081811c4)
|
||||
* Abhängigkeiten aktualisiert. (19c3322ef2, 0e2b004b0d)
|
||||
|
||||
[v1.0.3.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.4
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.3.3][v1.0.3.3] (2026-02-05) {#v1.0.3.3}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.3.3-bugfixes}
|
||||
|
||||
* Hotfix: Probleme beim Drucken seit der Installation von [v1.0.3.0](#v1.0.3.0). (c8911c0acb, 8f423e3e92)
|
||||
|
||||
[v1.0.3.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.3
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.3.2][v1.0.3.2] (2026-02-04) {#v1.0.3.2}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.3.2-bugfixes}
|
||||
|
||||
* Hotfix: Probleme bei der Installation von [v1.0.3.0](#v1.0.3.0). (7adc9f89e4)
|
||||
|
||||
[v1.0.3.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.2
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.3.1][v1.0.3.1] (2026-01-19) {#v1.0.3.1}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.3.1-bugfixes}
|
||||
|
||||
* Hotfix: Probleme bei der Installation von [v1.0.3.0](#v1.0.3.0). (e63de0d867)
|
||||
|
||||
[v1.0.3.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.1
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.3.0][v1.0.3.0] (2026-01-16) {#v1.0.3.0}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.3.0-features}
|
||||
|
||||
* Bei Zu-/Abschlägen ist es nun möglich den konkreten Wert dieser auf Lieferscheinen nicht anzuzeigen (Stammdaten -> Saisons). (495aa8a691)
|
||||
* Bei Teillieferungen kann nun die Art der Anlieferung angegeben werden: Planenwagen/Kipper, Lesewagen, Kiste(n). (bf6297f63b, 3419113dec, 6d6776f0f9)
|
||||
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) können nun die Gesamtmengen der gefilterten Lieferungen nach Mitglied und Sorte als Excel-Liste exportiert werden ("Liefermengen"). ([#73][i73], e97c29db43)
|
||||
* Im Datenbankabfragen-Fenster (`QueryWindow`) gibt es die Möglichkeit die Ergebnisse als CSV-Datei zu exportieren. (9af498287d, 5cec5b3556)
|
||||
* PDF-Dokumente wurden überarbeitet:
|
||||
* Traubengutschriften können nur noch ausgedruckt bzw. verschickt werden, wenn die zugehörige Auszahlungsvariante festgesetzt ist. (b31603554a)
|
||||
* Auf Dokumenten für interne Verwendung (alle außer an Mitglieder adressierte) wurden Kopf- und Fußzeile auf ein Minimum beschränkt. (f141485537)
|
||||
* Das Synchronisieren zwischen Zweigstellen ist für den Benutzer nun wesentlich vereinfacht. Geänderte Daten von Mitgliedern, Flächenbindungen oder Lieferungen werden beim Synchronisieren automatsich hochgeladen bzw. heruntergeladen. ([#66][i66], 3b335a568e)
|
||||
* Im Rundschreiben-Fenster (`MailWindow`) wurde ein Knopf zum Abbrechen des Generierens hinzugefügt. ([#50][i50])
|
||||
|
||||
### Behobene Fehler {#v1.0.3.0-bugfixes}
|
||||
|
||||
* Einzelne (nicht über das Rundschreiben-Fenster) verschickte E-Mails werde korrekt im Ausgangsprotokoll angeführt. (ac6d559e5d)
|
||||
* Bei automatischen Datenbank-Updates sind auftretende Fehler ignoriert worden. (f228ba3019)
|
||||
* Beim Aufteilen von Lieferungen sind Zu-/Abschläge nicht auf neu erstellen Teillieferungen übernommen worden. (da05a49e10)
|
||||
* Das Verbinden und Trennen von Waagen mittels Serial-/COM-Port ist nun bei laufendem Programm möglich. ([#71][i71])
|
||||
* In den Auszahlungsvarianten-Daten (`PaymentVariantSummary`) ist die statistische Summe der gebundenen und ungebundenen Menge ohne die Spalte _attributlos gebunden_ berechnet worden. (90def81cc5)
|
||||
* Seit [v0.13.7](#v0.13.7) (2025-01-21) wurde auch bei der ersten Auszahlung der Saison ein "Bisher berücksichtigt: € 0,00" auf den Traubengutschriften angeführt. (7bea4d9ee0)
|
||||
* Das Speichern der Parameter `MAIL_SEND_POSTAL` und `MAIL_SEND_EMAIL` war fehlerhaft. (01739ba42e)
|
||||
* Bessere Isolation der automatisierten Dokumenten-Tests. (640dbf705e)
|
||||
|
||||
### Sonstiges {#v1.0.3.0-misc}
|
||||
|
||||
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) wird bei Zu-/Abschlägen nur "Alle" angezeigt, wenn mehr als 1 ausgewählt sind. (811916a8b9)
|
||||
* Im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) wird die letzte Variante standardmäßig ausgewählt und beim Löschen wird um Bestätigung gebeten. (b6497fd422)
|
||||
* Abhängigkeiten aktualisiert. (889a17b21c, 9b37330362, 3b6333a6a2, d45c3f867f, 42121fe7da)
|
||||
|
||||
[v1.0.3.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.3.0
|
||||
[i50]: https://git.necronda.net/winzer/elwig/issues/50
|
||||
[i66]: https://git.necronda.net/winzer/elwig/issues/60
|
||||
[i71]: https://git.necronda.net/winzer/elwig/issues/71
|
||||
[i73]: https://git.necronda.net/winzer/elwig/issues/73
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.2.0][v1.0.2.0] (2025-11-10) {#v1.0.2.0}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.2.0-features}
|
||||
|
||||
* Im Mitglieder-Fenster (`MemberAdminWindow`) können Kontaktdaten der Mitglieder als .vcf-Datei exportiert werden. (01f4480a08, 9dc225d3e4)
|
||||
|
||||
### Sonstiges {#v1.0.2.0-misc}
|
||||
|
||||
* Wenn ein Serial-/COM-Port-USB-Adapter an- oder abgesteckt wird, wird das nun automatisch erkannt. (e6367da286)
|
||||
* Abhängigkeiten aktualisiert. (b10c744bf9, 0d513f7bff)
|
||||
|
||||
[v1.0.2.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.2.0
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.1.5][v1.0.1.5] (2025-10-29) {#v1.0.1.5}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.1.5-bugfixes}
|
||||
|
||||
* Im Rundschreiben-Fenster (`MailWindow`) kam es zu einem Absturz, wenn man das Fenster über den "Anlieferungsbestätigung"-Knopf im Leseabschluss-Abschnitt geöffnet hat. (af98c32026)
|
||||
|
||||
[v1.0.1.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.5
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.1.4][v1.0.1.4] (2025-10-28) {#v1.0.1.4}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.1.4-bugfixes}
|
||||
|
||||
* Im Rundschreiben-Fenster (`MailWindow`) kam es zu einem Absturz, wenn man die Zustelloptionen "Post zusenden an Mitglieder, die keine E-Mail erhalten würden" und "E-Mail zusenden an niemanden" kombiniert hat. (2de8af878b)
|
||||
|
||||
### Sonstiges {#v1.0.1.4-misc}
|
||||
|
||||
* Im Auszahlungsvariante-Fenster (`ChartWindow`) gibt es keine Fehlermeldung mehr wenn nicht für alle Sorten ein Preis definiert ist, nur noch eine Warnung. (428cd6ddc2)
|
||||
|
||||
[v1.0.1.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.4
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.1.3][v1.0.1.3] (2025-10-13) {#v1.0.1.3}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.1.3-features}
|
||||
|
||||
* In der Liste des Lieferungen-Fenster (`DeliveryAdminWindow`) werden
|
||||
* statt ausschließlich der Sorte auch Attribut und Bewirtschaftungsart angezeigt. (a0d4f19f30)
|
||||
* Kommentare der Lieferungen (und Teillieferungen) angezeigt. (548aeb2ce9)
|
||||
|
||||
### Sonstiges {#v1.0.1.3-misc}
|
||||
|
||||
* Verzögerung der Überprüfung auf automatische Updates auf einige Sekunden verlängert. (67ba342c28)
|
||||
* Verbesserung der Ladezeiten im Anmeldungen-Fenster (`DeliveryAncmtAdminWindow`). (7edd888aa2)
|
||||
|
||||
[v1.0.1.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.3
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.1.2][v1.0.1.2] (2025-09-25) {#v1.0.1.2}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.1.2-bugfixes}
|
||||
|
||||
* Beim automatischen Importieren/Synchronisieren wird bei einem Fehlerfall der Benutzer verständigt, aber der Vorgang nicht abgebrochen. (9d02f18bac)
|
||||
|
||||
### Sonstiges {#v1.0.1.2-misc}
|
||||
|
||||
* Beim Sichern der Datenbank werden Meta-Informationen in der ZIP-Datei gespeichert. (c8a95422af)
|
||||
|
||||
[v1.0.1.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.2
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.1.1][v1.0.1.1] (2025-09-21) {#v1.0.1.1}
|
||||
---------------------------------------------
|
||||
|
||||
### Sonstiges {#v1.0.1.1-misc}
|
||||
|
||||
* Eingabe von Sorten und Qualitätsstufen im Übernahme-Fenster (`DeliveryAdminWindows`) verbessert. (e2de7a1f1c, b27b89f599)
|
||||
|
||||
[v1.0.1.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.1
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.1.0][v1.0.1.0] (2025-09-18) {#v1.0.1.0}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.1.0-features}
|
||||
|
||||
* Neue Weinsorten gemäß Kürzelliste der Bundeskellereiinspektion hinzugefügt. (a0dcaf7b4f)
|
||||
|
||||
### Sonstiges {#v1.0.1.0-misc}
|
||||
|
||||
* HTTP-Anfragen haben jetzt das Feld `User-Agent` gesetzt. (844fc5217a)
|
||||
* Auto-Update-Funktion wird auch beim Erlangen von Netzwerkverbindung ausgeführt. (8bc053053c)
|
||||
|
||||
[v1.0.1.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.1.0
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.0.6][v1.0.0.6] (2025-09-17) {#v1.0.0.6}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.0.6-bugfixes}
|
||||
|
||||
* Die automatische Erkennung des Wiege-Modus hat im WKW nicht funktioniert. (463769b549)
|
||||
|
||||
### Sonstiges {#v1.0.0.6-misc}
|
||||
|
||||
* Tippfehler im Über-Fenster (`AboutWindow`) behoben. (2c383d0c55)
|
||||
* `SaveFileDialog` mit Multi-File-Extensions verbessert. (9e02b15ff1)
|
||||
|
||||
[v1.0.0.6]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.6
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.0.5][v1.0.0.5] (2025-09-15) {#v1.0.0.5}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.0.5-features}
|
||||
|
||||
* Die Datenbank kann im Haupt-Fenster (`MainWindow`) gesichert und wiederhergestellt werden. (f02598760f)
|
||||
|
||||
### Sonstiges {#v1.0.0.5-misc}
|
||||
|
||||
* In der WGM ist eine Auswahl eines Zu/-Abschlags im Übernahme-Fenster (`DeliveryAdminWindow`) nun erforderlich. (a9b5317e79)
|
||||
* In der Konfigurationsdatei kann im `[general]` Block `weighing = gross`, `weighing = net`, oder `weighing = box` angegeben werden. (d7012ebfa1)
|
||||
* Über-Fenser (`AboutWindow`) hinzugefügt. (3c9b3c2db1)
|
||||
* Abhängigkeiten aktualisiert. (44dcc5e19f, 98f8907817)
|
||||
|
||||
[v1.0.0.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.5
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.0.4][v1.0.0.4] (2025-09-01) {#v1.0.0.4}
|
||||
---------------------------------------------
|
||||
|
||||
### Behobene Fehler {#v1.0.0.4-bugfixes}
|
||||
|
||||
* Absturz beim Verschicken von einzelnen E-Mails außerhalb des Rundschreiben-Fensters (`MailWindow`) behoben. (07f9a0f522)
|
||||
|
||||
### Sonstiges {#v1.0.0.4-misc}
|
||||
|
||||
* Ladezeit des Fehler-Protokoll-Fensters (`LogWindow`) verbessert. (4653a4f129)
|
||||
* Im Übernahme-Fenster (`DeliveryAdminWindow`) ist es nun möglich die Qualitätsstufe zu verändern. (104798d4f2)
|
||||
|
||||
[v1.0.0.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.4
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.0.3][v1.0.0.3] (2025-08-11) {#v1.0.0.3}
|
||||
---------------------------------------------
|
||||
|
||||
### Neue Funktionen {#v1.0.0.3-features}
|
||||
|
||||
* Im Haupt-Fenster (`MainWindow`) ist es nun möglich die gesamte Datenbank zu hochzuladen bzw. herunterzuladen. ([#69][i69])
|
||||
|
||||
### Sonstiges {#v1.0.0.3-misc}
|
||||
|
||||
* Die Intigrität von Elwig-Export-Dateien (`.elwig.zip`) und anderen `.zip` Dateien wir nun überprüft. (d3157e4d48)
|
||||
* Die Herkunftshierarchie wird nun auch automatisch synchronisiert (benötigt für Stamm-KG bei Mitglied, Ried/KG bei Flächenbindung, und Herkunft bei Lieferung). ([#70][i70], b6c03892b1)
|
||||
* Abhängigkeiten aktualisiert. (1f165055c1, 4e2eca295d)
|
||||
|
||||
[v1.0.0.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.3
|
||||
[i69]: https://git.necronda.net/winzer/elwig/issues/69
|
||||
[i70]: https://git.necronda.net/winzer/elwig/issues/70
|
||||
|
||||
|
||||
|
||||
|
||||
[v1.0.0.2][v1.0.0.2] (2025-08-05) {#v1.0.0.2}
|
||||
---------------------------------------------
|
||||
|
||||
@@ -9,6 +320,8 @@ Changelog
|
||||
|
||||
* Explizit native SQLite-Bibliothek hinzugefügt. (77c3f388e7)
|
||||
|
||||
[v1.0.0.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.2
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +333,8 @@ Changelog
|
||||
* Abhängigkeiten aktualisiert. (466c8a322c)
|
||||
* Angepasste Fehlermeldung, wenn Importieren fehlschlägt. (ab7c7404e2)
|
||||
|
||||
[v1.0.0.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.0.1
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +355,7 @@ Changelog
|
||||
* Bei Traubengutschriften (`CreditNote`) wurde der Rebelzuschlag immer angeführt, auch wenn dieser in der zugrundeliegenden Berechnung nicht berücksichtigt wurde. (336aef5c70)
|
||||
* In den Variantendaten einer Auszahlungsvariante (`PaymentVariantSummary`) wurde neben den Spalten _gebunden_ und _ungebunden_ noch _attributlos gebunden_ hinzugefügt. Ohne diese neue Spalte wären die Werte der anderen beiden falsch. ([#58][i58])
|
||||
* Das erste Laden des Ausgangs-Protokoll-Fensters (`MailLogWindow`) hat nicht funktioniert. ([#65][i65])
|
||||
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) und im Mitglieder-Fenster (`MemberAdminWindow`) wird der Tool-Tip für Gewicht/Gradation mit korrektem Layout angezeigt. (e9f389b885)
|
||||
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) und im Mitglieder-Fenster (`MemberAdminWindow`) wird der Tooltip für Gewicht/Gradation mit korrektem Layout angezeigt. (e9f389b885)
|
||||
* Bei Traubengutschriften (`CreditNote`) werden längere Freitexte vollständig angezeigt statt abgeschnitten. ([#62][i62])
|
||||
|
||||
### Sonstiges {#v1.0.0.0-misc}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="WineVarietyTemplateExpanded">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding SortId}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/>
|
||||
<TextBlock Text="{Binding SortIdFormat}" Foreground="{Binding Color}" MinWidth="36" Margin="0,0,10,0"/>
|
||||
<TextBlock Text="{Binding Name}" Foreground="{Binding Color}"/>
|
||||
<TextBlock Text="{Binding CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
|
||||
</StackPanel>
|
||||
@@ -55,6 +55,12 @@
|
||||
<TextBlock Text="{Binding ValueStr}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="PublicModifierTemplate">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Name}" MinWidth="250" Margin="0,0,10,0"/>
|
||||
<TextBlock Text="{Binding PublicValueStr}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="WineAttributeTemplate">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.IO;
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Helpers.Weighing;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Threading;
|
||||
using System.Reflection;
|
||||
using Elwig.Helpers.Printing;
|
||||
using Elwig.Windows;
|
||||
using Elwig.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Helpers.Billing;
|
||||
using Elwig.Helpers.Export;
|
||||
using Elwig.Helpers.Printing;
|
||||
using Elwig.Helpers.Weighing;
|
||||
using Elwig.Models.Entities;
|
||||
using Elwig.Windows;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Elwig {
|
||||
public partial class App : Application {
|
||||
@@ -24,14 +26,12 @@ namespace Elwig {
|
||||
public static bool ForceShutdown { get; private set; } = false;
|
||||
|
||||
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
|
||||
public readonly SerialPortWatcher SerialPortWatcher = new();
|
||||
|
||||
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig");
|
||||
public static readonly string MailsPath = Path.Combine(DataPath, "mails");
|
||||
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
|
||||
public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Elwig");
|
||||
public static readonly string DocumentsPath = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
|
||||
Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Elwig/Documents") :
|
||||
Path.Combine(InstallPath, "resources/Documents");
|
||||
public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");
|
||||
|
||||
public static Config Config { get; private set; } = new(ConfigPath);
|
||||
@@ -46,9 +46,10 @@ namespace Elwig {
|
||||
public static string? BranchPhoneNr { get; private set; }
|
||||
public static string? BranchFaxNr { get; private set; }
|
||||
public static string? BranchMobileNr { get; private set; }
|
||||
public static IList<IScale> Scales { get; private set; }
|
||||
public static IList<ICommandScale> CommandScales => Scales.Where(s => s is ICommandScale).Cast<ICommandScale>().ToList();
|
||||
public static IList<IEventScale> EventScales => Scales.Where(s => s is IEventScale).Cast<IEventScale>().ToList();
|
||||
|
||||
public static IList<IScale> Scales { get; private set; } = [];
|
||||
public static IList<ICommandScale> CommandScales => [.. Scales.Where(s => s is ICommandScale).Cast<ICommandScale>()];
|
||||
public static IList<IEventScale> EventScales => [.. Scales.Where(s => s is IEventScale).Cast<IEventScale>()];
|
||||
public static ClientParameters Client { get; set; }
|
||||
|
||||
public static Dispatcher MainDispatcher { get; private set; }
|
||||
@@ -119,21 +120,24 @@ namespace Elwig {
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
Utils.RunBackground("HTML Initialization", () => Html.Init());
|
||||
Utils.RunBackground("PDF Initialization", () => Pdf.Init());
|
||||
Utils.RunBackground("JSON Schema Initialization", BillingData.Init);
|
||||
|
||||
if (Config.UpdateAuto && Config.UpdateUrl != null) {
|
||||
if (Utils.HasInternetConnectivity()) {
|
||||
Utils.RunBackground("Auto Updater", async () => {
|
||||
await Task.Delay(500);
|
||||
await Task.Delay(1000);
|
||||
await CheckForUpdates();
|
||||
});
|
||||
}
|
||||
NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
|
||||
_autoUpdateTimer.Tick += new EventHandler(OnAutoUpdateTimer);
|
||||
_autoUpdateTimer.Start();
|
||||
}
|
||||
|
||||
SerialPortWatcher.SerialPortConnected += OnSerialPortConnected;
|
||||
SerialPortWatcher.SerialPortDisconnected += OnSerialPortDisconnected;
|
||||
|
||||
var list = new List<IScale>();
|
||||
foreach (var s in Config.Scales) {
|
||||
try {
|
||||
@@ -141,7 +145,7 @@ namespace Elwig {
|
||||
} catch (Exception e) {
|
||||
list.Add(new InvalidScale(s.Id));
|
||||
if (s.Required)
|
||||
MessageBox.Show($"Unable to create scale {s.Id}:\n\n{e.Message}", "Scale Error",
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
@@ -149,7 +153,7 @@ namespace Elwig {
|
||||
|
||||
if (Config.Branch != null) {
|
||||
if (!branches.ContainsKey(Config.Branch.ToLower())) {
|
||||
MessageBox.Show("Invalid branch name in config!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show("Ungültige Zweigstelle in Konfigurationsdatei!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Shutdown();
|
||||
} else {
|
||||
SetBranch(branches[Config.Branch.ToLower()]);
|
||||
@@ -157,10 +161,20 @@ namespace Elwig {
|
||||
} else if (branches.Count == 1) {
|
||||
SetBranch(branches.First().Value);
|
||||
} else {
|
||||
MessageBox.Show("Unable to determine local branch!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show("Erkennen der lokalen Zweigstelle nicht möglich!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
if (Config.WeighingMode == null) {
|
||||
if (Client.IsMatzen || Client.IsWolkersdorf) {
|
||||
Config.WeighingMode = WeighingMode.Net;
|
||||
} else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
|
||||
Config.WeighingMode = WeighingMode.Box;
|
||||
} else if (Client.IsBaden || Client.IsGrInzersdorf) {
|
||||
Config.WeighingMode = WeighingMode.Gross;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnStartup(evt);
|
||||
|
||||
var window = new MainWindow();
|
||||
@@ -168,6 +182,7 @@ namespace Elwig {
|
||||
}
|
||||
|
||||
private async void Application_Exit(object sender, ExitEventArgs evt) {
|
||||
SerialPortWatcher.Dispose();
|
||||
foreach (var s in EventScales) {
|
||||
s.Dispose();
|
||||
}
|
||||
@@ -217,6 +232,63 @@ namespace Elwig {
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkAvailabilityChanged(object? sender, NetworkAvailabilityEventArgs evt) {
|
||||
if (!evt.IsAvailable) return;
|
||||
if (Utils.HasInternetConnectivity()) {
|
||||
Utils.RunBackground("Auto Updater", async () => {
|
||||
await Task.Delay(2000);
|
||||
await CheckForUpdates();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSerialPortConnected(object? sender, string name) {
|
||||
for (var i = 0; i < Config.Scales.Count; i++) {
|
||||
var s = Config.Scales[i];
|
||||
if (s.Connection?.StartsWith($"serial://{name}:") ?? false) {
|
||||
if (Scales[i] is InvalidScale) {
|
||||
try {
|
||||
Scales[i] = Scale.FromConfig(s);
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} catch (Exception e) {
|
||||
Scales[i] = new InvalidScale(s.Id);
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
} else if (Scales[i] is IEventScale) {
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateScales();
|
||||
}
|
||||
|
||||
private void OnSerialPortDisconnected(object? sender, string name) {
|
||||
for (var i = 0; i < Config.Scales.Count; i++) {
|
||||
var s = Config.Scales[i];
|
||||
if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) {
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
if (Scales[i] is ICommandScale) {
|
||||
try {
|
||||
Scales[i].Dispose();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
Scales[i] = new InvalidScale(s.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateScales();
|
||||
}
|
||||
|
||||
public static void UpdateScales() {
|
||||
foreach (Window w in CurrentApp.Windows) {
|
||||
if (w is DeliveryAdminWindow t && t.ViewModel.IsReceipt) {
|
||||
t.UpdateScales();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task CheckForUpdates(bool showAlert = false) {
|
||||
if (Config.UpdateUrl == null) return;
|
||||
var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
|
||||
@@ -239,6 +311,19 @@ namespace Elwig {
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ReplaceDatabase(string filename) {
|
||||
try {
|
||||
await Task.Run(async () => {
|
||||
await Database.Import(filename);
|
||||
});
|
||||
MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
ForceShutdown = true;
|
||||
Current.Shutdown();
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static T FocusWindow<T>(Func<T> constructor, Predicate<T>? selector = null) where T : Window {
|
||||
foreach (Window w in CurrentApp.Windows) {
|
||||
if (w is T t && (selector == null || selector(t))) {
|
||||
|
||||
@@ -124,19 +124,24 @@ namespace Elwig.Controls {
|
||||
SelectItemsReverse();
|
||||
var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null;
|
||||
if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
|
||||
_textBox.Text = AllItemsSelectedContent;
|
||||
AllItemsSelected = true;
|
||||
} else if (SelectedItems.Count == 0) {
|
||||
_textBox.Text = "";
|
||||
AllItemsSelected = false;
|
||||
} else {
|
||||
AllItemsSelected = null;
|
||||
}
|
||||
if (SelectedItems.Count > 1 && SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
|
||||
_textBox.Text = AllItemsSelectedContent;
|
||||
} else if (SelectedItems.Count == 0) {
|
||||
_textBox.Text = "";
|
||||
} else {
|
||||
_textBox.Text = string.Join(Delimiter,
|
||||
dmp == null ? SelectedItems.Cast<object>() :
|
||||
SelectedItems.Cast<object>()
|
||||
.Select(i => i.GetType().GetProperty(dmp)?.GetValue(i))
|
||||
);
|
||||
AllItemsSelected = null;
|
||||
}
|
||||
|
||||
RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, evt.RemovedItems, evt.AddedItems));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Colors;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Kernel.Pdf.Action;
|
||||
using iText.Kernel.Pdf.Canvas;
|
||||
using iText.Layout;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public abstract class BusinessDocument : Document {
|
||||
public class BusinessDocument : Document {
|
||||
|
||||
public Member Member;
|
||||
public string? Location;
|
||||
public bool IncludeSender = false;
|
||||
public bool UseBillingAddress = false;
|
||||
public bool ShowDateAndLocation = false;
|
||||
public string Aside;
|
||||
|
||||
public BusinessDocument(string title, Member m, bool includeSender = false) : base(title) {
|
||||
Member = m;
|
||||
Location = App.BranchLocation;
|
||||
IncludeSender = includeSender;
|
||||
var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " <i>(pauschaliert)</i>");
|
||||
Aside = $"<table><colgroup><col span='1' style='width: 22.5mm;'/><col span='1' style='width: 42.5mm;'/></colgroup>" +
|
||||
$"<thead><tr><th colspan='2'>Mitglied</th></tr></thead><tbody>" +
|
||||
$"<tr><th>Mitglieds-Nr.:</th><td>{m.MgNr}</td></tr>" +
|
||||
$"<tr><th>Betriebs-Nr.:</th><td>{m.LfbisNr}</td></tr>" +
|
||||
$"<tr><th>UID:</th><td>{uid}</td></tr>" +
|
||||
$"</tbody></table>";
|
||||
}
|
||||
protected Table? Aside;
|
||||
|
||||
public string Address {
|
||||
get {
|
||||
@@ -36,11 +31,115 @@ namespace Elwig.Documents {
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetColGroup(IEnumerable<double> cols) {
|
||||
return "<colgroup>\n" + string.Join("\n", cols.Select(g => $"<col style=\"width: {g.ToString(CultureInfo.InvariantCulture)}mm;\"/>")) + "\n</colgroup>\n";
|
||||
protected Cell NewAsideCell(Paragraph text, int colspan = 1, bool isName = false) {
|
||||
var cell = NewCell(text, colspan: colspan).SetPaddingsMM(0.25f, 0.5f, 0.25f, isName ? 1 : 0);
|
||||
if (colspan > 1) {
|
||||
cell.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0))
|
||||
.SetBorderTop(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness))
|
||||
.SetPaddingsMM(0.5f, 1, 0.5f, 1);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static string PrintSortenaufteilung(List<MemberStat> stats) {
|
||||
protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false) {
|
||||
return NewAsideCell(new KernedParagraph(text, 10), colspan, isName);
|
||||
}
|
||||
|
||||
public BusinessDocument(string title, Member m, bool includeSender = false) :
|
||||
base(title) {
|
||||
Member = m;
|
||||
Location = App.BranchLocation;
|
||||
IncludeSender = includeSender;
|
||||
}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
|
||||
var uid = new KernedParagraph(Member.UstIdNr ?? "-", 10);
|
||||
if (!Member.IsBuchführend) uid.Add(Normal(" ")).Add(Italic("(pauschaliert)"));
|
||||
Aside = new Table(ColsMM(22.5, 42.5))
|
||||
.SetFont(NF).SetFontSize(10)
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetBorder(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness))
|
||||
.AddCell(NewAsideCell("Mitglied", 2))
|
||||
.AddCell(NewAsideCell("Mitglieds-Nr.:", isName: true)).AddCell(NewAsideCell($"{Member.MgNr}"))
|
||||
.AddCell(NewAsideCell("Betriebs-Nr.:", isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? ""))
|
||||
.AddCell(NewAsideCell("UID:", isName: true)).AddCell(NewAsideCell(uid));
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
|
||||
var page = pdf.AddNewPage();
|
||||
var pageSize = page.GetPageSize();
|
||||
var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdf);
|
||||
using (var canvas = new Canvas(pdfCanvas, pageSize)) {
|
||||
// header
|
||||
var header = new Div()
|
||||
.SetFixedPositionMM(25, 10, pageSize.GetWidth() / PtInMM - 45, 45, pageSize)
|
||||
.SetTextAlignment(TextAlignment.CENTER);
|
||||
header.Add(new KernedParagraph(App.Client.Name, 18).SetFont(BF).SetMarginsMM(8, 0, 0, 0));
|
||||
if (App.Client.NameSuffix != null) header.Add(new KernedParagraph(App.Client.NameSuffix, 14).SetFont(BF).SetMargin(0));
|
||||
header.Add(new KernedParagraph(App.Client.NameTypeFull, 12).SetFont(NF).SetMargin(0));
|
||||
canvas.Add(header);
|
||||
// address
|
||||
canvas.Add(new Div()
|
||||
.SetFixedPositionMM(25, 50, 80, 45, pageSize)
|
||||
.SetFont(NF)
|
||||
.Add(new KernedParagraph(IncludeSender ? $"{App.Client.Sender1}\n{App.Client.Sender2}" : "", 8).SetMargin(0).SetHeight(16).SetPaddings(8, 0, 8, 0))
|
||||
.Add(new KernedParagraph(Address, 12).SetMargin(0).SetHeight(12 * 5)));
|
||||
// aside
|
||||
if (Aside != null) {
|
||||
canvas.Add(new Div()
|
||||
.SetFixedPositionMM(125, 50, 65, 50, pageSize)
|
||||
.Add(Aside));
|
||||
}
|
||||
}
|
||||
|
||||
doc.Add(new KernedParagraph(ShowDateAndLocation ? $"{Location}, am {Date:dd.MM.yyyy}" : "", 12).SetTextAlignment(TextAlignment.RIGHT).SetVerticalAlignment(VerticalAlignment.MIDDLE).SetHeight(24).SetMargin(0));
|
||||
doc.Add(new KernedParagraph(Title, 12).SetFont(BF).SetMargins(0, 0, 12, 0));
|
||||
}
|
||||
|
||||
protected override Paragraph GetFooter() {
|
||||
var c = App.Client;
|
||||
var link1 = new Link(c.EmailAddress ?? "", PdfAction.CreateURI($"mailto:{Uri.EscapeDataString(c.Name)}%20<{c.EmailAddress}>"));
|
||||
var link2 = new Link(c.Website ?? "", PdfAction.CreateURI($"http://{c.Website}/"));
|
||||
link1.GetLinkAnnotation().SetBorder(new PdfAnnotationBorder(0, 0, 0));
|
||||
link2.GetLinkAnnotation().SetBorder(new PdfAnnotationBorder(0, 0, 0));
|
||||
return new KernedParagraph(10)
|
||||
.AddAll(Utils.GenerateFooter("\n", " \u00b7 ")
|
||||
.Item(c.NameFull).NextLine()
|
||||
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
|
||||
.Item(c.EmailAddress != null ? link1 : null)
|
||||
.Item(c.Website != null ? link2 : null)
|
||||
.Item("Betriebs-Nr.", c.LfbisNr).Item("Bio-KSt.", c.OrganicAuthority).NextLine()
|
||||
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
|
||||
.ToLeafElements());
|
||||
}
|
||||
|
||||
protected Cell NewWeightsHdr(Paragraph p) {
|
||||
return NewCell(p)
|
||||
.SetPaddingsMM(0.125f, 0, 0.125f, 0);
|
||||
}
|
||||
|
||||
protected Cell NewWeightsHdr(string text) {
|
||||
return NewCell(new KernedParagraph(text, 8).SetFont(IF).SetTextAlignment(TextAlignment.RIGHT))
|
||||
.SetPaddingsMM(0.125f, 0, 0.125f, 0);
|
||||
}
|
||||
|
||||
protected Cell NewWeightsTh(string name) {
|
||||
return NewCell(new KernedParagraph(name, 10).SetFont(IF))
|
||||
.SetPaddingsMM(0.125f, 0, 0.125f, 0);
|
||||
}
|
||||
|
||||
protected Cell NewWeightsTd(string text, bool sum = false) {
|
||||
return NewCell(new KernedParagraph(text, 10).SetFont(sum ? BF : NF).SetTextAlignment(TextAlignment.RIGHT))
|
||||
.SetPaddingsMM(sum ? 0.25f : 0.125f, 0, sum ? 0.25f : 0.125f, 0)
|
||||
.SetBorderTop(sum ? new SolidBorder(BorderThickness) : Border.NO_BORDER);
|
||||
}
|
||||
|
||||
protected Table NewWeightsTable(List<MemberStat> stats) {
|
||||
List<string> discrs = [""];
|
||||
List<string> names = ["ohne Attr./Bewirt."];
|
||||
List<string> bucketAttrs = [
|
||||
@@ -56,82 +155,136 @@ namespace Elwig.Documents {
|
||||
|
||||
List<double> cols = [40];
|
||||
cols.AddRange(names.Select(_ => 125.0 / names.Count));
|
||||
string tbl = GetColGroup(cols);
|
||||
tbl += "<thead><tr>" +
|
||||
$"<th><b>Sortenaufteilung</b> [kg]</th>" +
|
||||
string.Join("", names.Select(c => $"<th>{c}</th>")) +
|
||||
"</tr></thead>";
|
||||
var tbl = new Table(ColsMM([.. cols]))
|
||||
.AddHeaderCell(NewWeightsHdr(new KernedParagraph(8).Add(BoldItalic("Sortenaufteilung ")).Add(Italic("[kg]"))));
|
||||
foreach (var name in names)
|
||||
tbl.AddHeaderCell(NewWeightsHdr(name));
|
||||
|
||||
foreach (var g in stats.GroupBy(b => b.Variety).OrderBy(b => b.Key)) {
|
||||
var dict = g.ToDictionary(a => a.Discr, a => a.Weight);
|
||||
var vals = discrs.Select(a => dict.GetValueOrDefault(a, 0)).ToList();
|
||||
tbl.AddCell(NewWeightsTh(g.Key));
|
||||
foreach (var v in vals)
|
||||
tbl.AddCell(NewWeightsTd(v == 0 ? "-" : $"{v:N0}"));
|
||||
tbl.AddCell(NewWeightsTd($"{dict.Values.Sum():N0}"));
|
||||
}
|
||||
|
||||
tbl += string.Join("\n", stats
|
||||
.GroupBy(b => b.Variety)
|
||||
.OrderBy(b => b.Key)
|
||||
.Select(g => {
|
||||
var dict = g.ToDictionary(a => a.Discr, a => a.Weight);
|
||||
var vals = discrs.Select(a => dict.GetValueOrDefault(a, 0)).ToList();
|
||||
return $"<tr><th>{g.Key}</th>" + string.Join("", vals.Select(v => "<td class=\"number\">" + (v == 0 ? "-" : $"{v:N0}") + "</td>")) +
|
||||
$"<td class=\"number\">{dict.Values.Sum():N0}</td></tr>";
|
||||
})
|
||||
);
|
||||
var totalDict = stats.GroupBy(s => s.Discr).ToDictionary(g => g.Key, g => g.Sum(a => a.Weight));
|
||||
var totals = discrs.Select(a => totalDict.TryGetValue(a, out int value) ? value : 0);
|
||||
tbl += "<tr class=\"sum bold\"><td></td>" + string.Join("", totals.Select(v => $"<td class=\"number\">{v:N0}</td>")) +
|
||||
$"<td class=\"number\">{totalDict.Values.Sum():N0}</td></tr>";
|
||||
tbl.AddCell(NewWeightsTd("", true));
|
||||
foreach (var v in totals)
|
||||
tbl.AddCell(NewWeightsTd($"{v:N0}", true));
|
||||
tbl.AddCell(NewWeightsTd($"{totalDict.Values.Sum():N0}", true));
|
||||
|
||||
return "<table class=\"sortenaufteilung small number cohere\">" + tbl + "</table>";
|
||||
return tbl;
|
||||
}
|
||||
|
||||
private static string FormatRow(
|
||||
int obligation, int right, int delivery, int? totalDelivery = null, int? payment = null, int? area = null,
|
||||
bool isGa = false, bool showPayment = false, bool showArea = false
|
||||
protected Cell NewBucketHdr(Paragraph p, int rowspan = 1, bool left = false, bool unit = false) {
|
||||
p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true);
|
||||
p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN);
|
||||
return NewCell(p, rowspan: rowspan)
|
||||
.SetPaddingsMM(0.125f, unit ? 2 : 0, 0.125f, 0)
|
||||
.SetTextAlignment(left ? TextAlignment.LEFT : TextAlignment.RIGHT).SetVerticalAlignment(VerticalAlignment.MIDDLE)
|
||||
.SetFont(IF);
|
||||
}
|
||||
|
||||
protected Cell NewBucketHdr(string text, int rowspan = 1, bool left = false, bool unit = false) {
|
||||
return NewBucketHdr(new KernedParagraph(text, 8), rowspan, left, unit);
|
||||
}
|
||||
|
||||
protected Cell NewBucketSubHdr(string text, int colspan, bool isTiny = false) {
|
||||
var p = new KernedParagraph(text, 8);
|
||||
p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true);
|
||||
p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN);
|
||||
return NewCell(p, colspan: colspan)
|
||||
.SetBorderTop(!isTiny ? new SolidBorder(BorderThickness) : Border.NO_BORDER)
|
||||
.SetPaddingsMM(isTiny ? 0 : 0.125f, 0, isTiny ? 0 : 0.125f, 0).SetVerticalAlignment(VerticalAlignment.MIDDLE)
|
||||
.SetTextAlignment(TextAlignment.LEFT).SetFont(BI);
|
||||
}
|
||||
|
||||
protected Cell NewBucketTh(string text, bool isTiny = false) {
|
||||
var p = new KernedParagraph(text, isTiny ? 8 : 10);
|
||||
p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true);
|
||||
p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN);
|
||||
return NewCell(p)
|
||||
.SetPaddingsMM(isTiny ? 0 : 0.125f, 0, isTiny ? 0 : 0.125f, 0)
|
||||
.SetTextAlignment(TextAlignment.LEFT).SetFont(IF);
|
||||
}
|
||||
|
||||
protected Cell NewBucketTd(string text, bool bold = false, bool isTiny = false) {
|
||||
var p = new KernedParagraph(text, isTiny ? 8 : 10);
|
||||
p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true);
|
||||
p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN);
|
||||
return NewCell(p)
|
||||
.SetPaddingsMM(isTiny ? 0 : 0.125f, 0, isTiny ? 0 : 0.125f, 0)
|
||||
.SetTextAlignment(TextAlignment.RIGHT).SetFont(bold ? BF : NF);
|
||||
}
|
||||
|
||||
protected Cell[] FormatRow(
|
||||
int obligation, int right, int delivery, int? totalDelivery = null, int? payment = null, int? area = null,
|
||||
bool isGa = false, bool showPayment = false, bool showArea = false, bool isTiny = false
|
||||
) {
|
||||
totalDelivery ??= delivery;
|
||||
payment ??= delivery;
|
||||
|
||||
if (showArea) {
|
||||
return $"<td>{(area == null ? "" : $"{area:N0}")}</td>" +
|
||||
$"<td>{obligation:N0}</td>" +
|
||||
$"<td>{right:N0}</td>";
|
||||
return [
|
||||
NewBucketTd(area == null ? "" : $"{area:N0}", isTiny: isTiny),
|
||||
NewBucketTd($"{obligation:N0}", isTiny: isTiny),
|
||||
NewBucketTd($"{right:N0}", isTiny: isTiny),
|
||||
];
|
||||
}
|
||||
|
||||
return $"<td>{(obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
|
||||
$"<td>{(right == 0 ? "-" : $"{right: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>{totalDelivery:N0}</td>";
|
||||
return [
|
||||
NewBucketTd(obligation == 0 ? "-" : $"{obligation:N0}", isTiny: isTiny),
|
||||
NewBucketTd(right == 0 ? "-" : $"{right:N0}", isTiny: isTiny),
|
||||
NewBucketTd(totalDelivery < obligation ? $"{obligation - totalDelivery:N0}" : "-", totalDelivery < obligation, isTiny: isTiny),
|
||||
NewBucketTd(delivery <= right ? $"{right - delivery:N0}" : "-", isTiny: isTiny),
|
||||
NewBucketTd(obligation == 0 && right == 0 ? "-" : (delivery > right ? $"{delivery - right:N0}" : "-"), delivery > right && isGa, isTiny: isTiny),
|
||||
..(showPayment ? new List<Cell>() {
|
||||
NewBucketTd(isGa ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}", isTiny: isTiny)
|
||||
} : []),
|
||||
NewBucketTd($"{totalDelivery:N0}", isTiny: isTiny),
|
||||
];
|
||||
}
|
||||
|
||||
private static string FormatRow(MemberBucket bucket, bool isGa = false, bool showPayment = false, bool showArea = false) {
|
||||
return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.DeliveryTotal, bucket.Payment, bucket.Area, isGa, showPayment, showArea);
|
||||
protected Cell[] FormatRow(MemberBucket bucket, bool isGa = false, bool showPayment = false, bool showArea = false, bool isTiny = false) {
|
||||
return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.DeliveryTotal, bucket.Payment, bucket.Area, isGa, showPayment, showArea, isTiny);
|
||||
}
|
||||
|
||||
public string PrintBucketTable(
|
||||
protected Table NewBucketTable(
|
||||
Season season, Dictionary<string, MemberBucket> buckets,
|
||||
bool includeDelivery = true, bool includePayment = false,
|
||||
bool isTiny = false, IEnumerable<string>? filter = null
|
||||
) {
|
||||
includePayment = includePayment && includeDelivery;
|
||||
string tbl = GetColGroup(!includeDelivery ? [105, 20, 20, 20] : includePayment ? [45, 17, 17, 17, 19, 16, 17, 17] : [45, 20, 20, 20, 20, 20, 20]);
|
||||
tbl += $"""
|
||||
<thead>
|
||||
<tr>
|
||||
<th{(!includeDelivery ? " rowspan=\"2\"" : "")}>
|
||||
<b>{(includeDelivery ? "Lese " + season.Year : "Zusammengefasste Flächenbindungen")}</b>
|
||||
per {Date:dd.MM.yyyy} {(includeDelivery ? "[kg]" : "")}
|
||||
</th>
|
||||
{(!includeDelivery ? "<th>Fläche</th>" : "")}
|
||||
<th>Lieferpflicht</th>
|
||||
<th>Lieferrecht</th>
|
||||
{(includeDelivery ? "<th>Unterliefert</th>" : "")}
|
||||
{(includeDelivery ? "<th>Noch lieferbar</th>" : "")}
|
||||
{(includeDelivery ? "<th>Überliefert</th>" : "")}
|
||||
{(includePayment ? "<th>Zugeteilt</th>" : "")}
|
||||
{(includeDelivery ? "<th>Geliefert</th>" : "")}
|
||||
</tr>
|
||||
{(!includeDelivery ? "<tr><th class=\"unit\">[m²]</th><th class=\"unit\">[kg]</th><th class=\"unit\">[kg]</th></tr>" : "")}
|
||||
</thead>
|
||||
""";
|
||||
|
||||
var tbl = new Table(ColsMM(!includeDelivery ? [105, 20, 20, 20] : includePayment ? [45, 17, 17, 17, 19, 16, 17, 17] : [45, 20, 20, 20, 20, 20, 20]))
|
||||
.SetBorder(Border.NO_BORDER).SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetFont(NF).SetFontSize(isTiny ? 8 : 10);
|
||||
|
||||
if (includeDelivery) {
|
||||
tbl.AddHeaderCell(NewBucketHdr(new KernedParagraph(8)
|
||||
.Add(BoldItalic($"Lese {season.Year}"))
|
||||
.Add(Italic($" per {Date:dd.MM.yyyy} [kg]")), left: true))
|
||||
.AddHeaderCell(NewBucketHdr("Lieferpflicht"))
|
||||
.AddHeaderCell(NewBucketHdr("Lieferrecht"))
|
||||
.AddHeaderCell(NewBucketHdr("Unterliefert"))
|
||||
.AddHeaderCell(NewBucketHdr("Noch lieferbar"))
|
||||
.AddHeaderCell(NewBucketHdr("Überliefert"));
|
||||
if (includePayment) tbl.AddHeaderCell(NewBucketHdr("Zugeteilt"));
|
||||
tbl.AddHeaderCell(NewBucketHdr("Geliefert"));
|
||||
} else {
|
||||
tbl.AddHeaderCell(NewBucketHdr(new KernedParagraph(8)
|
||||
.Add(BoldItalic("Zusammengefasste Flächenbindungen"))
|
||||
.Add(Italic($" per {Date:dd.MM.yyyy}")), 2, left: true))
|
||||
.AddHeaderCell(NewBucketHdr("Fläche"))
|
||||
.AddHeaderCell(NewBucketHdr("Lieferpflicht"))
|
||||
.AddHeaderCell(NewBucketHdr("Lieferrecht"))
|
||||
.AddHeaderCell(NewBucketHdr("[m²]", unit: true))
|
||||
.AddHeaderCell(NewBucketHdr("[kg]", unit: true))
|
||||
.AddHeaderCell(NewBucketHdr("[kg]", unit: true));
|
||||
}
|
||||
|
||||
var mBuckets = buckets
|
||||
.Where(b => ((!includeDelivery && b.Value.Area > 0) ||
|
||||
@@ -153,30 +306,26 @@ namespace Elwig.Documents {
|
||||
.Where(b => !fbVars.Contains(b.Key))
|
||||
.OrderBy(b => b.Value.Name);
|
||||
|
||||
tbl += "\n<tbody>\n";
|
||||
tbl += $"<tr><th>Gesamtlieferung lt. gez. GA</th>{FormatRow(
|
||||
Member.BusinessShares * season.MinKgPerBusinessShare,
|
||||
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny));
|
||||
tbl.AddCells(FormatRow(Member.BusinessShares * season.MinKgPerBusinessShare,
|
||||
Member.BusinessShares * season.MaxKgPerBusinessShare,
|
||||
season.Deliveries.Where(d => d.MgNr == Member.MgNr).Sum(d => d.Weight),
|
||||
isGa: true, showPayment: includePayment, showArea: !includeDelivery
|
||||
)}</tr>";
|
||||
isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
|
||||
|
||||
if (fbs.Any()) {
|
||||
tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
|
||||
$"Flächenbindungen{(vtr.Any() ? " (inkl. Verträge)" : "")}:</th></tr>";
|
||||
tbl.AddCell(NewBucketSubHdr("Flächenbindungen" + (vtr.Any() ? " (inkl. Verträge)" : "") + ":", includePayment ? 8 : 7, isTiny: isTiny));
|
||||
foreach (var (id, b) in fbs) {
|
||||
tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}</tr>";
|
||||
tbl.AddCell(NewBucketTh(b.Name, isTiny: isTiny)).AddCells(FormatRow(b, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
|
||||
}
|
||||
}
|
||||
if (vtr.Any()) {
|
||||
tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
|
||||
"Verträge:</th></tr>";
|
||||
tbl.AddCell(NewBucketSubHdr("Verträge:", includePayment ? 8 : 7, isTiny: isTiny));
|
||||
foreach (var (id, b) in vtr) {
|
||||
tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}</tr>";
|
||||
tbl.AddCell(NewBucketTh(b.Name, isTiny: isTiny)).AddCells(FormatRow(b, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
|
||||
}
|
||||
}
|
||||
tbl += "\n</tbody>\n";
|
||||
|
||||
return $"<table class=\"buckets {(isTiny ? "tiny" : "small")} number cohere\">\n" + tbl + "\n</table>";
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.BusinessDocument>
|
||||
@model Elwig.Documents.BusinessDocument
|
||||
@{ Layout = "Document"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\BusinessDocument.css"/>
|
||||
<div class="info-wrapper">
|
||||
<div class="address-wrapper">
|
||||
<div class="sender">
|
||||
@if (Model.IncludeSender) {
|
||||
<div>@Elwig.App.Client.Sender1</div>
|
||||
<div>@Elwig.App.Client.Sender2</div>
|
||||
}
|
||||
</div>
|
||||
<address>@Model.Address</address>
|
||||
</div>
|
||||
<aside>@Raw(Model.Aside)</aside>
|
||||
@if (Model.ShowDateAndLocation) {
|
||||
<div class="date">@Model.Location, am @($"{Model.Date:dd.MM.yyyy}")</div>
|
||||
}
|
||||
</div>
|
||||
@RenderBody()
|
||||
@@ -1,150 +0,0 @@
|
||||
|
||||
.address-wrapper, aside {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
height: 20mm;
|
||||
}
|
||||
|
||||
.info-wrapper {
|
||||
width: 100%;
|
||||
height: 45mm;
|
||||
margin: 0 0 2mm 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.info-wrapper .date {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -1.5em;
|
||||
}
|
||||
|
||||
.address-wrapper {
|
||||
height: 45mm;
|
||||
width: 85mm;
|
||||
margin: 0;
|
||||
padding: 5mm;
|
||||
position: absolute;
|
||||
left: -5mm;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.address-wrapper .sender {
|
||||
height: 4em;
|
||||
font-size: 8pt;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
address {
|
||||
height: 5em;
|
||||
white-space: pre-line;
|
||||
font-size: 12pt;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
aside {
|
||||
height: 40mm;
|
||||
width: 75mm;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
left: 100mm;
|
||||
top: 5mm;
|
||||
}
|
||||
|
||||
aside table {
|
||||
border-collapse: collapse;
|
||||
border: var(--border-thickness) solid #808080;
|
||||
width: 65mm;
|
||||
margin-right: 10mm;
|
||||
}
|
||||
|
||||
aside table thead:not(:first-child) tr {
|
||||
border-top: var(--border-thickness) solid #808080;
|
||||
}
|
||||
|
||||
aside table thead tr {
|
||||
background-color: #E0E0E0;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
aside table tbody th,
|
||||
aside table tbody td {
|
||||
text-align: left;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
aside table tbody th {
|
||||
padding: 0.25mm 0.5mm 0.25mm 1mm;
|
||||
}
|
||||
|
||||
aside table tbody td {
|
||||
padding: 0.25mm 0;
|
||||
}
|
||||
|
||||
aside table tbody th {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 2em 0 1em 0;
|
||||
}
|
||||
|
||||
main > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
main h1,
|
||||
main h2,
|
||||
main h3,
|
||||
.main-wrapper p {
|
||||
font-size: 12pt;
|
||||
margin: 1em 0;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.main-wrapper p {
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
hyphens: manual;
|
||||
}
|
||||
|
||||
main h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.main-wrapper p.comment {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.main-wrapper p.custom {
|
||||
white-space: pre-wrap;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.main-wrapper .hidden {
|
||||
break-before: avoid;
|
||||
}
|
||||
|
||||
.main-wrapper .bottom {
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
width: 165mm;
|
||||
}
|
||||
|
||||
.main-wrapper .signatures {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin: 20mm 0 2mm 0;
|
||||
}
|
||||
|
||||
.main-wrapper .signatures > * {
|
||||
width: 50mm;
|
||||
border-top: var(--border-thickness) solid black;
|
||||
padding-top: 1mm;
|
||||
text-align: center;
|
||||
font-size: 10pt;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using Elwig.Models.Entities;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class BusinessLetter : BusinessDocument {
|
||||
public BusinessLetter(string title, Member m) : base(title, m) { }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.BusinessLetter>
|
||||
@model Elwig.Documents.BusinessLetter
|
||||
@{ Layout = "BusinessDocument"; }
|
||||
<main>
|
||||
<p>Sehr geehrtes Mitglied,</p>
|
||||
<p>nein.</p>
|
||||
<p>Mit freundlichen Grüßen<br/>Ihre Winzergenossenschaft</p>
|
||||
</main>
|
||||
@@ -1,6 +1,10 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models.Dtos;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -17,7 +21,7 @@ namespace Elwig.Documents {
|
||||
public string CurrencySymbol;
|
||||
public int Precision;
|
||||
public string MemberModifier;
|
||||
public IEnumerable<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries;
|
||||
public List<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries;
|
||||
public decimal MemberTotalUnderDelivery;
|
||||
public int MemberAutoBusinessShares;
|
||||
public decimal MemberAutoBusinessSharesAmount;
|
||||
@@ -39,6 +43,7 @@ namespace Elwig.Documents {
|
||||
Data = data;
|
||||
Payment = p;
|
||||
Credit = p.Credit;
|
||||
IsPreview = Payment == null || Credit == null;
|
||||
var season = p.Variant.Season;
|
||||
if (considerCustomModifiers) {
|
||||
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
|
||||
@@ -48,16 +53,10 @@ namespace Elwig.Documents {
|
||||
if (CustomPayment?.ModComment != null) {
|
||||
MemberModifier = CustomPayment.ModComment;
|
||||
} else if (mod != null) {
|
||||
MemberModifier = $"{mod.Name} ({mod.ValueStr})";
|
||||
MemberModifier = $"{mod.Name} ({mod.PublicValueStr})";
|
||||
} else {
|
||||
MemberModifier = "Sonstige Zu-/Abschläge";
|
||||
}
|
||||
Aside = Aside.Replace("</table>", "") +
|
||||
$"<thead><tr><th colspan='2'>Gutschrift</th></tr></thead><tbody>" +
|
||||
$"<tr><th>TG-Nr.:</th><td>{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : "-")}</td></tr>" +
|
||||
$"<tr><th>Datum:</th><td>{p.Variant.Date:dd.MM.yyyy}</td></tr>" +
|
||||
$"<tr><th>Überw. am:</th><td>{p.Variant.TransferDate:dd.MM.yyyy}</td></tr>" +
|
||||
$"</tbody></table>";
|
||||
Text = App.Client.TextCreditNote;
|
||||
DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr);
|
||||
CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code;
|
||||
@@ -95,4 +94,222 @@ namespace Elwig.Documents {
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
Aside?.AddCell(NewAsideCell("Gutschrift", 2))
|
||||
.AddCell(NewAsideCell("TG-Nr.:", isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-"))
|
||||
.AddCell(NewAsideCell("Datum:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}"))
|
||||
.AddCell(NewAsideCell("Überw. am:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}"));
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(NewCreditTable(Data));
|
||||
|
||||
var div = new Table(ColsMM(60, 105))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
var hint = new KernedParagraph(8)
|
||||
.Add(Italic("Hinweis:\n" +
|
||||
$"Die Summe der Lieferungen und die Summe der anfal\u00adlenden Pönalen werden mit " +
|
||||
$"{Payment?.Variant.Season.Precision} Nach\u00adkomma-stellen berechnent, " +
|
||||
$"erst das Ergebnis wird kauf-männisch auf 2 Nach\u00adkomma\u00adstellen gerundet."))
|
||||
.SetWidth(56 * PtInMM).SetMarginsMM(4, 2, 4, 2);
|
||||
div.AddCell(new Cell(1, 2).SetPadding(0).SetBorder(Border.NO_BORDER).SetBorderTop(new SolidBorder(BorderThickness)));
|
||||
div.AddCell(new Cell(3, 1).SetPadding(0).SetBorder(Border.NO_BORDER).Add(hint).SetKeepTogether(true));
|
||||
var tbl1 = new Table(ColsMM(70, 5, 5, 25))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetKeepTogether(true);
|
||||
var tbl2 = new Table(ColsMM(70, 5, 5, 25))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetKeepTogether(true);
|
||||
var tbl3 = new Table(ColsMM(70, 5, 5, 25))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetKeepTogether(true);
|
||||
|
||||
var sum = Data.Rows.Sum(p => p.Amount);
|
||||
if (Payment == null) {
|
||||
tbl1.AddCells(FormatRow("Gesamt", sum, bold: true, noTopBorder: true));
|
||||
} else {
|
||||
var noBorder = true;
|
||||
if (Payment.NetAmount != Payment.Amount) {
|
||||
tbl1.AddCells(FormatRow("Zwischensumme", Payment.NetAmount, noTopBorder: noBorder));
|
||||
noBorder = false;
|
||||
tbl1.AddCells(FormatRow(MemberModifier, Payment.Amount - Payment.NetAmount, add: true));
|
||||
}
|
||||
if (Credit == null) {
|
||||
tbl1.AddCells(FormatRow("Gesamtbetrag", Payment.Amount, bold: true, noTopBorder: noBorder));
|
||||
// TODO Mock VAT
|
||||
} else {
|
||||
var hasPrev = Credit.PrevNetAmount != null;
|
||||
tbl1.AddCells(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Credit.NetAmount, bold: true, noTopBorder: noBorder));
|
||||
if (hasPrev) {
|
||||
tbl1.AddCells(FormatRow("Bisher berücksichtigt", -Credit.PrevNetAmount, add: true));
|
||||
tbl1.AddCells(FormatRow("Nettobetrag", Credit.NetAmount - (Credit.PrevNetAmount ?? 0)));
|
||||
}
|
||||
tbl1.AddCells(FormatRow($"Mehrwertsteuer ({Credit.Vat * 100} %)", Credit.VatAmount, add: true));
|
||||
tbl1.AddCells(FormatRow("Bruttobetrag", Credit.GrossAmount, bold: true));
|
||||
}
|
||||
}
|
||||
|
||||
decimal penalty = 0;
|
||||
string? comment = null;
|
||||
|
||||
if (MemberUnderDeliveries != null && MemberUnderDeliveries.Count > 0) {
|
||||
tbl2.AddCell(NewTd("Anfallende Pönalen durch Unterlieferungen:", colspan: 2).SetPaddingTopMM(5))
|
||||
.AddCell(NewCell(colspan: 2));
|
||||
foreach (var u in MemberUnderDeliveries) {
|
||||
tbl2.AddCells(FormatRow($"{u.Name} ({u.Kg:N0} kg)", u.Amount, add: true, subCat: true));
|
||||
penalty += u.Amount;
|
||||
}
|
||||
penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
if (MemberTotalUnderDelivery != 0) {
|
||||
tbl2.AddCells(FormatRow("Unterlieferung (GA)", MemberTotalUnderDelivery, add: true));
|
||||
penalty += MemberTotalUnderDelivery;
|
||||
}
|
||||
if (MemberAutoBusinessSharesAmount != 0) {
|
||||
tbl2.AddCells(FormatRow($"Autom. Nachz. von GA ({MemberAutoBusinessShares})", MemberAutoBusinessSharesAmount, add: true));
|
||||
penalty += MemberAutoBusinessSharesAmount;
|
||||
}
|
||||
if (CustomPayment?.Amount != null) {
|
||||
comment = CustomPayment.Comment;
|
||||
string text = (CustomPayment.Amount.Value < 0 ? "Weitere Abzüge" : "Weitere Zuschläge") + (comment != null ? "*" : "");
|
||||
if (comment != null && comment!.Length <= 30) {
|
||||
text = comment;
|
||||
comment = null;
|
||||
}
|
||||
tbl2.AddCells(FormatRow(text, CustomPayment.Amount.Value, add: true));
|
||||
penalty += CustomPayment.Amount.Value;
|
||||
}
|
||||
|
||||
if (Credit == null) {
|
||||
tbl3.AddCells(FormatRow("Auszahlungsbetrag", (Payment?.Amount + penalty) ?? (sum + penalty), bold: true));
|
||||
} else {
|
||||
var diff = Credit.Modifiers - penalty;
|
||||
if (diff != 0) {
|
||||
tbl3.AddCells(FormatRow(diff < 0 ? "Sonstige Abzüge" : "Sonstige Zuschläge", diff, add: true));
|
||||
}
|
||||
if (Credit.PrevModifiers != null && Credit.PrevModifiers != 0) {
|
||||
tbl3.AddCells(FormatRow("Bereits berücksichtigte Abzüge", -Credit.PrevModifiers, add: true));
|
||||
}
|
||||
tbl3.AddCells(FormatRow("Auszahlungsbetrag", Credit.Amount, bold: true));
|
||||
}
|
||||
|
||||
div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl1));
|
||||
div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl2));
|
||||
div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl3));
|
||||
doc.Add(div);
|
||||
|
||||
if (comment != null) {
|
||||
doc.Add(new KernedParagraph($"*{comment}", 12).SetMarginTopMM(10));
|
||||
}
|
||||
doc.Add(new KernedParagraph($"Überweisung erfolgt auf Konto {Utils.FormatIban(Member.Iban ?? "-")}.", 12).SetPaddingTopMM(10));
|
||||
if (Text != null) {
|
||||
doc.Add(new KernedParagraph(Text, 12).SetMarginTop(12).SetKeepTogether(true));
|
||||
}
|
||||
}
|
||||
|
||||
protected Cell[] FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) {
|
||||
float textSize = subCat ? 8 : !add ? 12 : 10;
|
||||
float numSize = subCat ? 8 : 12;
|
||||
var border = !add && !noTopBorder;
|
||||
var pad = !add && !noTopBorder ? 1 : 0.5f;
|
||||
return [
|
||||
NewTd($"{name}:", textSize, bold: bold, borderTop: border).SetPaddingTopMM(pad),
|
||||
NewTd(value < 0 ? "–" : (add ? "+" : ""), numSize, right: true, bold: bold, borderTop: border).SetPaddingTopMM(pad),
|
||||
NewTd(CurrencySymbol, numSize, bold: bold, borderTop: border).SetPaddingTopMM(pad),
|
||||
NewTd($"{Math.Abs(value ?? 0):N2}", numSize, right: true, bold: bold, borderTop: border).SetPaddingTopMM(pad),
|
||||
];
|
||||
}
|
||||
|
||||
protected Table NewCreditTable(CreditNoteDeliveryData data) {
|
||||
var tbl = new Table(ColsMM(25, 6, 36, 10, 10, 15, 12, 13, 5, 17, 16), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Pos.", rowspan: 2).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("Sorte/Attribut/Bewirtschaftg.\nZu-/Abschlag", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Gradation", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Flächenbindung", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Preis"))
|
||||
.AddHeaderCell(NewTh("Rbl.").SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("Zu-/Abschläge").SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("Betrag"))
|
||||
.AddHeaderCell(NewTh("[°Oe]"))
|
||||
.AddHeaderCell(NewTh("[°KMW]").SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("[kg]", colspan: 2))
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]"))
|
||||
.AddHeaderCell(NewTh("[%]").SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}]"))
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}]"));
|
||||
|
||||
foreach (var p in data.Rows) {
|
||||
var sub = new Table(ColsMM(25, 6, 36, 10, 10, 15, 12, 13, 5, 17, 16))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetKeepTogether(true);
|
||||
var attr = p.Attribute != null || p.Cultivation != null || p.QualId == "WEI";
|
||||
var rows = Math.Max(p.Buckets.Length, 1 + (attr ? 1 : 0) + p.Modifiers.Length);
|
||||
for (int i = 0; i < rows; i++) {
|
||||
if (i == 0) {
|
||||
sub.AddCell(NewTd(p.LsNr))
|
||||
.AddCell(NewTd($"{p.DPNr:N0}", center: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd(p.Variety))
|
||||
.AddCell(NewTd($"{p.Gradation.Oe:N0}", center: true))
|
||||
.AddCell(NewTd($"{p.Gradation.Kmw:N1}", center: true));
|
||||
} else if (i == 1 && attr) {
|
||||
var varibute = new KernedParagraph(8);
|
||||
if (p.Attribute != null) varibute.Add(Normal(p.Attribute));
|
||||
if (p.Attribute != null && p.Cultivation != null) varibute.Add(Normal(" / "));
|
||||
if (p.Cultivation != null) varibute.Add(Normal(p.Cultivation));
|
||||
if ((p.Attribute != null || p.Cultivation != null) && p.QualId == "WEI") varibute.Add(Normal(" / "));
|
||||
if (p.QualId == "WEI") varibute.Add(Italic("abgew."));
|
||||
sub.AddCell(NewCell(colspan: 2))
|
||||
.AddCell(NewTd(varibute, colspan: 3).SetPaddingTop(0));
|
||||
} else if (i - (rows - p.Modifiers.Length) < p.Modifiers.Length) {
|
||||
sub.AddCell(NewCell(colspan: 2))
|
||||
.AddCell(NewTd(p.Modifiers[i - (rows - p.Modifiers.Length)], 8, colspan: 3).SetPaddingTop(0).SetPaddingLeftMM(5));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 5));
|
||||
}
|
||||
|
||||
if (i < p.Buckets.Length) {
|
||||
var bucket = p.Buckets[i];
|
||||
var pad = i == 0 ? 0.5f : 0;
|
||||
sub.AddCell(NewTd($"{bucket.Name}:", 8)
|
||||
.SetPaddingTopMM(pad))
|
||||
.AddCell(NewTd($"{bucket.Value:N0}", right: true)
|
||||
.SetPaddingTopMM(pad))
|
||||
.AddCell(NewTd($"{bucket.Price:N4}", right: true)
|
||||
.SetPaddingTopMM(pad));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 3));
|
||||
}
|
||||
|
||||
if (i == p.Buckets.Length - 1) {
|
||||
var rebelMod = p.WeighingModifier * 100;
|
||||
var totalMod = p.TotalModifiers ?? 0;
|
||||
var pad = i == 0 ? 0.5f : 0;
|
||||
sub.AddCell(NewTd(rebelMod == 0 ? "-" : (Utils.GetSign(rebelMod) + $"{Math.Abs(rebelMod):0.0##}"), 6, center: true)
|
||||
.SetPaddingTopMM(pad))
|
||||
.AddCell(NewTd(totalMod == 0 ? "-" : Utils.GetSign(totalMod) + $"{Math.Abs(totalMod):N2}", center: totalMod == 0, right: true)
|
||||
.SetPaddingTopMM(pad))
|
||||
.AddCell(NewTd($"{p.Amount:N2}", right: true)
|
||||
.SetPaddingTopMM(pad));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 3));
|
||||
}
|
||||
}
|
||||
tbl.AddCell(new Cell(1, 12).SetPadding(0).SetBorder(Border.NO_BORDER).Add(sub));
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
@using Elwig.Helpers
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.CreditNote>
|
||||
@model Elwig.Documents.CreditNote
|
||||
@{ Layout = "BusinessDocument"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\CreditNote.css" />
|
||||
<main>
|
||||
<h1>@Model.Title</h1>
|
||||
<table class="credit">
|
||||
<colgroup>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 6mm;"/>
|
||||
<col style="width: 21mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 10mm;"/>
|
||||
<col style="width: 10mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 12mm;"/>
|
||||
<col style="width: 13mm;"/>
|
||||
<col style="width: 5mm;"/>
|
||||
<col style="width: 17mm;"/>
|
||||
<col style="width: 16mm;"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" style="text-align: left;">Lieferschein-Nr.</th>
|
||||
<th rowspan="2" class="narrow">Pos.</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 colspan="2">Flächenbindung</th>
|
||||
<th>Preis</th>
|
||||
<th class="narrow">Rbl.</th>
|
||||
<th class="narrow">Zu-/Abschläge</th>
|
||||
<th>Betrag</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="unit">[°Oe]</th>
|
||||
<th class="unit narrow">[°KMW]</th>
|
||||
<th class="unit" colspan="2">[kg]</th>
|
||||
<th class="unit">[@Model.CurrencySymbol/kg]</th>
|
||||
<th class="narrow unit">[%]</th>
|
||||
<th class="unit">[@Model.CurrencySymbol]</th>
|
||||
<th class="unit">[@Model.CurrencySymbol]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sum">
|
||||
@foreach (var p in Model.Data.Rows) {
|
||||
var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1);
|
||||
@for (int i = 0; i < rows; i++) {
|
||||
<tr class="@(i == 0 ? "first" : "") @(rows == i + 1 ? "last" : "")">
|
||||
@if (i == 0) {
|
||||
<td rowspan="@rows">@p.LsNr</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
|
||||
@((p.Attribute != null || p.Cultivation != null) && p.QualId == "WEI" ? " / " : "")@Raw(p.QualId == "WEI" ? "<i>abgew.</i>" : "")
|
||||
</td>
|
||||
<td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
|
||||
<td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
|
||||
}
|
||||
@if (i > 0 && i <= p.Modifiers.Length) {
|
||||
<td colspan="4" class="small mod">@p.Modifiers[i - 1]</td>
|
||||
} else if (i > 0) {
|
||||
<td colspan="4"></td>
|
||||
}
|
||||
@if (i < p.Buckets.Length) {
|
||||
var bucket = p.Buckets[i];
|
||||
<td class="small">@bucket.Name:</td>
|
||||
<td class="number">@($"{bucket.Value:N0}")</td>
|
||||
<td class="number">@($"{bucket.Price:N4}")</td>
|
||||
} else {
|
||||
<td></td>
|
||||
}
|
||||
@if (i == p.Buckets.Length - 1) {
|
||||
var rebelMod = p.WeighingModifier * 100;
|
||||
var totalMod = p.TotalModifiers ?? 0;
|
||||
<td class="tiny center">@(rebelMod == 0 ? "-" : (Utils.GetSign(rebelMod) + $"{Math.Abs(rebelMod):0.0##}"))</td>
|
||||
<td class="number@(totalMod == 0 ? " center" : "")">@(totalMod == 0 ? "-" : Utils.GetSign(totalMod) + $"{Math.Abs(totalMod):N2}")</td>
|
||||
<td class="number">@($"{p.Amount:N2}")</td>
|
||||
} else {
|
||||
<td colspan="2"></td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="hint">
|
||||
Hinweis:<br/>
|
||||
Die Summe der Lieferungen und die Summe der anfal­lenden Pönalen werden mit
|
||||
@Model.Payment?.Variant.Season.Precision Nach­komma­stellen berechnent,
|
||||
erst das Ergebnis wird kauf­männisch auf 2 Nach­komma­stellen gerundet.
|
||||
</div>
|
||||
<table class="credit-sum">
|
||||
<colgroup>
|
||||
<col style="width: auto;"/>
|
||||
<col style="width: 5mm;"/>
|
||||
<col style="width: 30mm;"/>
|
||||
</colgroup>
|
||||
@{
|
||||
string FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) {
|
||||
return $"<tr class=\"{(!add && !noTopBorder ? "sum" : !add ? "large" : "")} {(bold ? "large bold" : "")}\">"
|
||||
+ $"<td class=\"{(subCat ? "small" : "")}\">{name}:</td>"
|
||||
+ $"<td class=\"number {(subCat ? "small" : "large")}\">{(value < 0 ? "–" : (add ? "+" : ""))}</td>"
|
||||
+ $"<td class=\"number {(subCat ? "small" : "large")}\">"
|
||||
+ $"<span class=\"fleft\">{Model.CurrencySymbol}</span>{Math.Abs(value ?? 0):N2}</td>"
|
||||
+ $"</tr>\n";
|
||||
}
|
||||
}
|
||||
<tbody style="break-inside: avoid;">
|
||||
@{ var sum = Model.Data.Rows.Sum(p => p.Amount); }
|
||||
@if (Model.Payment == null) {
|
||||
@Raw(FormatRow("Gesamt", sum, bold: true, noTopBorder: true))
|
||||
} else {
|
||||
var noBorder = true;
|
||||
if (Model.Payment.NetAmount != Model.Payment.Amount) {
|
||||
@Raw(FormatRow("Zwischensumme", Model.Payment.NetAmount, noTopBorder: noBorder))
|
||||
noBorder = false;
|
||||
@Raw(FormatRow(Model.MemberModifier, Model.Payment.Amount - Model.Payment.NetAmount, add: true))
|
||||
}
|
||||
if (Model.Credit == null) {
|
||||
@Raw(FormatRow("Gesamtbetrag", Model.Payment.Amount, bold: true, noTopBorder: noBorder))
|
||||
// TODO Mock VAT
|
||||
} else {
|
||||
var hasPrev = Model.Credit.PrevNetAmount != null;
|
||||
@Raw(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Model.Credit.NetAmount, bold: true, noTopBorder: noBorder))
|
||||
if (hasPrev) {
|
||||
@Raw(FormatRow("Bisher berücksichtigt", -Model.Credit.PrevNetAmount, add: true))
|
||||
@Raw(FormatRow("Nettobetrag", Model.Credit.NetAmount - (Model.Credit.PrevNetAmount ?? 0)))
|
||||
}
|
||||
@Raw(FormatRow($"Mehrwertsteuer ({Model.Credit.Vat * 100} %)", Model.Credit.VatAmount, add: true))
|
||||
@Raw(FormatRow("Bruttobetrag", Model.Credit.GrossAmount, bold: true))
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
<tbody style="break-inside: avoid;">
|
||||
@{
|
||||
decimal penalty = 0;
|
||||
string? comment = null;
|
||||
}
|
||||
|
||||
@if (Model.MemberUnderDeliveries != null && Model.MemberUnderDeliveries.Count() > 0) {
|
||||
<tr class="small">
|
||||
<td colspan="2" style="padding-top: 5mm;">Anfallende Pönalen durch Unterlieferungen:</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
foreach (var u in Model.MemberUnderDeliveries) {
|
||||
@Raw(FormatRow($"{u.Name} ({u.Kg:N0} kg)", u.Amount, add: true, subCat: true))
|
||||
penalty += u.Amount;
|
||||
}
|
||||
penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
@if (Model.MemberTotalUnderDelivery != 0) {
|
||||
@Raw(FormatRow("Unterlieferung (GA)", Model.MemberTotalUnderDelivery, add: true));
|
||||
penalty += Model.MemberTotalUnderDelivery;
|
||||
}
|
||||
@if (Model.MemberAutoBusinessSharesAmount != 0) {
|
||||
@Raw(FormatRow($"Autom. Nachz. von GA ({Model.MemberAutoBusinessShares})", Model.MemberAutoBusinessSharesAmount, add: true));
|
||||
penalty += Model.MemberAutoBusinessSharesAmount;
|
||||
}
|
||||
@if (Model.CustomPayment?.Amount != null) {
|
||||
comment = Model.CustomPayment.Comment;
|
||||
string text = (Model.CustomPayment.Amount.Value < 0 ? "Weitere Abzüge" : "Weitere Zuschläge") + (comment != null ? "*" : "");
|
||||
if (comment != null && comment!.Length <= 30) {
|
||||
text = comment;
|
||||
comment = null;
|
||||
}
|
||||
@Raw(FormatRow(text, Model.CustomPayment.Amount.Value, add: true));
|
||||
penalty += Model.CustomPayment.Amount.Value;
|
||||
}
|
||||
|
||||
@if (Model.Credit == null) {
|
||||
@Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true))
|
||||
} else {
|
||||
var diff = Model.Credit.Modifiers - penalty;
|
||||
if (diff != 0) {
|
||||
@Raw(FormatRow(diff < 0 ? "Sonstige Abzüge" : "Sonstige Zuschläge", diff, add: true))
|
||||
}
|
||||
if (Model.Credit.PrevModifiers != null && Model.Credit.PrevModifiers != 0) {
|
||||
@Raw(FormatRow("Bereits berücksichtigte Abzüge", -Model.Credit.PrevModifiers, add: true))
|
||||
}
|
||||
@Raw(FormatRow("Auszahlungsbetrag", Model.Credit.Amount, bold: true))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@if (comment != null) {
|
||||
<p>* @comment</p>
|
||||
}
|
||||
<p>Überweisung erfolgt auf Konto @(Elwig.Helpers.Utils.FormatIban(Model.Member.Iban ?? "-")).</p>
|
||||
<div style="margin-top: 1em;">
|
||||
@if (Model.Text != null) {
|
||||
<p class="custom">@Model.Text</p>
|
||||
}
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,48 +0,0 @@
|
||||
|
||||
table.credit {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table.credit .mod {
|
||||
padding-left: 5mm;
|
||||
}
|
||||
|
||||
table.credit tbody tr:not(.first) {
|
||||
break-before: avoid;
|
||||
}
|
||||
|
||||
table.credit tbody tr:not(.last) {
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
table.credit tr:not(.first) td {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
table.credit tr.last td {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
table.credit-sum {
|
||||
width: 60%;
|
||||
margin-left: 40%;
|
||||
}
|
||||
|
||||
table.credit-sum tr.sum,
|
||||
table.credit-sum tr .sum {
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
table.credit-sum tr.sum td,
|
||||
table.credit-sum td.sum {
|
||||
padding-top: 1mm !important;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-style: italic;
|
||||
font-size: 8pt;
|
||||
width: 56mm;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
margin: 2mm 4mm;
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
using Elwig.Models.Dtos;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class DeliveryAncmtList : Document {
|
||||
@@ -7,15 +11,58 @@ namespace Elwig.Documents {
|
||||
public new static string Name => "Anmeldeliste";
|
||||
|
||||
public string Filter;
|
||||
public IEnumerable<DeliveryAncmtListRow> Announcements;
|
||||
public List<DeliveryAncmtListRow> Announcements;
|
||||
|
||||
public DeliveryAncmtList(string filter, IEnumerable<DeliveryAncmtListRow> announcements) : base($"{Name} {filter}") {
|
||||
public DeliveryAncmtList(string filter, IEnumerable<DeliveryAncmtListRow> announcements) :
|
||||
base($"{Name} {filter}") {
|
||||
Filter = filter;
|
||||
Announcements = announcements;
|
||||
Announcements = [.. announcements];
|
||||
}
|
||||
|
||||
public DeliveryAncmtList(string filter, DeliveryAncmtListData data) :
|
||||
this(filter, data.Rows) {
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(new KernedParagraph(Name, 24)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 2, 0));
|
||||
doc.Add(new KernedParagraph(Filter, 14)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 2, 0));
|
||||
doc.Add(NewAncmtTable(Announcements));
|
||||
}
|
||||
|
||||
protected Table NewAncmtTable(List<DeliveryAncmtListRow> ancmts) {
|
||||
var tbl = new Table(ColsMM(15, 12, 50, 25, 38, 11, 14), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Datum", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("MgNr.", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Mitglied", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Ort", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Sorte", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Anmldg.", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Menge"))
|
||||
.AddHeaderCell(NewTh("[kg]"));
|
||||
|
||||
foreach (var a in ancmts) {
|
||||
tbl.AddCell(NewTd($"{a.Date:dd.MM.yyyy}", 8))
|
||||
.AddCell(NewTd($"{a.MgNr}", right: true))
|
||||
.AddCell(NewTd(a.AdministrativeName))
|
||||
.AddCell(NewTd(a.DefaultKg, 8))
|
||||
.AddCell(NewTd(a.Variety))
|
||||
.AddCell(NewTd(a.Status ?? "-", 8, center: true))
|
||||
.AddCell(NewTd($"{a.Weight:N0}", right: true));
|
||||
}
|
||||
|
||||
tbl.AddCell(NewTd("Gesamt:", colspan: 2, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"Anmeldungen: {ancmts.Count:N0}", colspan: 3, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"{ancmts.Sum(a => a.Weight):N0}", colspan: 2, right: true, bold: true, borderTop: true));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.DeliveryAncmtList>
|
||||
@model Elwig.Documents.DeliveryAncmtList
|
||||
@{ Layout = "Document"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryAncmtList.css" />
|
||||
<main>
|
||||
<h1>Anmeldeliste</h1>
|
||||
<h2>@Model.Filter</h2>
|
||||
<table class="announcement-list">
|
||||
<colgroup>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 12mm;"/>
|
||||
<col style="width: 50mm;"/>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 38mm;"/>
|
||||
<col style="width: 11mm;"/>
|
||||
<col style="width: 14mm;"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">Datum</th>
|
||||
<th rowspan="2">MgNr.</th>
|
||||
<th rowspan="2" style="text-align: left;">Mitglied</th>
|
||||
<th rowspan="2" style="text-align: left;">Ort</th>
|
||||
<th rowspan="2" style="text-align: left;">Sorte</th>
|
||||
<th rowspan="2">Anmldg.</th>
|
||||
<th>Menge</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="unit">[kg]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var a in Model.Announcements) {
|
||||
<tr>
|
||||
<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>
|
||||
</tr>
|
||||
}
|
||||
<tr class="sum bold">
|
||||
<td colspan="2">Gesamt:</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>
|
||||
</table>
|
||||
</main>
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 14pt;
|
||||
margin-top: 2mm;
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models.Dtos;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class DeliveryConfirmation : BusinessDocument {
|
||||
@@ -25,5 +30,124 @@ namespace Elwig.Documents {
|
||||
MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult();
|
||||
MemberStats = AppDbContext.GetMemberStats(Season.Year, m.MgNr).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
var firstDay = Data.Rows.MinBy(r => r.Date)?.Date;
|
||||
var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date;
|
||||
Aside?.AddCell(NewAsideCell("Saison", 2))
|
||||
.AddCell(NewAsideCell("Lieferungen:", isName: true)).AddCell(NewAsideCell($"{Data.Rows.DistinctBy(r => r.LsNr).Count():N0} (Teil-Lfrg.: {Data.RowNum:N0})"))
|
||||
.AddCell(NewAsideCell("Zeitraum:", isName: true)).AddCell(NewAsideCell(firstDay == null || lastDay == null ? "-" : firstDay == lastDay ? $"{firstDay:dd.MM.} (1 Tag)" : $"{firstDay:dd.MM.}\u2013{lastDay:dd.MM.} ({lastDay?.DayNumber - firstDay?.DayNumber + 1:N0} Tage)"))
|
||||
.AddCell(NewAsideCell("Menge:", isName: true)).AddCell(NewAsideCell($"{MemberStats.Sum(s => s.Weight):N0} kg"));
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
|
||||
doc.Add(NewDeliveryListTable(Data));
|
||||
doc.Add(NewWeightsTable(MemberStats)
|
||||
.SetMarginTopMM(10).SetKeepTogether(true));
|
||||
doc.Add(NewBucketTable(Season, MemberBuckets, includePayment: true)
|
||||
.SetMarginTopMM(10).SetKeepTogether(true));
|
||||
|
||||
if (Text != null) {
|
||||
doc.Add(new KernedParagraph(Text, 10)
|
||||
.SetMarginTop(20).SetKeepTogether(true));
|
||||
}
|
||||
}
|
||||
|
||||
protected Table NewDeliveryListTable(DeliveryConfirmationDeliveryData data) {
|
||||
var tbl = new Table(ColsMM(25, 7, 39, 14, 11, 11, 15, 12, 14, 3, 14), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Pos.", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Sorte/Attribut/Bewirtschaftung\nZu-/Abschlag", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Qual.", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Gradation", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Flächenbindung", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Menge"))
|
||||
.AddHeaderCell(NewTh("gerebelt", rowspan: 3, rotated: true))
|
||||
.AddHeaderCell(NewTh("Davon\nabzuwerten"))
|
||||
.AddHeaderCell(NewTh("[°Oe]"))
|
||||
.AddHeaderCell(NewTh("[°KMW]"))
|
||||
.AddHeaderCell(NewTh("[kg]", colspan: 2))
|
||||
.AddHeaderCell(NewTh("[kg]"))
|
||||
.AddHeaderCell(NewTh("[kg]"));
|
||||
|
||||
var lastVariety = "";
|
||||
foreach (var p in data.Rows) {
|
||||
var sub = new Table(ColsMM(25, 7, 39, 14, 11, 11, 15, 12, 14, 3, 14))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetKeepTogether(true);
|
||||
if (lastVariety != "" && lastVariety != p.Variety) {
|
||||
sub.SetBorderTop(new SolidBorder(BorderThickness));
|
||||
}
|
||||
var attr = p.Attribute != null || p.Cultivation != null;
|
||||
var rows = Math.Max(p.Buckets.Length, 1 + (attr ? 1 : 0) + p.Modifiers.Length);
|
||||
for (int i = 0; i < rows; i++) {
|
||||
if (i == 0) {
|
||||
sub.AddCell(NewTd(p.LsNr))
|
||||
.AddCell(NewTd($"{p.DPNr:N0}", center: true))
|
||||
.AddCell(NewTd(p.Variety))
|
||||
.AddCell(NewTd(p.QualId, center: true))
|
||||
.AddCell(NewTd($"{p.Gradation.Oe:N0}", center: true))
|
||||
.AddCell(NewTd($"{p.Gradation.Kmw:N1}", center: true));
|
||||
} else if (i == 1 && attr) {
|
||||
sub.AddCell(NewCell(colspan: 2))
|
||||
.AddCell(NewTd($"{p.Attribute}{(p.Attribute != null && p.Cultivation != null ? " / " : "")}{p.Cultivation}", 8, colspan: 2)
|
||||
.SetPaddingsMM(0.125f, 1, 0.125f, 1))
|
||||
.AddCell(NewCell(colspan: 2));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 2));
|
||||
if (i - (rows - p.Modifiers.Length) < p.Modifiers.Length) {
|
||||
sub.AddCell(NewTd(p.Modifiers[i - (rows - p.Modifiers.Length)], 8, colspan: 2)
|
||||
.SetPaddingsMM(0.125f, 0, 0.125f, 5));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 2));
|
||||
}
|
||||
sub.AddCell(NewCell(colspan: 2));
|
||||
}
|
||||
|
||||
if (i < p.Buckets.Length) {
|
||||
var bucket = p.Buckets[i];
|
||||
sub.AddCell(NewTd($"{bucket.Name}:", 8).SetHeight(10).SetPaddingsMM(0.125f, 0, 0.125f, 0));
|
||||
sub.AddCell(NewTd($"{bucket.Value:N0}", right: true));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 2));
|
||||
}
|
||||
|
||||
if (i == p.Buckets.Length - 1) {
|
||||
sub.AddCell(NewTd($"{p.Weight:N0}"));
|
||||
sub.AddCell(NewTd(p.IsNetWeight ? "\u2611" : "\u2610", 7, center: true).SetFont(SF).SetPadding(0));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 2));
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
sub.AddCell(NewCell(rowspan: rows));
|
||||
}
|
||||
lastVariety = p.Variety;
|
||||
}
|
||||
tbl.AddCell(new Cell(1, 11).SetPadding(0).SetBorder(Border.NO_BORDER).Add(sub));
|
||||
}
|
||||
|
||||
tbl.AddCell(NewTd("Gesamt:", 12, colspan: 7, bold: true, borderTop: true)
|
||||
.SetPaddingsMM(1, 1, 0, 1))
|
||||
.AddCell(NewTd($"{data.Rows.Sum(p => p.Weight):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true)
|
||||
.SetPaddingsMM(1, 1, 0, 1))
|
||||
.AddCell(NewTd(colspan: 2, borderTop: true)
|
||||
.SetPaddingsMM(1, 1, 0, 1))
|
||||
.AddCell(NewTd("Davon abgewertet:", 10, colspan: 7)
|
||||
.SetPaddingsMM(0, 1, 0.5f, 1))
|
||||
.AddCell(NewTd($"{data.Rows.Where(p => p.IsDepreciated).Sum(p => p.Weight):N0}", 10, colspan: 2, right: true)
|
||||
.SetPaddingsMM(0, 1, 0.5f, 1))
|
||||
.AddCell(NewCell(colspan: 2)
|
||||
.SetPaddingsMM(0, 1, 0.5f, 1));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
@using Elwig.Documents
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.DeliveryConfirmation>
|
||||
@model Elwig.Documents.DeliveryConfirmation
|
||||
@{ Layout = "BusinessDocument"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryConfirmation.css" />
|
||||
<main>
|
||||
<h1>@Model.Title</h1>
|
||||
<table class="delivery-confirmation">
|
||||
<colgroup>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 6mm;"/>
|
||||
<col style="width: 23mm;"/>
|
||||
<col style="width: 16mm;"/>
|
||||
<col style="width: 17mm;"/>
|
||||
<col style="width: 10mm;"/>
|
||||
<col style="width: 10mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 12mm;"/>
|
||||
<col style="width: 14mm;"/>
|
||||
<col style="width: 3mm;"/>
|
||||
<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" style="text-align: left;">Sorte</th>
|
||||
<th rowspan="2" style="text-align: left;">Attr./Bewirt.</th>
|
||||
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
|
||||
<th colspan="2">Gradation</th>
|
||||
<th colspan="2">Flächenbindung</th>
|
||||
<th>Menge</th>
|
||||
<th rowspan="3" style="padding: 0;">
|
||||
<svg width="10" height="40" xmlns="http://www.w3.org/2000/svg">
|
||||
<text x="-40" y="4" transform="rotate(270)" font-size="8pt" font-style="italic" font-family="Times New Roman"
|
||||
style="text-anchor: start; alignment-baseline: middle;">
|
||||
gerebelt
|
||||
</text>
|
||||
</svg>
|
||||
</th>
|
||||
<th>Davon<br/>abzuwerten</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="unit">[°Oe]</th>
|
||||
<th class="unit narrow">[°KMW]</th>
|
||||
<th class="unit" colspan="2">[kg]</th>
|
||||
<th class="unit">[kg]</th>
|
||||
<th class="unit">[kg]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{
|
||||
var lastVariety = "";
|
||||
}
|
||||
@foreach (var p in Model.Data.Rows) {
|
||||
var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1);
|
||||
var first = true;
|
||||
@for (int i = 0; i < rows; i++) {
|
||||
<tr class="@(first ? "first" : "") @(p.Variety != lastVariety && lastVariety != "" ? "new": "") @(rows > i + 1 ? "last" : "")">
|
||||
@if (first) {
|
||||
<td rowspan="@rows">@p.LsNr</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>
|
||||
<td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
|
||||
<td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
|
||||
}
|
||||
@if (i > 0 && i <= p.Modifiers.Length) {
|
||||
<td colspan="3" class="small mod">@(p.Modifiers[i - 1])</td>
|
||||
} else if (i > 0) {
|
||||
<td colspan="3"></td>
|
||||
}
|
||||
@if (i < p.Buckets.Length) {
|
||||
var bucket = p.Buckets[i];
|
||||
<td class="small">@bucket.Name:</td>
|
||||
<td class="number">@($"{bucket.Value:N0}")</td>
|
||||
} else {
|
||||
<td colspan="2"></td>
|
||||
}
|
||||
@if (i == p.Buckets.Length - 1) {
|
||||
<td class="number">@($"{p.Weight:N0}")</td>
|
||||
<td style="font-size: 7pt;">@(p.IsNetWeight ? "\u2611" : "\u2610")</td>
|
||||
} else {
|
||||
<td></td>
|
||||
<td></td>
|
||||
}
|
||||
@if (first) {
|
||||
<td rowspan="@rows" class="number"></td>
|
||||
first = false;
|
||||
}
|
||||
</tr>
|
||||
lastVariety = p.Variety;
|
||||
}
|
||||
}
|
||||
<tr class="sum bold">
|
||||
<td colspan="8">Gesamt:</td>
|
||||
<td colspan="2" class="number">@($"{Model.Data.Rows.Sum(p => p.Weight):N0}")</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@Raw(BusinessDocument.PrintSortenaufteilung(Model.MemberStats))
|
||||
@Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includePayment: true))
|
||||
<div style="margin-top: 2em;">
|
||||
@if (Model.Text != null) {
|
||||
<p class="custom comment">@Model.Text</p>
|
||||
}
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
table.delivery-confirmation .mod {
|
||||
padding-left: 5mm;
|
||||
}
|
||||
|
||||
table.delivery-confirmation tr:not(.first) {
|
||||
break-before: avoid;
|
||||
}
|
||||
|
||||
table.delivery-confirmation tr:not(.first) td {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
table.delivery-confirmation tr.last td {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
table.delivery-confirmation tr.sum {
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
table.delivery-confirmation tr.sum td {
|
||||
padding-top: 1mm;
|
||||
}
|
||||
|
||||
table.sortenaufteilung {
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
table.buckets {
|
||||
break-before: avoid;
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
using Elwig.Models.Dtos;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class DeliveryDepreciationList : Document {
|
||||
@@ -7,16 +11,98 @@ namespace Elwig.Documents {
|
||||
public new static string Name => "Abwertungsliste";
|
||||
|
||||
public string Filter;
|
||||
public IEnumerable<DeliveryJournalRow> Deliveries;
|
||||
public List<DeliveryJournalRow> Deliveries;
|
||||
|
||||
public DeliveryDepreciationList(string filter, IEnumerable<DeliveryJournalRow> deliveries) :
|
||||
base($"{Name} {filter}") {
|
||||
Filter = filter;
|
||||
Deliveries = deliveries;
|
||||
Deliveries = [.. deliveries];
|
||||
}
|
||||
|
||||
public DeliveryDepreciationList(string filter, DeliveryJournalData data) :
|
||||
this(filter, data.Rows) {
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(new KernedParagraph(Name, 24)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 2, 0));
|
||||
doc.Add(new KernedParagraph(Filter, 14)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 5, 0));
|
||||
doc.Add(NewJournalTable(Deliveries));
|
||||
}
|
||||
|
||||
protected Table NewJournalTable(List<DeliveryJournalRow> deliveries) {
|
||||
var tbl = new Table(ColsMM(25, 6, 20, 12, 38, 18, 12, 10, 10, 14), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Pos.", rowspan: 2).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("Datum", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Zeit", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Sorte", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Attr./Bewirt.", rowspan: 2, colspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Gradation", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Menge"))
|
||||
.AddHeaderCell(NewTh("[°Oe]"))
|
||||
.AddHeaderCell(NewTh("[°KMW]").SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("[kg]"));
|
||||
|
||||
int? lastMember = null;
|
||||
foreach (var p in deliveries) {
|
||||
if (lastMember != p.MgNr) {
|
||||
var border = lastMember != null;
|
||||
var memberDeliveries = deliveries.Where(d => d.MgNr == p.MgNr).ToList();
|
||||
var memberKmw = Helpers.Utils.AggregateDeliveryPartsKmw(memberDeliveries);
|
||||
var memberOe = Helpers.Utils.KmwToOe(memberKmw);
|
||||
tbl.AddCell(NewTd($"{p.MgNr}, {p.AdministrativeName}", colspan: 5, borderTop: border)
|
||||
.SetFont(BI))
|
||||
.AddCell(NewTd("Teil-Lfrg.:", borderTop: border, bold: true))
|
||||
.AddCell(NewTd($"{memberDeliveries.Count:N0}", right: true, borderTop: border, bold: true))
|
||||
.AddCell(NewTd($"{memberOe:N0}", center: true, borderTop: border, bold: true))
|
||||
.AddCell(NewTd($"{memberKmw:N1}", center: true, borderTop: border, bold: true))
|
||||
.AddCell(NewTd($"{memberDeliveries.Sum(p => p.Weight):N0}", right: true, borderTop: border, bold: true));
|
||||
}
|
||||
|
||||
tbl.AddCell(NewTd(p.LsNr))
|
||||
.AddCell(NewTd($"{p.Pos:N0}", center: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd($"{p.Date:dd.MM.yyyy}", center: true))
|
||||
.AddCell(NewTd($"{p.Time:HH:mm}", center: true).SetPadding(0))
|
||||
.AddCell(NewTd(p.Variety))
|
||||
.AddCell(NewTd($"{p.Attribute}{(p.Attribute != null && p.Cultivation != null ? " / " : "")}{p.Cultivation}", colspan: 2))
|
||||
.AddCell(NewTd($"{p.Oe:N0}", center: true))
|
||||
.AddCell(NewTd($"{p.Kmw:N1}", center: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd($"{p.Weight:N0}", right: true));
|
||||
lastMember = p.MgNr;
|
||||
}
|
||||
|
||||
var branches = deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray();
|
||||
if (branches.Length > 1) {
|
||||
foreach (var b in branches) {
|
||||
var border = branches[0] == b;
|
||||
var branchDeliveries = deliveries.Where(d => d.DeliveryBranch == b).ToList();
|
||||
var branchKmw = Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries);
|
||||
var branchOe = Helpers.Utils.KmwToOe(branchKmw);
|
||||
tbl.AddCell(NewTd($"{b}:", colspan: 2, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"(Teil-)Lieferungen: {branchDeliveries.DistinctBy(p => p.LsNr).Count():N0} ({branchDeliveries.Count:N0})", colspan: 5, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"{branchOe:N0}", center: true, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"{branchOe:N1}", center: true, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"{branchDeliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: border));
|
||||
}
|
||||
}
|
||||
|
||||
var kmw = Helpers.Utils.AggregateDeliveryPartsKmw(deliveries);
|
||||
var oe = Helpers.Utils.KmwToOe(kmw);
|
||||
tbl.AddCell(NewTd("Gesamt:", colspan: 2, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"(Teil-)Lieferungen: {deliveries.DistinctBy(p => p.LsNr).Count():N0} ({deliveries.Count:N0})", colspan: 5, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"{oe:N0}", center: true, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"{kmw:N1}", center: true, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"{deliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: true));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.DeliveryDepreciationList>
|
||||
@model Elwig.Documents.DeliveryDepreciationList
|
||||
@{ Layout = "Document"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\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>Menge</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>
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 14pt;
|
||||
margin-top: 2mm;
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
using Elwig.Models.Dtos;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class DeliveryJournal : Document {
|
||||
@@ -7,16 +11,84 @@ namespace Elwig.Documents {
|
||||
public new static string Name => "Lieferjournal";
|
||||
|
||||
public string Filter;
|
||||
public IEnumerable<DeliveryJournalRow> Deliveries;
|
||||
public List<DeliveryJournalRow> Deliveries;
|
||||
|
||||
public DeliveryJournal(string filter, IEnumerable<DeliveryJournalRow> deliveries) :
|
||||
base($"{Name} {filter}") {
|
||||
Filter = filter;
|
||||
Deliveries = deliveries;
|
||||
Deliveries = [.. deliveries];
|
||||
}
|
||||
|
||||
public DeliveryJournal(string filter, DeliveryJournalData data) :
|
||||
this(filter, data.Rows) {
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(new KernedParagraph(Name, 24)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 2, 0));
|
||||
doc.Add(new KernedParagraph(Filter, 14)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 5, 0));
|
||||
doc.Add(NewJournalTable(Deliveries));
|
||||
}
|
||||
|
||||
protected Table NewJournalTable(List<DeliveryJournalRow> deliveries) {
|
||||
var tbl = new Table(ColsMM(25, 6, 15, 8, 11, 38, 28, 10, 10, 14), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Pos.", rowspan: 2).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("Datum", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Zeit", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("MgNr.", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Mitglied", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Sorte", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Gradation", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Menge"))
|
||||
.AddHeaderCell(NewTh("[°Oe]"))
|
||||
.AddHeaderCell(NewTh("[°KMW]").SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("[kg]"));
|
||||
|
||||
foreach (var p in deliveries) {
|
||||
tbl.AddCell(NewTd(p.LsNr))
|
||||
.AddCell(NewTd($"{p.Pos:N0}", center: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd($"{p.Date:dd.MM.yyyy}", 8, center: true))
|
||||
.AddCell(NewTd($"{p.Time:HH:mm}", 8, center: true).SetPadding(0))
|
||||
.AddCell(NewTd($"{p.MgNr}", right: true))
|
||||
.AddCell(NewTd(p.AdministrativeName, 8))
|
||||
.AddCell(NewTd(p.Variety, 8))
|
||||
.AddCell(NewTd($"{p.Oe:N0}", center: true))
|
||||
.AddCell(NewTd($"{p.Kmw:N1}", center: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd($"{p.Weight:N0}", right: true));
|
||||
}
|
||||
|
||||
var branches = deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray();
|
||||
if (branches.Length > 1) {
|
||||
foreach (var b in branches) {
|
||||
var border = branches[0] == b;
|
||||
var branchDeliveries = deliveries.Where(d => d.DeliveryBranch == b).ToList();
|
||||
var branchKmw = Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries);
|
||||
var branchOe = Helpers.Utils.KmwToOe(branchKmw);
|
||||
tbl.AddCell(NewTd($"{b}:", colspan: 2, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"(Teil-)Lieferungen: {branchDeliveries.DistinctBy(p => p.LsNr).Count():N0} ({branchDeliveries.Count:N0})", colspan: 5, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"{branchOe:N0}", center: true, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"{branchOe:N1}", center: true, bold: true, borderTop: border))
|
||||
.AddCell(NewTd($"{branchDeliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: border));
|
||||
}
|
||||
}
|
||||
|
||||
var kmw = Helpers.Utils.AggregateDeliveryPartsKmw(deliveries);
|
||||
var oe = Helpers.Utils.KmwToOe(kmw);
|
||||
tbl.AddCell(NewTd("Gesamt:", colspan: 2, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"(Teil-)Lieferungen: {deliveries.DistinctBy(p => p.LsNr).Count():N0} ({deliveries.Count:N0})", colspan: 5, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"{oe:N0}", center: true, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"{kmw:N1}", center: true, bold: true, borderTop: true))
|
||||
.AddCell(NewTd($"{deliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: true));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.DeliveryJournal>
|
||||
@model Elwig.Documents.DeliveryJournal
|
||||
@{ Layout = "Document"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryJournal.css"/>
|
||||
<main>
|
||||
<h1>Lieferjournal</h1>
|
||||
<h2>@Model.Filter</h2>
|
||||
<table class="journal">
|
||||
<colgroup>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 6mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 8mm;"/>
|
||||
<col style="width: 11mm;"/>
|
||||
<col style="width: 38mm;"/>
|
||||
<col style="width: 28mm;"/>
|
||||
<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">MgNr.</th>
|
||||
<th rowspan="2" style="text-align: left;">Mitglied</th>
|
||||
<th rowspan="2" style="text-align: left;">Sorte</th>
|
||||
<th colspan="2">Gradation</th>
|
||||
<th>Menge</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="unit">[°Oe]</th>
|
||||
<th class="unit narrow">[°KMW]</th>
|
||||
<th class="unit">[kg]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var p in Model.Deliveries) {
|
||||
<tr>
|
||||
<td>@p.LsNr</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>
|
||||
<td class="small">@p.AdministrativeName</td>
|
||||
<td class="small">@p.Variety</td>
|
||||
<td class="center">@($"{p.Oe:N0}")</td>
|
||||
<td class="center">@($"{p.Kmw:N1}")</td>
|
||||
<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);
|
||||
var oe = Elwig.Helpers.Utils.KmwToOe(kmw);
|
||||
}
|
||||
<td colspan="2">Gesamt:</td>
|
||||
<td colspan="5">(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>
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 14pt;
|
||||
margin-top: 2mm;
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Layout;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class DeliveryNote : BusinessDocument {
|
||||
@@ -17,19 +25,183 @@ namespace Elwig.Documents {
|
||||
// 3 - full
|
||||
public int DisplayStats = App.Client.ModeDeliveryNoteStats;
|
||||
|
||||
public DeliveryNote(Delivery d, AppDbContext? ctx = null) : base($"{Name} Nr. {d.LsNr}", d.Member) {
|
||||
public DeliveryNote(Delivery d, AppDbContext? ctx = null) :
|
||||
base($"{Name} Nr. {d.LsNr}", d.Member) {
|
||||
UseBillingAddress = true;
|
||||
ShowDateAndLocation = true;
|
||||
Delivery = d;
|
||||
Aside = Aside.Replace("</table>", "") +
|
||||
$"<thead><tr><th colspan='2'>Lieferung</th></tr></thead><tbody>" +
|
||||
$"<tr><th>LS-Nr.:</th><td>{d.LsNr}</td></tr>" +
|
||||
$"<tr><th>Datum/Zeit:</th><td>{d.Date:dd.MM.yyyy} / {d.Time:HH:mm}</td></tr>" +
|
||||
$"<tr><th>Zweigstelle:</th><td>{d.Branch.Name}</td></tr>" +
|
||||
$"</tbody></table>";
|
||||
Text = App.Client.TextDeliveryNote;
|
||||
DocumentId = d.LsNr;
|
||||
IsDoublePaged = true;
|
||||
MemberBuckets = ctx?.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult() ?? [];
|
||||
}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
Aside?.AddCell(NewAsideCell("Lieferung", 2))
|
||||
.AddCell(NewAsideCell("LS-Nr.:", isName: true)).AddCell(NewAsideCell(Delivery.LsNr))
|
||||
.AddCell(NewAsideCell("Datum/Zeit:", isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}"))
|
||||
.AddCell(NewAsideCell("Zweigstelle:", isName: true)).AddCell(NewAsideCell(Delivery.Branch.Name));
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
|
||||
doc.Add(NewDeliveryTable());
|
||||
if (Delivery.Comment != null) {
|
||||
doc.Add(new KernedParagraph($"Anmerkung zur Lieferung: {Delivery.Comment}", 10).SetMarginsMM(5, 0, 0, 0));
|
||||
}
|
||||
if (DisplayStats > 0) {
|
||||
doc.Add(NewBucketTable(Delivery.Season, MemberBuckets, isTiny: true,
|
||||
filter: DisplayStats > 2 ? null : DisplayStats == 1 ? [] : Delivery.Parts.Select(p => p.SortId).Distinct().ToList())
|
||||
.SetKeepTogether(true)
|
||||
.SetMarginsMM(5, 0, 0, 0));
|
||||
}
|
||||
|
||||
var sig = new Div().SetWidth(165 * PtInMM);
|
||||
if (Text != null)
|
||||
sig.Add(new KernedParagraph(Regex.Replace(Text, @"\s+", " "), 10).SetTextAlignment(TextAlignment.JUSTIFIED).SetSpacingRatio(1));
|
||||
sig.Add(new Table(ColsMM(15, 50, 35, 50, 15)).SetMarginsMM(18, 0, 2, 0)
|
||||
.AddCell(NewTd())
|
||||
.AddCell(NewTd("Genossenschaft", center: true, borderTop: true).SetPaddingTopMM(1))
|
||||
.AddCell(NewTd())
|
||||
.AddCell(NewTd("Mitglied", center: true, borderTop: true).SetPaddingTopMM(1))
|
||||
.AddCell(NewTd()));
|
||||
|
||||
var renderer = sig.CreateRendererSubTree();
|
||||
renderer.SetParent(doc.GetRenderer());
|
||||
var layoutResult = renderer.Layout(new LayoutContext(new LayoutArea(1, pdf.GetDefaultPageSize())));
|
||||
float sigHeight = layoutResult.GetOccupiedArea().GetBBox().GetHeight();
|
||||
doc.Add(new Div().SetWidth(165 * PtInMM).SetHeight(sigHeight));
|
||||
|
||||
var size = pdf.GetDefaultPageSize();
|
||||
doc.Add(sig.SetFixedPosition(
|
||||
size.GetLeft() + doc.GetLeftMargin(),
|
||||
size.GetBottom() + doc.GetBottomMargin(),
|
||||
size.GetWidth() - doc.GetLeftMargin() - doc.GetRightMargin())); ;
|
||||
}
|
||||
|
||||
protected Cell NewDeliveryMainTd(string? text, int rowspan = 1, int colspan = 1, bool center = false, bool right = false) {
|
||||
return NewTd(text, 12, rowspan: rowspan, colspan: colspan, bold: true, center: center, right: right)
|
||||
.SetPaddingTopMM(2);
|
||||
}
|
||||
|
||||
protected Table NewDeliveryTable() {
|
||||
var tbl = new Table(ColsMM(10, 21, 25, 19.5, 19.5, 30, 12.5, 12.5, 15), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorder(Border.NO_BORDER).SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Pos.", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Sorte", colspan: 2, rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Attribut", colspan: 2, rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Qualitätsstufe", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Gradation", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Menge"))
|
||||
.AddHeaderCell(NewTh("[°Oe]", 8))
|
||||
.AddHeaderCell(NewTh("[°KMW]", 8))
|
||||
.AddHeaderCell(NewTh("[kg]", 8));
|
||||
|
||||
foreach (var part in Delivery.Parts.OrderBy(p => p.DPNr)) {
|
||||
var sub = new Table(ColsMM(10, 21, 25, 19.5, 19.5, 30, 12.5, 12.5, 15), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
int rowspan = 1 + (part.Cultivation != null ? 1 : 0) + 1 + part.Modifiers.Count() + 1 + (part.Comment != null ? 1 : 0) + (part.Temperature != null || part.Acid != null ? 1 : 0);
|
||||
sub.AddCell(NewDeliveryMainTd($"{part.DPNr:N0}", center: true))
|
||||
.AddCell(NewDeliveryMainTd(part.Variety.Name, colspan: 2))
|
||||
.AddCell(NewDeliveryMainTd(part.Attribute?.Name, colspan: 2))
|
||||
.AddCell(NewDeliveryMainTd(part.Quality.Name))
|
||||
.AddCell(NewDeliveryMainTd($"{part.Oe:N0}", center: true))
|
||||
.AddCell(NewDeliveryMainTd($"{part.Kmw:N1}", center: true))
|
||||
.AddCell(NewDeliveryMainTd($"{part.Weight:N0}", center: true));
|
||||
|
||||
if (part.Cultivation != null) {
|
||||
var cult = new KernedParagraph(8);
|
||||
cult.Add(Italic("Bewirtschaftung:")).Add(Normal(" " + part.Cultivation.Name + (part.Cultivation.Description != null ? $" ({part.Cultivation.Description})" : "")));
|
||||
sub.AddCell(NewTd())
|
||||
.AddCell(NewTd(cult, colspan: 5))
|
||||
.AddCell(NewTd(colspan: 3));
|
||||
}
|
||||
sub.AddCell(NewTd())
|
||||
.AddCell(NewTd(new KernedParagraph(8).Add(Italic("Herkunft:")).Add(Normal(" " + part.OriginString.Replace("\n ", "\n\u00a0"))), colspan: 5))
|
||||
.AddCell(NewTd(colspan: 3));
|
||||
if (part.Modifiers.Any()) {
|
||||
sub.AddCell(NewTd())
|
||||
.AddCell(NewTd("Zu-/Abschläge:", 8, italic: true));
|
||||
int i = 0, last = part.Modifiers.Count() - 1;
|
||||
foreach (var mod in part.Modifiers) {
|
||||
if (i > 0) sub.AddCell(NewCell(colspan: 2));
|
||||
sub.AddCell(NewTd(mod.Name, 8, bold: true, colspan: 3).SetPaddingsMM(i == 0 ? 0.5f : 0, 1, i == last ? 0.5f : 0, 1))
|
||||
.AddCell(NewTd(mod.PublicValueStr, 8).SetPaddingsMM(i == 0 ? 0.5f : 0, 1, i == last ? 0.5f : 0, 1))
|
||||
.AddCell(NewTd(colspan: 3));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
var weighing = new KernedParagraph(8);
|
||||
if (part.IsManualWeighing) {
|
||||
weighing.Add(Italic("Handwiegung " + (part.IsNetWeight ? "(gerebelt gewogen)" : "(nicht gerebelt gewogen)")));
|
||||
if (part.WeighingReason != null) {
|
||||
weighing.Add(Normal(", "))
|
||||
.Add(Italic("Begründung:"))
|
||||
.Add(Normal(" " + part.WeighingReason));
|
||||
}
|
||||
} else {
|
||||
var info = part.WeighingInfo;
|
||||
weighing.Add(Italic("Waage:"))
|
||||
.Add(Normal(" " + (part.ScaleId ?? "?") + ", "))
|
||||
.Add(Italic("ID:"))
|
||||
.Add(Normal(" " + (info.Id ?? "?")));
|
||||
if (info.Date != null || info.Time != null)
|
||||
weighing.Add(Normal(" \u2013 "));
|
||||
if (info.Time != null)
|
||||
weighing.Add(Normal($"{info.Time:HH:mm}"));
|
||||
if (info.Date != null)
|
||||
weighing.Add(Normal($", {info.Date:dd.MM.yyyy}"));
|
||||
if (info.Gross != null && info.Tare != null && info.Net != null) {
|
||||
weighing.Add("\n")
|
||||
.Add(Italic("Brutto:"))
|
||||
.Add(Normal($" {info.Gross:N0} kg \u2013 "))
|
||||
.Add(Italic("Tara:"))
|
||||
.Add(Normal($" {info.Tare:N0} kg \u2013 "))
|
||||
.Add(Italic("Netto:"))
|
||||
.Add(Normal($" {info.Net:N0} kg \u2013 "))
|
||||
.Add(Italic(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen"));
|
||||
} else {
|
||||
weighing.Add(" ")
|
||||
.Add(Italic(part.IsNetWeight ? "(gerebelt gewogen)" : "(nicht gerebelt gewogen)"));
|
||||
}
|
||||
}
|
||||
sub.AddCell(NewCell())
|
||||
.AddCell(NewCell(weighing, colspan: 5))
|
||||
.AddCell(NewCell(colspan: 3));
|
||||
if (part.Comment != null) {
|
||||
sub.AddCell(NewCell())
|
||||
.AddCell(NewCell(new KernedParagraph(8).Add(Italic("Anmerkung")).Add(Normal(" " + part.Comment)), colspan: 5))
|
||||
.AddCell(NewCell(colspan: 3));
|
||||
}
|
||||
if (part.Temperature != null || part.Acid != null) {
|
||||
var p = new KernedParagraph(8);
|
||||
if (part.Temperature != null)
|
||||
p.Add(Italic("Temperatur:")).Add(Normal($" {part.Temperature:N1} °C"));
|
||||
if (part.Temperature != null && part.Acid != null)
|
||||
p.Add(Normal(", "));
|
||||
if (part.Acid != null)
|
||||
p.Add(Italic("Säure")).Add(Normal($" {part.Acid:N1} g/l"));
|
||||
sub.AddCell(NewCell())
|
||||
.AddCell(NewCell(p, colspan: 5))
|
||||
.AddCell(NewCell(colspan: 3));
|
||||
}
|
||||
tbl.AddCell(new Cell(1, 9).SetPadding(0).SetBorder(Border.NO_BORDER).Add(sub));
|
||||
}
|
||||
|
||||
if (Delivery.Parts.Count > 1) {
|
||||
tbl.AddCell(NewTd("Gesamt:", 12, bold: true, borderTop: true, colspan: 6).SetPaddingsMM(1, 1, 1, 1))
|
||||
.AddCell(NewTd($"{Delivery.Oe:N0}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1))
|
||||
.AddCell(NewTd($"{Delivery.Kmw:N1}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1))
|
||||
.AddCell(NewTd($"{Delivery.Weight:N0}", 12, bold: true, right: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.DeliveryNote>
|
||||
@model Elwig.Documents.DeliveryNote
|
||||
@{ Layout = "BusinessDocument"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\DeliveryNote.css" />
|
||||
<main>
|
||||
<h1>@Model.Title</h1>
|
||||
<table class="delivery large">
|
||||
<colgroup>
|
||||
<col style="width: 10.00mm;"/>
|
||||
<col style="width: 21.00mm;"/>
|
||||
<col style="width: 25.00mm;"/>
|
||||
<col style="width: 19.50mm;"/>
|
||||
<col style="width: 19.50mm;"/>
|
||||
<col style="width: 30.00mm;"/>
|
||||
<col style="width: 12.50mm;"/>
|
||||
<col style="width: 12.50mm;"/>
|
||||
<col style="width: 15.00mm;"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="main center narrow" rowspan="2">Pos.</th>
|
||||
<th class="main" rowspan="2" colspan="2">Sorte</th>
|
||||
<th class="main" rowspan="2" colspan="2">Attribut</th>
|
||||
<th class="main" rowspan="2">Qualitätsstufe</th>
|
||||
<th colspan="2">Gradation</th>
|
||||
<th>Menge</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="unit">[°Oe]</th>
|
||||
<th class="unit narrow">[°KMW]</th>
|
||||
<th class="unit">[kg]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) {
|
||||
<tr class="main">
|
||||
<td class="center">@part.DPNr</td>
|
||||
<td colspan="2">@part.Variety.Name</td>
|
||||
<td colspan="2">@part.Attribute?.Name</td>
|
||||
<td>@part.Quality.Name</td>
|
||||
<td class="center">@($"{part.Oe:N0}")</td>
|
||||
<td class="center">@($"{part.Kmw:N1}")</td>
|
||||
<td class="number">@($"{part.Weight:N0}")</td>
|
||||
</tr>
|
||||
@if (part.Cultivation != null) {
|
||||
<tr><td></td><td><i>Bewirtschaftung:</i></td><td colspan="4"><b>
|
||||
@part.Cultivation.Name
|
||||
@if(part.Cultivation.Description != null) {
|
||||
@("(")@part.Cultivation.Description@(")")
|
||||
}
|
||||
</b></td></tr>
|
||||
}
|
||||
<tr><td></td><td colspan="5" style="white-space: pre;"><i>Herkunft:</i> @part.OriginString</td></tr>
|
||||
@if (part.Modifiers.Count() > 0) {
|
||||
var first = true;
|
||||
foreach (var mod in part.Modifiers) {
|
||||
<tr class="tight @(first ? "first" : "")"><td></td><td>@Raw(first ? "<i>Zu-/Abschläge:</i>" : "")</td><td colspan="3"><b>@mod.Name</b></td><td style="white-space: pre;">@mod.ValueStr</td></tr>
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
<tr><td></td><td colspan="5">
|
||||
@if (part.IsManualWeighing) {
|
||||
<i>Handwiegung @(part.IsNetWeight ? " (gerebelt gewogen)" : " (nicht gerebelt gewogen)")</i>@Raw(part.WeighingReason != null ? ", <i>Begründung:</i> " : "") @part.WeighingReason
|
||||
} else {
|
||||
var info = part.WeighingInfo;
|
||||
<i>Waage:</i> @(part.ScaleId ?? "?")@(", ") <i>ID:</i> @(info.Id ?? "?")
|
||||
@(info.Date != null || info.Time != null ? " – " : "")@(info.Time != null ? $"{info.Time:HH:mm}" : "")@(info.Date != null ? $", {info.Date:dd.MM.yyyy}" : "")
|
||||
@if (info.Gross != null && info.Tare != null && info.Net != null) {
|
||||
<br/><i>Brutto:</i> @($"{info.Gross:N0} kg")@(" – ") <i>Tara:</i> @($"{info.Tare:N0} kg")@(" – ") <i>Netto:</i> @($"{info.Net:N0} kg")@(" – ")@Raw(part.IsNetWeight ? "<i>gerebelt gewogen</i>" : "<i>nicht gerebelt gewogen</i>")
|
||||
} else {
|
||||
@Raw($" <i>({(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen")})</i>")
|
||||
}
|
||||
}
|
||||
</td></tr>
|
||||
@if (part.Comment != null) {
|
||||
<tr><td></td><td colspan="5"><i>Anmerkung:</i> @part.Comment</td></tr>
|
||||
}
|
||||
@if (part.Temperature != null || part.Acid != null) {
|
||||
<tr><td></td><td colspan="5">@Raw(part.Temperature != null ? $"<i>Temperatur:</i> {part.Temperature:N1} °C" : "")@(part.Temperature != null && part.Acid != null ? ", " : "")@Raw(part.Acid != null ? $"<i>Säure:</i> {part.Acid:N1} g/l" : "")</td></tr>
|
||||
}
|
||||
}
|
||||
@if (Model.Delivery.Parts.Count() > 1) {
|
||||
<tr class="main sum bold">
|
||||
<td colspan="6">Gesamt:</td>
|
||||
<td class="center">@($"{Model.Delivery.Oe:N0}")</td>
|
||||
<td class="center">@($"{Model.Delivery.Kmw:N1}")</td>
|
||||
<td class="number">@($"{Model.Delivery.Weight:N0}")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@if (Model.Delivery.Comment != null) {
|
||||
<p class="comment">Amerkung zur Lieferung: @Model.Delivery.Comment</p>
|
||||
}
|
||||
@if (Model.DisplayStats > 0) {
|
||||
@Raw(Model.PrintBucketTable(
|
||||
Model.Delivery.Season, Model.MemberBuckets, isTiny: true,
|
||||
filter: Model.DisplayStats > 2 ? null :
|
||||
Model.DisplayStats == 1 ? new List<string>() :
|
||||
Model.Delivery.Parts.Select(p => p.SortId).Distinct().ToList()
|
||||
))
|
||||
}
|
||||
</main>
|
||||
@for (int i = 0; i < 2; i++) {
|
||||
<div class="text @(i == 0 ? "hidden" : "bottom")">
|
||||
@if (Model.Text != null) {
|
||||
<p class="comment">@Model.Text</p>
|
||||
}
|
||||
<div class="signatures">
|
||||
<div>Genossenschaft</div>
|
||||
<div>Mitglied</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
main h1 {
|
||||
margin-bottom: 1.5em !important;
|
||||
}
|
||||
|
||||
table.delivery {
|
||||
margin-bottom: 5mm;
|
||||
}
|
||||
|
||||
table.delivery tr:not(.main) {
|
||||
break-before: avoid;
|
||||
}
|
||||
|
||||
table.delivery th.main {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.delivery tr.main td {
|
||||
font-weight: bold;
|
||||
padding-top: 2mm;
|
||||
}
|
||||
|
||||
table.delivery tbody tr:not(.main) td {
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
table.delivery tr.tight:not(.first) td {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
table.delivery tr.tight.first td {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
table.delivery tr.tight:has(+ tr:not(.tight)) td {
|
||||
padding-bottom: 0.5mm !important;
|
||||
}
|
||||
|
||||
table.delivery tr.sum td {
|
||||
padding-top: 1mm;
|
||||
}
|
||||
|
||||
table.delivery-note-stats {
|
||||
break-after: avoid;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
|
||||
.m1, .m2, .m3 {
|
||||
height: 0;
|
||||
width: 10mm;
|
||||
position: fixed;
|
||||
left: -25mm;
|
||||
border-top: var(--border-thickness) solid black;
|
||||
}
|
||||
.m1.r, .m2.r, .m3.r {
|
||||
left: initial;
|
||||
right: -20mm;
|
||||
}
|
||||
.m1 {top: 80mm;}
|
||||
.m2 {top: 123.5mm;}
|
||||
.m3 {top: 185mm;}
|
||||
|
||||
.page-break {
|
||||
break-before: page;
|
||||
}
|
||||
hr.page-break {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 20mm 35mm 25mm;
|
||||
|
||||
@bottom-center {
|
||||
content: element(page-footer);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen {
|
||||
body, header, .footer-wrapper {
|
||||
width: 210mm;
|
||||
}
|
||||
|
||||
header, .address-wrapper, aside, main {
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.m1, .m2, .m3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
height: 45mm;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 0 20mm 40mm 25mm;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.page::after {
|
||||
content: "Seite " counter(page) " von " counter(pages) !important;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
|
||||
main table {
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10mm;
|
||||
font-size: 10pt;
|
||||
}
|
||||
main table.large {font-size: 12pt;}
|
||||
main table.tiny {
|
||||
font-size: 8pt;
|
||||
margin-bottom: 5mm;
|
||||
}
|
||||
|
||||
main table.border {
|
||||
border: var(--border-thickness) solid black;
|
||||
}
|
||||
|
||||
main table tr {
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
main table th,
|
||||
main table td {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
padding: 0.5mm 1mm;
|
||||
font-weight: normal;
|
||||
}
|
||||
main table.small th,
|
||||
main table.small td,
|
||||
main table.tiny th,
|
||||
main table.tiny td {
|
||||
padding: 0.125mm 0.125mm;
|
||||
}
|
||||
|
||||
main table td[rowspan] {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
main table thead {
|
||||
font-size: 8pt;
|
||||
}
|
||||
main table.large thead {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
main table th {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
main table tbody {
|
||||
}
|
||||
|
||||
main table .small {
|
||||
font-size: 8pt;
|
||||
}
|
||||
main table .large {
|
||||
font-size: 12pt;
|
||||
}
|
||||
main table .tiny {
|
||||
font-size: 6pt;
|
||||
}
|
||||
|
||||
main table.number td,
|
||||
main table.number th {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
main table.number thead th,
|
||||
main table.number td,
|
||||
main table tbody td.number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
main table.center tbody td,
|
||||
main table tbody td.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
main table tbody th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
main table.cohere {
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
main table tr.subheading th,
|
||||
main table tr.subheading td {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
main table tr.subheading th {
|
||||
text-align: left;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
main table.small tr.subheading th,
|
||||
main table.small tr.subheading td,
|
||||
main table.tiny tr.subheading th,
|
||||
main table.tiny tr.subheading td {
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
main table tr.sectionheading {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
main table tr.sectionheading th {
|
||||
padding-top: 0.5mm;
|
||||
padding-bottom: 0.5mm;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
font-size: 10pt;
|
||||
border-top: var(--border-thickness) solid black;
|
||||
}
|
||||
|
||||
main table tr.header {
|
||||
border: var(--border-thickness) solid black;
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
main table tr.header th {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-size: 16pt;
|
||||
padding: 1mm 2mm;
|
||||
}
|
||||
|
||||
main table tr.spacing td,
|
||||
main table tr.spacing th {
|
||||
height: 5mm;
|
||||
}
|
||||
|
||||
main table tr.spacing ~ tr.header {
|
||||
break-before: avoid;
|
||||
}
|
||||
|
||||
main table.number thead tr:first-child th:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
main table tr.bold td {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
main table tr.sum,
|
||||
main table td.sum,
|
||||
main table tr.new,
|
||||
main table tr.border {
|
||||
border-top: var(--border-thickness) solid black;
|
||||
}
|
||||
|
||||
main table th.unit {
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
main table.number th.unit {
|
||||
padding-right: 2mm;
|
||||
}
|
||||
|
||||
main table th.narrow {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
main table .tborder {border-top: var(--border-thickness) solid black;}
|
||||
main table .lborder {border-left: var(--border-thickness) solid black;}
|
||||
main table .rborder {border-right: var(--border-thickness) solid black;}
|
||||
|
||||
main table .fleft {
|
||||
float: left;
|
||||
}
|
||||
|
||||
main tbody.sum tr:last-child {
|
||||
border-bottom: var(--border-thickness) solid black;
|
||||
}
|
||||
@@ -1,52 +1,64 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using Elwig.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elwig.Helpers.Printing;
|
||||
using iText.IO.Font;
|
||||
using iText.Kernel.Font;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Kernel.Pdf.Canvas;
|
||||
using iText.Kernel.Pdf.Event;
|
||||
using iText.Kernel.Pdf.Xobject;
|
||||
using iText.Kernel.Utils;
|
||||
using iText.Layout;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using MimeKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public abstract partial class Document : IDisposable {
|
||||
public class Document : IDisposable {
|
||||
|
||||
public static string Name => "Dokument";
|
||||
|
||||
protected static readonly double GenerationProportion = 0.125;
|
||||
public const float PtInMM = 2.8346456693f;
|
||||
public const float BorderThickness = 0.5f;
|
||||
|
||||
protected PdfFont NF = null!;
|
||||
protected PdfFont BF = null!;
|
||||
protected PdfFont IF = null!;
|
||||
protected PdfFont BI = null!;
|
||||
protected PdfFont SF = null!;
|
||||
|
||||
//protected readonly PdfFont NF = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN);
|
||||
//protected readonly PdfFont BF = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLD);
|
||||
//protected readonly PdfFont IF = PdfFontFactory.CreateFont(StandardFonts.TIMES_ITALIC);
|
||||
//protected readonly PdfFont BI = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLDITALIC);
|
||||
|
||||
protected TempFile? _pdfFile = null;
|
||||
protected string? _pdfPath;
|
||||
protected string? PdfPath => _pdfPath ?? _pdfFile?.FilePath;
|
||||
public int? TotalPages { get; private set; }
|
||||
public int? Pages => TotalPages / (DoublePaged ? 2 : 1);
|
||||
public int? Pages => TotalPages / (IsDoublePaged ? 2 : 1);
|
||||
|
||||
public bool ShowFoldMarks = App.Config.Debug;
|
||||
public bool DoublePaged = false;
|
||||
public bool IsDoublePaged = false;
|
||||
public bool IsPreview = false;
|
||||
private iText.Layout.Document? _doc;
|
||||
|
||||
public string DocumentsPath;
|
||||
public int CurrentNextSeason;
|
||||
public string? DocumentId;
|
||||
public string Title;
|
||||
public string Author;
|
||||
public string Header;
|
||||
public string Footer;
|
||||
public DateOnly Date;
|
||||
|
||||
public Document(string title) {
|
||||
var c = App.Client;
|
||||
DocumentsPath = App.DocumentsPath;
|
||||
CurrentNextSeason = Utils.CurrentNextSeason;
|
||||
Title = title;
|
||||
Author = c.NameFull;
|
||||
Header = $"<div class='name'>{c.Name}</div><div class='suffix'>{c.NameSuffix}</div><div class='type'>{c.NameTypeFull}</div>";
|
||||
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
|
||||
.Item(c.NameFull).NextLine()
|
||||
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
|
||||
.Item(c.EmailAddress != null ? $"<a href=\"mailto:{c.Name} {c.NameSuffix} <{c.EmailAddress}>\">{c.EmailAddress}</a>" : null)
|
||||
.Item(c.Website != null ? $"<a href=\"http://{c.Website}/\">{c.Website}</a>" : null)
|
||||
.Item("Betriebs-Nr.", c.LfbisNr).Item("Bio-KSt.", c.OrganicAuthority).NextLine()
|
||||
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
|
||||
.ToString();
|
||||
Author = App.Client.NameFull;
|
||||
Date = DateOnly.FromDateTime(Utils.Today);
|
||||
}
|
||||
|
||||
@@ -65,83 +77,171 @@ namespace Elwig.Documents {
|
||||
}
|
||||
|
||||
public static Document FromPdf(string path) {
|
||||
return new PdfDocument(path);
|
||||
return new RawPdfDocument(path);
|
||||
}
|
||||
|
||||
private async Task<string> Render() {
|
||||
string name;
|
||||
if (this is BusinessLetter) {
|
||||
name = "BusinessLetter";
|
||||
} else if (this is DeliveryNote) {
|
||||
name = "DeliveryNote";
|
||||
} else if (this is CreditNote) {
|
||||
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) {
|
||||
name = "DeliveryConfirmation";
|
||||
} else if (this is MemberDataSheet) {
|
||||
name = "MemberDataSheet";
|
||||
} else if (this is MemberList) {
|
||||
name = "MemberList";
|
||||
} else if (this is WineQualityStatistics) {
|
||||
name = "WineQualityStatistics";
|
||||
} else if (this is PaymentVariantSummary) {
|
||||
name = "PaymentVariantSummary";
|
||||
} else if (this is DeliveryAncmtList) {
|
||||
name = "DeliveryAncmtList";
|
||||
} else {
|
||||
throw new InvalidOperationException("Invalid document object");
|
||||
private class RawPdfDocument : Document {
|
||||
public RawPdfDocument(string pdfPath) :
|
||||
base(Path.GetFileNameWithoutExtension(pdfPath)) {
|
||||
_pdfPath = pdfPath;
|
||||
}
|
||||
return await Render(name);
|
||||
}
|
||||
|
||||
private async Task<string> Render(string name) {
|
||||
return await Html.CompileRenderAsync(name, this); ;
|
||||
public int Render(string path) {
|
||||
using var writer = new PdfWriter(path);
|
||||
return Render(writer);
|
||||
}
|
||||
|
||||
public async Task Generate(IProgress<double>? progress = null) {
|
||||
private int Render(PdfWriter writer) {
|
||||
NF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\times.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
BF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesbd.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
IF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesi.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
BI = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesbi.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
SF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\seguisym.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
NF.SetSubset(true);
|
||||
BF.SetSubset(true);
|
||||
IF.SetSubset(true);
|
||||
BI.SetSubset(true);
|
||||
SF.SetSubset(true);
|
||||
writer.SetCompressionLevel(CompressionConstants.BEST_COMPRESSION);
|
||||
writer.SetSmartMode(true);
|
||||
using var pdf = new PdfDocument(writer);
|
||||
pdf.GetDocumentInfo()
|
||||
.SetTitle(Title)
|
||||
.SetAuthor(Author)
|
||||
.SetCreator($"Elwig {App.Version}");
|
||||
var handler = new EventHandler(this);
|
||||
pdf.AddEventHandler(PdfDocumentEvent.START_PAGE, handler);
|
||||
pdf.AddEventHandler(PdfDocumentEvent.END_PAGE, handler);
|
||||
pdf.AddEventHandler(PdfDocumentEvent.START_DOCUMENT_CLOSING, handler);
|
||||
_doc = new iText.Layout.Document(pdf, iText.Kernel.Geom.PageSize.A4);
|
||||
try {
|
||||
_doc.SetFont(NF).SetFontSize(12);
|
||||
RenderHeader(_doc, pdf);
|
||||
RenderBody(_doc, pdf);
|
||||
var pageNum = pdf.GetNumberOfPages();
|
||||
return pageNum;
|
||||
} finally {
|
||||
_doc.Close();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { }
|
||||
|
||||
protected virtual void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { }
|
||||
|
||||
protected virtual Paragraph GetFooter() {
|
||||
return new KernedParagraph(App.Client.NameFull, 10);
|
||||
}
|
||||
|
||||
public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
|
||||
if (_pdfFile != null)
|
||||
return;
|
||||
progress?.Report(0.0);
|
||||
if (this is PdfDocument) {
|
||||
if (this is RawPdfDocument) {
|
||||
// nothing to do
|
||||
} else if (this is MergedDocument m) {
|
||||
using var tmpPdf = new TempFile("pdf");
|
||||
var pdf = new TempFile("pdf");
|
||||
var tmpHtmls = new List<TempFile>();
|
||||
var tmpFiles = new List<string>();
|
||||
var n = m.Documents.Count();
|
||||
int i = 0;
|
||||
foreach (var doc in m.Documents) {
|
||||
if (doc is PdfDocument) {
|
||||
tmpFiles.Add(doc.PdfPath!);
|
||||
continue;
|
||||
try {
|
||||
var pageNums = new List<int>();
|
||||
|
||||
using var writer = new PdfWriter(pdf.FilePath);
|
||||
using var mergedPdf = new PdfDocument(writer);
|
||||
var merger = new PdfMerger(mergedPdf);
|
||||
|
||||
PdfPage? letterheadPage = null;
|
||||
int letterheadInsertIndex = 0;
|
||||
int letterheadDocIndex = 0;
|
||||
|
||||
for (int i = 0; i < m.Documents.Count; i++) {
|
||||
if (cancelToken?.IsCancellationRequested ?? false)
|
||||
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
|
||||
var doc = m.Documents[i];
|
||||
int p0 = mergedPdf.GetNumberOfPages();
|
||||
|
||||
if (letterheadPage != null && doc is Letterhead) {
|
||||
if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) {
|
||||
mergedPdf.AddPage(letterheadPage);
|
||||
mergedPdf.AddNewPage();
|
||||
} else {
|
||||
mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage);
|
||||
mergedPdf.AddNewPage(letterheadInsertIndex + 2);
|
||||
}
|
||||
|
||||
pageNums[letterheadDocIndex] = 1;
|
||||
letterheadPage = null;
|
||||
}
|
||||
|
||||
if (doc is RawPdfDocument) {
|
||||
if (IsDoublePaged && doc is Letterhead) {
|
||||
using var reader = new PdfReader(doc.PdfPath);
|
||||
using var src = new PdfDocument(reader);
|
||||
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
|
||||
letterheadInsertIndex = p0;
|
||||
letterheadDocIndex = i;
|
||||
} else {
|
||||
using var reader = new PdfReader(doc.PdfPath);
|
||||
using var src = new PdfDocument(reader);
|
||||
merger.Merge(src, 1, src.GetNumberOfPages());
|
||||
}
|
||||
} else {
|
||||
int pageNum = doc.Render(tmpPdf.FilePath);
|
||||
if (IsDoublePaged && doc is Letterhead) {
|
||||
using var reader = new PdfReader(tmpPdf.FilePath);
|
||||
using var src = new PdfDocument(reader);
|
||||
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
|
||||
letterheadInsertIndex = p0;
|
||||
letterheadDocIndex = i;
|
||||
} else {
|
||||
using var reader = new PdfReader(tmpPdf.FilePath);
|
||||
using var src = new PdfDocument(reader);
|
||||
merger.Merge(src, 1, pageNum);
|
||||
}
|
||||
}
|
||||
|
||||
int p1 = mergedPdf.GetNumberOfPages();
|
||||
pageNums.Add(p1 - p0);
|
||||
|
||||
if (IsDoublePaged && doc is not Letterhead && mergedPdf.GetNumberOfPages() % 2 != 0) {
|
||||
if (letterheadPage != null) {
|
||||
mergedPdf.AddPage(letterheadPage);
|
||||
letterheadPage = null;
|
||||
} else {
|
||||
mergedPdf.AddNewPage();
|
||||
}
|
||||
}
|
||||
|
||||
progress?.Report(100.0 * (i + 1) / (m.Documents.Count + 1));
|
||||
}
|
||||
var tmpHtml = new TempFile("html");
|
||||
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
|
||||
tmpHtmls.Add(tmpHtml);
|
||||
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FileName);
|
||||
i++;
|
||||
progress?.Report(GenerationProportion * 100 * i / n);
|
||||
}
|
||||
progress?.Report(GenerationProportion * 100);
|
||||
var pages = await Pdf.Convert(tmpFiles, pdf.FileName, DoublePaged, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
|
||||
TotalPages = pages.Pages;
|
||||
foreach (var tmp in tmpHtmls) {
|
||||
tmp.Dispose();
|
||||
|
||||
if (letterheadPage != null) {
|
||||
if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) {
|
||||
mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf));
|
||||
mergedPdf.AddNewPage();
|
||||
} else {
|
||||
mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage.CopyTo(mergedPdf));
|
||||
mergedPdf.AddNewPage(letterheadInsertIndex + 2);
|
||||
}
|
||||
|
||||
pageNums[letterheadDocIndex] = 1;
|
||||
}
|
||||
|
||||
TotalPages = pageNums.Sum();
|
||||
} catch {
|
||||
pdf.Dispose();
|
||||
throw;
|
||||
}
|
||||
_pdfFile = pdf;
|
||||
} else {
|
||||
if (cancelToken?.IsCancellationRequested ?? false)
|
||||
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
|
||||
var pdf = new TempFile("pdf");
|
||||
using (var tmpHtml = new TempFile("html")) {
|
||||
await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
|
||||
progress?.Report(50.0);
|
||||
var pages = await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, DoublePaged);
|
||||
TotalPages = pages.Pages;
|
||||
try {
|
||||
TotalPages = Render(pdf.FilePath);
|
||||
} catch {
|
||||
pdf.Dispose();
|
||||
throw;
|
||||
}
|
||||
_pdfFile = pdf;
|
||||
}
|
||||
@@ -155,7 +255,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, DoublePaged);
|
||||
await Pdf.Print(PdfPath, copies, IsDoublePaged);
|
||||
}
|
||||
|
||||
public void Show() {
|
||||
@@ -173,14 +273,239 @@ namespace Elwig.Documents {
|
||||
};
|
||||
}
|
||||
|
||||
private class MergedDocument(IEnumerable<Document> docs) : Document("Mehrere Dokumente") {
|
||||
public IEnumerable<Document> Documents = docs;
|
||||
private class MergedDocument : Document {
|
||||
public List<Document> Documents;
|
||||
|
||||
public MergedDocument(IEnumerable<Document> docs) :
|
||||
base("Mehrere Dokumente") {
|
||||
Documents = [.. docs];
|
||||
IsDoublePaged = docs.Any(x => x.IsDoublePaged);
|
||||
}
|
||||
}
|
||||
|
||||
private class PdfDocument : Document {
|
||||
public PdfDocument(string pdfPath) :
|
||||
base(Path.GetFileNameWithoutExtension(pdfPath)) {
|
||||
_pdfPath = pdfPath;
|
||||
protected static Cell NewCell(Paragraph? p = null, int rowspan = 1, int colspan = 1, bool overflow = false) {
|
||||
var cell = new Cell(rowspan, colspan)
|
||||
.SetBorder(Border.NO_BORDER)
|
||||
.SetPaddingsMM(0.5f, 1, 0.5f, 1);
|
||||
if (p != null) {
|
||||
p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true);
|
||||
if (!overflow) p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN);
|
||||
cell.Add(p);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
protected Cell NewTh(string? text, float fontSize = 8, int rowspan = 1, int colspan = 1, bool left = false, bool rotated = false) {
|
||||
var p = new KernedParagraph(text ?? "", fontSize);
|
||||
if (rotated) p.SetRotationAngle(rotated ? 0.5 * Math.PI : 0);
|
||||
var cell = NewCell(p, rowspan: rowspan, colspan: colspan)
|
||||
.SetTextAlignment(left ? TextAlignment.LEFT : TextAlignment.CENTER)
|
||||
.SetVerticalAlignment(VerticalAlignment.MIDDLE)
|
||||
.SetFont(IF);
|
||||
if (rotated || !left) cell.SetPadding(0);
|
||||
return cell;
|
||||
}
|
||||
|
||||
protected Cell NewTd(string? text = null, float fontSize = 10, int rowspan = 1, int colspan = 1, bool center = false, bool right = false, bool bold = false, bool italic = false, bool borderTop = false, bool overflow = false) {
|
||||
return NewTd(new KernedParagraph(text ?? "", fontSize), rowspan: rowspan, colspan: colspan, center: center, right: right, bold: bold, italic: italic, borderTop: borderTop, overflow: overflow);
|
||||
}
|
||||
|
||||
protected Cell NewTd(KernedParagraph p, int rowspan = 1, int colspan = 1, bool center = false, bool right = false, bool bold = false, bool italic = false, bool borderTop = false, bool overflow = false) {
|
||||
return NewCell(p, rowspan: rowspan, colspan: colspan, overflow: overflow)
|
||||
.SetTextAlignment(center ? TextAlignment.CENTER : right ? TextAlignment.RIGHT : TextAlignment.LEFT)
|
||||
.SetVerticalAlignment(VerticalAlignment.MIDDLE)
|
||||
.SetBorderTop(borderTop ? new SolidBorder(BorderThickness) : Border.NO_BORDER)
|
||||
.SetFont(bold ? (italic ? BI : BF) : (italic ? IF : NF));
|
||||
}
|
||||
|
||||
public static float[] ColsMM(params double[] widths) {
|
||||
return [.. widths.Select(w => (float)w * PtInMM)];
|
||||
}
|
||||
|
||||
public Text Normal(string? text, float? fontSize = null) {
|
||||
var t = new Text(text ?? "").SetFont(NF);
|
||||
if (fontSize != null) t.SetFontSize(fontSize.Value);
|
||||
return t;
|
||||
}
|
||||
|
||||
public Text Bold(string? text, float? fontSize = null) {
|
||||
var t = new Text(text ?? "").SetFont(BF);
|
||||
if (fontSize != null) t.SetFontSize(fontSize.Value);
|
||||
return t;
|
||||
}
|
||||
|
||||
public Text Italic(string? text, float? fontSize = null) {
|
||||
var t = new Text(text ?? "").SetFont(IF);
|
||||
if (fontSize != null) t.SetFontSize(fontSize.Value);
|
||||
return t;
|
||||
}
|
||||
|
||||
public Text BoldItalic(string? text, float? fontSize = null) {
|
||||
var t = new Text(text ?? "").SetFont(BI);
|
||||
if (fontSize != null) t.SetFontSize(fontSize.Value);
|
||||
return t;
|
||||
}
|
||||
|
||||
private class EventHandler : AbstractPdfDocumentEventHandler {
|
||||
|
||||
private const float _fontSize = 10;
|
||||
private const float _placeholderWidth = 50 * PtInMM;
|
||||
|
||||
private readonly Document _doc;
|
||||
private readonly List<PdfFormXObject> _pageNumPlaceholders;
|
||||
|
||||
|
||||
public int NumberOfPages { get; private set; }
|
||||
|
||||
public EventHandler(Document doc) {
|
||||
_doc = doc;
|
||||
_pageNumPlaceholders = [];
|
||||
}
|
||||
|
||||
protected override void OnAcceptedEvent(AbstractPdfDocumentEvent evt) {
|
||||
if (evt.GetType() == PdfDocumentEvent.START_PAGE) {
|
||||
OnPageStart((PdfDocumentEvent)evt);
|
||||
} else if (evt.GetType() == PdfDocumentEvent.END_PAGE) {
|
||||
OnPageEnd((PdfDocumentEvent)evt);
|
||||
} else if (evt.GetType() == PdfDocumentEvent.START_DOCUMENT_CLOSING) {
|
||||
OnDocumentClose((PdfDocumentEvent)evt);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPageStart(PdfDocumentEvent evt) {
|
||||
var pdf = evt.GetDocument();
|
||||
var page = evt.GetPage();
|
||||
var pageNum = pdf.GetPageNumber(page);
|
||||
|
||||
if (pageNum == 1) {
|
||||
// first page
|
||||
if (_doc is BusinessDocument) {
|
||||
_doc._doc?.SetMarginsMM(90, 20, 35, 25);
|
||||
} else {
|
||||
_doc._doc?.SetMarginsMM(20, 20, 35, 25);
|
||||
}
|
||||
} else if (_doc.IsDoublePaged && (pageNum % 2) == 0) {
|
||||
// left page (= swapped)
|
||||
_doc._doc?.SetMarginsMM(20, 25, 25, 20);
|
||||
} else {
|
||||
// right page
|
||||
_doc._doc?.SetMarginsMM(20, 20, 25, 25);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPageEnd(PdfDocumentEvent evt) {
|
||||
var pdf = evt.GetDocument();
|
||||
var page = evt.GetPage();
|
||||
var pageNum = pdf.GetPageNumber(page);
|
||||
|
||||
var pageSize = page.GetPageSize();
|
||||
float leftX1 = pageSize.GetLeft() + 25 * PtInMM;
|
||||
float leftX2 = pageSize.GetLeft() + 20 * PtInMM;
|
||||
float rightX1 = pageSize.GetRight() - 20 * PtInMM;
|
||||
float rightX2 = pageSize.GetRight() - 25 * PtInMM;
|
||||
float footerY = pageSize.GetBottom() + 25 * PtInMM;
|
||||
float y1 = footerY + _fontSize;
|
||||
float y2 = footerY - _fontSize;
|
||||
|
||||
var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdf);
|
||||
using var canvas = new Canvas(pdfCanvas, pageSize);
|
||||
|
||||
var placeholder = new PdfFormXObject(new iText.Kernel.Geom.Rectangle(0, 0, _placeholderWidth, _fontSize));
|
||||
_pageNumPlaceholders.Add(placeholder);
|
||||
|
||||
var c = App.Client;
|
||||
var dateP = new KernedParagraph($"{_doc.Date:dddd, d. MMMM yyyy}", _fontSize).SetFont(_doc.NF);
|
||||
var centerP = new KernedParagraph(_doc.DocumentId ?? "", _fontSize).SetFont(_doc.IF);
|
||||
var pageNumP = new KernedParagraph(_fontSize).Add(new Image(placeholder)).SetFont(_doc.NF);
|
||||
|
||||
if (pageNum == 1) {
|
||||
// first page
|
||||
canvas.ShowTextAligned(dateP, leftX1, y1, TextAlignment.LEFT, VerticalAlignment.BOTTOM);
|
||||
canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, y1, TextAlignment.CENTER, VerticalAlignment.BOTTOM);
|
||||
canvas.ShowTextAligned(pageNumP, rightX1, y1, TextAlignment.RIGHT, VerticalAlignment.BOTTOM);
|
||||
|
||||
var footer = _doc.GetFooter();
|
||||
using var footerCanvas = new Canvas(page, pageSize);
|
||||
footerCanvas.Add(new Table(1).AddCell(new Cell().Add(footer).SetBorder(Border.NO_BORDER).SetPaddingsMM(1, 0, 0, 0))
|
||||
.SetFixedPositionMM(25, 0, 165).SetHeightMM(25)
|
||||
.SetFont(_doc.NF).SetFontSize(10)
|
||||
.SetTextAlignment(TextAlignment.CENTER)
|
||||
.SetBorder(Border.NO_BORDER)
|
||||
.SetBorderTop(new SolidBorder(BorderThickness)));
|
||||
} else if (_doc.IsDoublePaged && (pageNum % 2 == 0)) {
|
||||
// left page (= swapped)
|
||||
canvas.ShowTextAligned(pageNumP, leftX2, y2, TextAlignment.LEFT, VerticalAlignment.TOP);
|
||||
canvas.ShowTextAligned(centerP, (leftX2 + rightX2) / 2, y2, TextAlignment.CENTER, VerticalAlignment.TOP);
|
||||
canvas.ShowTextAligned(dateP, rightX2, y2, TextAlignment.RIGHT, VerticalAlignment.TOP);
|
||||
} else {
|
||||
// right page
|
||||
canvas.ShowTextAligned(dateP, leftX1, y2, TextAlignment.LEFT, VerticalAlignment.TOP);
|
||||
canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, y2, TextAlignment.CENTER, VerticalAlignment.TOP);
|
||||
canvas.ShowTextAligned(pageNumP, rightX1, y2, TextAlignment.RIGHT, VerticalAlignment.TOP);
|
||||
}
|
||||
|
||||
if (_doc.ShowFoldMarks) {
|
||||
var m1 = pageSize.GetTop() - 105 * PtInMM;
|
||||
var m2 = pageSize.GetTop() - 148.5 * PtInMM;
|
||||
var m3 = pageSize.GetTop() - 210 * PtInMM;
|
||||
pdfCanvas.SetLineWidth(BorderThickness);
|
||||
|
||||
pdfCanvas.MoveTo(0, m1);
|
||||
pdfCanvas.LineTo(10 * PtInMM, m1);
|
||||
pdfCanvas.MoveTo(pageSize.GetRight(), m1);
|
||||
pdfCanvas.LineTo(pageSize.GetRight() - 10 * PtInMM, m1);
|
||||
|
||||
pdfCanvas.MoveTo(0, m2);
|
||||
pdfCanvas.LineTo(7 * PtInMM, m2);
|
||||
pdfCanvas.MoveTo(pageSize.GetRight(), m2);
|
||||
pdfCanvas.LineTo(pageSize.GetRight() - 7 * PtInMM, m2);
|
||||
|
||||
pdfCanvas.MoveTo(0, m3);
|
||||
pdfCanvas.LineTo(10 * PtInMM, m3);
|
||||
pdfCanvas.MoveTo(pageSize.GetRight(), m3);
|
||||
pdfCanvas.LineTo(pageSize.GetRight() - 10 * PtInMM, m3);
|
||||
|
||||
pdfCanvas.ClosePathStroke();
|
||||
}
|
||||
|
||||
if (NumberOfPages > 0) {
|
||||
// FillPlaceholders() was already called
|
||||
FillPlaceholder(pdf, pageNum);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDocumentClose(PdfDocumentEvent evt) {
|
||||
var pdf = evt.GetDocument();
|
||||
var page = evt.GetPage();
|
||||
var pageNum = pdf.GetPageNumber(page);
|
||||
|
||||
// ...
|
||||
|
||||
FillPlaceholders(pdf);
|
||||
}
|
||||
|
||||
private void FillPlaceholders(PdfDocument pdf) {
|
||||
NumberOfPages = pdf.GetNumberOfPages();
|
||||
for (int i = 0; i < _pageNumPlaceholders.Count; i++)
|
||||
FillPlaceholder(pdf, i + 1);
|
||||
}
|
||||
|
||||
private void FillPlaceholder(PdfDocument pdf, int pageNum) {
|
||||
var placeholder = _pageNumPlaceholders[pageNum - 1];
|
||||
using var canvas = new Canvas(placeholder, pdf);
|
||||
if (_doc.IsDoublePaged && (pageNum % 2 == 0)) {
|
||||
// left page (= swapped)
|
||||
var p = new KernedParagraph(_fontSize).SetFont(_doc.NF);
|
||||
if (_doc.IsPreview) p.Add(_doc.Bold("(vorläufig) "));
|
||||
p.Add(_doc.Normal($"Seite {pageNum:N0} von {NumberOfPages:N0} "));
|
||||
canvas.ShowTextAligned(p, 0, 0, TextAlignment.LEFT);
|
||||
} else {
|
||||
// right page
|
||||
var p = new KernedParagraph(_fontSize).SetFont(_doc.NF)
|
||||
.Add(_doc.Normal($"Seite {pageNum:N0} von {NumberOfPages:N0}"));
|
||||
if (_doc.IsPreview) p.Add(_doc.Bold(" (vorläufig)"));
|
||||
canvas.ShowTextAligned(p, _placeholderWidth, 0, TextAlignment.RIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.Document>
|
||||
@model Elwig.Documents.Document
|
||||
<!DOCTYPE html>
|
||||
<html lang="de-AT">
|
||||
<head>
|
||||
<title>@Model.Title</title>
|
||||
<meta name="author" value="@Model.Author"/>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.css" />
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Page.css" />
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\Document.Table.css" />
|
||||
@if (Model.DoublePaged) {
|
||||
<style>
|
||||
@@page :left {
|
||||
margin: 25mm 25mm 35mm 20mm;
|
||||
@@bottom-center {
|
||||
content: element(page-footer-left);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
@if (Model.ShowFoldMarks) {
|
||||
<div class="m1"></div>
|
||||
<div class="m2"></div>
|
||||
<div class="m3"></div>
|
||||
<div class="m1 r"></div>
|
||||
<div class="m2 r"></div>
|
||||
<div class="m3 r"></div>
|
||||
}
|
||||
<div class="footer-wrapper">
|
||||
<div class="pre-footer">
|
||||
<span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
|
||||
<span class="doc-id">@Model.DocumentId</span>
|
||||
<span class="page"></span>
|
||||
</div>
|
||||
<footer>@Raw(Model.Footer)</footer>
|
||||
</div>
|
||||
@if (Model.DoublePaged) {
|
||||
<div class="footer-wrapper left">
|
||||
<div class="pre-footer">
|
||||
<span class="page"></span>
|
||||
<span class="doc-id">@Model.DocumentId</span>
|
||||
<span class="date">@($"{Model.Date:dddd, d. MMMM yyyy}")</span>
|
||||
</div>
|
||||
<footer>@Raw(Model.Footer)</footer>
|
||||
</div>
|
||||
}
|
||||
<header>@Raw(Model.Header)</header>
|
||||
<div class="spacing"></div>
|
||||
<div class="main-wrapper">
|
||||
@RenderBody()
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,113 +0,0 @@
|
||||
|
||||
:root {
|
||||
font-family: "Times New Roman", serif;
|
||||
line-height: 1;
|
||||
--border-thickness: 0.5pt;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
table td,
|
||||
table th {
|
||||
padding: 0.5mm 1mm;
|
||||
}
|
||||
|
||||
table th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header {
|
||||
height: 45mm;
|
||||
padding: 10mm 0 0 0;
|
||||
position: absolute;
|
||||
top: -25mm;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header .name {
|
||||
font-size: 18pt;
|
||||
margin-top: 8mm;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header .suffix {
|
||||
font-size: 14pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header .type {
|
||||
font-size: 12pt;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.footer-wrapper {
|
||||
position: running(page-footer);
|
||||
width: 165mm;
|
||||
/* for some reason the position without the following statement changes on the second page */
|
||||
border: var(--border-thickness) solid #00000000;
|
||||
}
|
||||
|
||||
.footer-wrapper.left {
|
||||
position: running(page-footer-left);
|
||||
}
|
||||
|
||||
.pre-footer {
|
||||
margin: 1em 0;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.pre-footer > * {
|
||||
display: inline-block;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.pre-footer > *:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pre-footer > *:nth-child(2) {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.pre-footer > *:last-child {
|
||||
text-align: right;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.pre-footer .page::after {
|
||||
content: "Seite 1 von 1";
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 10pt;
|
||||
border-top: var(--border-thickness) solid black;
|
||||
height: 25mm;
|
||||
padding-top: 1mm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: var(--border-thickness) solid black;
|
||||
margin: 5mm 0;
|
||||
}
|
||||
93
Elwig/Documents/Extensions.cs
Normal file
93
Elwig/Documents/Extensions.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using iText.Kernel.Geom;
|
||||
using iText.Layout;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public static class Extensions {
|
||||
|
||||
public static T SetFixedPositionMM<T>(this BlockElement<T> element, float left, float top, float width, Rectangle pageSize) where T : IElement {
|
||||
element.SetVerticalAlignment(VerticalAlignment.TOP);
|
||||
return element.SetFixedPosition(left * Document.PtInMM, pageSize.GetTop() - top * Document.PtInMM, width * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetFixedPositionMM<T>(this BlockElement<T> element, float left, float top, float width, float height, Rectangle pageSize) where T : IElement {
|
||||
element.SetHeight(height * Document.PtInMM);
|
||||
return element.SetFixedPosition(left * Document.PtInMM, pageSize.GetTop() - (top + height) * Document.PtInMM, width * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetFixedPositionMM<T>(this ElementPropertyContainer<T> element, float left, float bottom, float width) where T : IPropertyContainer {
|
||||
return element.SetFixedPosition(left * Document.PtInMM, bottom * Document.PtInMM, width * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetHeightMM<T>(this BlockElement<T> element, float height) where T : IElement {
|
||||
return element.SetHeight(height * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetPaddingTopMM<T>(this BlockElement<T> element, float top) where T : IElement {
|
||||
return element.SetPaddingTop(top * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetPaddingRightMM<T>(this BlockElement<T> element, float right) where T : IElement {
|
||||
return element.SetPaddingRight(right * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetPaddingBottomMM<T>(this BlockElement<T> element, float bottom) where T : IElement {
|
||||
return element.SetPaddingBottom(bottom * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetPaddingLeftMM<T>(this BlockElement<T> element, float left) where T : IElement {
|
||||
return element.SetPaddingLeft(left * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetPaddingsMM<T>(this BlockElement<T> element, float top, float right, float bottom, float left) where T : IElement {
|
||||
return element.SetPaddings(top * Document.PtInMM, right * Document.PtInMM, bottom * Document.PtInMM, left * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetMarginTopMM<T>(this BlockElement<T> element, float top) where T : IElement {
|
||||
return element.SetMarginTop(top * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetMarginRightMM<T>(this BlockElement<T> element, float right) where T : IElement {
|
||||
return element.SetMarginRight(right * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetMarginBottomMM<T>(this BlockElement<T> element, float bottom) where T : IElement {
|
||||
return element.SetMarginBottom(bottom * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetMarginLeftMM<T>(this BlockElement<T> element, float left) where T : IElement {
|
||||
return element.SetMarginLeft(left * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static T SetMarginsMM<T>(this BlockElement<T> element, float top, float right, float bottom, float left) where T : IElement {
|
||||
return element.SetMargins(top * Document.PtInMM, right * Document.PtInMM, bottom * Document.PtInMM, left * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static void SetTopMarginMM(this iText.Layout.Document element, float top) {
|
||||
element.SetTopMargin(top * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static void SetRightMarginMM(this iText.Layout.Document element, float right) {
|
||||
element.SetRightMargin(right * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static void SetBottomMarginMM(this iText.Layout.Document element, float bottom) {
|
||||
element.SetBottomMargin(bottom * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static void SetLeftMarginMM(this iText.Layout.Document element, float left) {
|
||||
element.SetLeftMargin(left * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static void SetMarginsMM(this iText.Layout.Document element, float top, float right, float bottom, float left) {
|
||||
element.SetMargins(top * Document.PtInMM, right * Document.PtInMM, bottom * Document.PtInMM, left * Document.PtInMM);
|
||||
}
|
||||
|
||||
public static Table AddCells(this Table table, params Cell[] cells) {
|
||||
foreach (var cell in cells)
|
||||
table.AddCell(cell);
|
||||
return table;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Elwig/Documents/KernedParagraph.cs
Normal file
82
Elwig/Documents/KernedParagraph.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using iText.IO.Font;
|
||||
using iText.IO.Font.Otf;
|
||||
using iText.Kernel.Font;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using iText.Layout.Renderer;
|
||||
using System;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class KernedParagraph : Paragraph {
|
||||
|
||||
public KernedParagraph(float fontSize) :
|
||||
base() {
|
||||
SetFontKerning(FontKerning.YES);
|
||||
SetFixedLeading(fontSize);
|
||||
SetFontSize(fontSize);
|
||||
}
|
||||
|
||||
public KernedParagraph(string text, float fontSize) :
|
||||
base(text) {
|
||||
SetFontKerning(FontKerning.YES);
|
||||
SetFixedLeading(fontSize);
|
||||
SetFontSize(fontSize);
|
||||
}
|
||||
|
||||
public KernedParagraph(Text text, float fontSize) :
|
||||
base(text) {
|
||||
SetFontKerning(FontKerning.YES);
|
||||
SetFixedLeading(fontSize);
|
||||
SetFontSize(fontSize);
|
||||
}
|
||||
|
||||
public override KernedParagraph Add(ILeafElement element) {
|
||||
if (element is Text t) {
|
||||
t.SetFontKerning(FontKerning.YES);
|
||||
t.SetNextRenderer(new KerningTextRenderer(t));
|
||||
}
|
||||
base.Add(element);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override Paragraph Add(IBlockElement element) {
|
||||
base.Add(element);
|
||||
return this;
|
||||
}
|
||||
|
||||
public class KerningTextRenderer(Text textElement) : TextRenderer(textElement) {
|
||||
|
||||
public override IRenderer GetNextRenderer() {
|
||||
return new KerningTextRenderer((Text)modelElement);
|
||||
}
|
||||
|
||||
public override void ApplyOtf() {
|
||||
PdfFont font;
|
||||
try {
|
||||
font = GetPropertyAsFont(Property.FONT);
|
||||
} catch (InvalidCastException) {
|
||||
return;
|
||||
}
|
||||
if (strToBeConverted != null) {
|
||||
SetProcessedGlyphLineAndFont(TextPreprocessingUtil.ReplaceSpecialWhitespaceGlyphs(font.CreateGlyphLine(strToBeConverted), font), font);
|
||||
}
|
||||
if (otfFeaturesApplied || text.GetStart() >= text.GetEnd()) {
|
||||
return;
|
||||
}
|
||||
if (GetProperty(Property.FONT_KERNING, (FontKerning?)FontKerning.NO) == FontKerning.YES) {
|
||||
ApplyKerning(font.GetFontProgram(), text);
|
||||
}
|
||||
otfFeaturesApplied = true;
|
||||
}
|
||||
|
||||
private static void ApplyKerning(FontProgram font, GlyphLine text) {
|
||||
for (int i = 1; i < text.Size(); i++) {
|
||||
var kerning = font.GetKerning(text.Get(i - 1), text.Get(i));
|
||||
if (kerning != 0) {
|
||||
text.Set(i - 1, new Glyph(text.Get(i - 1), 0, 0, kerning, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@ using Elwig.Models.Entities;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class Letterhead : BusinessDocument {
|
||||
public Letterhead(Member m) : base($"Briefkopf {m.FullName}", m, true) {
|
||||
Aside = "";
|
||||
public Letterhead(Member m) :
|
||||
base($"Briefkopf {m.FullName}", m, true) {
|
||||
Aside = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.Letterhead>
|
||||
@model Elwig.Documents.Letterhead
|
||||
@{ Layout = "BusinessDocument"; }
|
||||
<style>
|
||||
header, .footer-wrapper {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,14 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Colors;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class MemberDataSheet : BusinessDocument {
|
||||
@@ -11,13 +17,168 @@ namespace Elwig.Documents {
|
||||
|
||||
public Season Season;
|
||||
public Dictionary<string, MemberBucket> MemberBuckets;
|
||||
public IEnumerable<AreaCom> ActiveAreaCommitments;
|
||||
public List<AreaCom> ActiveAreaCommitments;
|
||||
|
||||
public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) {
|
||||
DocumentId = $"{Name} {m.MgNr}";
|
||||
Season = ctx.Seasons.ToList().MaxBy(s => s.Year) ?? throw new ArgumentException("invalid season");
|
||||
MemberBuckets = ctx.GetMemberBuckets(Utils.CurrentYear, m.MgNr).GetAwaiter().GetResult();
|
||||
ActiveAreaCommitments = m.ActiveAreaCommitments(ctx);
|
||||
ActiveAreaCommitments = [.. m.ActiveAreaCommitments(ctx)];
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(NewMemberData().SetMarginBottomMM(5));
|
||||
doc.Add(NewBucketTable(Season, MemberBuckets, includeDelivery: false));
|
||||
if (ActiveAreaCommitments.Count != 0) {
|
||||
bool firstOnPage = false;
|
||||
if (pdf.GetNumberOfPages() == 1) {
|
||||
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
|
||||
firstOnPage = true;
|
||||
}
|
||||
doc.Add(new KernedParagraph(12).Add(Bold($"Flächenbindungen per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 12, 0));
|
||||
doc.Add(NewAreaComTable());
|
||||
}
|
||||
}
|
||||
|
||||
protected Cell NewDataHdr(string title, int colspan) {
|
||||
return NewTd(title, 10, colspan: colspan, bold: true, italic: true, center: true, borderTop: true)
|
||||
.SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0));
|
||||
}
|
||||
|
||||
protected Cell NewDataTh(string text, float fontSize = 10, int colspan = 1) {
|
||||
return NewTd(text, fontSize, colspan: colspan, italic: true)
|
||||
.SetPaddingRightMM(0);
|
||||
}
|
||||
|
||||
protected Table NewMemberData() {
|
||||
var tbl = new Table(ColsMM(30.0, 51.5, 20.0, 12.0, 18.0, 31.5))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetBorder(new SolidBorder(BorderThickness));
|
||||
|
||||
tbl.AddCell(NewDataHdr("Persönliche Daten", 6));
|
||||
if (Member.IsJuridicalPerson) {
|
||||
tbl
|
||||
.AddCell(NewDataTh("Name", 8, colspan: 3))
|
||||
.AddCell(NewDataTh("Zu Handen", 8, colspan: 3))
|
||||
.AddCell(NewTd(Member.Name, 12, colspan: 3))
|
||||
.AddCell(NewTd(Member.ForTheAttentionOf, 12, colspan: 3));
|
||||
} else {
|
||||
tbl
|
||||
.AddCell(NewDataTh("Titel (vorangestellt)", 8))
|
||||
.AddCell(NewDataTh("Vorname", 8))
|
||||
.AddCell(NewDataTh("Nachname", 8, colspan: 3))
|
||||
.AddCell(NewDataTh("Titel (nachgestellt)", 8))
|
||||
.AddCell(NewTd(Member.Prefix, 12))
|
||||
.AddCell(NewTd($"{Member.GivenName} {Member.MiddleName}", 12))
|
||||
.AddCell(NewTd(Member.Name, 12, colspan: 3))
|
||||
.AddCell(NewTd(Member.Suffix, 12));
|
||||
}
|
||||
|
||||
tbl
|
||||
.AddCell(NewDataTh("Mitglieds-Nr.:")).AddCell(NewTd($"{Member.MgNr}"))
|
||||
.AddCell(NewDataTh(Member.IsJuridicalPerson ? "Gründungsjahr/-tag:" : "Geburtsjahr/-tag:", colspan: 2))
|
||||
.AddCell(NewTd(string.Join('.', Member.Birthday?.Split('-')?.Reverse() ?? []), colspan: 2))
|
||||
.AddCell(NewDataTh("Adresse:")).AddCell(NewTd(Member.Address, colspan: 5))
|
||||
.AddCell(NewDataTh("PLZ/Ort:"))
|
||||
.AddCell(NewTd($"{Member.PostalDest.AtPlz?.Plz} {Member.PostalDest.AtPlz?.Dest} ({Member.PostalDest.AtPlz?.Ort.Name})", colspan: 5))
|
||||
.AddCell(NewDataHdr("Rechnungsadresse (optional)", colspan: 6))
|
||||
.AddCell(NewDataTh("Name:")).AddCell(NewTd(Member.BillingAddress?.FullName, colspan: 5))
|
||||
.AddCell(NewDataTh("Adresse:")).AddCell(NewTd(Member.BillingAddress?.Address, colspan: 5))
|
||||
.AddCell(NewDataTh("PLZ/Ort:"))
|
||||
.AddCell(NewTd(Member.BillingAddress != null ? $"{Member.BillingAddress.PostalDest.AtPlz?.Plz} {Member.BillingAddress.PostalDest.AtPlz?.Dest} ({Member.BillingAddress.PostalDest.AtPlz?.Ort.Name})" : "", colspan: 5));
|
||||
|
||||
tbl.AddCell(NewDataHdr("Kontaktdaten", colspan: 3))
|
||||
.AddCell(NewDataHdr("Bankverbindung", colspan: 3).SetBorderLeft(new SolidBorder(BorderThickness)));
|
||||
List<string?[]> subTbl1 = [
|
||||
.. Member.EmailAddresses.Select(a => new[] { "E-Mail-Adresse", a.Address }),
|
||||
.. Member.TelephoneNumbers.Select(n => new[] { Utils.PhoneNrTypeToString(n.Type), n.Number, n.Comment }),
|
||||
["Tel.-Nr./E-Mail-Adr.", null],
|
||||
];
|
||||
List<string?[]> subTbl2 = [
|
||||
["IBAN", Member.Iban != null ? Utils.FormatIban(Member.Iban) : null],
|
||||
["BIC", Member.Bic],
|
||||
];
|
||||
for (int i = 0; i < Math.Max(subTbl1.Count, subTbl2.Count); i++) {
|
||||
tbl.AddCell(NewDataTh(i < subTbl1.Count ? subTbl1[i][0] + ":" : ""));
|
||||
if (i < subTbl1.Count && subTbl1[i].Length >= 3 && subTbl1[i][2] != null) {
|
||||
tbl.AddCell(NewTd(subTbl1[i][1])).AddCell(NewTd($"({subTbl1[i][2]})"));
|
||||
} else {
|
||||
tbl.AddCell(NewTd(i < subTbl1.Count ? subTbl1[i][1] : "", colspan: 2));
|
||||
}
|
||||
tbl.AddCell(NewDataTh(i < subTbl2.Count ? subTbl2[i][0] + ":" : "").SetBorderLeft(new SolidBorder(BorderThickness)))
|
||||
.AddCell(NewTd(i < subTbl2.Count ? subTbl2[i][1] : "", colspan: 2));
|
||||
}
|
||||
|
||||
tbl.AddCell(NewDataHdr("Betrieb", colspan: 6))
|
||||
.AddCell(NewDataTh("Betriebs-Nr.:")).AddCell(NewTd(Member.LfbisNr))
|
||||
.AddCell(NewDataTh("UID:", colspan: 2)).AddCell(NewTd(Member.UstIdNr, colspan: 2))
|
||||
.AddCell(NewDataTh("Stammgemeinde:")).AddCell(NewTd(Member.DefaultKg?.Name))
|
||||
.AddCell(NewDataTh("Buchführend:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsBuchführend ? "Ja " : "Nein ", 10)
|
||||
.Add(Normal($"({(Member.IsBuchführend ? Season.VatNormal : Season.VatFlatrate) * 100:N0}% USt.)", 8)), colspan: 2))
|
||||
.AddCell(NewDataTh("(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)", 8, colspan: 2))
|
||||
.AddCell(NewDataTh("Bio:", colspan: 2)).AddCell(NewTd(Member.IsOrganic ? "Ja" : "Nein", colspan: 2))
|
||||
.AddCell(NewDataHdr("Genossenschaft", colspan: 6))
|
||||
.AddCell(NewDataTh("Status:")).AddCell(NewTd(new KernedParagraph(Member.IsActive ? "Aktiv " : "Nicht aktiv ", 10)
|
||||
.Add(Normal("(" + (Member.ExitDate != null ? $"{Member.EntryDate:dd.MM.yyyy}\u2013{Member.ExitDate:dd.MM.yyyy}" : $"seit {Member.EntryDate:dd.MM.yyyy}") + ")", 8))))
|
||||
.AddCell(NewDataTh("Geschäftsanteile:", colspan: 2)).AddCell(NewTd($"{Member.BusinessShares:N0}", colspan: 2))
|
||||
.AddCell(NewDataTh("Stamm-Zweigstelle:")).AddCell(NewTd(Member.Branch?.Name))
|
||||
.AddCell(NewDataTh("Volllieferant:", colspan: 2)).AddCell(NewTd(Member.IsVollLieferant ? "Ja" : "Nein", colspan: 2))
|
||||
.AddCell(NewDataTh("Zusendungen per\u2026")).AddCell(NewTd(new KernedParagraph(10)
|
||||
.Add(Italic("Post:")).Add(Normal(Member.ContactViaPost ? " Ja \u2013 " : " Nein \u2013 "))
|
||||
.Add(Italic("E-Mail:")).Add(Normal(Member.ContactViaEmail ? " Ja" : " Nein"))))
|
||||
.AddCell(NewDataTh("Funktionär:", colspan: 2)).AddCell(NewTd(Member.IsFunktionär ? "Ja" : "Nein", colspan: 2));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
protected Table NewAreaComTable() {
|
||||
var areaComs = ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new {
|
||||
Type = group.Key,
|
||||
AreaComs = group.OrderBy(c => c.Kg.AtKg.Name).ToList(),
|
||||
Size = group.Sum(c => c.Area)
|
||||
}).OrderByDescending(a => a.Size).ToList();
|
||||
|
||||
var tbl = new Table(ColsMM(40, 30, 35, 15, 25, 20), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetBorder(Border.NO_BORDER)
|
||||
.SetFontSize(10);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Katastralgemeinde", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Ried", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Parzelle(n)", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Fläche"))
|
||||
.AddHeaderCell(NewTh("Bewirt.", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Laufzeit", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("[m²]"));
|
||||
|
||||
var lastContract = "";
|
||||
foreach (var contractType in areaComs) {
|
||||
tbl.AddCell(NewCell(new KernedParagraph(10).Add(BoldItalic($"{contractType.Type.WineVar.Name} {(contractType.Type.WineAttr != null ? "(" + contractType.Type.WineAttr + ")" : "")}")), colspan: 3)
|
||||
.SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER))
|
||||
.AddCell(NewCell(new KernedParagraph(10).Add(Bold($"{contractType.Size:N0}")).SetTextAlignment(TextAlignment.RIGHT))
|
||||
.SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER))
|
||||
.AddCell(NewCell(colspan: 2)
|
||||
.SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER));
|
||||
|
||||
foreach (var areaCom in contractType.AreaComs) {
|
||||
tbl.AddCell(NewTd(new KernedParagraph(10).Add(Normal($"{areaCom.Kg.AtKg.Name} ")).Add(Normal($"({areaCom.Kg.AtKg.KgNr:00000})", 8))))
|
||||
.AddCell(NewTd(areaCom.Rd?.Name))
|
||||
.AddCell(NewTd(Regex.Replace(areaCom.GstNr.Replace(",", ", ").Replace("-", "\u2013"), @"\s+", " "), 10))
|
||||
.AddCell(NewTd($"{areaCom.Area:N0}", right: true))
|
||||
.AddCell(NewTd(areaCom.WineCult?.Name, center: true))
|
||||
.AddCell(NewTd(areaCom.YearTo == null ? (areaCom.YearFrom == null ? "unbefristet" : $"ab {areaCom.YearFrom}") : (areaCom.YearFrom == null ? $"bis {areaCom.YearTo}" : $"{areaCom.YearFrom}–{areaCom.YearTo}"), center: true));
|
||||
lastContract = contractType.Type.DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
tbl.AddCell(NewTd("Gesamt:", 12, colspan: 2, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
|
||||
tbl.AddCell(NewTd($"{ActiveAreaCommitments.Sum(a => a.Area):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
|
||||
tbl.AddCell(NewTd(colspan: 2, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
@using RazorLight
|
||||
@using Elwig.Helpers
|
||||
@inherits TemplatePage<Elwig.Documents.MemberDataSheet>
|
||||
@model Elwig.Documents.MemberDataSheet
|
||||
@{ Layout = "BusinessDocument"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\MemberDataSheet.css" />
|
||||
<main>
|
||||
<h1>@Model.Title</h1>
|
||||
<table class="member border">
|
||||
<colgroup>
|
||||
<col style="width: 30.0mm;"/>
|
||||
<col style="width: 51.5mm;"/>
|
||||
<col style="width: 20.0mm;"/>
|
||||
<col style="width: 12.0mm;"/>
|
||||
<col style="width: 18.0mm;"/>
|
||||
<col style="width: 31.5mm;"/>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr class="sectionheading"><th colspan="6">Persönliche Daten</th></tr>
|
||||
<tr>
|
||||
@if (Model.Member.IsJuridicalPerson) {
|
||||
<th colspan="3" class="small">Name</th>
|
||||
<th colspan="3" class="small">Zu Handen</th>
|
||||
} else {
|
||||
<th class="small">Titel (vorangestellt)</th>
|
||||
<th class="small">Vorname</th>
|
||||
<th colspan="3" class="small">Nachname</th>
|
||||
<th class="small">Titel (nachgestellt)</th>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
@if (Model.Member.IsJuridicalPerson) {
|
||||
<td colspan="3" class="large">@Model.Member.Name</td>
|
||||
<td colspan="3" class="large">@Model.Member.ForTheAttentionOf</td>
|
||||
} else {
|
||||
<td class="large">@Model.Member.Prefix</td>
|
||||
<td class="large">@Model.Member.GivenName @Model.Member.MiddleName</td>
|
||||
<td class="large" colspan="3">@Model.Member.Name</td>
|
||||
<td class="large">@Model.Member.Suffix</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mitglieds-Nr.:</th>
|
||||
<td>@Model.Member.MgNr</td>
|
||||
<th colspan="2">@(Model.Member.IsJuridicalPerson ? "Gründungsjahr/-tag" : "Geburtsjahr/-tag"):</th>
|
||||
<td colspan="2">@(string.Join('.', Model.Member.Birthday?.Split('-')?.Reverse() ?? Array.Empty<string>()))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Adresse:</th>
|
||||
<td colspan="5">@Model.Member.Address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>PLZ/Ort:</th>
|
||||
<td colspan="5">
|
||||
@Model.Member.PostalDest.AtPlz?.Plz
|
||||
@Model.Member.PostalDest.AtPlz?.Dest
|
||||
(@Model.Member.PostalDest.AtPlz?.Ort.Name)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="sectionheading"><th colspan="6">Rechnungsadresse (optional)</th></tr>
|
||||
<tr>
|
||||
<th>Name:</th>
|
||||
<td colspan="5">@Model.Member.BillingAddress?.FullName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Adresse:</th>
|
||||
<td colspan="5">@Model.Member.BillingAddress?.Address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>PLZ/Ort:</th>
|
||||
<td colspan="5">
|
||||
@if (Model.Member.BillingAddress != null) {
|
||||
@Model.Member.BillingAddress.PostalDest.AtPlz?.Plz
|
||||
@(" ")@Model.Member.BillingAddress.PostalDest.AtPlz?.Dest
|
||||
@(" (")@Model.Member.BillingAddress.PostalDest.AtPlz?.Ort.Name@(")")
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="sectionheading">
|
||||
<th colspan="3">Kontaktdaten</th>
|
||||
<th colspan="3" class="lborder">Bankverbindung</th>
|
||||
</tr>
|
||||
@{
|
||||
List<string?[]> subTbl1 = new();
|
||||
subTbl1.AddRange(Model.Member.EmailAddresses.Select(a => new[] { "E-Mail-Adresse", a.Address }));
|
||||
subTbl1.AddRange(Model.Member.TelephoneNumbers.Select(n => new[] { Utils.PhoneNrTypeToString(n.Type), n.Number, n.Comment }));
|
||||
subTbl1.Add(new[] { "Tel.-Nr./E-Mail-Adr.", null });
|
||||
|
||||
List<string?[]> subTbl2 = new();
|
||||
subTbl2.Add(new[] { "IBAN", Model.Member.Iban != null ? Elwig.Helpers.Utils.FormatIban(Model.Member.Iban) : null });
|
||||
subTbl2.Add(new[] { "BIC", Model.Member.Bic });
|
||||
}
|
||||
@for (int i = 0; i < Math.Max(subTbl1.Count, subTbl2.Count); i++) {
|
||||
<tr>
|
||||
<th>@(i < subTbl1.Count ? subTbl1[i][0] + ":" : "")</th>
|
||||
@if (i < subTbl1.Count && subTbl1[i].Length >= 3 && subTbl1[i][2] != null) {
|
||||
<td>@subTbl1[i][1]</td>
|
||||
<td>(@subTbl1[i][2])</td>
|
||||
} else {
|
||||
<td colspan="2">@(i < subTbl1.Count ? subTbl1[i][1] : "")</td>
|
||||
}
|
||||
|
||||
<th class="lborder">@(i < subTbl2.Count ? subTbl2[i][0] + ":" : "")</th>
|
||||
<td colspan="2">@(i < subTbl2.Count ? subTbl2[i][1] : "")</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="sectionheading"><th colspan="6">Betrieb</th></tr>
|
||||
<tr>
|
||||
<th>Betriebs-Nr.:</th>
|
||||
<td>@Model.Member.LfbisNr</td>
|
||||
<th colspan="2">UID:</th>
|
||||
<td colspan="2">@Model.Member.UstIdNr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Stammgemeinde:</th>
|
||||
<td>@Model.Member.DefaultKg?.Name</td>
|
||||
<th colspan="2">Buchführend:</th>
|
||||
<td colspan="2">@(Model.Member.IsBuchführend ? "Ja" : "Nein") <span class="small">(@((Model.Member.IsBuchführend ? Model.Season.VatNormal : Model.Season.VatFlatrate) * 100)% USt.)</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2" class="small">(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)</th>
|
||||
<th colspan="2">Bio:</th>
|
||||
<td colspan="2">@(Model.Member.IsOrganic ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
<tr class="sectionheading"><th colspan="6">Genossenschaft</th></tr>
|
||||
<tr>
|
||||
<th>Status:</th>
|
||||
<td>
|
||||
@(Model.Member.IsActive ? "Aktiv" : "Nicht aktiv")
|
||||
<span class="small">
|
||||
(@(Model.Member.ExitDate != null ?
|
||||
$"{Model.Member.EntryDate:dd.MM.yyyy}–{Model.Member.ExitDate:dd.MM.yyyy}" :
|
||||
$"seit {Model.Member.EntryDate:dd.MM.yyyy}"))
|
||||
</span>
|
||||
</td>
|
||||
<th colspan="2">Geschäftsanteile:</th>
|
||||
<td colspan="2">@Model.Member.BusinessShares</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Stamm-Zweigstelle:</th>
|
||||
<td>@Model.Member.Branch?.Name</td>
|
||||
<th colspan="2">Volllierferant:</th>
|
||||
<td colspan="2">@(Model.Member.IsVollLieferant ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zusendungen via...</th>
|
||||
<td>
|
||||
<i>Post:</i> @(Model.Member.ContactViaPost ? "Ja" : "Nein") –
|
||||
<i>E-Mail:</i> @(Model.Member.ContactViaEmail ? "Ja" : "Nein")
|
||||
</td>
|
||||
<th colspan="2">Funktionär:</th>
|
||||
<td colspan="2">@(Model.Member.IsFunktionär ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includeDelivery: false))
|
||||
|
||||
@{
|
||||
var areaComs = Model.ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new {
|
||||
AreaComType = group.Key,
|
||||
AreaComs = group.OrderBy(c => c.Kg.AtKg.Name),
|
||||
Size = group.Sum(c => c.Area)
|
||||
}).OrderByDescending(a => a.Size).ToList();
|
||||
var lastContract = "";
|
||||
}
|
||||
|
||||
@if (areaComs.Count != 0) {
|
||||
<br class="area-commitements"/>
|
||||
<h2>Flächenbindungen per @($"{Model.Date:dd.MM.yyyy}")</h2>
|
||||
<table class="area-commitements">
|
||||
<colgroup>
|
||||
<col style="width: 40mm;"/>
|
||||
<col style="width: 30mm;"/>
|
||||
<col style="width: 35mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 20mm;"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" style="text-align: left;">Katastralgemeinde</th>
|
||||
<th rowspan="2" style="text-align: left;">Ried</th>
|
||||
<th rowspan="2" style="text-align: left;">Parzelle(n)</th>
|
||||
<th>Fläche</th>
|
||||
<th rowspan="2" style="text-align: center;">Bewirt.</th>
|
||||
<th rowspan="2" style="text-align: center;">Laufzeit</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>[m²]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var contractType in areaComs) {
|
||||
<tr class="subheading @(contractType.AreaComType.DisplayName != lastContract && lastContract != "" ? "new" : "")">
|
||||
<th colspan="3">
|
||||
@($"{contractType.AreaComType.WineVar.Name} {(contractType.AreaComType.WineAttr != null ? "(" + contractType.AreaComType.WineAttr + ")" : "")}")
|
||||
</th>
|
||||
<td class="number">@($"{contractType.Size:N0}")</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
@foreach (var areaCom in contractType.AreaComs) {
|
||||
<tr class="area-commitment">
|
||||
<td>@areaCom.Kg.AtKg.Name <span style="font-size: 8pt;">(@($"{areaCom.Kg.AtKg.KgNr:00000}"))</span></td>
|
||||
<td>@areaCom.Rd?.Name</td>
|
||||
<td class="text">@areaCom.GstNr.Replace(",", ", ").Replace("-", "–")</td>
|
||||
<td class="number">@($"{areaCom.Area:N0}")</td>
|
||||
<td class="center">@areaCom.WineCult?.Name</td>
|
||||
<td class="center">@(areaCom.YearTo == null ? (areaCom.YearFrom == null ? "unbefristet" : $"ab {areaCom.YearFrom}") : (areaCom.YearFrom == null ? $"bis {areaCom.YearTo}" : $"{areaCom.YearFrom}–{areaCom.YearTo}"))</td>
|
||||
</tr>
|
||||
lastContract = contractType.AreaComType.DisplayName;
|
||||
}
|
||||
}
|
||||
<tr class="sum bold">
|
||||
<td colspan="3">Gesamt:</td>
|
||||
<td class="number">@($"{Model.ActiveAreaCommitments.Sum(a => a.Area):N0}")</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</main>
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
h2 {
|
||||
margin-bottom: 0.5em !important;
|
||||
}
|
||||
|
||||
table.member {
|
||||
margin-bottom: 5mm;
|
||||
}
|
||||
|
||||
table.area-commitements {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
table.area-commitements td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.area-commitements td.text {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
table.area-commitements tr.sum {
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
@page :not(:first) {
|
||||
br.area-commitements {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
using Elwig.Models.Dtos;
|
||||
using iText.Kernel.Colors;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -8,14 +14,15 @@ namespace Elwig.Documents {
|
||||
public new static string Name => "Mitgliederliste";
|
||||
|
||||
public string Filter;
|
||||
public IEnumerable<MemberListRow> Members;
|
||||
public List<MemberListRow> Members;
|
||||
|
||||
public string[] AreaComFilters;
|
||||
public bool FilterAreaComs => AreaComFilters.Length > 0;
|
||||
|
||||
public MemberList(string filter, IEnumerable<MemberListRow> members) : base(Name) {
|
||||
public MemberList(string filter, IEnumerable<MemberListRow> members) :
|
||||
base(Name) {
|
||||
Filter = filter;
|
||||
Members = members;
|
||||
Members = [.. members];
|
||||
AreaComFilters = [..members
|
||||
.SelectMany(m => m.AreaCommitmentsFiltered)
|
||||
.Select(c => c.VtrgId)
|
||||
@@ -26,5 +33,79 @@ namespace Elwig.Documents {
|
||||
public MemberList(string filter, MemberListData data) :
|
||||
this(filter, data.Rows) {
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(new KernedParagraph(Name, 24)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 2, 0));
|
||||
doc.Add(new KernedParagraph(Filter, 14)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 2, 0));
|
||||
doc.Add(NewMemberTable(Members));
|
||||
}
|
||||
|
||||
protected Table NewMemberTable(List<MemberListRow> members) {
|
||||
var tbl = new Table(AreaComFilters.Length > 1 ? ColsMM(8, 38, 36, 8, 18, 12, 5, 16, 12, 12) : ColsMM(8, 42, 40, 8, 20, 12, 5, 18, 12), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
var headerSpan = FilterAreaComs ? 3 : 2;
|
||||
tbl.AddHeaderCell(NewTh("Nr.", rowspan: headerSpan))
|
||||
.AddHeaderCell(NewTh("Name", rowspan: headerSpan, left: true))
|
||||
.AddHeaderCell(NewTh("Adresse", rowspan: headerSpan, left: true))
|
||||
.AddHeaderCell(NewTh("PLZ", rowspan: headerSpan))
|
||||
.AddHeaderCell(NewTh("Ort", rowspan: headerSpan, left: true))
|
||||
.AddHeaderCell(NewTh("Betr.-Nr.", rowspan: headerSpan))
|
||||
.AddHeaderCell(NewTh("GA", rowspan: headerSpan).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("Stamm-KG", rowspan: headerSpan, left: true))
|
||||
.AddHeaderCell(NewTh("Geb. Fl.", colspan: FilterAreaComs ? AreaComFilters.Length : 1));
|
||||
if (FilterAreaComs) {
|
||||
foreach (var vtrgId in AreaComFilters) {
|
||||
tbl.AddHeaderCell(NewTh(vtrgId));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < (FilterAreaComs ? AreaComFilters.Length : 1); i++) {
|
||||
tbl.AddHeaderCell(NewTh("[m²]"));
|
||||
}
|
||||
|
||||
string? lastBranch = members.Select(m => m.Branch).Distinct().Count() == 1 ? null : "";
|
||||
foreach (var m in members) {
|
||||
if (lastBranch != null && m.Branch != lastBranch) {
|
||||
tbl.AddCell(NewCell(colspan: 8 + Math.Max(AreaComFilters.Length, 1)).SetHeightMM(5).SetKeepWithNext(true));
|
||||
tbl.AddCell(NewCell(new KernedParagraph(m.Branch ?? "", 16).SetFont(BF), colspan: 8 + Math.Max(AreaComFilters.Length, 1))
|
||||
.SetPaddingsMM(1, 2, 1, 2)
|
||||
.SetBorder(new SolidBorder(BorderThickness))
|
||||
.SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)));
|
||||
lastBranch = m.Branch;
|
||||
}
|
||||
|
||||
tbl.AddCell(NewTd($"{m.MgNr}", 8, rowspan: m.BillingName != null ? 2 : 1, right: true).SetVerticalAlignment(VerticalAlignment.TOP))
|
||||
.AddCell(NewTd($"{m.AdminName1} {m.Name2}", 8))
|
||||
.AddCell(NewTd(m.Address, 8))
|
||||
.AddCell(NewTd($"{m.Plz}", 8))
|
||||
.AddCell(NewTd(m.Locality, 6))
|
||||
.AddCell(NewTd(m.LfbisNr ?? "", 8))
|
||||
.AddCell(NewTd($"{m.BusinessShares:N0}", 8, right: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd(m.DefaultKg ?? "", 6));
|
||||
if (AreaComFilters.Length > 0) {
|
||||
foreach (var v in AreaComFilters) {
|
||||
tbl.AddCell(NewTd($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}", 8, right: true));
|
||||
}
|
||||
} else {
|
||||
tbl.AddCell(NewTd($"{m.AreaCommitment:N0}", 8, right: true));
|
||||
}
|
||||
|
||||
if (m.BillingName != null) {
|
||||
tbl.AddCell(NewTd(m.BillingName, 8))
|
||||
.AddCell(NewTd(m.BillingAddress ?? "", 8))
|
||||
.AddCell(NewTd($"{m.BillingPlz}", 8))
|
||||
.AddCell(NewTd(m.BillingLocality ?? "", 6))
|
||||
.AddCell(NewTd(colspan: 3 + Math.Max(AreaComFilters.Length, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
@using RazorLight
|
||||
@inherits TemplatePage<Elwig.Documents.MemberList>
|
||||
@model Elwig.Documents.MemberList
|
||||
@{ Layout = "Document"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\MemberList.css" />
|
||||
<main>
|
||||
<h1>Mitgliederliste</h1>
|
||||
<h2>@Model.Filter</h2>
|
||||
<table class="members">
|
||||
<colgroup>
|
||||
<col style="width: 8mm;"/>
|
||||
@if (Model.AreaComFilters.Length > 1) {
|
||||
<col style="width: 38mm;"/>
|
||||
} else {
|
||||
<col style="width: 42mm;"/>
|
||||
}
|
||||
@if (Model.AreaComFilters.Length > 1) {
|
||||
<col style="width: 36mm;"/>
|
||||
} else {
|
||||
<col style="width: 40mm;"/>
|
||||
}
|
||||
<col style="width: 8mm;"/>
|
||||
@if (Model.AreaComFilters.Length > 1) {
|
||||
<col style="width: 18mm;"/>
|
||||
} else {
|
||||
<col style="width: 20mm;"/>
|
||||
}
|
||||
<col style="width: 12mm;"/>
|
||||
<col style="width: 5mm;"/>
|
||||
@if (Model.AreaComFilters.Length > 1) {
|
||||
<col style="width: 16mm;"/>
|
||||
} else {
|
||||
<col style="width: 18mm;"/>
|
||||
}
|
||||
<col style="width: 12mm;"/>
|
||||
@if (Model.AreaComFilters.Length > 1) {
|
||||
<col style="width: 12mm;"/>
|
||||
}
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
@{
|
||||
var headerSpan = Model.FilterAreaComs ? 3 : 2;
|
||||
}
|
||||
<th rowspan="@headerSpan">Nr.</th>
|
||||
<th rowspan="@headerSpan" style="text-align: left;">Name</th>
|
||||
<th rowspan="@headerSpan" style="text-align: left;">Adresse</th>
|
||||
<th rowspan="@headerSpan">PLZ</th>
|
||||
<th rowspan="@headerSpan" style="text-align: left;">Ort</th>
|
||||
<th rowspan="@headerSpan">Betr.-Nr.</th>
|
||||
<th rowspan="@headerSpan">GA</th>
|
||||
<th rowspan="@headerSpan" style="text-align: left;">Stamm-KG</th>
|
||||
<th colspan="@(Model.FilterAreaComs ? Model.AreaComFilters.Length : 1)">Geb. Fl.</th>
|
||||
</tr>
|
||||
@if (Model.FilterAreaComs) {
|
||||
<tr>
|
||||
@foreach (var vtrgId in Model.AreaComFilters) {
|
||||
<th>@vtrgId</th>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
@for (int i = 0; i < Math.Max(Model.AreaComFilters.Length, 1); i++) {
|
||||
<th class="unit">[m²]</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="small">
|
||||
@{
|
||||
string? lastBranch = Model.Members.Select(m => m.Branch).Distinct().Count() == 1 ? null : "";
|
||||
}
|
||||
@foreach (var m in Model.Members) {
|
||||
if (lastBranch != null && m.Branch != lastBranch) {
|
||||
<tr class="spacing"><td colspan="@(8 + Math.Max(Model.AreaComFilters.Length, 1))"></td></tr>
|
||||
<tr class="header">
|
||||
<th colspan="@(8 + Math.Max(Model.AreaComFilters.Length, 1))">@m.Branch</th>
|
||||
</tr>
|
||||
lastBranch = m.Branch;
|
||||
}
|
||||
<tr>
|
||||
<td class="number" rowspan="@(m.BillingName != null ? 2 : 1)">@m.MgNr</td>
|
||||
<td>@m.AdminName1 @m.Name2</td>
|
||||
<td>@m.Address</td>
|
||||
<td>@m.Plz</td>
|
||||
<td class="tiny">@m.Locality</td>
|
||||
<td>@m.LfbisNr</td>
|
||||
<td class="number">@m.BusinessShares</td>
|
||||
<td class="tiny">@m.DefaultKg</td>
|
||||
@if (Model.AreaComFilters.Length > 0) {
|
||||
foreach (var v in Model.AreaComFilters) {
|
||||
<td class="number">@($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}")</td>
|
||||
}
|
||||
} else {
|
||||
<td class="number">@($"{m.AreaCommitment:N0}")</td>
|
||||
}
|
||||
</tr>
|
||||
if (m.BillingName != null) {
|
||||
<tr>
|
||||
<td>@m.BillingName</td>
|
||||
<td>@m.BillingAddress</td>
|
||||
<td>@m.BillingPlz</td>
|
||||
<td class="tiny">@m.BillingLocality</td>
|
||||
<td colspan="4"></td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 14pt;
|
||||
margin-top: 2mm;
|
||||
}
|
||||
@@ -2,6 +2,12 @@ using Elwig.Helpers;
|
||||
using Elwig.Helpers.Billing;
|
||||
using Elwig.Models.Dtos;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Colors;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -27,10 +33,264 @@ namespace Elwig.Documents {
|
||||
Data = data;
|
||||
CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code;
|
||||
MemberNum = v.Credits.Count;
|
||||
IsPreview = MemberNum == 0;
|
||||
DeliveryNum = v.DeliveryPartPayments.DistinctBy(p => p.DeliveryPart.Delivery).Count();
|
||||
DeliveryPartNum = v.DeliveryPartPayments.Count;
|
||||
ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult();
|
||||
Modifiers = v.Season.Modifiers.ToDictionary(m => m.ModId);
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(new KernedParagraph($"{Name} Lese {Variant.Year}", 24)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 2, 0));
|
||||
doc.Add(new KernedParagraph(Variant.Name, 14)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 10, 0));
|
||||
doc.Add(NewVariantStatTable().SetMarginBottomMM(10));
|
||||
doc.Add(NewModifierStatTable());
|
||||
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
|
||||
doc.Add(NewPriceTable());
|
||||
}
|
||||
|
||||
protected Cell NewSectionHdr(string text, int colspan = 1, bool borderLeft = false) {
|
||||
return NewTd(text, 10, colspan: colspan, bold: true, italic: true, center: true, borderTop: true)
|
||||
.SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0))
|
||||
.SetPaddingsMM(0.5f, 1, 0.5f, 1)
|
||||
.SetBorderLeft(borderLeft ? new SolidBorder(BorderThickness) : Border.NO_BORDER);
|
||||
}
|
||||
|
||||
protected Cell NewSectionTh(string? text = null, float fontSize = 10, int colspan = 1, bool borderTop = false, bool borderLeft = false, bool overflow = false) {
|
||||
return NewTd(text, fontSize, colspan: colspan, italic: true, borderTop: borderTop, overflow: overflow)
|
||||
.SetPaddingRightMM(0)
|
||||
.SetBorderLeft(borderLeft ? new SolidBorder(BorderThickness) : Border.NO_BORDER);
|
||||
}
|
||||
|
||||
protected Table NewVariantStatTable() {
|
||||
var tbl = new Table(ColsMM(20, 30, 4.5, 4.5, 23.5, 47.5, 15, 20))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetBorder(new SolidBorder(BorderThickness));
|
||||
|
||||
//var sum1 = Variant.DeliveryPartPayments.Sum(p => p.NetAmount);
|
||||
//var sum2 = Variant.Credits.Sum(p => p.); //Variant.MemberPayments.Sum(p => p.Amount);
|
||||
var deliveryModifiers = Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount);
|
||||
var memberModifiers = Variant.Credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount);
|
||||
var sum2 = Variant.Credits.Sum(p => p.NetAmount);
|
||||
var sum1 = sum2 - deliveryModifiers - memberModifiers;
|
||||
var payed = -Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
|
||||
var netSum = Variant.Credits.Sum(p => p.NetAmount) - Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
|
||||
var vat = Variant.Credits.Sum(p => p.VatAmount);
|
||||
var grossSum = Variant.Credits.Sum(p => p.GrossAmount);
|
||||
var totalMods = Variant.Credits.Sum(p => p.Modifiers ?? 0m);
|
||||
var considered = -Variant.Credits.Sum(p => p.PrevModifiers ?? 0m);
|
||||
var totalSum = Variant.Credits.Sum(p => p.Amount);
|
||||
|
||||
var weiRows = Data.Rows.Where(r => r.QualityLevel == "Wein");
|
||||
var minWei = weiRows.Min(r => r.Ungeb.MinPrice);
|
||||
var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice);
|
||||
var quwRows = Data.Rows.Where(r => r.QualityLevel != "Wein");
|
||||
var minPrice = quwRows.Min(r => r.Ungeb.MinPrice);
|
||||
var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice);
|
||||
var gebRows = Data.Rows
|
||||
.Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null)
|
||||
.Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice);
|
||||
var minGeb = gebRows.Min();
|
||||
var maxGeb = gebRows.Max();
|
||||
|
||||
tbl.AddCell(NewSectionHdr("Allgemein", colspan: 5))
|
||||
.AddCell(NewSectionHdr("Berücksichtigt", colspan: 3, borderLeft: true))
|
||||
|
||||
.AddCell(NewSectionTh("Name:"))
|
||||
.AddCell(NewTd(Variant.Name, colspan: 4))
|
||||
.AddCell(NewSectionTh("Zu-/Abschläge bei Lieferungen:", colspan: 2, borderLeft: true))
|
||||
.AddCell(NewTd(BillingData.ConsiderDelieryModifiers ? "Ja" : "Nein", center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Beschr.:"))
|
||||
.AddCell(NewTd(Variant.Comment, colspan: 4))
|
||||
.AddCell(NewSectionTh("Pönalen bei Unterlieferungen (FB):", colspan: 2, borderLeft: true))
|
||||
.AddCell(NewTd(BillingData.ConsiderContractPenalties ? "Ja" : "Nein", center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Rebel-Zuschl.:", overflow: true))
|
||||
.AddCell(NewTd($"{Utils.GetSign(BillingData.NetWeightModifier)}{Math.Abs(BillingData.NetWeightModifier) * 100:N2} % / {Utils.GetSign(BillingData.GrossWeightModifier)}{Math.Abs(BillingData.GrossWeightModifier) * 100:N2} %", colspan: 4, center: true))
|
||||
.AddCell(NewSectionTh("Strafen bei Unterlieferungen (GA):", colspan: 2, borderLeft: true))
|
||||
.AddCell(NewTd(BillingData.ConsiderTotalPenalty ? "Ja" : "Nein", center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Datum/Überw.:", overflow: true))
|
||||
.AddCell(NewTd($"{Variant.Date:dd.MM.yyyy} / {Variant.TransferDate:dd.MM.yyyy}", colspan: 4, center: true))
|
||||
.AddCell(NewSectionTh("Automatische Nachzeichnung der GA:", colspan: 2, borderLeft: true))
|
||||
.AddCell(NewTd(BillingData.ConsiderAutoBusinessShares ? "Ja" : "Nein", center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Berechnung:"))
|
||||
.AddCell(NewTd($"{Variant.CalcTime:dd.MM.yyyy, HH:mm:ss}", colspan: 4, center: true))
|
||||
.AddCell(NewSectionTh("Benutzerdef. Zu-/Abschläge pro Mitglied:", colspan: 2, borderLeft: true))
|
||||
.AddCell(NewTd(BillingData.ConsiderCustomModifiers ? "Ja" : "Nein", center: true))
|
||||
|
||||
.AddCell(NewSectionHdr("Beträge", colspan: 5))
|
||||
.AddCell(NewSectionHdr("Statistik", colspan: 3, borderLeft: true))
|
||||
|
||||
.AddCell(NewSectionTh("Zwischensumme:", colspan: 2))
|
||||
.AddCell(NewTd())
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{sum1:N2}", right: true))
|
||||
.AddCell(NewSectionTh("Lieferanten:", borderLeft: true))
|
||||
.AddCell(NewTd($"{MemberNum:N0}", colspan: 2, right: true))
|
||||
|
||||
.AddCell(NewSectionTh("Zu-/Abschläge (Mitglieder):", colspan: 2))
|
||||
.AddCell(NewTd(Utils.GetSign(memberModifiers), right: true))
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{Math.Abs(memberModifiers):N2}", right: true))
|
||||
.AddCell(NewSectionTh("Lieferungen:", borderLeft: true))
|
||||
.AddCell(NewTd($"{DeliveryNum:N0}", colspan: 2, right: true))
|
||||
|
||||
.AddCell(NewSectionTh("Zu-/Abschläge (Lieferungen):", colspan: 2))
|
||||
.AddCell(NewTd(Utils.GetSign(deliveryModifiers), right: true))
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{Math.Abs(deliveryModifiers):N2}", right: true))
|
||||
.AddCell(NewSectionTh("Teillieferungen:", borderLeft: true))
|
||||
.AddCell(NewTd($"{DeliveryPartNum:N0}", colspan: 2, right: true))
|
||||
|
||||
.AddCell(NewSectionTh("Gesamtsumme:", colspan: 2))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
.AddCell(NewTd(CurrencySymbol, borderTop: true))
|
||||
.AddCell(NewTd($"{sum2:N2}", right: true, borderTop: true))
|
||||
.AddCell(NewSectionTh(borderLeft: true))
|
||||
.AddCell(NewTd(colspan: 2))
|
||||
|
||||
.AddCell(NewSectionTh("Bisher ausgezahlt:", colspan: 2))
|
||||
.AddCell(NewTd(Utils.GetSign(payed), right: true))
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{Math.Abs(payed):N2}", right: true))
|
||||
.AddCell(NewSectionTh("Preis (abgewertet):", borderTop: true, borderLeft: true))
|
||||
.AddCell(NewTd((minWei != maxWei ? $"{minWei:N4}\u2013{maxWei:N4}" : $"{minWei:N4}") + $" {CurrencySymbol}/kg", colspan: 2, center: true, borderTop: true))
|
||||
|
||||
.AddCell(NewSectionTh("Nettosumme:", colspan: 2))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
.AddCell(NewTd(CurrencySymbol, borderTop: true))
|
||||
.AddCell(NewTd($"{netSum:N2}", right: true, borderTop: true))
|
||||
.AddCell(NewSectionTh("Preis (ungeb., nicht abgew.):", borderLeft: true))
|
||||
.AddCell(NewTd((minPrice != maxPrice ? $"{minPrice:N4}–{maxPrice:N4}" : $"{minPrice:N4}") + $" {CurrencySymbol}/kg", colspan: 2, center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Mehrwertsteuer:", colspan: 2))
|
||||
.AddCell(NewTd(Utils.GetSign(vat), right: true))
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{Math.Abs(vat):N2}", right: true))
|
||||
.AddCell(NewSectionTh("Gebunden-Zuschlag:", borderLeft: true))
|
||||
.AddCell(NewTd(minGeb != maxGeb ? $"{minGeb:N4}\u2013{maxGeb:N4} {CurrencySymbol}/kg" : minGeb == 0 ? "-" : $"{minGeb:N4} {CurrencySymbol}/kg", colspan: 2, center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Bruttosumme:", colspan: 2))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
.AddCell(NewTd(CurrencySymbol, borderTop: true))
|
||||
.AddCell(NewTd($"{grossSum:N2}", right: true, borderTop: true))
|
||||
.AddCell(NewSectionTh(borderLeft: true))
|
||||
.AddCell(NewTd(colspan: 2))
|
||||
|
||||
.AddCell(NewSectionTh("Abzüge (Strafen/Pönalen, GA, \u2026):", colspan: 2))
|
||||
.AddCell(NewTd(Utils.GetSign(totalMods)))
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{Math.Abs(totalMods):N2}", right: true))
|
||||
.AddCell(NewSectionTh("Menge (ungebunden):", borderLeft: true, borderTop: true))
|
||||
.AddCell(NewTd($"{Data.Rows.Sum(r => r.Ungeb.Weight):N0} kg", colspan: 2, right: true, borderTop: true))
|
||||
|
||||
.AddCell(NewSectionTh("Bereits berücksichtigte Abzüge:", colspan: 2))
|
||||
.AddCell(NewTd(Utils.GetSign(considered)))
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{Math.Abs(considered):N2}", right: true))
|
||||
.AddCell(NewSectionTh("Menge (gebunden):", borderLeft: true))
|
||||
.AddCell(NewTd($"{Data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0} kg", colspan: 2, right: true))
|
||||
|
||||
.AddCell(NewSectionTh("Auszahlungsbetrag:", colspan: 2))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
.AddCell(NewTd(CurrencySymbol, borderTop: true))
|
||||
.AddCell(NewTd($"{totalSum:N2}", right: true, borderTop: true))
|
||||
.AddCell(NewSectionTh("Gesamtmenge:", borderLeft: true))
|
||||
.AddCell(NewTd($"{Data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0} kg", colspan: 2, right: true, borderTop: true));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
protected Table NewModifierStatTable() {
|
||||
var tbl = new Table(ColsMM(35, 30, 25, 25, 25, 25))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetBorder(new SolidBorder(BorderThickness));
|
||||
|
||||
tbl.AddCell(NewSectionHdr("Statistik Zu-/Abschläge", colspan: 6))
|
||||
.AddCell(NewTh("Name", rowspan: 2))
|
||||
.AddCell(NewTh("Zu-/Abschlag", rowspan: 2))
|
||||
.AddCell(NewTh("Lieferungen"))
|
||||
.AddCell(NewTh("Minimum"))
|
||||
.AddCell(NewTh("Maximum"))
|
||||
.AddCell(NewTh("Betrag"))
|
||||
.AddCell(NewTh("[#]"))
|
||||
.AddCell(NewTh($"[{CurrencySymbol}]"))
|
||||
.AddCell(NewTh($"[{CurrencySymbol}]"))
|
||||
.AddCell(NewTh($"[{CurrencySymbol}]"));
|
||||
|
||||
foreach (var m in ModifierStat) {
|
||||
var mod = Modifiers[m.ModId];
|
||||
tbl.AddCell(NewTd(mod.Name, italic: true))
|
||||
.AddCell(NewTd(mod.ValueStr, right: true))
|
||||
.AddCell(NewTd($"{m.Count:N0}", right: true))
|
||||
.AddCell(NewTd($"{m.Min:N2}", right: true))
|
||||
.AddCell(NewTd($"{m.Max:N2}", right: true))
|
||||
.AddCell(NewTd($"{m.Sum:N2}", right: true));
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
protected Table NewPriceTable() {
|
||||
var tbl = new Table(ColsMM(25, 19, 18, 15, 18, 15, 18, 15, 22))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
tbl.AddHeaderCell(NewTh("Sorte/Attr./Bewirt.\nQualitätsstufe", rowspan: 2, left: true))
|
||||
.AddHeaderCell(NewTh("Gradation"))
|
||||
.AddHeaderCell(NewTh("ungebunden", colspan: 2))
|
||||
.AddHeaderCell(NewTh("attributlos gebunden", colspan: 2))
|
||||
.AddHeaderCell(NewTh("gebunden", colspan: 2))
|
||||
.AddHeaderCell(NewTh("Gesamt"))
|
||||
.AddHeaderCell(NewTh(true ? "[°Oe]" : "[°KMW]"))
|
||||
.AddHeaderCell(NewTh("[kg]"))
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]"))
|
||||
.AddHeaderCell(NewTh("[kg]"))
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]"))
|
||||
.AddHeaderCell(NewTh("[kg]"))
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]"))
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}]"));
|
||||
|
||||
string? lastHdr = null;
|
||||
foreach (var row in Data.Rows) {
|
||||
var hdr = $"{row.Variety}{(row.Attribute != null ? " / " : "")}{row.Attribute}{(row.Cultivation != null ? " / " : "")}{row.Cultivation}";
|
||||
if (lastHdr != hdr) {
|
||||
var rows = Data.Rows
|
||||
.Where(r => r.Variety == row.Variety && r.Attribute == row.Attribute && r.Cultivation == row.Cultivation)
|
||||
.ToList();
|
||||
var border = lastHdr != null;
|
||||
tbl.AddCell(NewTd(hdr, colspan: 2, bold: true, italic: true, borderTop: border))
|
||||
.AddCell(NewTd($"{rows.Sum(r => r.Ungeb.Weight):N0}", right: true, bold: true, borderTop: border))
|
||||
.AddCell(NewTd(borderTop: border))
|
||||
.AddCell(NewTd($"{rows.Sum(r => r.LowGeb.Weight):N0}", right: true, bold: true, borderTop: border))
|
||||
.AddCell(NewTd(borderTop: border))
|
||||
.AddCell(NewTd($"{rows.Sum(r => r.Geb.Weight):N0}", right: true, bold: true, borderTop: border))
|
||||
.AddCell(NewTd(borderTop: border))
|
||||
.AddCell(NewTd($"{rows.Sum(r => r.Amount):N2}", right: true, bold: true, borderTop: border));
|
||||
}
|
||||
tbl.AddCell(NewTd(row.QualityLevel))
|
||||
.AddCell(NewTd($"{row.Oe:N0}", center: true))
|
||||
.AddCell(NewTd(row.Ungeb.Weight != 0 ? $"{row.Ungeb.Weight:N0}" : "-", right: true))
|
||||
.AddCell(NewTd(row.Ungeb.MaxPrice != null ? $"{row.Ungeb.MaxPrice:N4}" : "-", right: true))
|
||||
.AddCell(NewTd(row.LowGeb.Weight != 0 ? $"{row.LowGeb.Weight:N0}" : "-", right: true))
|
||||
.AddCell(NewTd(row.LowGeb.MaxPrice != null ? $"{row.LowGeb.MaxPrice:N4}" : "-", right: true))
|
||||
.AddCell(NewTd(row.Geb.Weight != 0 ? $"{row.Geb.Weight:N0}" : "-", right: true))
|
||||
.AddCell(NewTd(row.Geb.MaxPrice != null ? $"{row.Geb.MaxPrice:N4}" : "-", right: true))
|
||||
.AddCell(NewTd($"{row.Amount:N2}", right: true));
|
||||
lastHdr = hdr;
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
@using RazorLight
|
||||
@using Elwig.Helpers
|
||||
@inherits TemplatePage<Elwig.Documents.PaymentVariantSummary>
|
||||
@model Elwig.Documents.PaymentVariantSummary
|
||||
@{ Layout = "Document"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\PaymentVariantSummary.css" />
|
||||
<main>
|
||||
<h1>Auszahlungsvariante Lese @Model.Variant.Year</h1>
|
||||
<h2>@Model.Variant.Name</h2>
|
||||
<table class="payment-variant border">
|
||||
<colgroup>
|
||||
<col style="width: 20.0mm;"/>
|
||||
<col style="width: 30.0mm;"/>
|
||||
<col style="width: 4.5mm;"/>
|
||||
<col style="width: 28.0mm;"/>
|
||||
<col style="width: 47.5mm;"/>
|
||||
<col style="width: 15.0mm;"/>
|
||||
<col style="width: 20.0mm;"/>
|
||||
</colgroup>
|
||||
@{
|
||||
//var sum1 = Model.Variant.DeliveryPartPayments.Sum(p => p.NetAmount);
|
||||
//var sum2 = Model.Variant.Credits.Sum(p => p.); //Model.Variant.MemberPayments.Sum(p => p.Amount);
|
||||
var deliveryModifiers = Model.Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount);
|
||||
var memberModifiers = Model.Variant.Credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount);
|
||||
var sum2 = Model.Variant.Credits.Sum(p => p.NetAmount);
|
||||
var sum1 = sum2 - deliveryModifiers - memberModifiers;
|
||||
var payed = -Model.Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
|
||||
var netSum = Model.Variant.Credits.Sum(p => p.NetAmount) - Model.Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
|
||||
var vat = Model.Variant.Credits.Sum(p => p.VatAmount);
|
||||
var grossSum = Model.Variant.Credits.Sum(p => p.GrossAmount);
|
||||
var totalMods = Model.Variant.Credits.Sum(p => p.Modifiers ?? 0m);
|
||||
var considered = -Model.Variant.Credits.Sum(p => p.PrevModifiers ?? 0m);
|
||||
var totalSum = Model.Variant.Credits.Sum(p => p.Amount);
|
||||
}
|
||||
<tbody>
|
||||
<tr class="sectionheading">
|
||||
<th colspan="4">Allgemein</th>
|
||||
<th colspan="3" class="lborder">Berücksichtigt</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name:</th>
|
||||
<td colspan="3">@Model.Variant.Name</td>
|
||||
<th colspan="2" class="lborder">Zu-/Abschläge bei Lieferungen:</th>
|
||||
<td class="center">@(Model.BillingData.ConsiderDelieryModifiers ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Beschr.:</th>
|
||||
<td colspan="3">@Model.Variant.Comment</td>
|
||||
<th colspan="2" class="lborder">Pönalen bei Unterlieferungen (FB):</th>
|
||||
<td class="center">@(Model.BillingData.ConsiderContractPenalties ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="overflow: visible;">Rebel-Zuschl.:</th>
|
||||
<td colspan="3" class="center">
|
||||
@($"{Utils.GetSign(Model.BillingData.NetWeightModifier)}{Math.Abs(Model.BillingData.NetWeightModifier) * 100:N2}") % /
|
||||
@($"{Utils.GetSign(Model.BillingData.GrossWeightModifier)}{Math.Abs(Model.BillingData.GrossWeightModifier) * 100:N2}") %
|
||||
</td>
|
||||
<th colspan="2" class="lborder">Strafen bei Unterlieferungen (GA):</th>
|
||||
<td class="center">@(Model.BillingData.ConsiderTotalPenalty ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="overflow: visible;">Datum/Überw.:</th>
|
||||
<td colspan="3" class="center">
|
||||
@($"{Model.Variant.Date:dd.MM.yyyy}") /
|
||||
@($"{Model.Variant.TransferDate:dd.MM.yyyy}")
|
||||
</td>
|
||||
<th colspan="2" class="lborder">Automatische Nachzeichnung der GA:</th>
|
||||
<td class="center">@(Model.BillingData.ConsiderAutoBusinessShares ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Berechnung:</th>
|
||||
<td colspan="3" class="center">@($"{Model.Variant.CalcTime:dd.MM.yyyy, HH:mm:ss}")</td>
|
||||
<th colspan="2" class="lborder">Benutzerdef. Zu-/Abschläge pro Mitglied:</th>
|
||||
<td class="center">@(Model.BillingData.ConsiderCustomModifiers ? "Ja" : "Nein")</td>
|
||||
</tr>
|
||||
<tr class="sectionheading">
|
||||
<th colspan="4">Beträge</th>
|
||||
<th colspan="3" class="lborder">Statistik</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Zwischensumme:</th>
|
||||
<td></td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{sum1:N2}")</td>
|
||||
<th class="lborder">Lieferanten:</th>
|
||||
<td colspan="2" class="number">@($"{Model.MemberNum:N0}")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Zu-/Abschläge (Mitglieder):</th>
|
||||
<td class="number">@Utils.GetSign(memberModifiers)</td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(memberModifiers):N2}")</td>
|
||||
<th class="lborder">Lieferungen:</th>
|
||||
<td colspan="2" class="number">@($"{Model.DeliveryNum:N0}")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Zu-/Abschläge (Lieferungen):</th>
|
||||
<td class="number">@Utils.GetSign(deliveryModifiers)</td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(deliveryModifiers):N2}")</td>
|
||||
<th class="lborder">Teillieferungen:</th>
|
||||
<td colspan="2" class="number">@($"{Model.DeliveryPartNum:N0}")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Gesamtsumme:</th>
|
||||
<td class="number tborder"></td>
|
||||
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{sum2:N2}")</td>
|
||||
<th class="lborder"></th>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Bisher ausgezahlt:</th>
|
||||
<td class="number">@Utils.GetSign(payed)</td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(payed):N2}")</td>
|
||||
@{
|
||||
var weiRows = Model.Data.Rows.Where(r => r.QualityLevel == "Wein");
|
||||
var minWei = weiRows.Min(r => r.Ungeb.MinPrice);
|
||||
var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice);
|
||||
}
|
||||
<th class="lborder tborder">Preis (abgewertet):</th>
|
||||
<td colspan="2" class="center tborder">@(minWei != maxWei ? $"{minWei:N4}–{maxWei:N4}" : $"{minWei:N4}") @Model.CurrencySymbol/kg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Nettosumme:</th>
|
||||
<td class="number tborder"></td>
|
||||
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{netSum:N2}")</td>
|
||||
@{
|
||||
var quwRows = Model.Data.Rows.Where(r => r.QualityLevel != "Wein");
|
||||
var minPrice = quwRows.Min(r => r.Ungeb.MinPrice);
|
||||
var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice);
|
||||
}
|
||||
<th class="lborder">Preis (ungeb., nicht abgew.):</th>
|
||||
<td colspan="2" class="center">@(minPrice != maxPrice ? $"{minPrice:N4}–{maxPrice:N4}" : $"{minPrice:N4}") @Model.CurrencySymbol/kg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Mehrwertsteuer:</th>
|
||||
<td class="number">@Utils.GetSign(vat)</td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(vat):N2}")</td>
|
||||
@{
|
||||
var gebRows = Model.Data.Rows
|
||||
.Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null)
|
||||
.Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice);
|
||||
var minGeb = gebRows.Min();
|
||||
var maxGeb = gebRows.Max();
|
||||
}
|
||||
<th class="lborder">Gebunden-Zuschlag:</th>
|
||||
<td colspan="2" class="center">
|
||||
@(minGeb != maxGeb ? $"{minGeb:N4}–{maxGeb:N4} {Model.CurrencySymbol}/kg" : minGeb == 0 ? "-" : $"{minGeb:N4} {Model.CurrencySymbol}/kg")
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Bruttosumme:</th>
|
||||
<td class="number tborder"></td>
|
||||
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{grossSum:N2}")</td>
|
||||
<th class="lborder"></th>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Abzüge (Strafen/Pönalen, GA, ...):</th>
|
||||
<td class="number">@Utils.GetSign(totalMods)</td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(totalMods):N2}")</td>
|
||||
<th class="lborder tborder">Menge (ungebunden):</th>
|
||||
<td colspan="2" class="number tborder">@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight):N0}") kg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Bereits berücksichtigte Abzüge:</th>
|
||||
<td class="number">@Utils.GetSign(considered)</td>
|
||||
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(considered):N2}")</td>
|
||||
<th class="lborder">Menge (gebunden):</th>
|
||||
<td colspan="2" class="number">@($"{Model.Data.Rows.Sum(r => r.Geb.Weight):N0}") kg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Auszahlungsbetrag:</th>
|
||||
<td class="number tborder"></td>
|
||||
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{totalSum:N2}")</td>
|
||||
<th class="lborder">Gesamtmenge:</th>
|
||||
<td colspan="2" class="number tborder">@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight + r.Geb.Weight):N0}") kg</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="payment-variant border">
|
||||
<colgroup>
|
||||
<col style="width: 35mm;"/>
|
||||
<col style="width: 30mm;"/>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 25mm;"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class="sectionheading">
|
||||
<th colspan="6">Statistik Zu-/Abschläge</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan="2">Name</th>
|
||||
<th rowspan="2">Zu-/Abschlag</th>
|
||||
<th>Lieferungen</th>
|
||||
<th>Minimum</th>
|
||||
<th>Maximum</th>
|
||||
<th>Betrag</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>[#]</th>
|
||||
<th>[@Model.CurrencySymbol]</th>
|
||||
<th>[@Model.CurrencySymbol]</th>
|
||||
<th>[@Model.CurrencySymbol]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var m in Model.ModifierStat) {
|
||||
var mod = Model.Modifiers[m.ModId];
|
||||
<tr>
|
||||
<th>@mod.Name</th>
|
||||
<td class="number">@mod.ValueStr</td>
|
||||
<td class="number">@($"{m.Count:N0}")</td>
|
||||
<td class="number">@($"{m.Min:N2}")</td>
|
||||
<td class="number">@($"{m.Max:N2}")</td>
|
||||
<td class="number">@($"{m.Sum:N2}")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="payment-variant-data">
|
||||
<colgroup>
|
||||
<col style="width: 25mm;"/>
|
||||
<col style="width: 19mm;"/>
|
||||
<col style="width: 18mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 18mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 18mm;"/>
|
||||
<col style="width: 15mm;"/>
|
||||
<col style="width: 22mm;"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
|
||||
<th>Gradation</th>
|
||||
<th colspan="2">ungebunden</th>
|
||||
<th colspan="2">attributlos gebunden</th>
|
||||
<th colspan="2">gebunden</th>
|
||||
<th>Gesamt</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>[@(true ? "°Oe" : "°KMW")]</th>
|
||||
<th>[kg]</th>
|
||||
<th>[@(Model.CurrencySymbol)/kg]</th>
|
||||
<th>[kg]</th>
|
||||
<th>[@(Model.CurrencySymbol)/kg]</th>
|
||||
<th>[kg]</th>
|
||||
<th>[@(Model.CurrencySymbol)/kg]</th>
|
||||
<th>[@(Model.CurrencySymbol)]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{
|
||||
string? lastHdr = null;
|
||||
}
|
||||
@foreach (var row in Model.Data.Rows) {
|
||||
var hdr = $"{row.Variety}{(row.Attribute != null ? " / " : "")}{row.Attribute}{(row.Cultivation != null ? " / " : "")}{row.Cultivation}";
|
||||
if (lastHdr != hdr) {
|
||||
var rows = Model.Data.Rows
|
||||
.Where(r => r.Variety == row.Variety && r.Attribute == row.Attribute && r.Cultivation == row.Cultivation)
|
||||
.ToList();
|
||||
<tr class="subheading @(lastHdr != null ? "new" : "")">
|
||||
<th colspan="2">@hdr</th>
|
||||
<td class="number">@($"{rows.Sum(r => r.Ungeb.Weight):N0}")</td>
|
||||
<td></td>
|
||||
<td class="number">@($"{rows.Sum(r => r.LowGeb.Weight):N0}")</td>
|
||||
<td></td>
|
||||
<td class="number">@($"{rows.Sum(r => r.Geb.Weight):N0}")</td>
|
||||
<td></td>
|
||||
<td class="number">@($"{rows.Sum(r => r.Amount):N2}")</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td>@(row.QualityLevel)</td>
|
||||
<td class="center">@($"{row.Oe:N0}")</td>
|
||||
<td class="number">@(row.Ungeb.Weight != 0 ? $"{row.Ungeb.Weight:N0}" : "-")</td>
|
||||
<td class="number">@(row.Ungeb.MaxPrice != null ? $"{row.Ungeb.MaxPrice:N4}" : "-")</td>
|
||||
<td class="number">@(row.LowGeb.Weight != 0 ? $"{row.LowGeb.Weight:N0}" : "-")</td>
|
||||
<td class="number">@(row.LowGeb.MaxPrice != null ? $"{row.LowGeb.MaxPrice:N4}" : "-")</td>
|
||||
<td class="number">@(row.Geb.Weight != 0 ? $"{row.Geb.Weight:N0}" : "-")</td>
|
||||
<td class="number">@(row.Geb.MaxPrice != null ? $"{row.Geb.MaxPrice:N4}" : "-")</td>
|
||||
<td class="number">@($"{row.Amount:N2}")</td>
|
||||
</tr>
|
||||
lastHdr = hdr;
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 14pt;
|
||||
margin-top: 2mm;
|
||||
}
|
||||
|
||||
table.payment-variant {
|
||||
margin-top: 10mm;
|
||||
}
|
||||
|
||||
table.payment-variant-data {
|
||||
break-before: page;
|
||||
}
|
||||
@@ -1,5 +1,13 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models.Dtos;
|
||||
using iText.Kernel.Colors;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Layout.Borders;
|
||||
using iText.Layout.Element;
|
||||
using iText.Layout.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class WineQualityStatistics : Document {
|
||||
@@ -19,9 +27,99 @@ namespace Elwig.Documents {
|
||||
public WineQualityStatisticsData Data;
|
||||
public bool UseOe => Data.UseOe;
|
||||
|
||||
public WineQualityStatistics(string filter, WineQualityStatisticsData data) : base($"{Name} {filter}") {
|
||||
public WineQualityStatistics(string filter, WineQualityStatisticsData data) :
|
||||
base($"{Name} {filter}") {
|
||||
Filter = filter;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderBody(doc, pdf);
|
||||
doc.Add(new KernedParagraph(Name, 24)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginBottomMM(2));
|
||||
doc.Add(new KernedParagraph(Filter, 14)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginBottomMM(10));
|
||||
foreach (var sec in Data.Sections) {
|
||||
doc.Add(NewQualitySectionTable(sec).SetMarginBottomMM(5));
|
||||
}
|
||||
}
|
||||
|
||||
protected Table NewQualityColumnTable(string[] qualIds, WineQualityStatisticsData.QualitySection sec) {
|
||||
var tbl = new Table(ColsMM(9.5, 10, 19.5))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetMarginsMM(1, 0, 1, 0)
|
||||
.AddCell(NewCell(new KernedParagraph(UseOe ? "[°Oe]" : "[°KMW]", 8)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(IF)).SetPaddingsMM(1, 1, 1, 2))
|
||||
.AddCell(NewCell(new KernedParagraph("[#]", 8)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(IF)).SetPaddingsMM(1, 1, 1, 1))
|
||||
.AddCell(NewCell(new KernedParagraph("[kg]", 8)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(IF)).SetPaddingsMM(1, 2, 1, 1));
|
||||
|
||||
foreach (var qualId in qualIds) {
|
||||
tbl.AddCell(NewCell(new KernedParagraph(QualityLevels.GetValueOrDefault(qualId, qualId), 10)
|
||||
.SetFont(BI).SetTextAlignment(TextAlignment.CENTER), colspan: 3)
|
||||
.SetPaddingsMM(2, 0, 2, 0));
|
||||
foreach (var (grad, avgKmw, num, weight) in sec.Data.GetValueOrDefault(qualId, Array.Empty<(double, double, int, int)>())) {
|
||||
tbl.AddCell(NewCell(new KernedParagraph(UseOe ? $"{grad:N0}" : $"{grad:N1}", 10)
|
||||
.SetTextAlignment(TextAlignment.CENTER)).SetPaddingsMM(0, 0, 0, 2))
|
||||
.AddCell(NewCell(new KernedParagraph($"{num:N0}", 10)
|
||||
.SetTextAlignment(TextAlignment.RIGHT)).SetPaddingsMM(0, 0, 0, 0))
|
||||
.AddCell(NewCell(new KernedParagraph($"{weight:N0}", 10)
|
||||
.SetTextAlignment(TextAlignment.RIGHT)).SetPaddingsMM(0, 2, 0, 0));
|
||||
}
|
||||
}
|
||||
return tbl;
|
||||
}
|
||||
|
||||
protected Table NewQualitySumTable(double kmw, int num, int weight) {
|
||||
return new Table(ColsMM(9.5, 10, 19.5))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetMarginsMM(1, 0, 1, 0)
|
||||
.AddCell(NewCell(new KernedParagraph(weight == 0 ? "-" : UseOe ? $"{Utils.KmwToOe(kmw):N0}" : $"{kmw:N1}", 10)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)).SetPaddingsMM(0, 0, 0, 2))
|
||||
.AddCell(NewCell(new KernedParagraph($"{num:N0}", 10)
|
||||
.SetTextAlignment(TextAlignment.RIGHT).SetFont(BF)).SetPaddingsMM(0, 0, 0, 0))
|
||||
.AddCell(NewCell(new KernedParagraph($"{weight:N0}", 10)
|
||||
.SetTextAlignment(TextAlignment.RIGHT).SetFont(BF)).SetPaddingsMM(0, 2, 0, 0));
|
||||
}
|
||||
|
||||
protected Table NewQualitySectionTable(WineQualityStatisticsData.QualitySection sec) {
|
||||
var tbl = new Table(4)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetBorder(new SolidBorder(BorderThickness))
|
||||
.SetKeepTogether(true);
|
||||
|
||||
var bgColor = sec.Type == "R" ? new DeviceRgb(0xff, 0xc0, 0xc0) : sec.Type == "W" ? new DeviceRgb(0xc0, 0xff, 0xc0) : new DeviceRgb(0xe0, 0xe0, 0xe0);
|
||||
tbl.AddCell(NewCell(new KernedParagraph(sec.Name, 14).SetFont(BF), colspan: 4)
|
||||
.SetBackgroundColor(bgColor).SetPaddingsMM(1, 2, 1, 2));
|
||||
|
||||
foreach (var qualIds in QualIds) {
|
||||
tbl.AddCell(NewCell().SetPadding(0).Add(NewQualityColumnTable(qualIds, sec))
|
||||
.SetBorder(new SolidBorder(BorderThickness)));
|
||||
}
|
||||
|
||||
foreach (var qualIds in QualIds) {
|
||||
var quals = qualIds.Select(q => sec.Data.GetValueOrDefault(q, Array.Empty<(double Grad, double AvgKmw, int Num, int Weight)>()));
|
||||
var weight = quals.Sum(q => q.Sum(kv => kv.Weight));
|
||||
var num = quals.Sum(q => q.Sum(kv => kv.Num));
|
||||
var kmw = quals.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / weight;
|
||||
tbl.AddCell(NewCell().SetPaddingsMM(0.5f, 0, 0.5f, 0).Add(NewQualitySumTable(kmw, num, weight))
|
||||
.SetBorder(new SolidBorder(BorderThickness)));
|
||||
}
|
||||
|
||||
var totalWeight = sec.Data.Values.Sum(q => q.Sum(kv => kv.Weight));
|
||||
var totalNum = sec.Data.Values.Sum(q => q.Sum(kv => kv.Num));
|
||||
var totalKmw = sec.Data.Values.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / totalWeight;
|
||||
tbl.AddCell(NewCell(colspan: 3).SetBackgroundColor(bgColor))
|
||||
.AddCell(NewCell().SetPadding(0).Add(NewQualitySumTable(totalKmw, totalNum, totalWeight))
|
||||
.SetBackgroundColor(bgColor));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
@using RazorLight
|
||||
@using Elwig.Helpers
|
||||
@inherits TemplatePage<Elwig.Documents.WineQualityStatistics>
|
||||
@model Elwig.Documents.WineQualityStatistics
|
||||
@{ Layout = "Document"; }
|
||||
<link rel="stylesheet" href="file:///@Raw(Model.DocumentsPath)\WineQualityStatistics.css" />
|
||||
<main>
|
||||
<h1>Qualitätsstatistik</h1>
|
||||
<h2>@Model.Filter</h2>
|
||||
@foreach (var sec in Model.Data.Sections) {
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style="width: 25%;"/>
|
||||
<col style="width: 25%;"/>
|
||||
<col style="width: 25%;"/>
|
||||
<col style="width: 25%;"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" class="header @(sec.Type == "R" ? "red" : sec.Type == "W" ? "green" : "")">
|
||||
<h3>@sec.Name</h3>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
@foreach (var qualIds in Model.QualIds) {
|
||||
<td class="container">
|
||||
<div class="row">
|
||||
<span class="units">[@(Model.UseOe ? "°Oe" : "°KMW")]</span>
|
||||
<span class="units">[#]</span>
|
||||
<span class="units">[kg]</span>
|
||||
</div>
|
||||
@foreach (var qualId in qualIds) {
|
||||
<h4>@(Model.QualityLevels.GetValueOrDefault(qualId, qualId))</h4>
|
||||
@foreach (var (grad, avgKmw, num, weight) in sec.Data.GetValueOrDefault(qualId, Array.Empty<(double, double, int, int)>())) {
|
||||
<div class="row">
|
||||
<span class="gradation">@(Model.UseOe ? $"{grad:N0}" : $"{grad:N1}")</span>
|
||||
<span class="number">@($"{num:N0}")</span>
|
||||
<span class="number">@($"{weight:N0}")</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
@foreach (var qualIds in Model.QualIds) {
|
||||
var quals = qualIds.Select(q => sec.Data.GetValueOrDefault(q, Array.Empty<(double Grad, double AvgKmw, int Num, int Weight)>()));
|
||||
var weight = quals.Sum(q => q.Sum(kv => kv.Weight));
|
||||
var num = quals.Sum(q => q.Sum(kv => kv.Num));
|
||||
var kmw = quals.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / weight;
|
||||
<td class="container bold">
|
||||
<div class="row">
|
||||
<span class="gradation">@(weight == 0 ? "-" : Model.UseOe ? $"{Utils.KmwToOe(kmw):N0}" : $"{kmw:N1}")</span>
|
||||
<span class="number">@($"{num:N0}")</span>
|
||||
<span class="number">@($"{weight:N0}")</span>
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
@{
|
||||
var totalWeight = sec.Data.Values.Sum(q => q.Sum(kv => kv.Weight));
|
||||
var totalNum = sec.Data.Values.Sum(q => q.Sum(kv => kv.Num));
|
||||
var totalKmw = sec.Data.Values.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / totalWeight;
|
||||
}
|
||||
<td colspan="4" class="container bold footer @(sec.Type == "R" ? "red" : sec.Type == "W" ? "green" : "")">
|
||||
<div class="row" style="width: 24%; margin-left: 76%;">
|
||||
<span class="gradation">@(totalWeight == 0 ? "-" : Model.UseOe ? $"{Utils.KmwToOe(totalKmw):N0}" : $"{totalKmw:N1}")</span>
|
||||
<span class="number">@($"{totalNum:N0}")</span>
|
||||
<span class="number">@($"{totalWeight:N0}")</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
}
|
||||
</main>
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 10mm;
|
||||
margin-bottom: 2mm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 14pt;
|
||||
margin-top: 2mm;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-size: 14pt;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
font-size: 10pt;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
margin: 2mm 0 2mm 0;
|
||||
}
|
||||
|
||||
.row:first-child { margin-top: 0.5mm; }
|
||||
.row:last-child { margin-bottom: 0.5mm; }
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-top: 10mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
border: var(--border-thickness) solid black;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
table .header {
|
||||
padding: 1mm 2mm;
|
||||
}
|
||||
|
||||
table .header,
|
||||
table .footer {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
table .header.red,
|
||||
table .footer.red {
|
||||
background-color: #FFC0C0;
|
||||
}
|
||||
|
||||
table .header.green,
|
||||
table .footer.green {
|
||||
background-color: #C0FFC0;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.row span {
|
||||
flex: 10mm 1 1;
|
||||
display: block;
|
||||
padding: 0 1mm;
|
||||
}
|
||||
|
||||
.row .units {
|
||||
text-align: center;
|
||||
font-size: 8pt;
|
||||
font-style: italic;
|
||||
padding: 1mm;
|
||||
}
|
||||
|
||||
.gradation {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.row span:first-child { flex-basis: 7.5mm; }
|
||||
.row span:last-child { flex-basis: 17.5mm; }
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
|
||||
<Version>1.0.0.2</Version>
|
||||
<Version>1.0.4.0</Version>
|
||||
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
@@ -21,22 +23,22 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="bblanchon.PDFium.Win32" Version="148.0.7734" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="LinqKit" Version="1.3.8" />
|
||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3351.48" />
|
||||
<PackageReference Include="NJsonSchema" Version="11.4.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.55" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.0" />
|
||||
<PackageReference Include="System.IO.Ports" Version="9.0.8" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
|
||||
<PackageReference Include="itext" Version="9.5.0" />
|
||||
<PackageReference Include="itext.bouncy-castle-adapter" Version="9.5.0" />
|
||||
<PackageReference Include="LinqKit" Version="1.3.11" />
|
||||
<PackageReference Include="MailKit" Version="4.15.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3856.49" />
|
||||
<PackageReference Include="NJsonSchema" Version="11.5.2" />
|
||||
<PackageReference Include="ScottPlot.WPF" Version="5.1.57" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.5" />
|
||||
<PackageReference Include="System.IO.Ports" Version="10.0.5" />
|
||||
<PackageReference Include="System.Management" Version="10.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -10,7 +10,6 @@ using Microsoft.Data.Sqlite;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using Elwig.Models.Dtos;
|
||||
using System.Reflection;
|
||||
using System.Data;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
@@ -67,7 +66,7 @@ namespace Elwig.Helpers {
|
||||
|
||||
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
|
||||
public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
|
||||
public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; }
|
||||
public DbSet<MemberDeliveryPerVarietyRowSingle> 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; }
|
||||
@@ -117,39 +116,6 @@ namespace Elwig.Helpers {
|
||||
return cnx;
|
||||
}
|
||||
|
||||
public static async Task ExecuteBatch(SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
await (await cmd.ExecuteReaderAsync()).CloseAsync();
|
||||
}
|
||||
|
||||
public static async Task ExecuteEmbeddedScript(SqliteConnection cnx, Assembly asm, string name) {
|
||||
using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource");
|
||||
using var reader = new StreamReader(stream);
|
||||
await ExecuteBatch(cnx, await reader.ReadToEndAsync());
|
||||
}
|
||||
|
||||
public static async Task<object?> ExecuteScalar(SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
return await cmd.ExecuteScalarAsync();
|
||||
}
|
||||
|
||||
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(SqliteConnection cnx) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA foreign_key_check";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var list = new List<(string, long, string, long)>();
|
||||
while (await reader.ReadAsync()) {
|
||||
var table = reader.GetString(0);
|
||||
var rowid = reader.GetInt64(1);
|
||||
var parent = reader.GetString(2);
|
||||
var fkid = reader.GetInt64(3);
|
||||
list.Add((table, rowid, parent, fkid));
|
||||
}
|
||||
return [.. list];
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
|
||||
optionsBuilder.UseSqlite(ConnectionString);
|
||||
optionsBuilder.UseLazyLoadingProxies();
|
||||
@@ -248,13 +214,6 @@ namespace Elwig.Helpers {
|
||||
return c + 1;
|
||||
}
|
||||
|
||||
public async Task<WineQualLevel> GetWineQualityLevel(double kmw) {
|
||||
return await WineQualityLevels
|
||||
.Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
|
||||
.OrderBy(q => q.MinKmw)
|
||||
.LastAsync();
|
||||
}
|
||||
|
||||
public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) {
|
||||
foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
|
||||
var mod = new DeliveryPartModifier {
|
||||
|
||||
@@ -9,17 +9,17 @@ namespace Elwig.Helpers {
|
||||
public static class AppDbUpdater {
|
||||
|
||||
// Don't forget to update value in Tests/fetch-resources.bat!
|
||||
public static readonly int RequiredSchemaVersion = 32;
|
||||
public static readonly int RequiredSchemaVersion = 37;
|
||||
|
||||
private static int VersionOffset = 0;
|
||||
|
||||
public static async Task<Version> CheckDb() {
|
||||
using var cnx = AppDbContext.Connect();
|
||||
|
||||
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
|
||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
||||
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
|
||||
|
||||
var schemaVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0;
|
||||
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
|
||||
VersionOffset = (int)(schemaVers % 100);
|
||||
if (VersionOffset != 0) {
|
||||
// schema was modified manually/externally
|
||||
@@ -27,12 +27,12 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
|
||||
|
||||
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
|
||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
|
||||
var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
|
||||
|
||||
if (App.Version > v) {
|
||||
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision;
|
||||
await AppDbContext.ExecuteBatch(cnx, $"PRAGMA user_version = {vers}");
|
||||
await cnx.ExecuteBatch($"PRAGMA user_version = {vers}");
|
||||
}
|
||||
|
||||
return v;
|
||||
@@ -67,20 +67,20 @@ namespace Elwig.Helpers {
|
||||
if (toExecute.Count == 0)
|
||||
return;
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, """
|
||||
await cnx.ExecuteBatch("""
|
||||
PRAGMA locking_mode = EXCLUSIVE;
|
||||
BEGIN EXCLUSIVE;
|
||||
""");
|
||||
foreach (var script in toExecute) {
|
||||
await AppDbContext.ExecuteEmbeddedScript(cnx, asm, script);
|
||||
await cnx.ExecuteEmbeddedScript(asm, script);
|
||||
}
|
||||
var violations = await AppDbContext.ForeignKeyCheck(cnx);
|
||||
var violations = await cnx.ForeignKeyCheck();
|
||||
if (violations.Length > 0) {
|
||||
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
|
||||
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
|
||||
}
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
COMMIT;
|
||||
VACUUM;
|
||||
PRAGMA schema_version = {toVersion * 100 + VersionOffset};
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
public async Task FinishSeason() {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE season
|
||||
SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year})
|
||||
WHERE year = {Year};
|
||||
@@ -37,7 +37,7 @@ namespace Elwig.Helpers.Billing {
|
||||
public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceBs = 0, int allowanceKgPerBs = 0, double allowanceRel = 0, int addMinBs = 1) {
|
||||
if (addMinBs < 1) addMinBs = 1;
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE member
|
||||
SET business_shares = member.business_shares - h.business_shares
|
||||
FROM member_history h
|
||||
@@ -66,7 +66,7 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
public async Task UnAdjustBusinessShares() {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE member
|
||||
SET business_shares = member.business_shares - h.business_shares
|
||||
FROM member_history h
|
||||
@@ -157,9 +157,9 @@ namespace Elwig.Helpers.Billing {
|
||||
lastMgNr = mgnr;
|
||||
}
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, $"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}");
|
||||
await cnx.ExecuteBatch($"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}");
|
||||
if (inserts.Count > 0) {
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
|
||||
VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
|
||||
ON CONFLICT DO UPDATE
|
||||
@@ -237,7 +237,7 @@ namespace Elwig.Helpers.Billing {
|
||||
if (needed == 0) break;
|
||||
}
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
|
||||
VALUES {string.Join(",\n ", posChanges.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '', {i.Item4})"))}
|
||||
ON CONFLICT DO UPDATE
|
||||
|
||||
@@ -20,13 +20,19 @@ namespace Elwig.Helpers.Billing {
|
||||
Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(ctx, Year, onlyDelivered: false));
|
||||
}
|
||||
|
||||
public async Task Calculate(bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
|
||||
public async Task Calculate(bool strictPrices = true, bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
using var tx = await cnx.BeginTransactionAsync();
|
||||
await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
|
||||
await DeleteInDb(cnx);
|
||||
await SetCalcTime(cnx);
|
||||
await CalculatePrices(cnx);
|
||||
KeyNotFoundException? exception = null;
|
||||
try {
|
||||
await CalculatePrices(cnx, strictPrices);
|
||||
} catch (KeyNotFoundException e) {
|
||||
if (strictPrices) throw;
|
||||
exception = e;
|
||||
}
|
||||
if (Data.ConsiderDelieryModifiers) {
|
||||
await CalculateDeliveryModifiers(cnx);
|
||||
}
|
||||
@@ -34,26 +40,28 @@ namespace Elwig.Helpers.Billing {
|
||||
await CalculateMemberModifiers(cnx);
|
||||
}
|
||||
await tx.CommitAsync();
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
}
|
||||
|
||||
public async Task Commit() {
|
||||
await Revert();
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers)
|
||||
SELECT s.year,
|
||||
COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr,
|
||||
m.mgnr,
|
||||
v.avnr,
|
||||
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
|
||||
IIF(lc.amount >= 0, ROUND(lp.amount / POW(10, s.precision - 2)), 0) AS prev_net_amount,
|
||||
IIF(lc.amount < 0, 0, ROUND(lp.amount / POW(10, s.precision - 2))) 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,
|
||||
IIF(lc.amount >= 0, lc.modifiers, 0) AS prev_modifiers
|
||||
IIF(lc.amount < 0, 0, lc.modifiers) 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
|
||||
@@ -72,29 +80,29 @@ namespace Elwig.Helpers.Billing {
|
||||
LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
|
||||
LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr)
|
||||
LEFT JOIN payment_custom x ON (x.year, x.mgnr) = (s.year, m.mgnr)
|
||||
WHERE s.year = {Year} AND v.avnr = {AvNr};
|
||||
WHERE s.year = {Year} AND v.avnr = {AvNr} AND p.amount > COALESCE(lp.amount, 0);
|
||||
""");
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
""");
|
||||
}
|
||||
|
||||
public async Task Revert() {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
""");
|
||||
}
|
||||
|
||||
protected async Task SetCalcTime(SqliteConnection cnx) {
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr})
|
||||
""");
|
||||
}
|
||||
|
||||
protected async Task DeleteInDb(SqliteConnection cnx) {
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
@@ -108,7 +116,7 @@ namespace Elwig.Helpers.Billing {
|
||||
var multiplier = 0.50;
|
||||
var includePredecessor = true;
|
||||
var modName = "Treue%";
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
|
||||
SELECT c.year, {AvNr}, s.mgnr, 0,
|
||||
ROUND(s.sum * COALESCE(m.abs, 0)),
|
||||
@@ -130,7 +138,7 @@ namespace Elwig.Helpers.Billing {
|
||||
mod_rel = mod_rel + excluded.mod_rel
|
||||
""");
|
||||
}
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
|
||||
SELECT x.year, {AvNr}, x.mgnr, 0, COALESCE(x.mod_abs * POW(10, s.precision - 2), 0), COALESCE(x.mod_rel, 0)
|
||||
FROM payment_custom x
|
||||
@@ -142,7 +150,8 @@ namespace Elwig.Helpers.Billing {
|
||||
""");
|
||||
}
|
||||
|
||||
protected async Task CalculatePrices(SqliteConnection cnx) {
|
||||
protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) {
|
||||
var invalid = new HashSet<string>();
|
||||
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"""
|
||||
@@ -172,21 +181,31 @@ namespace Elwig.Helpers.Billing {
|
||||
var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
|
||||
var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
|
||||
var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
|
||||
var price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
|
||||
decimal price = 0;
|
||||
try {
|
||||
price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
|
||||
} catch (KeyNotFoundException e) {
|
||||
invalid.Add(e.Message.Split('\'')[1]);
|
||||
}
|
||||
var priceL = PaymentVariant.Season.DecToDb(price);
|
||||
inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
|
||||
}
|
||||
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
var msg = invalid.Count == 0 ? null : "Für folgende Sorten wurde noch keine Preiskurve festgelegt: " + string.Join(", ", invalid);
|
||||
if (msg != null && strict)
|
||||
throw new KeyNotFoundException(msg);
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
|
||||
VALUES {string.Join(",\n ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))};
|
||||
""");
|
||||
if (msg != null)
|
||||
throw new KeyNotFoundException(msg);
|
||||
}
|
||||
|
||||
protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
|
||||
var netMod = Data.NetWeightModifier.ToString().Replace(',', '.');
|
||||
var grossMod = Data.GrossWeightModifier.ToString().Replace(',', '.');
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
|
||||
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, 0, IIF(d.net_weight, {netMod}, {grossMod})
|
||||
FROM delivery_part d
|
||||
|
||||
@@ -19,14 +19,6 @@ namespace Elwig.Helpers {
|
||||
public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
|
||||
public bool IsGrInzersdorf => IsWeinland;
|
||||
|
||||
public bool HasNetWeighing(string? zwstId) => IsMatzen || (IsWinzerkeller && zwstId == "W");
|
||||
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;
|
||||
@@ -214,15 +206,15 @@ namespace Elwig.Helpers {
|
||||
case 1: orderingMemberList = "NAME"; break;
|
||||
case 2: orderingMemberList = "KG"; break;
|
||||
}
|
||||
string mailSendPostal = "MGNR";
|
||||
switch (MailOrdering) {
|
||||
string mailSendPostal = "WISH";
|
||||
switch (MailSendPostal) {
|
||||
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) {
|
||||
string mailSendEmail = "WISH";
|
||||
switch (MailSendEmail) {
|
||||
case 0: mailSendEmail = "NONE"; break;
|
||||
case 1: mailSendEmail = "WISH"; break;
|
||||
case 2: mailSendEmail = "ALL"; break;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -5,6 +6,8 @@ using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
|
||||
public enum WeighingMode { Gross, Net, Box }
|
||||
|
||||
public record struct ScaleConfig {
|
||||
public string Id;
|
||||
public string? Type;
|
||||
@@ -41,6 +44,7 @@ namespace Elwig.Helpers {
|
||||
public string DatabaseFile = App.DataPath + "database.sqlite3";
|
||||
public string? DatabaseLog = null;
|
||||
public string? Branch = null;
|
||||
public WeighingMode? WeighingMode;
|
||||
public string? UpdateUrl = null;
|
||||
public bool UpdateAuto = false;
|
||||
public string? SyncUrl = null;
|
||||
@@ -74,6 +78,8 @@ namespace Elwig.Helpers {
|
||||
DatabaseLog = log != null ? Path.Combine(Path.GetDirectoryName(FileName) ?? App.DataPath, log) : null;
|
||||
Branch = config["general:branch"];
|
||||
Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
|
||||
var weighing = config["general:weighing"];
|
||||
WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null;
|
||||
UpdateUrl = config["update:url"];
|
||||
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
|
||||
SyncUrl = config["sync:url"];
|
||||
|
||||
@@ -15,7 +15,9 @@ namespace Elwig.Helpers.Export {
|
||||
protected readonly char Separator;
|
||||
protected string? Header;
|
||||
|
||||
public Csv(string filename, char separator = ';') : this(filename, separator, Utils.UTF8) { }
|
||||
public Csv(string filename, char separator = ';') :
|
||||
this(filename, separator, Utils.UTF8) {
|
||||
}
|
||||
|
||||
public Csv(string filename, char separator, Encoding encoding) {
|
||||
_writer = new StreamWriter(filename, false, encoding);
|
||||
@@ -58,4 +60,22 @@ namespace Elwig.Helpers.Export {
|
||||
|
||||
public abstract string FormatRow(T row);
|
||||
}
|
||||
|
||||
public class CsvSimple : Csv<IEnumerable<object?>> {
|
||||
|
||||
public CsvSimple(string filename, char separator, Encoding encoding) :
|
||||
base(filename, separator, encoding) {
|
||||
}
|
||||
|
||||
public CsvSimple(string filename, char separator = ';') :
|
||||
base(filename, separator) {
|
||||
}
|
||||
|
||||
public override string FormatRow(IEnumerable<object?> row) {
|
||||
return string.Join(Separator, row.Select(i => {
|
||||
var str = $"{i}";
|
||||
return str.Contains(Separator) || str.Contains('\n') ? $"\"{str.Replace("\"", "\"\"")}\"" : str;
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
243
Elwig/Helpers/Export/Database.cs
Normal file
243
Elwig/Helpers/Export/Database.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public static class Database {
|
||||
|
||||
private static async Task<(long? ApplicationId, string? UserVersion, long? SchemaVersion, long FileSize)> GetMeta() {
|
||||
long size = new FileInfo(App.Config.DatabaseFile).Length;
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id");
|
||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version");
|
||||
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version");
|
||||
return (applId, userVers != null ? $"{userVers >> 24}.{(userVers >> 16) & 0xFF}.{(userVers >> 8) & 0xFF}.{userVers & 0xFF}" : null, schemaVers, size);
|
||||
}
|
||||
|
||||
public static async Task ExportSqlite(string filename, bool zipFile) {
|
||||
if (zipFile) {
|
||||
File.Delete(filename);
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
|
||||
|
||||
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
|
||||
await writer.WriteAsync("elwig:1");
|
||||
}
|
||||
|
||||
var (applId, userVers, schemaVers, size) = await GetMeta();
|
||||
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
|
||||
var obj = new JsonObject {
|
||||
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
["database"] = new JsonObject {
|
||||
["application_id"] = applId,
|
||||
["user_version"] = userVers,
|
||||
["schema_version"] = schemaVers,
|
||||
["file_size"] = size,
|
||||
},
|
||||
};
|
||||
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
|
||||
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
|
||||
} else {
|
||||
File.Copy(App.Config.DatabaseFile, filename, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExportSql(string filename, bool zipFile) {
|
||||
if (zipFile) {
|
||||
File.Delete(filename);
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
|
||||
|
||||
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
|
||||
await writer.WriteAsync("elwig:1");
|
||||
}
|
||||
|
||||
var (applId, userVers, schemaVers, size) = await GetMeta();
|
||||
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
|
||||
var obj = new JsonObject {
|
||||
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
["database"] = new JsonObject {
|
||||
["application_id"] = applId,
|
||||
["user_version"] = userVers,
|
||||
["schema_version"] = schemaVers,
|
||||
["file_size"] = size,
|
||||
},
|
||||
};
|
||||
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
|
||||
var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
|
||||
using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) {
|
||||
await ExportSql(writer);
|
||||
}
|
||||
} else {
|
||||
using var stream = File.OpenWrite(filename);
|
||||
using var writer = new StreamWriter(stream, Utils.UTF8);
|
||||
await ExportSql(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExportSql(StreamWriter writer) {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
|
||||
var tables = new List<(string Name, string Sql)>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = "SELECT name, sql FROM sqlite_schema WHERE type = 'table'";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync()) {
|
||||
tables.Add((reader.GetString(0), reader.GetString(1)));
|
||||
}
|
||||
}
|
||||
|
||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
|
||||
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
|
||||
|
||||
await writer.WriteLineAsync($"-- Elwig database dump, {DateTime.Now:yyyy-MM-dd, HH:mm:ss}");
|
||||
await writer.WriteLineAsync($"-- {Environment.MachineName}, Zwst. {App.BranchName}, {App.Client.Name}");
|
||||
await writer.WriteLineAsync("BEGIN TRANSACTION;");
|
||||
await writer.WriteLineAsync("PRAGMA foreign_keys=OFF;");
|
||||
await writer.WriteLineAsync($"PRAGMA application_id=0x{applId:X8};");
|
||||
await writer.WriteLineAsync($"PRAGMA user_version=0x{userVers:X8};");
|
||||
|
||||
foreach (var t in tables) {
|
||||
await writer.WriteAsync(t.Sql);
|
||||
await writer.WriteLineAsync(";");
|
||||
|
||||
var columnNames = new List<string>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"PRAGMA table_info({t.Name})";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync()) {
|
||||
columnNames.Add(reader.GetString(1));
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"SELECT {string.Join(',', columnNames)} FROM {t.Name}";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var columns = await reader.GetColumnSchemaAsync();
|
||||
var values = new object[reader.FieldCount];
|
||||
while (await reader.ReadAsync()) {
|
||||
await writer.WriteAsync($"INSERT INTO {t.Name} VALUES (");
|
||||
|
||||
reader.GetValues(values);
|
||||
for (int i = 0; i < columns.Count; i++) {
|
||||
var c = columns[i];
|
||||
var v = values[i];
|
||||
if (i > 0) await writer.WriteAsync(",");
|
||||
if (v == null || v is DBNull) {
|
||||
await writer.WriteAsync("NULL");
|
||||
} else if (c.DataTypeName == "TEXT") {
|
||||
await writer.WriteAsync($"'{v.ToString()?.Replace("'", "''")}'");
|
||||
} else {
|
||||
await writer.WriteAsync(v.ToString()?.Replace(',', '.'));
|
||||
}
|
||||
}
|
||||
|
||||
await writer.WriteLineAsync(");");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = "SELECT sql FROM sqlite_schema WHERE type != 'table' AND sql IS NOT NULL";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync()) {
|
||||
await writer.WriteAsync(reader.GetString(0));
|
||||
await writer.WriteLineAsync(";");
|
||||
}
|
||||
}
|
||||
|
||||
await writer.WriteLineAsync($"PRAGMA schema_version={schemaVers};");
|
||||
await writer.WriteLineAsync("PRAGMA foreign_keys=ON;");
|
||||
await writer.WriteLineAsync("COMMIT;");
|
||||
await writer.WriteLineAsync("VACUUM;");
|
||||
}
|
||||
|
||||
public static async Task Import(string filename) {
|
||||
if (filename.EndsWith(".sql")) {
|
||||
await ImportSql(filename, false);
|
||||
} else if (filename.EndsWith(".sql.zip")) {
|
||||
await ImportSql(filename, true);
|
||||
} else if (filename.EndsWith(".sqlite3")) {
|
||||
await ImportSqlite(filename, false);
|
||||
} else if (filename.EndsWith(".sqlite3.zip")) {
|
||||
await ImportSqlite(filename, true);
|
||||
} else {
|
||||
throw new ArgumentException($"Unknown file extension for importing: '{filename}'");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ImportSql(string filename, bool zipFile = false) {
|
||||
if (zipFile) {
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
|
||||
await zip.CheckIntegrity();
|
||||
foreach (var entry in zip.Entries) {
|
||||
if (entry.Name.EndsWith(".sql")) {
|
||||
using var stream = entry.Open();
|
||||
using var reader = new StreamReader(stream, Utils.UTF8);
|
||||
await ImportSql(reader);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new FileFormatException("ZIP archive has to contain at least one .sql file");
|
||||
} else {
|
||||
using var stream = File.Open(filename, FileMode.Open);
|
||||
using var reader = new StreamReader(stream, Utils.UTF8);
|
||||
await ImportSql(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ImportSqlite(string filename, bool zipFile = false) {
|
||||
if (zipFile) {
|
||||
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
|
||||
try {
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
|
||||
await zip.CheckIntegrity();
|
||||
foreach (var entry in zip.Entries) {
|
||||
if (entry.Name.EndsWith(".sqlite3")) {
|
||||
entry.ExtractToFile(newName);
|
||||
await ImportSqlite(newName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new FileFormatException("ZIP archive has to contain at least one .sqlite3 file");
|
||||
} finally {
|
||||
if (File.Exists(newName)) File.Delete(newName);
|
||||
}
|
||||
}
|
||||
|
||||
var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3");
|
||||
File.Move(App.Config.DatabaseFile, oldName, true);
|
||||
File.Move(filename, App.Config.DatabaseFile, false);
|
||||
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await cnx.ExecuteBatch("VACUUM");
|
||||
}
|
||||
|
||||
public static async Task ImportSql(StreamReader reader) {
|
||||
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
|
||||
File.Delete(newName);
|
||||
try {
|
||||
using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) {
|
||||
await cnx.ExecuteBatch(await reader.ReadToEndAsync());
|
||||
}
|
||||
await ImportSqlite(newName);
|
||||
} finally {
|
||||
if (File.Exists(newName)) File.Delete(newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -18,8 +17,6 @@ namespace Elwig.Helpers.Export {
|
||||
|
||||
public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
||||
|
||||
public static async Task<string[]> GetImportedFiles() {
|
||||
try {
|
||||
return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
|
||||
@@ -41,6 +38,7 @@ namespace Elwig.Helpers.Export {
|
||||
Dictionary<string, int> currentLsNrs;
|
||||
Dictionary<int, List<WbRd>> currentWbRde;
|
||||
Dictionary<int, AT_Kg> kgs;
|
||||
List<WbGl> currentWbGls;
|
||||
|
||||
using (var ctx = new AppDbContext()) {
|
||||
branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId);
|
||||
@@ -52,6 +50,7 @@ namespace Elwig.Helpers.Export {
|
||||
currentWbRde = await ctx.WbRde
|
||||
.GroupBy(r => r.KgNr)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.ToList());
|
||||
currentWbGls = await ctx.WbGls.ToListAsync();
|
||||
kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr);
|
||||
}
|
||||
|
||||
@@ -62,6 +61,8 @@ namespace Elwig.Helpers.Export {
|
||||
List<MemberEmailAddr> EmailAddresses,
|
||||
List<AreaCom> AreaCommitments,
|
||||
List<WbRd> Riede,
|
||||
List<WbKg> WbKgs,
|
||||
List<WbGl> WbGls,
|
||||
List<Delivery> Deliveries,
|
||||
List<DeliveryPart> DeliveryParts,
|
||||
List<DeliveryPartModifier> Modifiers,
|
||||
@@ -73,81 +74,117 @@ namespace Elwig.Helpers.Export {
|
||||
int? DeliveryNum, string? DeliveryFilters)>();
|
||||
|
||||
foreach (var filename in filenames) {
|
||||
// TODO read encrypted files
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
|
||||
try {
|
||||
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
|
||||
["member"] = [],
|
||||
["area_commitment"] = [],
|
||||
["delivery"] = [],
|
||||
})));
|
||||
var r = data[^1];
|
||||
|
||||
var version = zip.GetEntry("version");
|
||||
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
|
||||
if (await reader.ReadToEndAsync() != "elwig:1")
|
||||
throw new FileFormatException($"Ungültige Export-Datei ({filename})");
|
||||
}
|
||||
// TODO read encrypted files
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
|
||||
await zip.CheckIntegrity();
|
||||
|
||||
var metaJson = zip.GetEntry("meta.json");
|
||||
var meta = await JsonNode.ParseAsync(metaJson!.Open());
|
||||
var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
|
||||
var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
|
||||
var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
|
||||
var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
metaData.Add((Path.GetFileName(filename),
|
||||
meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
|
||||
memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
|
||||
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
|
||||
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
|
||||
|
||||
data.Add(new([], [], [], [], [], [], [], new([], [], new() {
|
||||
["member"] = [],
|
||||
["area_commitment"] = [],
|
||||
["delivery"] = [],
|
||||
})));
|
||||
var r = data[^1];
|
||||
|
||||
var membersJson = zip.GetEntry("members.json");
|
||||
if (membersJson != null) {
|
||||
using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
|
||||
r.Members.Add(m);
|
||||
if (b != null) r.BillingAddresses.Add(b);
|
||||
r.TelephoneNumbers.AddRange(telNrs);
|
||||
r.EmailAddresses.AddRange(emailAddrs);
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
var version = zip.GetEntry("version");
|
||||
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
|
||||
if (await reader.ReadToEndAsync() != "elwig:1")
|
||||
throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})");
|
||||
}
|
||||
}
|
||||
|
||||
var areaComsJson = zip.GetEntry("area_commitments.json");
|
||||
if (areaComsJson != null) {
|
||||
using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (areaCom, wbrd, timestamps) = obj.ToAreaCom(kgs, currentWbRde);
|
||||
r.AreaCommitments.Add(areaCom);
|
||||
if (wbrd != null) {
|
||||
currentWbRde[wbrd.KgNr].Add(wbrd);
|
||||
r.Riede.Add(wbrd);
|
||||
var metaJson = zip.GetEntry("meta.json");
|
||||
var meta = await JsonNode.ParseAsync(metaJson!.Open());
|
||||
var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
|
||||
var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
|
||||
var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
|
||||
var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
metaData.Add((Path.GetFileName(filename),
|
||||
meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
|
||||
memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
|
||||
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
|
||||
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
|
||||
|
||||
var wbKgsJson = zip.GetEntry("wb_kgs.json");
|
||||
if (wbKgsJson != null) {
|
||||
using var reader = new StreamReader(wbKgsJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (k, g) = obj.ToWbKg(currentWbGls);
|
||||
r.WbKgs.Add(k);
|
||||
if (g != null) {
|
||||
currentWbGls[g.GlNr] = g;
|
||||
r.WbGls.Add(g);
|
||||
}
|
||||
}
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
}
|
||||
}
|
||||
|
||||
var deliveriesJson = zip.GetEntry("deliveries.json");
|
||||
if (deliveriesJson != null) {
|
||||
using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (d, parts, mods, timestamps) = obj.ToDelivery(currentLsNrs, currentDids);
|
||||
r.Deliveries.Add(d);
|
||||
r.DeliveryParts.AddRange(parts);
|
||||
r.Modifiers.AddRange(mods);
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
var membersJson = zip.GetEntry("members.json");
|
||||
if (membersJson != null) {
|
||||
using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
|
||||
r.Members.Add(m);
|
||||
if (b != null) r.BillingAddresses.Add(b);
|
||||
r.TelephoneNumbers.AddRange(telNrs);
|
||||
r.EmailAddresses.AddRange(emailAddrs);
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
}
|
||||
}
|
||||
|
||||
var areaComsJson = zip.GetEntry("area_commitments.json");
|
||||
if (areaComsJson != null) {
|
||||
using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
|
||||
r.AreaCommitments.Add(areaCom);
|
||||
if (wbrd != null) {
|
||||
r.Riede.Add(wbrd);
|
||||
}
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
}
|
||||
}
|
||||
|
||||
var deliveriesJson = zip.GetEntry("deliveries.json");
|
||||
if (deliveriesJson != null) {
|
||||
using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
|
||||
r.Deliveries.Add(d);
|
||||
r.DeliveryParts.AddRange(parts);
|
||||
r.Modifiers.AddRange(mods);
|
||||
r.Riede.AddRange(rde);
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
}
|
||||
}
|
||||
} catch (Exception exc) when (
|
||||
exc is InvalidDataException ||
|
||||
exc is FileFormatException ||
|
||||
exc is FileNotFoundException ||
|
||||
exc is IOException) {
|
||||
data.RemoveAt(data.Count - 1);
|
||||
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message;
|
||||
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
|
||||
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
await AddImportedFiles(Path.GetFileName(filename));
|
||||
} catch (Exception exc) {
|
||||
data.RemoveAt(data.Count - 1);
|
||||
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message;
|
||||
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
|
||||
var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
|
||||
if (r == MessageBoxResult.Yes) {
|
||||
await AddImportedFiles(Path.GetFileName(filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,12 +193,18 @@ namespace Elwig.Helpers.Export {
|
||||
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int Imported, int NotImported, string Filters)>();
|
||||
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
|
||||
|
||||
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
|
||||
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
|
||||
var branch = branches[meta.ZwstId];
|
||||
var device = meta.Device;
|
||||
|
||||
using var ctx = new AppDbContext();
|
||||
|
||||
var kgnrs = wbKgs.Select(k => k.KgNr).ToList();
|
||||
var duplicateKgNrs = await ctx.WbKgs
|
||||
.Where(k => kgnrs.Contains(k.KgNr))
|
||||
.Select(k => k.KgNr)
|
||||
.ToListAsync();
|
||||
|
||||
var mgnrs = members.Select(m => m.MgNr).ToList();
|
||||
var duplicateMgNrs = await ctx.Members
|
||||
.Where(m => mgnrs.Contains(m.MgNr))
|
||||
@@ -215,6 +258,12 @@ namespace Elwig.Helpers.Export {
|
||||
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
|
||||
}
|
||||
|
||||
if (importDuplicateMembers || importNewMembers || importDuplicateDeliveries || importNewDeliveries) {
|
||||
ctx.AddRange(wbGls);
|
||||
ctx.UpdateRange(wbKgs.Where(k => duplicateKgNrs.Contains(k.KgNr)));
|
||||
ctx.AddRange(wbKgs.Where(k => !duplicateKgNrs.Contains(k.KgNr)));
|
||||
}
|
||||
|
||||
if (importDuplicateMembers) {
|
||||
ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
|
||||
ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr)));
|
||||
@@ -296,7 +345,7 @@ namespace Elwig.Helpers.Export {
|
||||
$"mtime = {((DateTimeOffset)m.ModifiedAt.ToUniversalTime()).ToUnixTimeSeconds()} " +
|
||||
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2});"));
|
||||
using var cnx = AppDbContext.Connect();
|
||||
await AppDbContext.ExecuteBatch(cnx, $"""
|
||||
await cnx.ExecuteBatch($"""
|
||||
BEGIN;
|
||||
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
{string.Join("\n", updateStmts)}
|
||||
@@ -351,26 +400,30 @@ namespace Elwig.Helpers.Export {
|
||||
) == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
return new ElwigExport {
|
||||
Members = (members, filters)
|
||||
Members = (members, filters),
|
||||
WbKgs = (wbKgs, ["von exportierten Mitgliedern"]),
|
||||
}.Export(filename);
|
||||
}
|
||||
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
return new ElwigExport {
|
||||
Members = (members, filters),
|
||||
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
|
||||
WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]),
|
||||
}.Export(filename);
|
||||
}
|
||||
|
||||
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
return new ElwigExport {
|
||||
Deliveries = (deliveries, filters)
|
||||
Deliveries = (deliveries, filters),
|
||||
WbKgs = (wbKgs, ["von exportierten Lieferungen"]),
|
||||
}.Export(filename);
|
||||
}
|
||||
|
||||
public class ElwigExport {
|
||||
public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
|
||||
public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
|
||||
public (IEnumerable<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
|
||||
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
|
||||
@@ -391,6 +444,12 @@ namespace Elwig.Helpers.Export {
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
};
|
||||
if (WbKgs != null) {
|
||||
obj["wb_kgs"] = new JsonObject {
|
||||
["count"] = WbKgs.Value.WbKgs.Count(),
|
||||
["filters"] = new JsonArray(WbKgs.Value.Filters.Select(f => (JsonNode)f).ToArray()),
|
||||
};
|
||||
}
|
||||
if (Members != null)
|
||||
obj["members"] = new JsonObject {
|
||||
["count"] = Members.Value.Members.Count(),
|
||||
@@ -407,34 +466,69 @@ namespace Elwig.Helpers.Export {
|
||||
["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
|
||||
["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()),
|
||||
};
|
||||
await writer.WriteAsync(obj.ToJsonString(JsonOpts));
|
||||
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
|
||||
// TODO encrypt files
|
||||
if (WbKgs != null) {
|
||||
var json = zip.CreateEntry("wb_kgs.json", CompressionLevel.SmallestSize);
|
||||
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
|
||||
foreach (var k in WbKgs.Value.WbKgs) {
|
||||
await writer.WriteLineAsync(k.ToJson().ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
}
|
||||
if (Members != null) {
|
||||
var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
|
||||
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
|
||||
foreach (var m in Members.Value.Members) {
|
||||
await writer.WriteLineAsync(m.ToJson().ToJsonString(JsonOpts));
|
||||
await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
}
|
||||
if (AreaComs != null) {
|
||||
var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
|
||||
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
|
||||
foreach (var c in AreaComs.Value.AreaComs) {
|
||||
await writer.WriteLineAsync(c.ToJson().ToJsonString(JsonOpts));
|
||||
await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
}
|
||||
if (Deliveries != null) {
|
||||
var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
|
||||
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
|
||||
foreach (var d in Deliveries.Value.Deliveries) {
|
||||
await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts));
|
||||
await writer.WriteLineAsync(d.ToJson().ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonObject ToJson(this WbKg k) {
|
||||
return new JsonObject {
|
||||
["kgnr"] = k.KgNr,
|
||||
["großlage"] = k.Gl?.Name,
|
||||
};
|
||||
}
|
||||
|
||||
public static (WbKg, WbGl?) ToWbKg(this JsonNode json, List<WbGl> gls) {
|
||||
var grosslage = json["großlage"]?.AsValue().GetValue<string>();
|
||||
WbGl? gl = null;
|
||||
bool newGl = false;
|
||||
if (grosslage != null) {
|
||||
gl = gls.FirstOrDefault(g => g.Name == grosslage);
|
||||
if (gl == null) {
|
||||
newGl = true;
|
||||
gl = new WbGl {
|
||||
GlNr = (gls.Count == 0 ? 1 : gls.Max(g => g.GlNr)) + 1,
|
||||
Name = grosslage,
|
||||
};
|
||||
gls[gl.GlNr] = gl;
|
||||
}
|
||||
}
|
||||
return (new WbKg {
|
||||
KgNr = json["kgnr"]!.AsValue().GetValue<int>(),
|
||||
GlNr = gl?.GlNr,
|
||||
}, newGl ? gl : null);
|
||||
}
|
||||
|
||||
public static JsonObject ToJson(this Member m) {
|
||||
return new JsonObject {
|
||||
["mgnr"] = m.MgNr,
|
||||
@@ -580,22 +674,23 @@ namespace Elwig.Helpers.Export {
|
||||
};
|
||||
}
|
||||
|
||||
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
|
||||
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) {
|
||||
var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
|
||||
var ried = json["ried"]?.AsValue().GetValue<string>();
|
||||
WbRd? rd = null;
|
||||
bool newRd = false;
|
||||
if (ried != null) {
|
||||
var rde = riede[kgnr] ?? throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten → Herkunftshierarchie)");
|
||||
var rde = riede.GetValueOrDefault(kgnr, []);
|
||||
rd = rde.FirstOrDefault(r => r.Name == ried);
|
||||
if (rd == null) {
|
||||
newRd = true;
|
||||
rd = new WbRd {
|
||||
KgNr = kgnr,
|
||||
RdNr = (rde.Count == 0 ? 0 : rde.Max(r => r.RdNr)) + 1,
|
||||
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
|
||||
Name = ried,
|
||||
};
|
||||
rde.Add(rd);
|
||||
riede[rd.KgNr] = rde;
|
||||
}
|
||||
}
|
||||
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
|
||||
@@ -608,7 +703,7 @@ namespace Elwig.Helpers.Export {
|
||||
Area = json["area"]!.AsValue().GetValue<int>(),
|
||||
KgNr = kgnr,
|
||||
GstNr = json["gstnr"]?.AsValue().GetValue<string>() ?? "-",
|
||||
RdNr = rd?.RdNr,
|
||||
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(),
|
||||
YearFrom = json["year_from"]?.AsValue().GetValue<int>(),
|
||||
YearTo = json["year_to"]?.AsValue().GetValue<int>(),
|
||||
Comment = json["comment"]?.AsValue().GetValue<string>(),
|
||||
@@ -639,7 +734,7 @@ namespace Elwig.Helpers.Export {
|
||||
["qualid"] = p.QualId,
|
||||
["hkid"] = p.HkId,
|
||||
["kgnr"] = p.KgNr,
|
||||
["rdnr"] = p.RdNr,
|
||||
["ried"] = p.Rd?.Name,
|
||||
["net_weight"] = p.IsNetWeight,
|
||||
["manual_weighing"] = p.IsManualWeighing,
|
||||
["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
|
||||
@@ -649,8 +744,8 @@ namespace Elwig.Helpers.Export {
|
||||
};
|
||||
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
|
||||
if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
|
||||
if (p.IsLesewagen != null) obj["lesewagen"] = p.IsLesewagen;
|
||||
if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden;
|
||||
if (p.Unloading != null) obj["unloading"] = p.Unloading;
|
||||
if (p.Temperature != null) obj["temperature"] = p.Temperature;
|
||||
if (p.Acid != null) obj["acid"] = p.Acid;
|
||||
if (p.ScaleId != null) obj["scale_id"] = p.ScaleId;
|
||||
@@ -664,7 +759,7 @@ namespace Elwig.Helpers.Export {
|
||||
};
|
||||
}
|
||||
|
||||
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids) {
|
||||
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>, List<WbRd>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) {
|
||||
var year = json["year"]!.AsValue().GetValue<int>();
|
||||
var lsnr = json["lsnr"]!.AsValue().GetValue<string>();
|
||||
var did = currentLsNrs.GetValueOrDefault(lsnr, -1);
|
||||
@@ -675,6 +770,7 @@ namespace Elwig.Helpers.Export {
|
||||
currentLsNrs[lsnr] = did;
|
||||
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
|
||||
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
|
||||
var wbRde = new List<WbRd>();
|
||||
return (new Delivery {
|
||||
Year = year,
|
||||
DId = did,
|
||||
@@ -686,37 +782,57 @@ namespace Elwig.Helpers.Export {
|
||||
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
|
||||
Comment = json["comment"]?.AsValue().GetValue<string>(),
|
||||
ImportedAt = DateTime.Now,
|
||||
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
|
||||
Year = year,
|
||||
DId = did,
|
||||
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
|
||||
SortId = p["sortid"]!.AsValue().GetValue<string>(),
|
||||
AttrId = p["attrid"]?.AsValue().GetValue<string>(),
|
||||
CultId = p["cultid"]?.AsValue().GetValue<string>(),
|
||||
Weight = p["weight"]!.AsValue().GetValue<int>(),
|
||||
Kmw = p["kmw"]!.AsValue().GetValue<double>(),
|
||||
QualId = p["qualid"]!.AsValue().GetValue<string>(),
|
||||
HkId = p["hkid"]!.AsValue().GetValue<string>(),
|
||||
KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
|
||||
RdNr = p["rdnr"]?.AsValue().GetValue<int>(),
|
||||
IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
|
||||
IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
|
||||
Comment = p["comment"]?.AsValue().GetValue<string>(),
|
||||
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
|
||||
IsLesewagen = p["lesewagen"]?.AsValue().GetValue<bool>(),
|
||||
IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(),
|
||||
Temperature = p["temperature"]?.AsValue().GetValue<double>(),
|
||||
Acid = p["acid"]?.AsValue().GetValue<double>(),
|
||||
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
|
||||
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
|
||||
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
|
||||
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => {
|
||||
var kgnr = p["kgnr"]?.AsValue().GetValue<int>();
|
||||
var ried = p["ried"]?.AsValue().GetValue<string>();
|
||||
WbRd? rd = null;
|
||||
if (ried != null && kgnr != null) {
|
||||
var rde = riede.GetValueOrDefault(kgnr.Value, []);
|
||||
rd = rde.FirstOrDefault(r => r.Name == ried);
|
||||
if (rd == null) {
|
||||
rd = new WbRd {
|
||||
KgNr = kgnr.Value,
|
||||
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
|
||||
Name = ried,
|
||||
};
|
||||
rde.Add(rd);
|
||||
riede[rd.KgNr] = rde;
|
||||
wbRde.Add(rd);
|
||||
}
|
||||
}
|
||||
return new DeliveryPart {
|
||||
Year = year,
|
||||
DId = did,
|
||||
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
|
||||
SortId = p["sortid"]!.AsValue().GetValue<string>(),
|
||||
AttrId = p["attrid"]?.AsValue().GetValue<string>(),
|
||||
CultId = p["cultid"]?.AsValue().GetValue<string>(),
|
||||
Weight = p["weight"]!.AsValue().GetValue<int>(),
|
||||
Kmw = p["kmw"]!.AsValue().GetValue<double>(),
|
||||
QualId = p["qualid"]!.AsValue().GetValue<string>(),
|
||||
HkId = p["hkid"]!.AsValue().GetValue<string>(),
|
||||
KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
|
||||
RdNr = rd?.RdNr ?? p["rdnr"]?.AsValue().GetValue<int>(),
|
||||
IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
|
||||
IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
|
||||
Comment = p["comment"]?.AsValue().GetValue<string>(),
|
||||
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
|
||||
IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(),
|
||||
Unloading = p["unloading"]?.AsValue().GetValue<string>() ?? ((p["lesewagen"]?.AsValue().GetValue<bool>() ?? false) ? DeliveryPart.Pumped : null),
|
||||
Temperature = p["temperature"]?.AsValue().GetValue<double>(),
|
||||
Acid = p["acid"]?.AsValue().GetValue<double>(),
|
||||
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
|
||||
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts),
|
||||
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
|
||||
};
|
||||
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
|
||||
Year = year,
|
||||
DId = did,
|
||||
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
|
||||
ModId = m!.AsValue().GetValue<string>(),
|
||||
})).ToList(),
|
||||
wbRde,
|
||||
createdAt == null || modifiedAt == null ? null :
|
||||
(DateTime.ParseExact(createdAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None),
|
||||
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
|
||||
|
||||
69
Elwig/Helpers/Export/VCard.cs
Normal file
69
Elwig/Helpers/Export/VCard.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Elwig.Models.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public class VCard : IExporter<Member> {
|
||||
|
||||
public static string FileExtension => "vcf";
|
||||
|
||||
private readonly StreamWriter _writer;
|
||||
|
||||
public VCard(string filename) : this(filename, Utils.UTF8) { }
|
||||
|
||||
public VCard(string filename, Encoding encoding) {
|
||||
_writer = new StreamWriter(filename, false, encoding);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
GC.SuppressFinalize(this);
|
||||
_writer.Dispose();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() {
|
||||
GC.SuppressFinalize(this);
|
||||
return _writer.DisposeAsync();
|
||||
}
|
||||
|
||||
public async Task ExportAsync(IEnumerable<Member> data, IProgress<double>? progress = null) {
|
||||
progress?.Report(0.0);
|
||||
int count = data.Count() + 1, i = 0;
|
||||
|
||||
foreach (var row in data) {
|
||||
var billingAddr = row.BillingAddress != null ? $"ADR;TYPE=work;LANGUAGE=de;LABEL=\"{Escape(row.BillingAddress.FullName)}\\n{Escape(row.BillingAddress.Address)}\\n{row.BillingAddress.PostalDest.AtPlz?.Plz} {Escape(row.BillingAddress.PostalDest.AtPlz?.Ort.Name)}\\nÖsterreich\":;;{Escape(row.BillingAddress.Address)};{Escape(row.BillingAddress.PostalDest.AtPlz?.Ort.Name)};;{row.BillingAddress.PostalDest.AtPlz?.Plz};Österreich\r\n" : null;
|
||||
var tel = string.Join("", row.TelephoneNumbers
|
||||
.Where(n => n.Type != "fax")
|
||||
.Select(n => $"TEL;TYPE={(n.Type == "mobile" ? "cell" : "voice")}:{Escape(n.Number)}\r\n"));
|
||||
var email = string.Join("", row.EmailAddresses.Select(a => $"EMAIL:{Escape(a.Address)}\r\n"));
|
||||
await _writer.WriteLineAsync($"""
|
||||
BEGIN:VCARD
|
||||
VERSION:4.0
|
||||
UID:mg{row.MgNr}@{App.Client.NameToken.ToLower()}.elwig.at
|
||||
NOTE:MgNr. {row.MgNr}
|
||||
FN:{Escape(row.AdministrativeName)}
|
||||
N:{Escape(row.Name)};{Escape(row.GivenName)};{Escape(row.MiddleName)};{Escape(row.Prefix)};{Escape(row.Suffix)}
|
||||
KIND:{(row.IsJuridicalPerson ? "org" : "individual")}
|
||||
ADR{(billingAddr == null ? "" : ";TYPE=home")};LANGUAGE=de;LABEL="{Escape(row.Address)}\n{row.PostalDest.AtPlz?.Plz} {Escape(row.PostalDest.AtPlz?.Ort.Name)}\nÖsterreich":;;{Escape(row.Address)};{Escape(row.PostalDest.AtPlz?.Ort.Name)};;{row.PostalDest.AtPlz?.Plz};Österreich
|
||||
{billingAddr}{tel}{email}REV:{row.ModifiedAt.ToUniversalTime():yyyyMMdd\THHmmss\Z}
|
||||
END:VCARD
|
||||
""");
|
||||
progress?.Report(100.0 * ++i / count);
|
||||
}
|
||||
|
||||
await _writer.FlushAsync();
|
||||
progress?.Report(100.0);
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<Member> data, IProgress<double>? progress = null) {
|
||||
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static string? Escape(string? text) {
|
||||
return text?.Replace("\\", "\\\\").Replace(",", "\\,").Replace(";", "\\;").Replace("\n", "\\n").Replace("\r", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Elwig.Helpers {
|
||||
public enum ExportMode {
|
||||
Show, SaveList, SavePdf, Print, Email, Export, Upload
|
||||
Show, SaveList, SavePdf, Print, Email, Vcf, Export, Upload
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Hashing;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
static partial class Extensions {
|
||||
public static partial class Extensions {
|
||||
|
||||
[LibraryImport("msvcrt.dll")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
||||
@@ -95,5 +100,50 @@ namespace Elwig.Helpers {
|
||||
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
|
||||
progress.Report(100.0);
|
||||
}
|
||||
|
||||
public static async Task CheckIntegrity(this ZipArchive zip) {
|
||||
var crc = new Crc32();
|
||||
foreach (var entry in zip.Entries) {
|
||||
crc.Reset();
|
||||
using var stream = entry.Open();
|
||||
await crc.AppendAsync(stream);
|
||||
if (crc.GetCurrentHashAsUInt32() != entry.Crc32)
|
||||
throw new InvalidDataException($"CRC-32 mismatch in '{entry.FullName}'");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExecuteBatch(this SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.NextResultAsync()) ;
|
||||
}
|
||||
|
||||
public static async Task ExecuteEmbeddedScript(this SqliteConnection cnx, Assembly asm, string name) {
|
||||
using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource");
|
||||
using var reader = new StreamReader(stream);
|
||||
await ExecuteBatch(cnx, await reader.ReadToEndAsync());
|
||||
}
|
||||
|
||||
public static async Task<object?> ExecuteScalar(this SqliteConnection cnx, string sql) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
return await cmd.ExecuteScalarAsync();
|
||||
}
|
||||
|
||||
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(this SqliteConnection cnx) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA foreign_key_check";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var list = new List<(string, long, string, long)>();
|
||||
while (await reader.ReadAsync()) {
|
||||
var table = reader.GetString(0);
|
||||
var rowid = reader.GetInt64(1);
|
||||
var parent = reader.GetString(2);
|
||||
var fkid = reader.GetInt64(3);
|
||||
list.Add((table, rowid, parent, fkid));
|
||||
}
|
||||
return [.. list];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using RazorLight;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public static class Html {
|
||||
|
||||
private static RazorLightEngine? Engine = null;
|
||||
public static bool IsReady => Engine != null;
|
||||
|
||||
public static async Task Init(Action? evtHandler = null) {
|
||||
var e = new RazorLightEngineBuilder()
|
||||
.UseFileSystemProject(App.DocumentsPath)
|
||||
.UseMemoryCachingProvider()
|
||||
.Build();
|
||||
|
||||
await e.CompileTemplateAsync("Document");
|
||||
await e.CompileTemplateAsync("BusinessDocument");
|
||||
await e.CompileTemplateAsync("BusinessLetter");
|
||||
await e.CompileTemplateAsync("DeliveryNote");
|
||||
await e.CompileTemplateAsync("CreditNote");
|
||||
await e.CompileTemplateAsync("DeliveryJournal");
|
||||
await e.CompileTemplateAsync("Letterhead");
|
||||
await e.CompileTemplateAsync("DeliveryConfirmation");
|
||||
|
||||
Engine = e;
|
||||
evtHandler?.Invoke();
|
||||
}
|
||||
|
||||
public static async Task<string> CompileRenderAsync(string key, object model) {
|
||||
if (Engine == null) throw new InvalidOperationException("The razor engine has not been initialized yet");
|
||||
return await Engine.CompileRenderAsync(key, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,21 @@
|
||||
using System.Threading.Tasks;
|
||||
using Elwig.Windows;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using PdfiumViewer;
|
||||
using System.Drawing.Printing;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public static class Pdf {
|
||||
|
||||
private static readonly string WinziPrint = new string[] { App.InstallPath }
|
||||
.Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
|
||||
.Select(x => Path.Combine(x, "WinziPrint.exe"))
|
||||
.Where(File.Exists)
|
||||
.FirstOrDefault() ?? throw new FileNotFoundException("WiniPrint executable not found");
|
||||
private static Process? WinziPrintProc;
|
||||
public static bool IsReady => WinziPrintProc != null;
|
||||
|
||||
public static async Task Init(Action? evtHandler = null) {
|
||||
// NOTE: If the WinziPrint daemon is already running this will succeed, but the process will fail.
|
||||
// Should be no problem, as long as the daemon is not closed
|
||||
var p = new Process() { StartInfo = new() {
|
||||
FileName = WinziPrint,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
} };
|
||||
p.StartInfo.ArgumentList.Add("-D");
|
||||
p.StartInfo.ArgumentList.Add("-d");
|
||||
p.StartInfo.ArgumentList.Add(App.TempPath);
|
||||
p.Start();
|
||||
await p.StandardOutput.ReadLineAsync();
|
||||
WinziPrintProc = p;
|
||||
public static Task Init(Action? evtHandler = null) {
|
||||
PdfiumNative.FPDF_InitLibrary();
|
||||
evtHandler?.Invoke();
|
||||
}
|
||||
|
||||
public static Task Cleanup() {
|
||||
WinziPrintProc?.Kill(true);
|
||||
WinziPrintProc?.Close();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) {
|
||||
return await Convert([htmlPath], pdfPath, doublePaged, progress);
|
||||
}
|
||||
|
||||
public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, IProgress<double>? progress = null) {
|
||||
if (WinziPrintProc == null) throw new InvalidOperationException("The WinziPrint process has not been initialized yet");
|
||||
progress?.Report(0.0);
|
||||
using var client = new TcpClient("127.0.0.1", 30983);
|
||||
using var stream = client.GetStream();
|
||||
await stream.WriteAsync(Utils.UTF8.GetBytes(
|
||||
"-e utf-8;-p;" + (doublePaged ? "-2;" : "") +
|
||||
$"{string.Join(';', htmlPath)};{pdfPath}" +
|
||||
"\r\n"));
|
||||
using var reader = new StreamReader(stream);
|
||||
while (true) {
|
||||
var line = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
|
||||
if (line.StartsWith("error:")) {
|
||||
throw new IOException($"WinziPrint: {line[6..].Trim()}");
|
||||
} else if (line.StartsWith("progress:")) {
|
||||
var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray();
|
||||
progress?.Report(100.0 * parts[0] / parts[1]);
|
||||
} else if (line.StartsWith("success:")) {
|
||||
var m = Regex.Match(line, @"([0-9]+) pages \(([0-9, ]+)\)");
|
||||
return (int.Parse(m.Groups[1].Value), m.Groups[2].Value.Split(", ").Select(int.Parse).ToList());
|
||||
}
|
||||
}
|
||||
public static Task Cleanup() {
|
||||
PdfiumNative.FPDF_DestroyLibrary();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static void Show(TempFile file, string title) {
|
||||
@@ -98,9 +42,9 @@ namespace Elwig.Helpers.Printing {
|
||||
|
||||
public static Task Print(string path, PrinterSettings settings) {
|
||||
try {
|
||||
using var doc = PdfDocument.Load(path);
|
||||
using var printDoc = doc.CreatePrintDocument(PdfPrintMode.CutMargin);
|
||||
printDoc.PrinterSettings = settings;
|
||||
using var printDoc = new PdfPrintDocument(path) {
|
||||
PrinterSettings = settings,
|
||||
};
|
||||
printDoc.Print();
|
||||
} catch (Exception e) {
|
||||
MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
|
||||
102
Elwig/Helpers/Printing/PdfPrintDocument.cs
Normal file
102
Elwig/Helpers/Printing/PdfPrintDocument.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing.Printing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public class PdfPrintDocument : PrintDocument {
|
||||
|
||||
private readonly IntPtr _handle;
|
||||
private readonly int _pageCount;
|
||||
private readonly double _dpi;
|
||||
|
||||
private int _currentPage;
|
||||
|
||||
public PdfPrintDocument(string path, string? password = null, double dpi = 300.0) :
|
||||
base() {
|
||||
_handle = PdfiumNative.FPDF_LoadDocument(path, password);
|
||||
_pageCount = PdfiumNative.FPDF_GetPageCount(_handle);
|
||||
_dpi = dpi;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
PdfiumNative.FPDF_CloseDocument(_handle);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void OnBeginPrint(PrintEventArgs evt) {
|
||||
_currentPage = (PrinterSettings.FromPage != 0) ? (PrinterSettings.FromPage - 1) : 0;
|
||||
base.OnBeginPrint(evt);
|
||||
}
|
||||
|
||||
protected override void OnPrintPage(PrintPageEventArgs evt) {
|
||||
if (_currentPage < _pageCount) {
|
||||
IntPtr page = PdfiumNative.FPDF_LoadPage(_handle, _currentPage);
|
||||
double width = PdfiumNative.FPDF_GetPageWidth(page);
|
||||
double height = PdfiumNative.FPDF_GetPageHeight(page);
|
||||
int pixelWidth = (int)(width / 72.0 * _dpi);
|
||||
int pixelHeight = (int)(height / 72.0 * _dpi);
|
||||
|
||||
IntPtr bitmap = PdfiumNative.FPDFBitmap_Create(pixelWidth, pixelHeight, 1);
|
||||
PdfiumNative.FPDF_RenderPageBitmap(bitmap, page, 0, 0, pixelWidth, pixelHeight, 0, 0);
|
||||
|
||||
IntPtr buffer = PdfiumNative.FPDFBitmap_GetBuffer(bitmap);
|
||||
int stride = PdfiumNative.FPDFBitmap_GetStride(bitmap);
|
||||
using (var bmp = new Bitmap(pixelWidth, pixelHeight, stride, PixelFormat.Format32bppArgb, buffer)) {
|
||||
evt.Graphics?.DrawImage(bmp, evt.PageBounds);
|
||||
}
|
||||
_currentPage++;
|
||||
|
||||
PdfiumNative.FPDFBitmap_Destroy(bitmap);
|
||||
PdfiumNative.FPDF_ClosePage(page);
|
||||
}
|
||||
evt.HasMorePages = _currentPage < ((PrinterSettings.ToPage == 0) ? _pageCount : Math.Min(PrinterSettings.ToPage, _pageCount));
|
||||
base.OnPrintPage(evt);
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class PdfiumNative {
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_InitLibrary();
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_DestroyLibrary();
|
||||
|
||||
[LibraryImport("pdfium.dll", StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial IntPtr FPDF_LoadDocument(string filePath, string? password);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_CloseDocument(IntPtr document);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial int FPDF_GetPageCount(IntPtr document);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial IntPtr FPDF_LoadPage(IntPtr document, int pageIndex);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_ClosePage(IntPtr page);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial double FPDF_GetPageWidth(IntPtr page);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial double FPDF_GetPageHeight(IntPtr page);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial IntPtr FPDFBitmap_Create(int width, int height, int alpha);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDFBitmap_Destroy(IntPtr bitmap);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial IntPtr FPDFBitmap_GetBuffer(IntPtr bitmap);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial int FPDFBitmap_GetStride(IntPtr bitmap);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_RenderPageBitmap(IntPtr bitmap, IntPtr page, int start_x, int start_y, int size_x, int size_y, int rotate, int flags);
|
||||
}
|
||||
}
|
||||
60
Elwig/Helpers/SerialPortWatcher.cs
Normal file
60
Elwig/Helpers/SerialPortWatcher.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
public sealed class SerialPortWatcher : IDisposable {
|
||||
|
||||
private readonly ManagementEventWatcher _deviceArrivalWatcher;
|
||||
private readonly ManagementEventWatcher _deviceRemovalWatcher;
|
||||
|
||||
private string[] _knownPorts;
|
||||
|
||||
public event EventHandler<string>? SerialPortConnected;
|
||||
public event EventHandler<string>? SerialPortDisconnected;
|
||||
|
||||
public SerialPortWatcher() {
|
||||
_knownPorts = SerialPort.GetPortNames();
|
||||
_deviceArrivalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
|
||||
_deviceArrivalWatcher.EventArrived += OnDeviceArrived;
|
||||
_deviceRemovalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
|
||||
_deviceRemovalWatcher.EventArrived += OnDeviceRemoved;
|
||||
_deviceArrivalWatcher.Start();
|
||||
_deviceRemovalWatcher.Start();
|
||||
|
||||
}
|
||||
|
||||
private void OnDeviceArrived(object sender, EventArrivedEventArgs evt) {
|
||||
App.MainDispatcher.Invoke(() => {
|
||||
// "synchronized"
|
||||
string[] currentPorts = SerialPort.GetPortNames();
|
||||
var newPorts = currentPorts.Except(_knownPorts).ToArray();
|
||||
foreach (var port in newPorts)
|
||||
SerialPortConnected?.Invoke(this, port);
|
||||
_knownPorts = currentPorts;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDeviceRemoved(object sender, EventArrivedEventArgs evt) {
|
||||
App.MainDispatcher.Invoke(() => {
|
||||
// "synchronized"
|
||||
string[] currentPorts = SerialPort.GetPortNames();
|
||||
var removedPorts = _knownPorts.Except(currentPorts).ToArray();
|
||||
foreach (var port in removedPorts)
|
||||
SerialPortDisconnected?.Invoke(this, port);
|
||||
_knownPorts = currentPorts;
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
try {
|
||||
_deviceArrivalWatcher?.Stop();
|
||||
_deviceRemovalWatcher?.Stop();
|
||||
} finally {
|
||||
_deviceArrivalWatcher?.Dispose();
|
||||
_deviceRemovalWatcher?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.IO.Ports;
|
||||
using System.Net.Sockets;
|
||||
using Elwig.Dialogs;
|
||||
using System.Text;
|
||||
using System.Numerics;
|
||||
using Elwig.Models.Entities;
|
||||
using Elwig.Documents;
|
||||
using Elwig.Helpers.Billing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.IO;
|
||||
using Elwig.Models;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Layout.Element;
|
||||
using LinqKit;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
using Elwig.Documents;
|
||||
using MimeKit;
|
||||
using System.Windows.Input;
|
||||
using LinqKit;
|
||||
using System.Linq.Expressions;
|
||||
using Elwig.Models;
|
||||
using Microsoft.Win32;
|
||||
using MimeKit;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
public static partial class Utils {
|
||||
|
||||
public static readonly Encoding UTF8 = new UTF8Encoding(false, true);
|
||||
public static readonly Encoding UTF8BOM = new UTF8Encoding(true, true);
|
||||
public static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
||||
|
||||
public static int CurrentYear => DateTime.Now.Year;
|
||||
public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
|
||||
@@ -297,28 +300,23 @@ namespace Elwig.Helpers {
|
||||
return d.ShowDialog() == true ? d.Price : null;
|
||||
}
|
||||
|
||||
public static Footer GenerateFooter(string lineBreak, string seperator) {
|
||||
return new Footer(lineBreak, seperator);
|
||||
public static Footer GenerateFooter(string lineBreak, string separator) {
|
||||
return new Footer(lineBreak, separator);
|
||||
}
|
||||
|
||||
public class Footer {
|
||||
private string Text = "";
|
||||
private readonly List<List<object>> Items = [[]];
|
||||
private readonly string LineBreak;
|
||||
private readonly string Seperator;
|
||||
private bool FirstLine = true;
|
||||
private bool FirstItemInLine = true;
|
||||
private readonly string Separator;
|
||||
|
||||
public Footer(string lineBreak, string seperator) {
|
||||
public Footer(string lineBreak, string separator) {
|
||||
LineBreak = lineBreak;
|
||||
Seperator = seperator;
|
||||
Separator = separator;
|
||||
}
|
||||
|
||||
public Footer Item(string? text) {
|
||||
public Footer Item(object? text) {
|
||||
if (text == null) return this;
|
||||
Text += FirstItemInLine ? (FirstLine ? "" : LineBreak) : Seperator;
|
||||
Text += text;
|
||||
FirstLine = false;
|
||||
FirstItemInLine = false;
|
||||
Items[^1].Add(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -327,12 +325,28 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
public Footer NextLine() {
|
||||
FirstItemInLine = true;
|
||||
Items.Add([]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return Text;
|
||||
return string.Join(LineBreak, Items.Select(l => string.Join(Separator, l.ToString())));
|
||||
}
|
||||
|
||||
public IList<ILeafElement> ToLeafElements() {
|
||||
var l = new List<ILeafElement>();
|
||||
var first1 = true;
|
||||
foreach (var line in Items) {
|
||||
if (!first1) l.Add(new Text(LineBreak));
|
||||
var first2 = true;
|
||||
foreach (var item in line) {
|
||||
if (!first2) l.Add(new Text(Separator));
|
||||
l.Add(item as ILeafElement ?? new Text(item.ToString() ?? ""));
|
||||
first2 = false;
|
||||
}
|
||||
first1 = false;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,6 +444,8 @@ namespace Elwig.Helpers {
|
||||
var client = new HttpClient() {
|
||||
Timeout = TimeSpan.FromSeconds(5),
|
||||
};
|
||||
client.DefaultRequestHeaders.UserAgent.Clear();
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd($"Elwig/{App.Version} ({App.Client.NameToken}, {App.BranchName}, {Environment.MachineName}, {Environment.OSVersion})");
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
if (accept != null)
|
||||
client.DefaultRequestHeaders.Accept.Add(new(accept));
|
||||
@@ -457,7 +473,7 @@ namespace Elwig.Helpers {
|
||||
url = "https://sync.elwig.at/" + url[25..];
|
||||
if (!url.EndsWith('/')) url += "/";
|
||||
using var client = GetHttpClient(username, password, accept: "application/json");
|
||||
var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
|
||||
using var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
|
||||
content.Headers.ContentType = new("application/zip");
|
||||
using var res = await client.PutAsync(url + Path.GetFileName(zip), content);
|
||||
res.EnsureSuccessStatusCode();
|
||||
@@ -498,10 +514,8 @@ namespace Elwig.Helpers {
|
||||
public static async Task<bool> SendEmail(Member member, string subject, string text, IEnumerable<Document> docs) {
|
||||
if (App.Config.Smtp == null)
|
||||
return false;
|
||||
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
|
||||
var success = await Task.Run(async () => {
|
||||
return await Task.Run(async () => {
|
||||
await AddSentMailBody(subject, text, 1);
|
||||
SmtpClient? client = null;
|
||||
try {
|
||||
client = await GetSmtpClient();
|
||||
@@ -519,6 +533,11 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
msg.Body = body;
|
||||
await client!.SendAsync(msg);
|
||||
await AddSentMails([(
|
||||
"email", member.MgNr, member.AdministrativeName,
|
||||
member.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
|
||||
subject, docs.Select(d => d.Title).ToArray()
|
||||
)]);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return false;
|
||||
@@ -529,22 +548,33 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Mouse.OverrideCursor = null;
|
||||
return success;
|
||||
}
|
||||
|
||||
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member, string, string)? emailData = null) {
|
||||
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) {
|
||||
if (mode == ExportMode.Print && !App.Config.Debug) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
await doc.Generate();
|
||||
await doc.Print();
|
||||
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
await doc.Generate();
|
||||
var success = await SendEmail(e.Item1, e.Item2, e.Item3, [doc]);
|
||||
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
|
||||
if (success)
|
||||
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!", "E-Mail verschickt",
|
||||
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mail im Posteingang des Empfängers aufscheint.", "E-Mail verschickt",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} else if (mode == ExportMode.SavePdf) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
var d = new SaveFileDialog() {
|
||||
FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf",
|
||||
DefaultExt = "pdf",
|
||||
@@ -567,9 +597,7 @@ namespace Elwig.Helpers {
|
||||
Log = "Application",
|
||||
Source = ".NET Runtime",
|
||||
};
|
||||
return log.Entries.Cast<EventLogEntry>()
|
||||
.Where(e => e.Message.StartsWith("Application: Elwig.exe"))
|
||||
.ToList();
|
||||
return [.. log.Entries.OfType<EventLogEntry>().Where(e => e.InstanceId == 1026).Where(e => e.Message.StartsWith("Application: Elwig.exe"))];
|
||||
}
|
||||
|
||||
public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
|
||||
@@ -659,9 +687,9 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
public static async Task<string?> FindSentMailBody(DateTime target) {
|
||||
var dt = $"{target:yyyy-MM-dd_HH-mm-ss}_";
|
||||
var dt = $"{target:yyyy-MM-dd_HH-mm-ss}";
|
||||
var filename = Directory.GetFiles(App.MailsPath, "????-??-??_??-??-??_*.txt")
|
||||
.Where(n => Path.GetFileName(n).CompareTo(dt) <= 0)
|
||||
.Where(n => Path.GetFileName(n)[..19].CompareTo(dt) <= 0)
|
||||
.Order()
|
||||
.LastOrDefault();
|
||||
if (filename == null)
|
||||
|
||||
@@ -15,15 +15,17 @@ namespace Elwig.Helpers.Weighing {
|
||||
public bool IsReady { get; private set; }
|
||||
public bool HasFillingClearance { get; private set; }
|
||||
|
||||
public event IEventScale.EventHandler<WeighingEventArgs> WeighingEvent;
|
||||
public event IEventScale.EventHandler<WeighingEventArgs>? WeighingEvent;
|
||||
|
||||
private bool IsRunning = true;
|
||||
private readonly Thread BackgroundThread;
|
||||
private readonly string Connection;
|
||||
|
||||
public AveryEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
|
||||
base(cnx, empty, filling, limit, log) {
|
||||
public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) :
|
||||
base(cnx, null, null, null, log, true, !required) {
|
||||
ScaleId = id;
|
||||
Model = model;
|
||||
Connection = cnx;
|
||||
IsReady = true;
|
||||
HasFillingClearance = false;
|
||||
Stream.WriteTimeout = -1;
|
||||
@@ -50,19 +52,49 @@ namespace Elwig.Helpers.Weighing {
|
||||
var data = await Receive();
|
||||
if (data != null)
|
||||
RaiseWeighingEvent(new WeighingEventArgs(data.Value));
|
||||
} catch (ThreadInterruptedException) {
|
||||
// ignore
|
||||
} catch (IOException) {
|
||||
await Task.Delay(500);
|
||||
await Reconnect();
|
||||
} catch (TimeoutException) {
|
||||
await Task.Delay(500);
|
||||
await Reconnect();
|
||||
} catch (Exception ex) {
|
||||
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "Waagenfehler",
|
||||
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task Reconnect() {
|
||||
try { Reader.Close(); } catch { }
|
||||
try { Stream.Close(); } catch { }
|
||||
try { Serial?.Close(); } catch { }
|
||||
while (IsRunning) {
|
||||
try {
|
||||
if (Connection.StartsWith("serial:")) {
|
||||
Serial = Utils.OpenSerialConnection(Connection);
|
||||
Stream = Serial.BaseStream;
|
||||
} else if (Connection.StartsWith("tcp:")) {
|
||||
Tcp = Utils.OpenTcpConnection(Connection);
|
||||
Stream = Tcp.GetStream();
|
||||
}
|
||||
Reader = new(Stream, Encoding.ASCII, false, 512);
|
||||
break;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult?> Receive() {
|
||||
var line = "";
|
||||
while (line.Length < 33) {
|
||||
var ch = Reader.Read();
|
||||
if (ch == -1) {
|
||||
return null;
|
||||
throw new IOException("Connection closed");
|
||||
} else if (line.Length > 0 || ch == ' ') {
|
||||
line += char.ToString((char)ch);
|
||||
}
|
||||
@@ -71,7 +103,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
if (line == null || line == "") {
|
||||
return null;
|
||||
} else if (line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
|
||||
throw new IOException($"Invalid event from scale: '{line}'");
|
||||
throw new FormatException($"Invalid event from scale: '{line}'");
|
||||
}
|
||||
|
||||
var date = line[ 1.. 9];
|
||||
@@ -81,7 +113,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
var unit = line[30..32];
|
||||
|
||||
if (unit != "kg") {
|
||||
throw new IOException($"Unsupported unit in weighing event: '{unit}'");
|
||||
throw new WeighingException($"Unsupported unit in weighing event: '{unit}'");
|
||||
}
|
||||
|
||||
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
var line = await Reader.ReadUntilAsync('\x03');
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
|
||||
if (line == null || line.Length < 4 || !line.StartsWith('\x02')) {
|
||||
throw new IOException("Invalid response from scale");
|
||||
throw new FormatException("Invalid response from scale");
|
||||
}
|
||||
|
||||
var status = line[1..3];
|
||||
@@ -45,19 +45,26 @@ namespace Elwig.Helpers.Weighing {
|
||||
switch (status[1]) {
|
||||
case 'M': msg = "Waage in Bewegung"; break;
|
||||
}
|
||||
throw new IOException($"Waagenfehler {status}: {msg}");
|
||||
throw new WeighingException($"Waagenfehler {status}: {msg}");
|
||||
} else if (status[0] != ' ') {
|
||||
throw new IOException($"Invalid response from scale (error code {status})");
|
||||
throw new WeighingException($"Invalid response from scale (error code {status})");
|
||||
}
|
||||
|
||||
return line[1..^1];
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
|
||||
await SendCommand(incIdentNr ? '\x05' : '?');
|
||||
string record = await ReceiveResponse();
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) {
|
||||
string record;
|
||||
try {
|
||||
await SendCommand(incIdentNr ? '\x05' : '?');
|
||||
record = await ReceiveResponse();
|
||||
} catch (IOException) {
|
||||
if (!retry || Tcp == null) throw;
|
||||
ReconnectTcp();
|
||||
return await Weigh(incIdentNr, false);
|
||||
}
|
||||
if (record.Length != 45)
|
||||
throw new IOException("Invalid response from scale: Received record has invalid size");
|
||||
throw new FormatException("Invalid response from scale: Received record has invalid size");
|
||||
var line = record[2..];
|
||||
|
||||
var brutto = line[ 0.. 7].Trim();
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
using System.IO.Ports;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
namespace Elwig.Helpers.Weighing {
|
||||
public abstract class Scale : IDisposable {
|
||||
|
||||
protected enum Output { RTS, DTR, OUT1, OUT2 };
|
||||
|
||||
protected readonly string Connection;
|
||||
protected SerialPort? Serial = null;
|
||||
protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
|
||||
protected TcpClient? Tcp = null;
|
||||
@@ -27,7 +29,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
if (config.Type == "SysTec-IT") {
|
||||
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else if (config.Type == "Avery-Async") {
|
||||
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Log, config.Required);
|
||||
} else if (config.Type == "Gassner") {
|
||||
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else {
|
||||
@@ -35,10 +37,18 @@ namespace Elwig.Helpers.Weighing {
|
||||
}
|
||||
}
|
||||
|
||||
protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) {
|
||||
protected Scale(string cnx, string? empty, string? filling, int? limit, string? log, bool softFail = false, bool failSilent = false) {
|
||||
Connection = cnx;
|
||||
if (cnx.StartsWith("serial:")) {
|
||||
Serial = Utils.OpenSerialConnection(cnx);
|
||||
Stream = Serial.BaseStream;
|
||||
try {
|
||||
Serial = Utils.OpenSerialConnection(cnx);
|
||||
} catch (Exception e) {
|
||||
if (!softFail) throw;
|
||||
if (!failSilent)
|
||||
MessageBox.Show($"Verbindung zu Waage konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
Stream = Serial?.BaseStream ?? Stream.Null;
|
||||
} else if (cnx.StartsWith("tcp:")) {
|
||||
Tcp = Utils.OpenTcpConnection(cnx);
|
||||
Stream = Tcp.GetStream();
|
||||
@@ -87,6 +97,14 @@ namespace Elwig.Helpers.Weighing {
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void ReconnectTcp() {
|
||||
if (Connection.StartsWith("tcp:")) {
|
||||
Tcp = Utils.OpenTcpConnection(Connection);
|
||||
Stream = Tcp.GetStream();
|
||||
Reader = new(Stream, Encoding.ASCII, false, 512);
|
||||
}
|
||||
}
|
||||
|
||||
protected static Output? ConvertOutput(string? value) {
|
||||
return value switch {
|
||||
null => null,
|
||||
|
||||
@@ -33,15 +33,17 @@ namespace Elwig.Helpers.Weighing {
|
||||
protected async Task<string> ReceiveResponse() {
|
||||
var line = await Reader.ReadUntilAsync("\r\n");
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
|
||||
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
|
||||
throw new IOException("Invalid response from scale");
|
||||
if (line == null) {
|
||||
throw new IOException("Verbindung zu Waage verloren");
|
||||
} else if (line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
|
||||
throw new FormatException("Invalid response from scale");
|
||||
}
|
||||
|
||||
var error = line[1..3];
|
||||
string msg = $"Unbekannter Fehler (Fehler code {error})";
|
||||
if (error[0] == '0') {
|
||||
if (error[1] != '0') {
|
||||
throw new IOException($"Invalid response from scale (error code {error})");
|
||||
throw new WeighingException($"Invalid response from scale (error code {error})");
|
||||
}
|
||||
} else if (error[0] == '1') {
|
||||
switch (error[1]) {
|
||||
@@ -52,31 +54,38 @@ namespace Elwig.Helpers.Weighing {
|
||||
case '6': msg = "Drucker nicht bereit"; break;
|
||||
case '7': msg = "Druckmuster enthält ungültiges Kommando"; break;
|
||||
}
|
||||
throw new IOException($"Waagenfehler {error}: {msg}");
|
||||
throw new WeighingException($"Waagenfehler {error}: {msg}");
|
||||
} else if (error[0] == '2') {
|
||||
switch (error[1]) {
|
||||
case '0': msg = "Brutto negativ"; break;
|
||||
}
|
||||
throw new IOException($"Fehler {error}: {msg}");
|
||||
throw new WeighingException($"Fehler {error}: {msg}");
|
||||
} else if (error[0] == '3') {
|
||||
switch (error[1]) {
|
||||
case '1': msg = "Übertragunsfehler"; break;
|
||||
case '2': msg = "Ungültiger Befehl"; break;
|
||||
case '3': msg = "Ungültiger Parameter"; break;
|
||||
}
|
||||
throw new IOException($"Kommunikationsfehler {error}: {msg}");
|
||||
throw new WeighingException($"Kommunikationsfehler {error}: {msg}");
|
||||
} else {
|
||||
throw new IOException($"Invalid response from scale (error code {error})");
|
||||
throw new WeighingException($"Invalid response from scale (error code {error})");
|
||||
}
|
||||
|
||||
return line[1..^3];
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
|
||||
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}");
|
||||
string record = await ReceiveResponse();
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) {
|
||||
string record;
|
||||
try {
|
||||
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}");
|
||||
record = await ReceiveResponse();
|
||||
} catch (IOException) {
|
||||
if (!retry || Tcp == null) throw;
|
||||
ReconnectTcp();
|
||||
return await Weigh(incIdentNr, false);
|
||||
}
|
||||
if (record.Length != 62)
|
||||
throw new IOException("Invalid response from scale: Received record has invalid size");
|
||||
throw new FormatException("Invalid response from scale: Received record has invalid size");
|
||||
var line = record[2..];
|
||||
|
||||
var status = line[ 0.. 2];
|
||||
@@ -94,9 +103,9 @@ namespace Elwig.Helpers.Weighing {
|
||||
var crc16 = line[52..60].Trim();
|
||||
|
||||
if (Utils.CalcCrc16Modbus(record[..54]) != ushort.Parse(crc16)) {
|
||||
throw new IOException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})");
|
||||
throw new WeighingException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})");
|
||||
} else if (unit != "kg") {
|
||||
throw new IOException($"Unsupported unit in weighing response: '{unit}'");
|
||||
throw new WeighingException($"Unsupported unit in weighing response: '{unit}'");
|
||||
}
|
||||
|
||||
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
|
||||
|
||||
6
Elwig/Helpers/Weighing/WeighingException.cs
Normal file
6
Elwig/Helpers/Weighing/WeighingException.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Elwig.Helpers.Weighing {
|
||||
public class WeighingException(string? message = null) : Exception(message) {
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Elwig.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -69,12 +70,15 @@ namespace Elwig.Models.Dtos {
|
||||
}
|
||||
|
||||
public class DeliveryConfirmationDeliveryRow {
|
||||
public DateOnly Date;
|
||||
public string LsNr;
|
||||
public int DPNr;
|
||||
public string Variety;
|
||||
public string? Attribute;
|
||||
public string? Cultivation;
|
||||
public string QualityLevel;
|
||||
public string QualId;
|
||||
public bool IsDepreciated;
|
||||
public (double Oe, double Kmw) Gradation;
|
||||
public string[] Modifiers;
|
||||
public int Weight;
|
||||
@@ -83,23 +87,23 @@ namespace Elwig.Models.Dtos {
|
||||
|
||||
public DeliveryConfirmationDeliveryRow(DeliveryPart p) {
|
||||
var d = p.Delivery;
|
||||
Date = d.Date;
|
||||
LsNr = d.LsNr;
|
||||
DPNr = p.DPNr;
|
||||
Variety = p.Variety.Name;
|
||||
Attribute = p.Attribute?.Name;
|
||||
Cultivation = p.Cultivation?.Name;
|
||||
QualityLevel = p.Quality.Name;
|
||||
QualId = p.Quality.QualId;
|
||||
IsDepreciated = p.QualId == "WEI";
|
||||
Gradation = (p.Oe, p.Kmw);
|
||||
Modifiers = p.Modifiers
|
||||
.Select(m => m.Name)
|
||||
.ToArray();
|
||||
Modifiers = [.. p.Modifiers.Select(m => m.Name)];
|
||||
Weight = p.Weight;
|
||||
IsNetWeight = p.IsNetWeight;
|
||||
Buckets = p.Buckets
|
||||
Buckets = [.. p.Buckets
|
||||
.Where(b => b.Value > 0)
|
||||
.OrderByDescending(b => b.BktNr)
|
||||
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {p.SortId}{b.Discr}", b.Value))
|
||||
.ToArray();
|
||||
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {p.SortId}{b.Discr}", b.Value))];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
Elwig/Models/Dtos/MemberDeliveryData.cs
Normal file
59
Elwig/Models/Dtos/MemberDeliveryData.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Elwig.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Models.Dtos {
|
||||
public class MemberDeliveryData : DataTable<MemberDeliveryRow> {
|
||||
|
||||
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),
|
||||
("Weight", "Geliefert", "kg", 22),
|
||||
];
|
||||
|
||||
public MemberDeliveryData(IEnumerable<MemberDeliveryRow> rows, List<string> filterNames) :
|
||||
base("Liefermengen Gesamt", "Liefermengen pro Mitglied", string.Join(" / ", filterNames), rows, FieldNames) {
|
||||
}
|
||||
|
||||
public static async Task<MemberDeliveryData> FromQuery(IQueryable<DeliveryPart> query, List<string> filterNames) {
|
||||
return new((await query
|
||||
.GroupBy(p => new {
|
||||
p.Delivery.MgNr,
|
||||
p.Delivery.Member.Name,
|
||||
p.Delivery.Member.GivenName,
|
||||
p.Delivery.Member.Address,
|
||||
p.Delivery.Member.PostalDest.AtPlz!.Plz,
|
||||
Ort = p.Delivery.Member.PostalDest.AtPlz!.Ort.Name,
|
||||
})
|
||||
.Select(g => new {
|
||||
g.Key,
|
||||
Weight = g.Sum(p => p.Weight),
|
||||
}).ToListAsync())
|
||||
.Select(g => new MemberDeliveryRow {
|
||||
MgNr = g.Key.MgNr,
|
||||
Name1 = g.Key.Name,
|
||||
Name2 = g.Key.GivenName,
|
||||
Address = g.Key.Address,
|
||||
Plz = g.Key.Plz,
|
||||
Locality = g.Key.Ort,
|
||||
Weight = g.Weight,
|
||||
}), filterNames);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemberDeliveryRow {
|
||||
public required int MgNr;
|
||||
public required string Name1;
|
||||
public required string? Name2;
|
||||
public required string Address;
|
||||
public required int Plz;
|
||||
public required string Locality;
|
||||
public required int Weight;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using Elwig.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Models.Dtos {
|
||||
public class MemberDeliveryPerVarietyData : DataTable<MemberDeliveryPerVariantRow> {
|
||||
public class MemberDeliveryPerVarietyData : DataTable<MemberDeliveryPerVarietyRow> {
|
||||
|
||||
private static readonly (string, string, string?, int)[] FieldNames = [
|
||||
private static readonly (string, string, string?, int?)[] FieldNames = [
|
||||
("MgNr", "MgNr.", null, 12),
|
||||
("Name1", "Name", null, 40),
|
||||
("Name2", "Vorname", null, 40),
|
||||
@@ -16,104 +16,63 @@ namespace Elwig.Models.Dtos {
|
||||
("Locality", "Ort", null, 60),
|
||||
("SortIds", "Sorte", null, 12),
|
||||
("AttrIds", "Attribut", null, 16),
|
||||
("CultIds", "Bewirt.", null, 16),
|
||||
("Weights", "Geliefert", "kg", 22),
|
||||
("Areas", "Fläche", "m²", 22),
|
||||
("Yields", "Ertrag", "kg/ha", 22),
|
||||
];
|
||||
|
||||
public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
|
||||
base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) {
|
||||
public MemberDeliveryPerVarietyData(IEnumerable<MemberDeliveryPerVarietyRow> rows, List<string> filterNames) :
|
||||
base("Liefermengen pro Sorte", "Liefermengen pro Mitglied, Sorte, Attribut und Bewirtschaftungsart", string.Join(" / ", filterNames), rows, FieldNames) {
|
||||
}
|
||||
|
||||
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)
|
||||
), year);
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<MemberDeliveryPerVariantRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVariantRowSingle> 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,
|
||||
v.bucket, v.weight, v.area
|
||||
FROM (
|
||||
SELECT c.year AS year,
|
||||
c.mgnr AS mgnr,
|
||||
c.bucket AS bucket,
|
||||
COALESCE(d.weight, 0) AS weight,
|
||||
COALESCE(c.area, 0) AS area
|
||||
FROM v_area_commitment_bucket_strict c
|
||||
LEFT JOIN v_delivery_bucket_strict d ON (d.year, d.mgnr, d.bucket) = (c.year, c.mgnr, c.bucket)
|
||||
WHERE c.year = {year}
|
||||
UNION
|
||||
SELECT d.year,
|
||||
d.mgnr,
|
||||
d.bucket,
|
||||
COALESCE(d.weight, 0),
|
||||
COALESCE(c.area, 0)
|
||||
FROM v_delivery_bucket_strict d
|
||||
LEFT JOIN v_area_commitment_bucket_strict c ON (c.year, c.mgnr, c.bucket) = (d.year, d.mgnr, d.bucket)
|
||||
WHERE d.year = {year}
|
||||
) v
|
||||
LEFT JOIN member m ON m.mgnr = v.mgnr
|
||||
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
|
||||
LEFT JOIN AT_ort o ON o.okz = p.okz
|
||||
ORDER BY m.mgnr, v.bucket
|
||||
""").ToListAsync();
|
||||
public static async Task<MemberDeliveryPerVarietyData> FromQuery(IQueryable<DeliveryPart> query, List<string> filterNames) {
|
||||
return new((await query
|
||||
.GroupBy(p => new {
|
||||
p.Delivery.MgNr,
|
||||
p.Delivery.Member.Name,
|
||||
p.Delivery.Member.GivenName,
|
||||
p.Delivery.Member.Address,
|
||||
p.Delivery.Member.PostalDest.AtPlz!.Plz,
|
||||
Ort = p.Delivery.Member.PostalDest.AtPlz!.Ort.Name,
|
||||
p.SortId,
|
||||
p.AttrId,
|
||||
p.CultId,
|
||||
})
|
||||
.Select(g => new {
|
||||
g.Key,
|
||||
Weight = g.Sum(p => p.Weight),
|
||||
})
|
||||
.ToListAsync()).GroupBy(g => new {
|
||||
g.Key.MgNr,
|
||||
g.Key.Name,
|
||||
g.Key.GivenName,
|
||||
g.Key.Address,
|
||||
g.Key.Plz,
|
||||
g.Key.Ort,
|
||||
}).Select(g => new MemberDeliveryPerVarietyRow {
|
||||
MgNr = g.Key.MgNr,
|
||||
Name1 = g.Key.Name,
|
||||
Name2 = g.Key.GivenName,
|
||||
Address = g.Key.Address,
|
||||
Plz = g.Key.Plz,
|
||||
Locality = g.Key.Ort,
|
||||
SortIds = [.. g.Select(d => d.Key.SortId)],
|
||||
AttrIds = [.. g.Select(d => d.Key.AttrId)],
|
||||
CultIds = [.. g.Select(d => d.Key.CultId)],
|
||||
Weights = [.. g.Select(d => d.Weight)],
|
||||
}), filterNames);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemberDeliveryPerVariantRow {
|
||||
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[] Weights;
|
||||
public int?[] Yields => Weights.Zip(Areas).Select(i => i.Second > 0 ? (int?)i.First * 10_000 / i.Second : null).ToArray();
|
||||
|
||||
public MemberDeliveryPerVariantRow(IEnumerable<MemberDeliveryPerVariantRowSingle> 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();
|
||||
Weights = rows.Select(r => r.Weight).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[Keyless]
|
||||
public class MemberDeliveryPerVariantRowSingle {
|
||||
[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("weight")]
|
||||
public int Weight { get; set; }
|
||||
public class MemberDeliveryPerVarietyRow {
|
||||
public required int MgNr;
|
||||
public required string Name1;
|
||||
public required string? Name2;
|
||||
public required string Address;
|
||||
public required int Plz;
|
||||
public required string Locality;
|
||||
public required string[] SortIds;
|
||||
public required string?[] AttrIds;
|
||||
public required string?[] CultIds;
|
||||
public required int[] Weights;
|
||||
}
|
||||
}
|
||||
|
||||
119
Elwig/Models/Dtos/MemberDeliveryYieldsPerVarietyData.cs
Normal file
119
Elwig/Models/Dtos/MemberDeliveryYieldsPerVarietyData.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Models.Dtos {
|
||||
public class MemberDeliveryYieldsPerVarietyData : DataTable<MemberDeliveryYieldsPerVarietyRow> {
|
||||
|
||||
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),
|
||||
("Weights", "Geliefert", "kg", 22),
|
||||
("Areas", "Fläche", "m²", 22),
|
||||
("Yields", "Ertrag", "kg/ha", 22),
|
||||
];
|
||||
|
||||
public MemberDeliveryYieldsPerVarietyData(IEnumerable<MemberDeliveryYieldsPerVarietyRow> rows, int year) :
|
||||
base($"Liefermengen", $"Liefermengen pro Mitglied, Sorte und Attribut {year}", rows, FieldNames) {
|
||||
}
|
||||
|
||||
public static async Task<MemberDeliveryYieldsPerVarietyData> ForSeason(DbSet<MemberDeliveryPerVarietyRowSingle> table, int year) {
|
||||
return new MemberDeliveryYieldsPerVarietyData(
|
||||
(await FromDbSet(table, year)).GroupBy(
|
||||
r => r.MgNr,
|
||||
(k, g) => new MemberDeliveryYieldsPerVarietyRow(g)
|
||||
), year);
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<MemberDeliveryPerVarietyRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVarietyRowSingle> 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,
|
||||
v.bucket, v.weight, v.area
|
||||
FROM (
|
||||
SELECT c.year AS year,
|
||||
c.mgnr AS mgnr,
|
||||
c.bucket AS bucket,
|
||||
COALESCE(d.weight, 0) AS weight,
|
||||
COALESCE(c.area, 0) AS area
|
||||
FROM v_area_commitment_bucket_strict c
|
||||
LEFT JOIN v_delivery_bucket_strict d ON (d.year, d.mgnr, d.bucket) = (c.year, c.mgnr, c.bucket)
|
||||
WHERE c.year = {year}
|
||||
UNION
|
||||
SELECT d.year,
|
||||
d.mgnr,
|
||||
d.bucket,
|
||||
COALESCE(d.weight, 0),
|
||||
COALESCE(c.area, 0)
|
||||
FROM v_delivery_bucket_strict d
|
||||
LEFT JOIN v_area_commitment_bucket_strict c ON (c.year, c.mgnr, c.bucket) = (d.year, d.mgnr, d.bucket)
|
||||
WHERE d.year = {year}
|
||||
) v
|
||||
LEFT JOIN member m ON m.mgnr = v.mgnr
|
||||
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
|
||||
LEFT JOIN AT_ort o ON o.okz = p.okz
|
||||
ORDER BY m.mgnr, v.bucket
|
||||
""").ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class MemberDeliveryYieldsPerVarietyRow {
|
||||
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[] Weights;
|
||||
public int?[] Yields => Weights.Zip(Areas).Select(i => i.Second > 0 ? (int?)i.First * 10_000 / i.Second : null).ToArray();
|
||||
|
||||
public MemberDeliveryYieldsPerVarietyRow(IEnumerable<MemberDeliveryPerVarietyRowSingle> 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();
|
||||
Weights = rows.Select(r => r.Weight).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[Keyless]
|
||||
public class MemberDeliveryPerVarietyRowSingle {
|
||||
[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("weight")]
|
||||
public int Weight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Helpers.Billing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -62,6 +63,11 @@ namespace Elwig.Models.Entities {
|
||||
[Column("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string[] Comments => [.. Parts.Select(p => p.Comment).Prepend(Comment).Where(c => c != null).Cast<string>()];
|
||||
[NotMapped]
|
||||
public string CommentsString => string.Join(" / ", Comments);
|
||||
|
||||
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
|
||||
public long CTime { get; set; }
|
||||
[NotMapped]
|
||||
@@ -108,16 +114,16 @@ namespace Elwig.Models.Entities {
|
||||
public int Weight => Parts.Select(p => p.Weight).Sum();
|
||||
public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
|
||||
|
||||
public IEnumerable<string> SortIds => Parts
|
||||
.GroupBy(p => p.SortId)
|
||||
public IEnumerable<RawVaribute> Vaributes => Parts
|
||||
.GroupBy(p => (p.SortId, p.AttrId, p.CultId))
|
||||
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
|
||||
.Select(g => g.Key);
|
||||
public IEnumerable<string> FilteredSortIds => FilteredParts
|
||||
.GroupBy(p => p.SortId)
|
||||
.Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
|
||||
public IEnumerable<RawVaribute> FilteredVaributes => FilteredParts
|
||||
.GroupBy(p => (p.SortId, p.AttrId, p.CultId))
|
||||
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
|
||||
.Select(g => g.Key);
|
||||
public string SortIdString => string.Join(", ", SortIds);
|
||||
public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
|
||||
.Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
|
||||
public string VaributeString => string.Join(", ", Vaributes);
|
||||
public string FilteredVaributeString => string.Join(", ", FilteredVaributes);
|
||||
|
||||
public Brush? Color => Parts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
|
||||
public Brush? FilteredColor => FilteredParts.Select(p => p.Variety.Color).Distinct().SingleOrDefault();
|
||||
|
||||
@@ -9,6 +9,11 @@ using System.Text.Json.Nodes;
|
||||
namespace Elwig.Models.Entities {
|
||||
[Table("delivery_part"), PrimaryKey("Year", "DId", "DPNr")]
|
||||
public class DeliveryPart : IDelivery {
|
||||
|
||||
public const string Dumper = "dumper";
|
||||
public const string Pumped = "pumped";
|
||||
public const string Box = "box";
|
||||
|
||||
[Column("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
@@ -86,12 +91,27 @@ namespace Elwig.Models.Entities {
|
||||
[Column("hand_picked")]
|
||||
public bool? IsHandPicked { get; set; }
|
||||
|
||||
[Column("lesewagen")]
|
||||
public bool? IsLesewagen { get; set; }
|
||||
|
||||
[Column("gebunden")]
|
||||
public bool? IsGebunden { get; set; }
|
||||
|
||||
[Column("unloading")]
|
||||
public string? Unloading { get; set; }
|
||||
[NotMapped]
|
||||
public bool IsDumper {
|
||||
get => Unloading == Dumper;
|
||||
set => Unloading = value ? Dumper : Unloading;
|
||||
}
|
||||
[NotMapped]
|
||||
public bool IsPumped {
|
||||
get => Unloading == Pumped;
|
||||
set => Unloading = value ? Pumped : Unloading;
|
||||
}
|
||||
[NotMapped]
|
||||
public bool IsBox {
|
||||
get => Unloading == Box;
|
||||
set => Unloading = value ? Box : Unloading;
|
||||
}
|
||||
|
||||
[Column("temperature")]
|
||||
public double? Temperature { get; set; }
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ namespace Elwig.Models.Entities {
|
||||
[Column("max_weight")]
|
||||
public int? MaxWeight { get; set; }
|
||||
[NotMapped]
|
||||
public int AnnouncedWeight => Announcements.Sum(a => a.Weight);
|
||||
public int AnnouncedWeight => AnnouncedWeightOverride ?? Announcements.Sum(a => a.Weight);
|
||||
[NotMapped]
|
||||
public int? AnnouncedWeightOverride { get; set; }
|
||||
[NotMapped]
|
||||
public double? Percent => (double)AnnouncedWeight / MaxWeight * 100;
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace Elwig.Models.Entities {
|
||||
[Column("active")]
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
[Column("redacted")]
|
||||
public bool IsRedacted { get; set; }
|
||||
|
||||
[Column("ordering")]
|
||||
public int Ordering { get; set; }
|
||||
|
||||
@@ -46,6 +49,8 @@ namespace Elwig.Models.Entities {
|
||||
(Rel != null) ? $"{Utils.GetSign(Rel.Value)}{(Math.Abs(Rel.Value) < 0.1m ? "\u2007" : "")}{Math.Abs(Rel.Value):0.00##\u00a0%}" :
|
||||
"";
|
||||
|
||||
public string? PublicValueStr => IsRedacted ? null : ValueStr;
|
||||
|
||||
public override string ToString() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace Elwig.Models.Entities {
|
||||
[Table("wb_gl"), PrimaryKey("GlNr")]
|
||||
public class WbGl {
|
||||
[Column("glnr")]
|
||||
public int GlNr { get; private set; }
|
||||
public int GlNr { get; set; }
|
||||
|
||||
[Column("name")]
|
||||
public string Name { get; private set; } = null!;
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[InverseProperty(nameof(WbKg.Gl))]
|
||||
public virtual ICollection<WbKg> Kgs { get; private set; } = null!;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Elwig.Models.Entities {
|
||||
public virtual AT_Kg AtKg { get; private set; } = null!;
|
||||
|
||||
[ForeignKey("GlNr")]
|
||||
public virtual WbGl Gl { get; private set; } = null!;
|
||||
public virtual WbGl? Gl { get; private set; } = null!;
|
||||
|
||||
[InverseProperty(nameof(WbRd.Kg))]
|
||||
public virtual ICollection<WbRd> Rds { get; private set; } = null!;
|
||||
|
||||
@@ -11,16 +11,24 @@ namespace Elwig.Models.Entities {
|
||||
[Column("type")]
|
||||
public string Type { get; private set; } = null!;
|
||||
|
||||
[Column("max_qualid")]
|
||||
public string MaxQualId { get; private set; } = null!;
|
||||
|
||||
[ForeignKey("MaxQualId")]
|
||||
public virtual WineQualLevel MaxQualityLevel { get; private set; } = null!;
|
||||
|
||||
[Column("name")]
|
||||
public string Name { get; private set; } = null!;
|
||||
|
||||
[Column("comment")]
|
||||
public string? Comment { get; private set; }
|
||||
|
||||
public string SortIdFormat => IsQuw ? SortId : $"({SortId})";
|
||||
public string CommentFormat => (Comment != null) ? $" ({Comment})" : "";
|
||||
|
||||
public bool IsRed => Type == "R";
|
||||
public bool IsWhite => Type == "W";
|
||||
public bool IsQuw => MaxQualId == "QUW";
|
||||
public Brush? Color => IsWhite ? Brushes.DarkGreen : IsRed ? Brushes.DarkRed : null;
|
||||
|
||||
public WineVar() { }
|
||||
@@ -28,6 +36,7 @@ namespace Elwig.Models.Entities {
|
||||
public WineVar(string sortId, string name) {
|
||||
SortId = sortId;
|
||||
Name = name;
|
||||
MaxQualId = "QUW";
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
|
||||
@@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<PublishDir>bin\Publish</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
|
||||
17
Elwig/Resources/Sql/32-33.sql
Normal file
17
Elwig/Resources/Sql/32-33.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- schema version 32 to 33
|
||||
|
||||
ALTER TABLE wine_variety ADD COLUMN max_qualid TEXT NOT NULL DEFAULT 'QUW';
|
||||
UPDATE wine_quality_level SET qualid = 'ALW' WHERE qualid = 'AUL';
|
||||
UPDATE wine_variety SET comment = 'Muscato' WHERE sortid = 'MO';
|
||||
|
||||
INSERT INTO wine_variety (sortid, type, max_qualid, name, comment) VALUES
|
||||
('DR', 'W', 'QUW', 'Donauriesling', NULL),
|
||||
('DV', 'W', 'QUW', 'Donauveltliner', NULL),
|
||||
('BN', 'W', 'RSW', 'Bronner', NULL),
|
||||
('CB', 'W', 'RSW', 'Cabernet Blanc', NULL),
|
||||
('CJ', 'R', 'RSW', 'Cabernet Jura', NULL),
|
||||
('JO', 'W', 'RSW', 'Johanniter', NULL),
|
||||
('OR', 'W', 'RSW', 'Orangetraube', NULL),
|
||||
('PI', 'R', 'RSW', 'Pinot Nova', NULL),
|
||||
('RE', 'R', 'RSW', 'Regent', NULL),
|
||||
('SI', 'W', 'RSW', 'Solaris', NULL);
|
||||
3
Elwig/Resources/Sql/33-34.sql
Normal file
3
Elwig/Resources/Sql/33-34.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- schema version 33 to 34
|
||||
|
||||
ALTER TABLE modifier ADD COLUMN redacted INTEGER NOT NULL CHECK (redacted IN (TRUE, FALSE)) DEFAULT FALSE;
|
||||
8
Elwig/Resources/Sql/34-35.sql
Normal file
8
Elwig/Resources/Sql/34-35.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- schema version 34 to 35
|
||||
|
||||
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
ALTER TABLE delivery_part ADD COLUMN unloading TEXT DEFAULT NULL;
|
||||
UPDATE delivery_part SET unloading = 'pumped' WHERE lesewagen;
|
||||
UPDATE delivery_part SET unloading = 'box' WHERE (SELECT zwstid IN ('H','S') FROM delivery d WHERE (d.year, d.did) = (delivery_part.year, delivery_part.did));
|
||||
ALTER TABLE delivery_part DROP COLUMN lesewagen;
|
||||
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
19
Elwig/Resources/Sql/35-36.sql
Normal file
19
Elwig/Resources/Sql/35-36.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- schema version 35 to 36
|
||||
|
||||
CREATE VIEW v_member AS
|
||||
SELECT m.mgnr, m.name,
|
||||
(COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '')) AS other_names,
|
||||
m.address, p.plz, o.name AS locality,
|
||||
a.name AS billing_name, a.address AS billing_address, p2.plz AS billing_plz, o2.name AS billing_locality,
|
||||
k.name AS default_kg,
|
||||
GROUP_CONCAT(e.address, ', ') AS email_addresses
|
||||
FROM 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 member_billing_address a ON a.mgnr = m.mgnr
|
||||
LEFT JOIN AT_plz_dest p2 ON p2.id = a.postal_dest
|
||||
LEFT JOIN AT_ort o2 ON o2.okz = p2.okz
|
||||
LEFT JOIN AT_kg k ON k.kgnr = m.default_kgnr
|
||||
LEFT JOIN member_email_address e ON e.mgnr = m.mgnr
|
||||
GROUP BY m.mgnr
|
||||
ORDER BY m.mgnr;
|
||||
12
Elwig/Resources/Sql/36-37.sql
Normal file
12
Elwig/Resources/Sql/36-37.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- schema version 36 to 37
|
||||
|
||||
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
|
||||
-- fix old deliveries
|
||||
UPDATE delivery SET xtime = NULL, mtime = ctime WHERE year <= 2022 AND mtime >= 1768521600 AND mtime < 1772323200;
|
||||
UPDATE delivery_part SET xtime = NULL, mtime = ctime WHERE year <= 2022 AND mtime >= 1768521600 AND mtime < 1772323200;
|
||||
-- clear xtime at one laptop to force updates from this one
|
||||
UPDATE delivery SET xtime = NULL WHERE year >= 2023 AND xtime >= 1771200000 AND xtime < 1771286400;
|
||||
UPDATE delivery_part SET xtime = NULL WHERE year >= 2023 AND xtime >= 1771200000 AND xtime < 1771286400;
|
||||
|
||||
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
@@ -15,10 +15,8 @@ using LinqKit;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.IO;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using System.Windows.Controls;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Elwig.Services {
|
||||
public static class DeliveryService {
|
||||
@@ -80,9 +78,9 @@ namespace Elwig.Services {
|
||||
vm.PartComment = p.Comment ?? "";
|
||||
vm.TemperatureString = (p.Temperature != null) ? $"{p.Temperature:N1}" : "";
|
||||
vm.AcidString = (p.Acid != null) ? $"{p.Acid:N1}" : "";
|
||||
vm.IsLesewagen = p.IsLesewagen ?? false;
|
||||
vm.IsHandPicked = p.IsHandPicked;
|
||||
vm.IsGebunden = p.IsGebunden;
|
||||
vm.Unloading = p.Unloading;
|
||||
|
||||
vm.ScaleId = p.ScaleId;
|
||||
vm.WeighingData = p.WeighingData;
|
||||
@@ -188,14 +186,54 @@ namespace Elwig.Services {
|
||||
prd = prd.And(p => p.IsNetWeight == true);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("gerebelt gewogen");
|
||||
} else if (e.Length >= 5 && e.Length <= 11 && "planenwagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Unloading == DeliveryPart.Dumper);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("Planenw./Kipper");
|
||||
} else if (e.Length >= 6 && e.Length <= 12 && "!planenwagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Unloading != DeliveryPart.Dumper);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("kein Planenw./Kipper");
|
||||
} else if (e.Length >= 4 && e.Length <= 6 && "kipper".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Unloading == DeliveryPart.Dumper);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("Planenw./Kipper");
|
||||
} else if (e.Length >= 5 && e.Length <= 7 && "!kipper".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Unloading != DeliveryPart.Dumper);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("kein Planenw./Kipper");
|
||||
} else if (e.Length >= 5 && e.Length <= 9 && "lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.IsLesewagen == true);
|
||||
prd = prd.And(p => p.Unloading == DeliveryPart.Pumped);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("Lesewagen");
|
||||
} else if (e.Length >= 6 && e.Length <= 10 && "!lesewagen".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.IsLesewagen == false);
|
||||
prd = prd.And(p => p.Unloading != DeliveryPart.Pumped);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("kein Lesewagen");
|
||||
} else if (e.Length >= 5 && e.Length <= 6 && "kisten".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Unloading == DeliveryPart.Box);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("Kisten");
|
||||
} else if (e.Length >= 6 && e.Length <= 7 && "!kisten".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Unloading != DeliveryPart.Box);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("keine Kisten");
|
||||
} else if ("upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => (p.Delivery.XTime == null || p.Delivery.MTime > p.Delivery.XTime) && (p.Delivery.ITime == null || p.Delivery.MTime > p.Delivery.ITime));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Export");
|
||||
} else if ("!upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => !((p.Delivery.XTime == null || p.Delivery.MTime > p.Delivery.XTime) && (p.Delivery.ITime == null || p.Delivery.MTime > p.Delivery.ITime)));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Export");
|
||||
} else if (">import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.ITime != null && p.Delivery.MTime > p.Delivery.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Import");
|
||||
} else if ("<import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
prd = prd.And(p => p.Delivery.MTime <= p.Delivery.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Import");
|
||||
} else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
|
||||
filterVar.Add(e.ToUpper());
|
||||
filter.RemoveAt(i--);
|
||||
@@ -483,8 +521,8 @@ namespace Elwig.Services {
|
||||
|
||||
IsNetWeight = vm.IsNetWeight,
|
||||
IsHandPicked = vm.IsHandPicked,
|
||||
IsLesewagen = vm.IsLesewagen,
|
||||
IsGebunden = vm.IsGebunden,
|
||||
Unloading = vm.Unloading,
|
||||
Temperature = vm.Temperature,
|
||||
Acid = vm.Acid,
|
||||
Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment,
|
||||
@@ -571,6 +609,13 @@ namespace Elwig.Services {
|
||||
s.DPNr = dpnr++;
|
||||
s.Weight = w;
|
||||
ctx.Add(s);
|
||||
|
||||
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
|
||||
Year = s.Year,
|
||||
DId = s.DId,
|
||||
DPNr = s.DPNr,
|
||||
ModId = m.ModId,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,6 +653,13 @@ namespace Elwig.Services {
|
||||
s.DPNr = dpnr++;
|
||||
s.Weight = w;
|
||||
ctx.Add(s);
|
||||
|
||||
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
|
||||
Year = s.Year,
|
||||
DId = s.DId,
|
||||
DPNr = s.DPNr,
|
||||
ModId = m.ModId,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,6 +694,13 @@ namespace Elwig.Services {
|
||||
n.QualId = "WEI";
|
||||
n.HkId = "OEST";
|
||||
ctx.Add(n);
|
||||
|
||||
ctx.AddRange(p.PartModifiers.Select(m => new DeliveryPartModifier() {
|
||||
Year = n.Year,
|
||||
DId = n.DId,
|
||||
DPNr = n.DPNr,
|
||||
ModId = m.ModId,
|
||||
}));
|
||||
}
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
@@ -721,19 +780,30 @@ namespace Elwig.Services {
|
||||
FileName = subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}.elwig.zip" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
|
||||
DefaultExt = "elwig.zip",
|
||||
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
|
||||
Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
|
||||
Title = $"{DeliveryJournal.Name} speichern unter - Elwig",
|
||||
AddExtension = false,
|
||||
};
|
||||
if (d.ShowDialog() == true) {
|
||||
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
await ElwigData.Export(d.FileName, await query
|
||||
var list = await query
|
||||
.Select(p => p.Delivery)
|
||||
.Distinct()
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(), filterNames);
|
||||
.ToListAsync();
|
||||
var wbKgs = list
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
await ElwigData.Export(d.FileName, list, wbKgs, filterNames);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
@@ -743,32 +813,7 @@ namespace Elwig.Services {
|
||||
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var list = await query
|
||||
.Select(p => p.Delivery)
|
||||
.Distinct()
|
||||
.Include(d => d.Parts)
|
||||
.ThenInclude(p => p.PartModifiers)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
if (list.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} 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:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
} else {
|
||||
@@ -919,6 +964,52 @@ namespace Elwig.Services {
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task GenerateDeliveryDataList(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
|
||||
.OrderBy(p => p.Delivery.MgNr)
|
||||
.ThenBy(p => p.SortId)
|
||||
.ThenBy(p => p.AttrId)
|
||||
.ThenBy(p => p.CultId);
|
||||
|
||||
var d = new SaveFileDialog() {
|
||||
FileName = $"Liefermengen.ods",
|
||||
DefaultExt = "ods",
|
||||
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
|
||||
Title = $"Liefermengen speichern unter - Elwig"
|
||||
};
|
||||
if (d.ShowDialog() == true) {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
using var ods = new OdsFile(d.FileName);
|
||||
var tblTotal = await MemberDeliveryData.FromQuery(query, filterNames);
|
||||
var tbl = await MemberDeliveryPerVarietyData.FromQuery(query, filterNames);
|
||||
await ods.AddTable(tblTotal);
|
||||
await ods.AddTable(tbl);
|
||||
} 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,
|
||||
@@ -1089,5 +1180,104 @@ namespace Elwig.Services {
|
||||
await ctx.SaveChangesAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<(int Year, int DId, int DPNr)[]> GetDidsFromFilters(this DeliveryAdminViewModel vm) {
|
||||
using var ctx = new AppDbContext();
|
||||
var (_, _, parts, _, _) = await vm.GetFilters(ctx);
|
||||
return [.. (await parts.Select(p => new { p.Year, p.DId, p.DPNr }).ToListAsync()).Select(p => (p.Year, p.DId, p.DPNr))];
|
||||
}
|
||||
|
||||
public static async Task BulkSetAttribute(this DeliveryAdminViewModel vm, string? attributeName) {
|
||||
try {
|
||||
string attrid;
|
||||
if (attributeName == null) {
|
||||
attrid = "NULL";
|
||||
} else {
|
||||
using var ctx = new AppDbContext();
|
||||
var attr = await ctx.WineAttributes.Where(a => a.Name == attributeName).SingleAsync();
|
||||
attrid = $"'{attr.AttrId}'";
|
||||
}
|
||||
var dids = await vm.GetDidsFromFilters();
|
||||
var res = MessageBox.Show($"Soll wirklich für {dids.Length:N0} Teillieferung(en) das Attribut\n'{attributeName}' gesetz werden?",
|
||||
"Massenaktion: Attribut setzen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
|
||||
if (res != MessageBoxResult.OK) return;
|
||||
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE delivery_part SET attrid = {attrid}
|
||||
WHERE (year, did, dpnr) IN ({string.Join(", ", dids.Select(d => $"({d.Year},{d.DId},{d.DPNr})"))})
|
||||
""");
|
||||
}
|
||||
App.HintContextChange();
|
||||
});
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} finally {
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task BulkAddModifier(this DeliveryAdminViewModel vm, string modifierName) {
|
||||
try {
|
||||
string modid;
|
||||
using (var ctx = new AppDbContext()) {
|
||||
var attr = await ctx.Modifiers.Where(a => a.Name == modifierName).FirstAsync();
|
||||
modid = $"'{attr.ModId}'";
|
||||
}
|
||||
|
||||
var dids = await vm.GetDidsFromFilters();
|
||||
var res = MessageBox.Show($"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' hinzugefügt werden?",
|
||||
"Massenaktion: Zu-/Abschlag hinzufügen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
|
||||
if (res != MessageBoxResult.OK) return;
|
||||
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||
await cnx.ExecuteBatch($"""
|
||||
INSERT INTO delivery_part_modifier (year, did, dpnr, modid)
|
||||
VALUES {string.Join(", ", dids.Select(d => $"({d.Year},{d.DId},{d.DPNr},{modid})"))}
|
||||
ON CONFLICT DO NOTHING
|
||||
""");
|
||||
}
|
||||
App.HintContextChange();
|
||||
});
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} finally {
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task BulkRemoveModifier(this DeliveryAdminViewModel vm, string modifierName) {
|
||||
try {
|
||||
string modid;
|
||||
using (var ctx = new AppDbContext()) {
|
||||
var attr = await ctx.Modifiers.Where(a => a.Name == modifierName).FirstAsync();
|
||||
modid = $"'{attr.ModId}'";
|
||||
}
|
||||
|
||||
var dids = await vm.GetDidsFromFilters();
|
||||
var res = MessageBox.Show($"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' entfernt werden?",
|
||||
"Massenaktion: Zu-/Abschlag entfernen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
|
||||
if (res != MessageBoxResult.OK) return;
|
||||
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||
await cnx.ExecuteBatch($"""
|
||||
DELETE FROM delivery_part_modifier
|
||||
WHERE (year, did, dpnr, modid) IN ({string.Join(", ", dids.Select(d => $"({d.Year},{d.DId},{d.DPNr},{modid})"))})
|
||||
""");
|
||||
}
|
||||
App.HintContextChange();
|
||||
});
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} finally {
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
@@ -315,6 +313,22 @@ namespace Elwig.Services {
|
||||
memberQuery = memberQuery.Where(m => !m.ContactViaPost);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("nicht Kontaktart Post");
|
||||
} else if ("upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => (p.XTime == null || p.MTime > p.XTime) && (p.ITime == null || p.MTime > p.ITime));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Export");
|
||||
} else if ("!upload".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => !((p.XTime == null || p.MTime > p.XTime) && (p.ITime == null || p.MTime > p.ITime)));
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Export");
|
||||
} else if (">import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => p.MTime > p.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("geändert seit letztem Import");
|
||||
} else if ("<import".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
|
||||
memberQuery = memberQuery.Where(p => p.MTime <= p.ITime);
|
||||
filter.RemoveAt(i--);
|
||||
filterNames.Add("unverändert seit letztem Import");
|
||||
} else if (e.All(char.IsAsciiDigit) && mgnr.ContainsKey(e)) {
|
||||
filterMgNr.Add(int.Parse(e));
|
||||
filter.RemoveAt(i--);
|
||||
@@ -493,12 +507,12 @@ namespace Elwig.Services {
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
} else if (mode == ExportMode.Export) {
|
||||
} else if (mode == ExportMode.Vcf) {
|
||||
var d = new SaveFileDialog() {
|
||||
FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
|
||||
DefaultExt = ".elwig.zip",
|
||||
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
|
||||
Title = $"{MemberList.Name} speichern unter - Elwig"
|
||||
FileName = "Mitglieder.vcf",
|
||||
DefaultExt = "vcf",
|
||||
Filter = "vCard-Datei (*.vcf)|*.vcf",
|
||||
Title = "Kontakte speichern unter - Elwig"
|
||||
};
|
||||
if (d.ShowDialog() == true) {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
@@ -511,11 +525,48 @@ namespace Elwig.Services {
|
||||
.Include(m => m.EmailAddresses)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
using var exporter = new VCard(d.FileName);
|
||||
await exporter.ExportAsync(members);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
} else if (mode == ExportMode.Export) {
|
||||
var d = new SaveFileDialog() {
|
||||
FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip",
|
||||
DefaultExt = "elwig.zip",
|
||||
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
|
||||
Title = $"{MemberList.Name} speichern unter - Elwig",
|
||||
AddExtension = false,
|
||||
};
|
||||
if (d.ShowDialog() == true) {
|
||||
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
var members = await query
|
||||
.OrderBy(m => m.MgNr)
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var areaComs = await query
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.ToListAsync();
|
||||
await ElwigData.Export(d.FileName, members, areaComs, filterNames);
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Distinct()
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
await ElwigData.Export(d.FileName, members, areaComs, wbKgs, filterNames);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
@@ -525,36 +576,7 @@ namespace Elwig.Services {
|
||||
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
await Task.Run(async () => {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var members = await query
|
||||
.OrderBy(m => m.MgNr)
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var areaComs = await query
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.Include(c => c.Rd)
|
||||
.ToListAsync();
|
||||
if (members.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} 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:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
|
||||
});
|
||||
Mouse.OverrideCursor = null;
|
||||
} else {
|
||||
|
||||
281
Elwig/Services/SyncService.cs
Normal file
281
Elwig/Services/SyncService.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Helpers.Export;
|
||||
using Elwig.Models.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Elwig.Services {
|
||||
public static class SyncService {
|
||||
|
||||
public static readonly Expression<Func<Member, bool>> ChangedMembers = (m) => ((m.XTime == null && m.MTime > 1751328000) || m.MTime > m.XTime) && (m.ITime == null || m.MTime > m.ITime);
|
||||
public static readonly Expression<Func<Delivery, bool>> ChangedDeliveries = (d) => ((d.XTime == null && d.MTime > 1751328000) || d.MTime > d.XTime) && (d.ITime == null || d.MTime > d.ITime);
|
||||
|
||||
public static async Task Upload(string url, string username, string password, IQueryable<Member> query, IEnumerable<string> filterNames) {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var members = await query
|
||||
.OrderBy(m => m.MgNr)
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var areaComs = await query
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.ToListAsync();
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Distinct()
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (members.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Mitglieder zum Hochladen ausgewählt!", "Mitglieder hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt(members, [], exportedAt);
|
||||
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Upload(string url, string username, string password, IQueryable<DeliveryPart> query, IEnumerable<string> filterNames) {
|
||||
try {
|
||||
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip";
|
||||
var path = Path.Combine(App.TempPath, filename);
|
||||
var list = await query
|
||||
.Select(p => p.Delivery)
|
||||
.Distinct()
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg!.Gl)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = list
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (list.Count == 0) {
|
||||
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await ElwigData.Export(path, list, wbKgs, filterNames);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt([], list, exportedAt);
|
||||
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task UploadModified(string url, string username, string password) {
|
||||
try {
|
||||
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
|
||||
List<Member> members;
|
||||
List<AreaCom> areaComs;
|
||||
List<Delivery> deliveries;
|
||||
using (var ctx = new AppDbContext()) {
|
||||
members = await ctx.Members
|
||||
.Where(ChangedMembers)
|
||||
.Include(m => m.BillingAddress)
|
||||
.Include(m => m.TelephoneNumbers)
|
||||
.Include(m => m.EmailAddresses)
|
||||
.Include(m => m.DefaultWbKg!.Gl)
|
||||
.OrderBy(m => m.MgNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
areaComs = await ctx.Members
|
||||
.Where(ChangedMembers)
|
||||
.SelectMany(m => m.AreaCommitments)
|
||||
.Include(c => c.Rd)
|
||||
.Include(c => c.Kg.Gl)
|
||||
.OrderBy(c => c.MgNr).ThenBy(c => c.FbNr)
|
||||
.ToListAsync();
|
||||
deliveries = await ctx.Deliveries
|
||||
.Where(ChangedDeliveries)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
|
||||
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
var wbKgs = members
|
||||
.Where(m => m.DefaultWbKg != null)
|
||||
.Select(m => m.DefaultWbKg!)
|
||||
.Union(areaComs.Select(c => c.Kg))
|
||||
.Union(deliveries.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!))
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.OrderBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (members.Count == 0 && deliveries.Count == 0) {
|
||||
MessageBox.Show("Es gibt keine geänderten Mitglieder oder Lieferungen, die hochgeladen werden könnten!", "Mitglieder und Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await (new ElwigData.ElwigExport {
|
||||
Members = (members, ["geändert seit letztem Export"]),
|
||||
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
|
||||
Deliveries = (deliveries, ["geändert seit letzem Export"]),
|
||||
WbKgs = (wbKgs, ["von exportierten Mitgliedern, Flächenbindungen und Lieferungen"]),
|
||||
}).Export(path);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt(members, deliveries, exportedAt);
|
||||
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern und {deliveries.Count:N0} Lieferungen erfolgreich!", "Mitglieder und Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task UploadBranchDeliveries(string url, string username, string password, int? year = null) {
|
||||
try {
|
||||
year ??= Utils.CurrentLastSeason;
|
||||
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
|
||||
using var ctx = new AppDbContext();
|
||||
var deliveries = await ctx.Deliveries
|
||||
.Where(d => d.Year == year && d.ZwstId == App.ZwstId)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers).ThenInclude(m => m.Modifier)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Rd)
|
||||
.Include(d => d.Parts).ThenInclude(p => p.Kg).ThenInclude(k => k!.Gl)
|
||||
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString).ThenBy(d => d.LsNr)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
var wbKgs = deliveries
|
||||
.SelectMany(d => d.Parts)
|
||||
.Where(p => p.Kg != null)
|
||||
.Select(p => p.Kg!)
|
||||
.DistinctBy(k => k.KgNr)
|
||||
.ToList();
|
||||
if (deliveries.Count == 0) {
|
||||
MessageBox.Show("Es gibt keine Lieferungen, die hochgeladen werden können!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} else {
|
||||
var exportedAt = DateTime.Now;
|
||||
await ElwigData.Export(path, deliveries, wbKgs, [$"{year}", $"Zweigstelle {App.BranchName}"]);
|
||||
await Utils.UploadExportData(path, url, username, password);
|
||||
await UpdateExportedAt([], deliveries, exportedAt);
|
||||
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<List<(string Name, string Url)>> GetFilesToImport(string url, string username, string password) {
|
||||
var data = await Utils.GetExportMetaData(url, username, password);
|
||||
var files = data
|
||||
.Select(f => new {
|
||||
Name = f!["name"]!.AsValue().GetValue<string>(),
|
||||
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
|
||||
ZwstId = f!["meta"]?["zwstid"]?.AsValue().GetValue<string>() ?? f!["zwstid"]?.AsValue().GetValue<string>(),
|
||||
Device = f!["meta"]?["device"]?.AsValue().GetValue<string>(),
|
||||
Url = f!["url"]!.AsValue().GetValue<string>(),
|
||||
Size = f!["size"]!.AsValue().GetValue<long>(),
|
||||
})
|
||||
.Where(f => f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
|
||||
.ToList();
|
||||
|
||||
var imported = await ElwigData.GetImportedFiles();
|
||||
return [.. files
|
||||
.Where(f => f.Device != Environment.MachineName && !imported.Contains(f.Name))
|
||||
.Select(f => (f.Name, f.Url))
|
||||
];
|
||||
}
|
||||
|
||||
public static async Task Download(string url, string username, string password) {
|
||||
try {
|
||||
var import = await GetFilesToImport(url, username, password);
|
||||
var paths = new List<string>();
|
||||
using (var client = Utils.GetHttpClient(username, password)) {
|
||||
foreach (var f in import) {
|
||||
var filename = Path.Combine(App.TempPath, f.Name);
|
||||
using var stream = new FileStream(filename, FileMode.Create);
|
||||
await client.DownloadAsync(f.Url, stream);
|
||||
paths.Add(filename);
|
||||
}
|
||||
}
|
||||
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
|
||||
} catch (HttpRequestException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (TaskCanceledException exc) {
|
||||
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task UpdateExportedAt(IEnumerable<Member> member, IEnumerable<Delivery> deliveries, DateTime dateTime) {
|
||||
var timestamp = ((DateTimeOffset)dateTime.ToUniversalTime()).ToUnixTimeSeconds();
|
||||
var mgnrs = string.Join(",", member.Select(m => $"{m.MgNr}").Append("0"));
|
||||
var dids = string.Join(",", deliveries.Select(d => $"({d.Year},{d.DId})").Append("(0,0)"));
|
||||
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||
await cnx.ExecuteBatch($"""
|
||||
BEGIN;
|
||||
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
UPDATE member SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
|
||||
UPDATE area_commitment SET xtime = {timestamp} WHERE mgnr IN ({mgnrs});
|
||||
UPDATE delivery SET xtime = {timestamp} WHERE (year, did) IN ({dids});
|
||||
UPDATE delivery_part SET xtime = {timestamp} WHERE (year, did) IN ({dids});
|
||||
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
|
||||
COMMIT;
|
||||
""");
|
||||
}
|
||||
App.HintContextChange();
|
||||
}
|
||||
|
||||
public static async Task<bool> ChangesAvailable(AppDbContext ctx, string url, string username, string password) {
|
||||
try {
|
||||
return await ctx.Members.AnyAsync(ChangedMembers) || await ctx.Deliveries.AnyAsync(ChangedDeliveries) || (Utils.HasInternetConnectivity() && (await GetFilesToImport(url, username, password)).Count > 0);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,10 +169,28 @@ namespace Elwig.ViewModels {
|
||||
set => AcidString = $"{value:0.0}";
|
||||
}
|
||||
[ObservableProperty]
|
||||
private bool _isLesewagen;
|
||||
[ObservableProperty]
|
||||
private bool? _isHandPicked;
|
||||
|
||||
public string? Unloading {
|
||||
get => IsUnloadingDumper ? DeliveryPart.Dumper : IsUnloadingPumped ? DeliveryPart.Pumped : IsUnloadingBox ? DeliveryPart.Box : null;
|
||||
set {
|
||||
switch (value) {
|
||||
case DeliveryPart.Dumper: IsUnloadingDumper = true; break;
|
||||
case DeliveryPart.Pumped: IsUnloadingPumped = true; break;
|
||||
case DeliveryPart.Box: IsUnloadingBox = true; break;
|
||||
default: IsUnloadingOther = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
[ObservableProperty]
|
||||
private bool _isUnloadingDumper;
|
||||
[ObservableProperty]
|
||||
private bool _isUnloadingPumped;
|
||||
[ObservableProperty]
|
||||
private bool _isUnloadingBox;
|
||||
[ObservableProperty]
|
||||
private bool _isUnloadingOther;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _statusMembers = "-";
|
||||
[ObservableProperty]
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Elwig.ViewModels {
|
||||
private IEnumerable<PaymentVar> _paymentVariants = [];
|
||||
|
||||
public BillingData? BillingData;
|
||||
public bool SeasonLocked;
|
||||
public bool SeasonLocked = false; // never locked
|
||||
public bool WeightModifierChanged;
|
||||
|
||||
[ObservableProperty]
|
||||
|
||||
32
Elwig/Windows/AboutWindow.xaml
Normal file
32
Elwig/Windows/AboutWindow.xaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<Window x:Class="Elwig.Windows.AboutWindow"
|
||||
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.Windows"
|
||||
Title="Über - Elwig" Height="340" Width="460" ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<TextBlock Margin="20,10" FontSize="12">
|
||||
<Bold>Produkt:</Bold> Elwig<LineBreak/>
|
||||
<Bold>Beschreibung:</Bold> Elektronische Winzergenossenschaftsverwaltung<LineBreak/>
|
||||
<Bold>Typ:</Bold> Warenwirtschaftssystem (ERP-System)<LineBreak/>
|
||||
<Bold>Version:</Bold> <Run x:Name="Version">0.0.0.0</Run> (<Hyperlink NavigateUri="https://elwig.at/changelog" RequestNavigate="Hyperlink_RequestNavigate">Änderungsprotokoll</Hyperlink>)<LineBreak/>
|
||||
<Bold>Lizenz:</Bold> <Hyperlink NavigateUri="https://www.gnu.org/licenses/gpl-3.0.html" RequestNavigate="Hyperlink_RequestNavigate">GNU General Public License 3.0 (GPLv3)</Hyperlink><LineBreak/>
|
||||
<Bold>Website:</Bold> <Hyperlink NavigateUri="https://elwig.at/" RequestNavigate="Hyperlink_RequestNavigate">https://elwig.at/</Hyperlink><LineBreak/>
|
||||
<Bold>Entwickler:</Bold> Lorenz Stechauner, Thomas Hilscher<LineBreak/>
|
||||
<Bold>Kontakt:</Bold> <Hyperlink NavigateUri="mailto:lorenz.stechauner@necronda.net" RequestNavigate="Hyperlink_RequestNavigate">lorenz.stechauner@necronda.net</Hyperlink>, <Hyperlink NavigateUri="mailto:thomas.hilscher@gmail.com" RequestNavigate="Hyperlink_RequestNavigate">thomas.hilscher@gmail.com</Hyperlink><LineBreak/>
|
||||
<Bold>Quellcode:</Bold> <Hyperlink NavigateUri="https://git.necronda.net/winzer/elwig" RequestNavigate="Hyperlink_RequestNavigate">https://git.necronda.net/winzer/elwig</Hyperlink><LineBreak/>
|
||||
<Bold>Entwicklungszeitraum:</Bold> 2022–2026<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Bold>Verwendete Technologien:</Bold><LineBreak/>
|
||||
Programmiersprache: C#<LineBreak/>
|
||||
Framework: Windows Presentation Framework (WPF)<LineBreak/>
|
||||
Datenbank: <Hyperlink NavigateUri="https://sqlite.org/" RequestNavigate="Hyperlink_RequestNavigate">SQLite</Hyperlink><LineBreak/>
|
||||
PDF-Erstellung: <Hyperlink NavigateUri="https://itextpdf.com/" RequestNavigate="Hyperlink_RequestNavigate">iText</Hyperlink>, <Hyperlink NavigateUri="https://github.com/bblanchon/pdfium-binaries" RequestNavigate="Hyperlink_RequestNavigate">Pdfium</Hyperlink><LineBreak/>
|
||||
Paketierung: <Hyperlink NavigateUri="https://www.firegiant.com/wixtoolset/" RequestNavigate="Hyperlink_RequestNavigate">WiX Toolset</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<Image Source="\Resources\Images\Elwig.png" RenderOptions.BitmapScalingMode="HighQuality" Height="64"
|
||||
HorizontalAlignment="Right" Margin="10" VerticalAlignment="Top"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
17
Elwig/Windows/AboutWindow.xaml.cs
Normal file
17
Elwig/Windows/AboutWindow.xaml.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace Elwig.Windows {
|
||||
public partial class AboutWindow : Window {
|
||||
|
||||
public AboutWindow() {
|
||||
InitializeComponent();
|
||||
Version.Text = App.Version.ToString();
|
||||
}
|
||||
|
||||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) {
|
||||
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ using System.Windows.Input;
|
||||
namespace Elwig.Windows {
|
||||
public abstract class AdministrationWindow : ContextWindow {
|
||||
|
||||
protected Control[] ExemptInputs { private get; set; }
|
||||
protected Control[] RequiredInputs { private get; set; }
|
||||
protected Control[] ExemptInputs { get; set; }
|
||||
protected Control[] RequiredInputs { get; set; }
|
||||
|
||||
private bool _isEditing;
|
||||
private bool _isCreating;
|
||||
@@ -166,8 +166,10 @@ namespace Elwig.Windows {
|
||||
Valid[input] = false;
|
||||
} else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast<object>().Any()) {
|
||||
ControlUtils.SetInputInvalid(input);
|
||||
Valid[input] = false;
|
||||
} else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast<object>().Any()) {
|
||||
ControlUtils.SetInputInvalid(input);
|
||||
Valid[input] = false;
|
||||
} else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) {
|
||||
ControlUtils.SetInputInvalid(input);
|
||||
Valid[input] = false;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:local="clr-namespace:Elwig.Windows"
|
||||
xmlns:ctrl="clr-namespace:Elwig.Controls"
|
||||
mc:Ignorable="d"
|
||||
Title="Stammdaten - Elwig" Height="500" MinHeight="400" Width="850" MinWidth="810"
|
||||
Title="Stammdaten - Elwig" Height="520" MinHeight="400" Width="860" MinWidth="810"
|
||||
Loaded="Window_Loaded">
|
||||
<Window.Resources>
|
||||
<Style TargetType="Label">
|
||||
@@ -532,6 +532,10 @@
|
||||
<CheckBox x:Name="SeasonModifierActiveInput" Content="In Übernahme-Fenster anzeigen"
|
||||
Grid.Column="1" Grid.ColumnSpan="2" Margin="10,134,10,10" HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
Checked="SeasonModifier_Changed" Unchecked="SeasonModifier_Changed"/>
|
||||
|
||||
<CheckBox x:Name="SeasonModifierRedactedInput" Content="Wert auf Lieferschein verbergen"
|
||||
Grid.Column="1" Grid.ColumnSpan="2" Margin="10,154,10,10" HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
Checked="SeasonModifier_Changed" Unchecked="SeasonModifier_Changed"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
@@ -135,12 +135,14 @@ namespace Elwig.Windows {
|
||||
SeasonModifierRelInput.Text = "";
|
||||
SeasonModifierAbsInput.Text = "";
|
||||
SeasonModifierActiveInput.IsChecked = false;
|
||||
SeasonModifierRedactedInput.IsChecked = false;
|
||||
} else {
|
||||
SeasonModifierIdInput.Text = mod.ModId;
|
||||
SeasonModifierNameInput.Text = mod.Name;
|
||||
SeasonModifierRelInput.Text = (mod.Rel * 100)?.ToString() ?? "";
|
||||
SeasonModifierAbsInput.Text = mod.Abs?.ToString() ?? "";
|
||||
SeasonModifierActiveInput.IsChecked = mod.IsActive;
|
||||
SeasonModifierRedactedInput.IsChecked = mod.IsRedacted;
|
||||
}
|
||||
_modUpdate = false;
|
||||
}
|
||||
@@ -157,6 +159,7 @@ namespace Elwig.Windows {
|
||||
mod.Rel = decimal.TryParse(SeasonModifierRelInput.Text, out var vRel) ? vRel / 100 : null;
|
||||
mod.AbsValue = decimal.TryParse(SeasonModifierAbsInput.Text, out var vAbs) ? Utils.DecToDb(vAbs, s.Precision) : null;
|
||||
mod.IsActive = SeasonModifierActiveInput.IsChecked ?? false;
|
||||
mod.IsRedacted = SeasonModifierRedactedInput.IsChecked ?? false;
|
||||
|
||||
CollectionViewSource.GetDefaultView(_modList).Refresh();
|
||||
UpdateButtons();
|
||||
|
||||
@@ -664,8 +664,10 @@ namespace Elwig.Windows {
|
||||
try {
|
||||
await Task.Run(async () => {
|
||||
var b = new BillingVariant(PaymentVar.Year, PaymentVar.AvNr);
|
||||
await b.Calculate();
|
||||
await b.Calculate(false);
|
||||
});
|
||||
} catch (KeyNotFoundException exc) {
|
||||
MessageBox.Show(exc.Message, "Noch nicht alle Preise festgelegt", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Berechnungsfehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
@@ -129,6 +129,14 @@
|
||||
<MenuItem x:Name="Menu_DeliveryDepreciationList_PrintSeason" Header="...von Saison drucken"
|
||||
Click="Menu_DeliveryDepreciationList_PrintSeason_Click"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Liefermengen" x:Name="Menu_DeliveryDataList">
|
||||
<MenuItem x:Name="Menu_DeliveryDataList_SaveFilters" Header="...aus Filtern speichern... (Excel)"
|
||||
Click="Menu_DeliveryDataList_SaveFilters_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Statistik" x:Name="Menu_Statistics">
|
||||
<MenuItem Header="Qualitätsstatistik..." x:Name="Menu_Statistics_WineQuality">
|
||||
<MenuItem x:Name="Menu_Statistics_WineQuality_ShowFilters" Header="...aus Filtern anzeigen (PDF)"
|
||||
@@ -158,7 +166,11 @@
|
||||
</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"/>
|
||||
Click="Menu_Statistic_Locality_SaveFilters_Click">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="BKI">
|
||||
@@ -180,6 +192,23 @@
|
||||
<MenuItem x:Name="Menu_Export_UploadSeason" Header="...von Saison/Zweigstelle hochladen"
|
||||
Click="Menu_Export_UploadSeason_Click"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Massenaktion">
|
||||
<MenuItem x:Name="Menu_BulkAction_SetAttribute" Header="Attribut auf Lieferungen aus Filtern setzen...">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="Menu_BulkAction_AddModifier" Header="Zu-/Abschlag auf Lieferungen aus Filtern hinzufügen...">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="Menu_BulkAction_RemoveModifier" Header="Zu-/Abschlag auf Lieferungen aus Filtern entfernen...">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" FontSize="16" Text=""/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Einstellungen">
|
||||
<MenuItem x:Name="Menu_Settings_EnableFreeEditing" Header="Freie Bearbeitung aktivieren"
|
||||
IsCheckable="True" Checked="Menu_Settings_EnableFreeEditing_Checked" Unchecked="Menu_Settings_EnableFreeEditing_Unchecked"/>
|
||||
@@ -222,6 +251,7 @@
|
||||
<Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/>
|
||||
<Bold>Handwiegung</Bold>: handw[iegung], !Handw[iegung] (alle ohne Handwiegung)<LineBreak/>
|
||||
<Bold>Handlese</Bold>: Handl[ese], !handl[ese] (alle ohne Handlese)<LineBreak/>
|
||||
<Bold>Anlieferung</Bold>: Plane[nwagen]/Kipp[er], !plane[nwagen]/!kipp[er], Lesew[agen], !lesew[agen], kiste[n], !kiste[n]<LineBreak/>
|
||||
<Bold>Gebunden</Bold>: geb[unden], ungeb[unden], !geb[unden], !ungeb[unden]<LineBreak/>
|
||||
<Bold>Gerebelt</Bold>: gerebelt, !Gerebelt (nicht gerebelt gewogen)<LineBreak/>
|
||||
<Bold>Freitext</Bold>: z.B. Lieferscheinnummern, Anmerkung, "quw" (sucht nach dem Text "quw")
|
||||
@@ -265,13 +295,18 @@
|
||||
</Style>
|
||||
</DataGridTextColumn.CellStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
|
||||
<DataGridTextColumn Header="Sorte" Binding="{Binding FilteredVaributeString}" Width="60">
|
||||
<DataGridTextColumn.CellStyle>
|
||||
<Style>
|
||||
<Setter Property="TextBlock.Foreground" Value="{Binding FilteredColor}"/>
|
||||
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</DataGridTextColumn.CellStyle>
|
||||
<DataGridTextColumn.ElementStyle>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
|
||||
</Style>
|
||||
</DataGridTextColumn.ElementStyle>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Header="Menge" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
|
||||
<DataGridTextColumn.CellStyle>
|
||||
@@ -290,6 +325,7 @@
|
||||
<DataGridTextColumn Header="LsNr." Binding="{Binding LsNr}" Width="120"/>
|
||||
<DataGridTextColumn Header="Mitglied" Binding="{Binding Member.AdministrativeName}" Width="180"/>
|
||||
<DataGridTextColumn Header="Zu-/Abschläge" Binding="{Binding FilteredModifiersString}" Width="150"/>
|
||||
<DataGridTextColumn Header="Kommentar" Binding="{Binding CommentsString}" Width="150"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
@@ -363,8 +399,9 @@
|
||||
<Grid Grid.Row="1" Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="0.625*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="0.875*"/>
|
||||
<RowDefinition Height="0.875*"/>
|
||||
<RowDefinition Height="0.25*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -398,7 +435,7 @@
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Margin="5,5,5,5">
|
||||
<GroupBox Header="Lieferung" Grid.Column="0" Grid.Row="1" Grid.RowSpan="3" Margin="5,5,5,5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
@@ -557,7 +594,7 @@
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="Sonstiges" Grid.Column="1" Grid.Row="3" Margin="5,5,5,10">
|
||||
<GroupBox Header="Sonstiges" Grid.Column="1" Grid.Row="3" Grid.RowSpan="2" Margin="5,5,5,10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
@@ -586,18 +623,31 @@
|
||||
TextChanged="TemperatureAcidInput_TextChanged" LostFocus="TemperatureAcidInput_LostFocus"
|
||||
Grid.Column="1" Margin="0,100,10,10" VerticalAlignment="Top"/>
|
||||
|
||||
<CheckBox x:Name="LesewagenInput" IsChecked="{Binding IsLesewagen, Mode=TwoWay}"
|
||||
Content="Lesewagen" Margin="10,75,0,0" Grid.Column="2"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Checked="LesewagenInput_Changed" Unchecked="LesewagenInput_Changed"/>
|
||||
<CheckBox x:Name="HandPickedInput" IsChecked="{Binding IsHandPicked, Mode=TwoWay}"
|
||||
Content="Handlese" Margin="10,105,0,0" Grid.Column="2" IsThreeState="True"
|
||||
Content="Handlese" Margin="10,135,10,0" Grid.Column="0" Grid.ColumnSpan="2" IsThreeState="True"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Checked="HandPickedInput_Changed" Unchecked="HandPickedInput_Changed" Indeterminate="HandPickedInput_Changed"/>
|
||||
|
||||
<RadioButton x:Name="UnloadingDumperInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingDumper, Mode=TwoWay}"
|
||||
Content="Planenw./Kipper" Margin="10,75,0,0" Grid.Column="2"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
|
||||
<RadioButton x:Name="UnloadingPumpedInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingPumped, Mode=TwoWay}"
|
||||
Content="Lesewagen" Margin="10,95,0,0" Grid.Column="2"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
|
||||
<RadioButton x:Name="UnloadingBoxInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingBox, Mode=TwoWay}"
|
||||
Content="Kiste(n)" Margin="10,115,0,0" Grid.Column="2"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
|
||||
<RadioButton x:Name="UnloadingOtherInput" GroupName="Unloading" IsChecked="{Binding IsUnloadingOther, Mode=TwoWay}"
|
||||
Content="Andere/Unbek." Margin="10,135,0,0" Grid.Column="2"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Checked="UnloadingInput_Checked" Unchecked="UnloadingInput_Unchecked"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="Herkunft" Grid.Column="0" Grid.Row="3" Margin="5,5,5,10">
|
||||
<GroupBox Header="Herkunft" Grid.Column="0" Grid.Row="4" Margin="5,5,5,10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user