Compare commits

..

1 Commits

Author SHA1 Message Date
lorenz.stechauner a6e04ab1d2 Printing: Replace WinziPrint with iText
Test / Run tests (push) Failing after 2m18s
2026-02-18 23:02:18 +01:00
172 changed files with 5559 additions and 6662 deletions
-10
View File
@@ -23,16 +23,6 @@ jobs:
echo "No files with BOM found"
exit 0
}
- name: Check for code smells
shell: powershell
run: |
git grep -IEn "\.(Single|First|Min|Max|Any)(OrDefault)?Async\([^)]|^using System.Data.Entity;"
if ( $lastexitcode -ne 1 ) {
exit 1
} else {
echo "No files with code smells found"
exit 0
}
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
- name: Setup NuGet
-1
View File
@@ -7,4 +7,3 @@ Tests/Resources/Sql/Create.sql
*.exe
!WinziPrint.exe
*.sqlite3
*.zip
+1 -193
View File
@@ -2,198 +2,6 @@
Changelog
=========
[v1.0.5.6][v1.0.5.6] (2026-06-25) {#v1.0.5.6}
---------------------------------------------
### Behobene Fehler {#v1.0.5.6-bugfixes}
* Beim Speichern von Auszahlungsvarianten wurden Einstellungen für Abwertungen nicht immer übernommen. (15de07a4c3)
* Im Stammdaten-Fenster (`BaseDataWindow`) war es nicht möglich Zweigstellen, Attribute, oder Bewirtschaftungsarten zu bearbeiten/erstellen/löschen. (1261be001c)
### Sonstiges {#v1.0.5.6-misc}
* Mandant `Seewinkel` hinzugefügt. (1ad97a78ff)
[v1.0.5.6]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.5.6
[v1.0.5.5][v1.0.5.5] (2026-06-24) {#v1.0.5.5}
---------------------------------------------
### Behobene Fehler {#v1.0.5.5-bugfixes}
* Beim Exportieren der Überweisungsdaten (EBICS) im Auszahlungsvarianten-Fenster (`PaymentVariantsWindow`) kam es zu einem Absturz. (3f65b2350b)
* Einige Einstellungen einer Auszahlungsvariante wurden vom Auszahlungsvariante-Fenster (`ChartWindow`) beim Speichern überschrieben. (f32ff945ec)
### Sonstiges {#v1.0.5.5-misc}
* Traubengutschriften mit negativem Betrag sind nun wieder möglich. (ba3f66591e)
* Abhängigkeiten aktualisiert. (4e027a9add)
[v1.0.5.5]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.5.5
[v1.0.5.4][v1.0.5.4] (2026-06-22) {#v1.0.5.4}
---------------------------------------------
### Behobene Fehler {#v1.0.5.4-bugfixes}
* Seit [v1.0.5.0](#v1.0.5.0) (2026-04-08) war es bei nicht-aktiven Mitgliedern nicht möglich Lieferungen oder Flächenbindungen anzusehen. (d0ce377d92)
### Sonstiges {#v1.0.5.4-misc}
* Bei der Installation wird nun auch der Quellcode mit ausgeliefert. (5db5876905)
* Abhängigkeiten aktualisiert. (b4ab8349b4, f8b3147c72, 7a993585c3)
[v1.0.5.4]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.5.4
[v1.0.5.3][v1.0.5.3] (2026-04-29) {#v1.0.5.3}
---------------------------------------------
### Behobene Fehler {#v1.0.5.3-bugfixes}
* Manche Anlieferungsbestätigungen und Traubengutschriften konnten nicht angezeigt werden. Dies lag an einem internen Fehler, der manchmal bei Zu-/Abschlägen auftrat. (72155fc54e, 9dfe71d6d0)
[v1.0.5.3]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.5.3
[v1.0.5.2][v1.0.5.2] (2026-04-24) {#v1.0.5.2}
---------------------------------------------
### Behobene Fehler {#v1.0.5.2-bugfixes}
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) sind Attribut/Bewirtschaftungsart in der Liste für Teil-Lieferungen nicht angezeigt worden. (5c2fae1855)
### Sonstiges {#v1.0.5.2-misc}
* Abhängigkeiten aktualisiert. (2915f08a6b)
[v1.0.5.2]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.5.2
[v1.0.5.1][v1.0.5.1] (2026-04-20) {#v1.0.5.1}
---------------------------------------------
### Behobene Fehler {#v1.0.5.1-bugfixes}
* Zu-/Abschläge mit absolutem Wert (z.B. `€/kg`) wurden bei der Auszahlung nie mit der Menge multipliziert. (ea2b6db1fc)
* Massenaktionen im Lieferungen-Fenster (`DeliveryAdminWindow`) haben für Zu-/Abschläge nicht funktioniert. (4eac8cd629, cf48f005e2)
### Sonstiges {#v1.0.5.1-misc}
* Behebung von Kleinigkeiten im Layout bei Dokumenten (`CreditNote`, `PaymentVariantSummary`). (7edf395497, a852dbb242)
* Zuordnung von zwei Gemeinden vom Weinbaugebiet Neusiedlersee (`WLNS`) zu Leithaberg (`WLLB`). (2b3c293730)
* Abhängigkeiten aktualisiert. (20fd5d5826, 7861a15e2e)
[v1.0.5.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.5.1
[v1.0.5.0][v1.0.5.0] (2026-04-08) {#v1.0.5.0}
---------------------------------------------
### Neue Funktionen {#v1.0.5.0-features}
* Flächenbindungen werden nun in _Verträgen_ zusammengefasst um bessere Übersicht und historische Nachvollziehbarkeit zu gewährleisten. ([#77][i77])
### Sonstiges {#v1.0.5.0-misc}
* Erhebliche Verbesserung der Leistung/Geschwindigkeit. ([#79][i79], e5e5e10cd7, 22fbb0772f)
* Am Stammdatenblatt (`MemberDataSheet`) wird nun als Datum das Datum der letzten Bearbeitung angegeben. (0a9c800116)
* Abhängigkeiten aktualisiert. (07d93dd384, ce1a55df86, cc6e31a006)
[v1.0.5.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.5.0
[i77]: https://git.necronda.net/winzer/elwig/issues/77
[i79]: https://git.necronda.net/winzer/elwig/issues/79
[v1.0.4.1][v1.0.4.1] (2026-03-27) {#v1.0.4.1}
---------------------------------------------
### Behobene Fehler {#v1.0.4.1-bugfixes}
* In der Anlieferungsbestätigung (`DeliveryConfirmation`) war in einer Spalte der Text nicht rechtsbündig. (0aefab5d63)
* Die zweite Zeile des Absenders wurde nicht mehr abgedruckt und der Briefkopf (`Letterhead`) beinhaltete zu viele Informationen. (3c52156b7e)
### Sonstiges {#v1.0.4.1-misc}
* Evtl. wurden bei Änderungen in der Datenbank unnötigerweise einige Daten in Elwig doppelt angefordert. (cc018ded10)
* Massenaktionen im Lieferungen-Fenster (`DeliveryAdminWindow`) sind nun nicht mehr während dem Bearbeiten oder Erstellen möglich. (b1075d1e69)
* Das Datum auf Lieferscheinen (`DeliveryNote`) ist nun statt des heutigen, das Datum der letzten Änderung der Lieferung. (982a6616e1)
* Sollte es im Rundschreiben-Fenster (`MailWindow`) zu einem Fehler während dem Bereinigen der Dokumenten kommen wird dieser ignoriert. (ae0a082421)
* Abhängigkeiten aktualisiert. (ddfc86197d, cdb4b0a2bd)
[v1.0.4.1]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.4.1
[v1.0.4.0][v1.0.4.0] (2026-03-16) {#v1.0.4.0}
---------------------------------------------
### Neue Funktionen {#v1.0.4.0-features}
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) gibt es nun über die Menü-Leiste die Möglichkeit _Massenaktionen_ für mehrere Lieferungen durchzuführen (z.B. Attribut oder Zu-/Abschlag setzen). ([#78][i78])
### Behobene Fehler {#v1.0.4.0-bugfixes}
* Waagen mit Netzwerkschnittstelle versuchen die Verbindung nun wieder neu aufzubauen, sollte diese unterbrochen werden. ([#74][i74])
### Sonstiges {#v1.0.4.0-misc}
* Die bisher verwendeten Programm-Bibliotheken zum Erstellen von PDF-Dokumente wurden vollständig durch [iText](https://itextpdf.com/) ersetzt.
Das ermöglicht ein schnelleres, effizienteres, und stabileres Erstellen der PDF-Dokumente.
Außerdem konnte so die Größe der Installations-Datei von ~138 MB auf ~98 MB um ca. 30 % reduziert werden. (d8c967b2f2, 8054a024f4)
* Abhängigkeiten aktualisiert. (af73226c90, f4fddd111f)
[v1.0.4.0]: https://git.necronda.net/winzer/elwig/releases/tag/v1.0.4.0
[i74]: https://git.necronda.net/winzer/elwig/issues/74
[i78]: https://git.necronda.net/winzer/elwig/issues/78
[v1.0.3.4][v1.0.3.4] (2026-02-19) {#v1.0.3.4}
---------------------------------------------
### 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}
---------------------------------------------
@@ -495,7 +303,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 Tooltip für Gewicht/Gradation mit korrektem Layout angezeigt. (e9f389b885)
* Im Lieferungen-Fenster (`DeliveryAdminWindow`) und im Mitglieder-Fenster (`MemberAdminWindow`) wird der Tool-Tip 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}
+27 -25
View File
@@ -5,9 +5,7 @@ using Elwig.Helpers.Export;
using Elwig.Helpers.Printing;
using Elwig.Helpers.Weighing;
using Elwig.Models.Entities;
using Elwig.Services;
using Elwig.Windows;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data;
@@ -34,6 +32,9 @@ namespace 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);
@@ -92,11 +93,11 @@ namespace Elwig {
try {
await AppDbUpdater.CheckDb();
} catch (Exception exc) {
} catch (Exception e) {
if (Config.UpdateUrl != null && Utils.HasInternetConnectivity()) {
await CheckForUpdates();
}
InteractionService.ShowException("Fehlerhafte Datenbank", "Fehlerhafte Datenbank", exc);
MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown();
return;
}
@@ -106,13 +107,11 @@ namespace Elwig {
Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = [];
using (var ctx = new AppDbContext()) {
branches = ctx.FetchBranches()
.ToDictionaryAsync(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr))
.GetAwaiter().GetResult();
branches = ctx.Branches.ToDictionary(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
try {
Client = new(ctx);
} catch (Exception exc) {
InteractionService.ShowException("Fehler", "Fehler beim Laden der Mandantendaten", exc);
} catch (Exception e) {
MessageBox.Show($"Fehler beim Laden der Mandantendaten:\n\n{e.Message}", "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown();
return;
}
@@ -124,6 +123,7 @@ namespace Elwig {
return Task.CompletedTask;
});
Utils.RunBackground("HTML Initialization", () => Html.Init());
Utils.RunBackground("PDF Initialization", () => Pdf.Init());
Utils.RunBackground("JSON Schema Initialization", BillingData.Init);
@@ -146,17 +146,18 @@ namespace Elwig {
foreach (var s in Config.Scales) {
try {
list.Add(Scale.FromConfig(s));
} catch (Exception exc) {
} catch (Exception e) {
list.Add(new InvalidScale(s.Id));
if (s.Required)
InteractionService.ShowException("Waagenfehler", $"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden", exc);
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
Scales = list;
if (Config.Branch != null) {
if (!branches.ContainsKey(Config.Branch.ToLower())) {
InteractionService.ShowError("Ungültige Zweigstelle", "Ungültige Zweigstelle in Konfigurationsdatei!");
MessageBox.Show("Ungültige Zweigstelle in Konfigurationsdatei!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown();
} else {
SetBranch(branches[Config.Branch.ToLower()]);
@@ -164,7 +165,7 @@ namespace Elwig {
} else if (branches.Count == 1) {
SetBranch(branches.First().Value);
} else {
InteractionService.ShowError("Ungültige Zweigstelle", "Erkennen der lokalen Zweigstelle nicht möglich!");
MessageBox.Show("Erkennen der lokalen Zweigstelle nicht möglich!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown();
}
@@ -201,7 +202,6 @@ namespace Elwig {
BranchName = entry.Item2;
BranchPlz = entry.Item3;
BranchLocation = entry.Item4?
.Split(",")[0]
.Split(" in ")[0]
.Split(" im ")[0]
.Split(" an ")[0]
@@ -222,8 +222,7 @@ namespace Elwig {
MainDispatcher.Invoke(() => {
foreach (Window w in CurrentApp.Windows) {
if (w is not ContextWindow c) continue;
MainDispatcher.Invoke(c.HintContextChange);
MainDispatcher.BeginInvoke(c.TryContextReload);
MainDispatcher.BeginInvoke(c.HintContextChange);
}
});
}
@@ -254,13 +253,14 @@ namespace Elwig {
if (Scales[i] is InvalidScale) {
try {
Scales[i] = Scale.FromConfig(s);
InteractionService.ShowInformation($"Waage {s.Id}", $"Verbindung zu Waage {s.Id} wieder hergestellt!");
} catch (Exception exc) {
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);
InteractionService.ShowException("Waagenfehler", $"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden", exc);
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) {
InteractionService.ShowInformation($"Waage {s.Id}", $"Verbindung zu Waage {s.Id} wieder hergestellt!");
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
}
@@ -271,7 +271,7 @@ namespace Elwig {
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) {
InteractionService.ShowWarning($"Waagen {s.Id}", $"Verbindung zu Waage {s.Id} unterbrochen!");
MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning);
if (Scales[i] is ICommandScale) {
try {
Scales[i].Dispose();
@@ -306,9 +306,11 @@ namespace Elwig {
});
} else if (showAlert) {
if (latest == null) {
InteractionService.ShowError("Nach Updates suchen", "Informationen konnten nicht abgerufen werden!");
MessageBox.Show("Informationen konnten nicht abgerufen werden!", "Nach Updates suchen",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
InteractionService.ShowInformation("Nach Updates suchen", $"Elwig ist auf dem aktuellsten Stand! (Version: {latest.Value.Version})");
MessageBox.Show($"Elwig ist auf dem aktuellsten Stand! (Version: {latest.Value.Version})", "Nach Updates suchen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
}
@@ -318,11 +320,11 @@ namespace Elwig {
await Task.Run(async () => {
await Database.Import(filename);
});
InteractionService.ShowInformation("Datenbank ersetzen", "Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!");
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) {
InteractionService.ShowException("Datenbank ersetzen", "Fehler beim Ersetzen", exc);
MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
+2 -11
View File
@@ -41,21 +41,13 @@ namespace Elwig.Controls {
incButton!.Click += IncrementButton_Click;
decButton!.Click += DecrementButton_Click;
base.OnApplyTemplate();
UpdateButtons();
}
private void UpdateButtons() {
var incButton = GetTemplateChild("IncrementButton") as RepeatButton;
var decButton = GetTemplateChild("DecrementButton") as RepeatButton;
incButton?.IsEnabled = Maximum == null || Value < Maximum;
decButton?.IsEnabled = Minimum == null || Value > Minimum;
}
private void IntegerUpDown_TextChanged(object sender, TextChangedEventArgs evt) {
var idx = CaretIndex;
Text = new string([.. Text.Where(char.IsAsciiDigit).Take(4)]);
Text = new string(Text.Where(char.IsAsciiDigit).Take(4).ToArray());
CaretIndex = idx;
evt.Handled = !((!Minimum.HasValue || Value >= Minimum) && (!Maximum.HasValue || Value <= Maximum));
evt.Handled = !(Value >= Minimum && Value <= Maximum);
if (idx >= 4) {
if (Value < Minimum) {
Value = Minimum;
@@ -64,7 +56,6 @@ namespace Elwig.Controls {
}
CaretIndex = 4;
}
UpdateButtons();
}
private void IntegerUpDown_LostFocus(object sender, RoutedEventArgs evt) {
+4 -9
View File
@@ -1,5 +1,4 @@
using Elwig.Helpers;
using System;
using System.Windows;
using System.Windows.Controls;
@@ -13,27 +12,23 @@ namespace Elwig.Dialogs {
public string OrigYearTo { get; set; }
public string Area { get; set; }
public AreaComModifyDialog(int? yearFrom, int? yearTo, int area, bool delete, bool forceRetroactive = false) {
public AreaComModifyDialog(int? yearFrom, int? yearTo, int area, bool delete) {
Area = $"{area:N0}";
OrigYearFrom = $"{yearFrom}";
OrigYearTo = $"{yearTo}";
InitializeComponent();
Title = delete ? "Flächenbindung löschen" : "Flächenbindung bearbeiten";
RetroactiveInput.Content = delete ? "Rückwirkend löschen" : "Rückwirkend bearbeiten";
forceRetroactive = forceRetroactive || yearFrom.HasValue && yearTo.HasValue && yearFrom.Value == yearTo.Value;
RetroactiveInput.IsEnabled = !forceRetroactive;
if (delete) {
QuestionBlock1.Visibility = Visibility.Hidden;
QuestionBlock2.Visibility = Visibility.Visible;
DescBlock1.Visibility = Visibility.Hidden;
DescBlock2.Visibility = Visibility.Visible;
}
SeasonInput.Minimum = yearFrom.HasValue ? yearFrom + 1 : null;
SeasonInput.Maximum = yearTo.HasValue ? yearTo : null;
if (forceRetroactive || (yearTo.HasValue && yearTo < Utils.CurrentYear) || (yearFrom.HasValue && yearFrom >= Utils.CurrentYear)) {
if ((yearTo.HasValue && yearTo < Utils.CurrentNextSeason) || (yearFrom.HasValue && yearFrom >= Utils.CurrentNextSeason)) {
RetroactiveInput.IsChecked = true;
} else {
SeasonInput.Text = $"{Utils.CurrentYear}";
SeasonInput.Text = $"{Utils.CurrentNextSeason}";
}
}
@@ -53,7 +48,7 @@ namespace Elwig.Dialogs {
DescBlock2.Visibility = Visibility.Hidden;
} else {
SeasonInput.IsEnabled = true;
SeasonInput.Text = $"{Math.Max(SeasonInput.Minimum ?? Utils.CurrentYear, Utils.CurrentYear)}";
SeasonInput.Text = $"{Utils.CurrentNextSeason}";
DescBlock1.Visibility = QuestionBlock1.Visibility;
DescBlock2.Visibility = QuestionBlock2.Visibility;
}
+6 -2
View File
@@ -8,7 +8,7 @@
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Flächenbindungen übertragen" Height="240" Width="450">
Title="Flächenbindungen übertragen" Height="260" Width="450">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
@@ -48,8 +48,12 @@
Minimum="1900" Maximum="9999"
HorizontalAlignment="Center" VerticalAlignment="Top"
TextChanged="SeasonInput_TextChanged"/>
<CheckBox x:Name="CopyYearToInput" Content="Beginn der Laufzeit von Vorgänger übernehmen" Margin="0,80,0,0"
HorizontalAlignment="Center" VerticalAlignment="Top"
Checked="CopyYearToInput_Changed" Unchecked="CopyYearToInput_Changed"
IsChecked="{Binding MaintainYearFrom}"/>
<TextBlock x:Name="DescBlock1" Margin="0,90,0,0" TextAlignment="Center"
<TextBlock x:Name="DescBlock1" Margin="0,105,0,0" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Top">
Die Flächenbindungen beim <Bold>Vorgänger</Bold> sind bis inkl. Saison <Bold><Run x:Name="CancelSeason1"/></Bold> gültig,<LineBreak/>
und werden beim <Bold>Nachfolger</Bold> ab <Run x:Name="DescBlock1Season" Text="inkl. Saison "/><Bold><Run x:Name="TransferSeason"/></Bold> übernommen.
+13 -13
View File
@@ -5,51 +5,51 @@ using System.Windows.Controls;
namespace Elwig.Dialogs {
public partial class AreaComTransferDialog : Window {
public int CancelSeason => SuccessorSeason - 1;
public int SuccessorSeason { get; set; }
public int CancelSeason { get; set; }
public int SuccessorSeason => CancelSeason + 1;
public bool MaintainYearFrom { get; set; }
public string AreaComNum { get; set; }
public string Area { get; set; }
public AreaComTransferDialog(string name, int areaComNum, int area) {
SuccessorSeason = Utils.FollowingSeason;
CancelSeason = Utils.FollowingSeason - 1;
AreaComNum = $"{areaComNum:N0}";
Area = $"{area:N0}";
InitializeComponent();
SeasonInput.Text = $"{SuccessorSeason}";
SeasonInput.Minimum = Utils.CurrentLastSeason;
SeasonInput.Text = $"{CancelSeason}";
Title = $"Aktive Flächenbindungen kündigen - {name}";
QuestionBlock1.Visibility = Visibility.Hidden;
QuestionBlock2.Visibility = Visibility.Visible;
DescBlock1.Visibility = Visibility.Hidden;
DescBlock2.Visibility = Visibility.Visible;
CopyYearToInput.Visibility = Visibility.Hidden;
Height = 240;
SeasonInput.Margin = new(0, 40, 0, 0);
SeasonLabel.Margin = new(0, 40, 100, 0);
}
public AreaComTransferDialog(string name, string successorName, int areaComNum, int area) {
SuccessorSeason = Utils.FollowingSeason;
CancelSeason = Utils.FollowingSeason - 1;
AreaComNum = $"{areaComNum:N0}";
Area = $"{area:N0}";
InitializeComponent();
SeasonInput.Text = $"{SuccessorSeason}";
SeasonInput.Minimum = Utils.CurrentLastSeason;
SeasonInput.Text = $"{CancelSeason}";
Title = $"Aktive Flächenbindungen übertragen - {name} - {successorName}";
InfoBlock.Visibility = Visibility.Hidden;
}
private void SeasonInput_TextChanged(object sender, TextChangedEventArgs evt) {
SuccessorSeason = (int)SeasonInput.Value!;
CancelSeason = (int)SeasonInput.Value!;
CancelSeason1.Text = $"{CancelSeason}";
CancelSeason2.Text = $"{CancelSeason}";
TransferSeason.Text = $"{SuccessorSeason}";
DescBlock1Season.Text = "inkl. Saison ";
TransferSeason.Text = MaintainYearFrom ? "" : $"{SuccessorSeason}";
DescBlock1Season.Text = MaintainYearFrom ? "dem originalen Beginn der FB" : "inkl. Saison ";
}
private void CopyYearToInput_Changed(object sender, RoutedEventArgs evt) {
TransferSeason.Text = $"{SuccessorSeason}";
DescBlock1Season.Text = "inkl. Saison ";
TransferSeason.Text = MaintainYearFrom ? "" : $"{SuccessorSeason}";
DescBlock1Season.Text = MaintainYearFrom ? "dem originalen Beginn der FB" : "inkl. Saison ";
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
@@ -44,10 +44,15 @@ namespace Elwig.Dialogs {
}
protected override async Task OnRenewContext(AppDbContext ctx) {
ControlUtils.RenewItemsSource(MemberInput, await ctx.FetchMembers().ToListAsync());
ControlUtils.RenewItemsSource(MemberInput, await ctx.Members
.Where(m => m.IsActive)
.OrderBy(m => m.Name)
.ThenBy(m => m.GivenName)
.ToListAsync());
ControlUtils.RenewItemsSource(DeliveryInput, await ctx.Deliveries
.Where(d => d.DateString == $"{_delivery.Date:yyyy-MM-dd}" && d.ZwstId == _delivery.ZwstId)
.OrderBy(d => d.LsNr)
.Include(d => d.Member)
.Include(d => d.Parts)
.ToListAsync());
if (DeliveryInput.SelectedItem == null)
+1 -2
View File
@@ -1,5 +1,4 @@
using Elwig.Helpers;
using Elwig.Services;
using System;
using System.Diagnostics;
using System.IO;
@@ -52,7 +51,7 @@ namespace Elwig.Dialogs {
File.Delete(fileName);
return;
} catch (Exception exc) {
InteractionService.ShowException(exc);
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Process.Start(fileName);
DialogResult = true;
+88 -257
View File
@@ -1,30 +1,34 @@
using Elwig.Helpers;
using Elwig.Models;
using Elwig.Models.Entities;
using iText.Kernel.Colors;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Action;
using iText.Kernel.Pdf.Canvas;
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 class BusinessDocument : Document {
public abstract class BusinessDocument : Document {
public Member Member;
public MemberHistoryPoint MemberHistory;
public string? Location;
public bool IncludeSender = false;
public bool UseBillingAddress = false;
public bool ShowDateAndLocation = false;
public DateOnly? DateFrom;
protected Table? Aside;
public string Aside;
public BusinessDocument(string title, Member m, bool includeSender = false) : base(title) {
Member = m;
Location = App.BranchLocation;
IncludeSender = includeSender;
var c = App.Client;
Header = $"<div class='name'>{c.Name}</div><div class='suffix'>{c.NameSuffix}</div><div class='type'>{c.NameTypeFull}</div>";
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>";
}
public string Address {
get {
@@ -34,124 +38,11 @@ namespace Elwig.Documents {
}
}
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;
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(string text, int colspan = 1, bool isName = false) {
return NewAsideCell(new KernedParagraph(text, 10), colspan, isName);
}
public BusinessDocument(string title, Member m, DateOnly? dateFrom, bool includeSender = false) :
base(title) {
Member = m;
MemberHistory = new(m.Shares, m.SharesRed, m.SharesWhite, m.SharesDormant);
Location = App.BranchLocation;
IncludeSender = includeSender;
DateFrom = dateFrom;
}
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf);
var uid = new KernedParagraph(Member.UstIdNr ?? "-", 10);
if (!Member.IsBuchführend) uid.Add(Normal(" ")).Add(Italic("(pauschaliert)"));
Aside = new Table(ColsMM(22.5, 42.5))
.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 void RenderAddress(Canvas canvas, Rectangle pageSize) {
canvas.Add(new Div()
.SetFixedPositionMM(25, 50, 80, 45, pageSize)
.SetFont(NF)
.Add(new KernedParagraph(IncludeSender ? $"{App.Client.Sender1}\n{App.Client.Sender2}" : "", 8).SetMargins(8, 0, 8, 0).SetHeight(16.0625f))
.Add(new KernedParagraph(Address, 12).SetMargin(0).SetHeight(12 * 5)));
}
protected static void RenderAside(Table aside, Canvas canvas, Rectangle pageSize) {
canvas.Add(new Div()
.SetFixedPositionMM(125, 50, 65, 50, pageSize)
.Add(aside));
}
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.RenderBody(doc, pdf);
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
RenderAddress(canvas, pageSize);
// aside
if (Aside != null) {
RenderAside(Aside, canvas, pageSize);
}
}
doc.Add(new KernedParagraph(ShowDateAndLocation ? $"{Location}, am {DateFrom ?? 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) {
public static string PrintSortenaufteilung(List<MemberStat> stats) {
List<string> discrs = [""];
List<string> names = ["ohne Attr./Bewirt."];
List<string> bucketAttrs = [
@@ -167,136 +58,82 @@ namespace Elwig.Documents {
List<double> cols = [40];
cols.AddRange(names.Select(_ => 125.0 / names.Count));
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));
string tbl = GetColGroup(cols);
tbl += "<thead><tr>" +
$"<th><b>Sortenaufteilung</b> [kg]</th>" +
string.Join("", names.Select(c => $"<th>{c}</th>")) +
"</tr></thead>";
foreach (var g in stats.GroupBy(b => b.Variety).OrderBy(b => b.Key)) {
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();
tbl.AddCell(NewWeightsTh(g.Key));
foreach (var v in vals)
tbl.AddCell(NewWeightsTd(v == 0 ? "-" : $"{v:N0}"));
tbl.AddCell(NewWeightsTd($"{dict.Values.Sum():N0}"));
}
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.AddCell(NewWeightsTd("", true));
foreach (var v in totals)
tbl.AddCell(NewWeightsTd($"{v:N0}", true));
tbl.AddCell(NewWeightsTd($"{totalDict.Values.Sum():N0}", true));
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>";
return tbl;
return "<table class=\"sortenaufteilung small number cohere\">" + tbl + "</table>";
}
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(
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, bool isTiny = false
bool isGa = false, bool showPayment = false, bool showArea = false
) {
totalDelivery ??= delivery;
payment ??= delivery;
if (showArea) {
return [
NewBucketTd(area == null ? "" : $"{area:N0}", isTiny: isTiny),
NewBucketTd($"{obligation:N0}", isTiny: isTiny),
NewBucketTd($"{right:N0}", isTiny: isTiny),
];
return $"<td>{(area == null ? "" : $"{area:N0}")}</td>" +
$"<td>{obligation:N0}</td>" +
$"<td>{right: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),
];
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>";
}
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);
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 Table NewBucketTable(
Season season, Dictionary<string, MemberBucket> buckets, int deliveredWeightRed, int deliveredWeightWhite,
public string PrintBucketTable(
Season season, Dictionary<string, MemberBucket> buckets,
bool includeDelivery = true, bool includePayment = false,
bool isTiny = false, IEnumerable<string>? filter = null
) {
includePayment = includePayment && includeDelivery;
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));
}
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 mBuckets = buckets
.Where(b => ((!includeDelivery && b.Value.Area > 0) ||
@@ -318,36 +155,30 @@ namespace Elwig.Documents {
.Where(b => !fbVars.Contains(b.Key))
.OrderBy(b => b.Value.Name);
if (MemberHistory.Shares != 0 || (MemberHistory.SharesRed == 0 && MemberHistory.SharesWhite == 0)) {
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny));
tbl.AddCells(FormatRow(MemberHistory.Shares * (season.MinKgPerShare ?? 0), MemberHistory.Shares * (season.MaxKgPerShare ?? 0),
deliveredWeightRed + deliveredWeightWhite, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
}
if (MemberHistory.SharesRed != 0 || MemberHistory.SharesWhite != 0) {
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA (rot)", isTiny: isTiny));
tbl.AddCells(FormatRow(MemberHistory.SharesRed * (season.MinKgPerShareRed ?? season.MinKgPerShare ?? 0), MemberHistory.SharesRed * (season.MaxKgPerShareRed ?? season.MaxKgPerShare ?? 0),
deliveredWeightRed, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA (weiß)", isTiny: isTiny));
tbl.AddCells(FormatRow(MemberHistory.SharesWhite * (season.MinKgPerShareWhite ?? season.MinKgPerShare ?? 0), MemberHistory.SharesWhite * (season.MaxKgPerShareWhite ?? season.MaxKgPerShare ?? 0),
deliveredWeightWhite, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
}
tbl += "\n<tbody>\n";
tbl += $"<tr><th>Gesamtlieferung lt. gez. GA</th>{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>";
if (fbs.Any()) {
tbl.AddCell(NewBucketSubHdr("Flächenbindungen" + (vtr.Any() ? " (inkl. Verträge)" : "") + ":", includePayment ? 8 : 7, isTiny: isTiny));
tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
$"Flächenbindungen{(vtr.Any() ? " (inkl. Verträge)" : "")}:</th></tr>";
foreach (var (id, b) in fbs) {
tbl.AddCell(NewBucketTh(b.Name, isTiny: isTiny)).AddCells(FormatRow(b, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}</tr>";
}
}
if (vtr.Any()) {
tbl.AddCell(NewBucketSubHdr("Verträge:", includePayment ? 8 : 7, isTiny: isTiny));
tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
"Verträge:</th></tr>";
foreach (var (id, b) in vtr) {
tbl.AddCell(NewBucketTh(b.Name, isTiny: isTiny)).AddCells(FormatRow(b, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}</tr>";
}
}
tbl += "\n</tbody>\n";
return tbl;
return $"<table class=\"buckets {(isTiny ? "tiny" : "small")} number cohere\">\n" + tbl + "\n</table>";
}
}
}
+21
View File
@@ -0,0 +1,21 @@
@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()
+150
View File
@@ -0,0 +1,150 @@
.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;
}
+7
View File
@@ -0,0 +1,7 @@
using Elwig.Models.Entities;
namespace Elwig.Documents {
public class BusinessLetter : BusinessDocument {
public BusinessLetter(string title, Member m) : base(title, m) { }
}
}
+9
View File
@@ -0,0 +1,9 @@
@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>
+46 -297
View File
@@ -1,82 +1,51 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
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 Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Documents {
public class CreditNote : BusinessDocument {
public new static string Name => "Traubengutschrift";
public PaymentMember Payment;
public PaymentMember? Payment;
public Credit? Credit;
public CreditNoteDeliveryData Data;
public string? Text;
public string CurrencySymbol;
public int Precision;
public string? MemberModifier;
public List<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries;
public string MemberModifier;
public IEnumerable<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries;
public decimal MemberTotalUnderDelivery;
public int MemberAutoBusinessShares;
public decimal MemberAutoBusinessSharesAmount;
public PaymentCustom? CustomPayment;
protected bool ConsiderContractPenalties;
protected bool ConsiderTotalPenalty;
protected bool ConsiderAutoBusinessShares;
protected bool ConsiderCustomModifiers;
private CreditNoteDeliveryData? _data;
private Dictionary<string, UnderDelivery>? _underDeliveries;
public CreditNote(PaymentMember p, DateOnly? dateFrom, BillingData? billingData = null, CreditNoteDeliveryData? data = null, Dictionary<string, UnderDelivery>? underDeliveries = null) :
base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.FullName)} {p.Variant.Name}", p.Member, dateFrom) {
public CreditNote(
AppDbContext ctx,
PaymentMember p,
CreditNoteDeliveryData data,
bool considerContractPenalties,
bool considerTotalPenalty,
bool considerAutoBusinessShares,
bool considerCustomModifiers,
Dictionary<string, UnderDelivery>? underDeliveries = null
) :
base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.FullName)} {p.Variant.Name}", p.Member) {
UseBillingAddress = true;
ShowDateAndLocation = true;
Data = data;
Payment = p;
Credit = p.Credit;
Text = App.Client.TextCreditNote;
DocumentId = $"Tr.-Gutschr. " + (Credit != null ? $"{Credit.Year}/{Credit.TgNr:000}" : Payment.MgNr);
IsPreview = Credit == null;
_data = data;
_underDeliveries = underDeliveries;
CurrencySymbol = Payment.Variant.Season.Currency.Symbol ?? Payment.Variant.Season.Currency.Code;
Precision = Payment.Variant.Season.Precision;
billingData ??= BillingData.FromJson(Payment.Variant.Data);
ConsiderContractPenalties = billingData.ConsiderContractPenalties;
ConsiderTotalPenalty = billingData.ConsiderTotalPenalty;
ConsiderAutoBusinessShares = billingData.ConsiderAutoBusinessShares;
ConsiderCustomModifiers = billingData.ConsiderCustomModifiers;
IsPreview = Payment == null || Credit == null;
var season = p.Variant.Season;
if (considerCustomModifiers) {
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
}
public static async Task<CreditNote> Initialize(int year, int avnr, int mgnr, DateOnly? dateFrom, BillingData? billingData = null, CreditNoteDeliveryData? data = null, Dictionary<string, UnderDelivery>? underDeliveries = null) {
using var ctx = new AppDbContext();
var p = await ctx.MemberPayments
.Where(p => p.Year == year && p.AvNr == avnr && p.MgNr == mgnr)
.SingleAsync();
return new CreditNote(p, dateFrom, billingData, data, underDeliveries);
}
protected override async Task LoadData(AppDbContext ctx) {
await base.LoadData(ctx);
var season = Payment.Variant.Season;
if (ConsiderCustomModifiers) {
CustomPayment = await ctx.CustomPayments.FindAsync(Payment.Year, Payment.MgNr);
}
_data ??= (await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, Payment.Year, Payment.AvNr))[Member.MgNr];
_underDeliveries ??= await ctx.GetMemberUnderDelivery(Payment.Year, Member.MgNr);
var mod = App.Client.IsMatzen ? await ctx.FetchModifiers(season.Year).Where(m => m.Name.StartsWith("Treue")).FirstOrDefaultAsync() : null;
var mod = App.Client.IsMatzen ? ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefault() : null;
if (CustomPayment?.ModComment != null) {
MemberModifier = CustomPayment.ModComment;
} else if (mod != null) {
@@ -84,36 +53,38 @@ namespace Elwig.Documents {
} 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;
Precision = season.Precision;
if (ConsiderTotalPenalty) {
var weights = _data.Rows.Select(r => new {r.Type, Weight = r.Buckets.Sum(b => b.Value)}).GroupBy(r => r.Type).ToDictionary(r => r.Key, g => g.Sum(r => r.Weight));
var red = weights.GetValueOrDefault("R", 0);
var white = weights.GetValueOrDefault("W", 0);
var total = red + white;
var underDeliveryTotal = total - Member.Shares * (season.MinKgPerShare ?? 0);
var underDeliveryRed = red - Member.SharesRed * (season.MinKgPerShareRed ?? season.MinKgPerShare ?? 0);
var underDeliveryWhite = white - Member.SharesWhite * (season.MinKgPerShareWhite ?? season.MinKgPerShare ?? 0);
MemberTotalUnderDelivery =
(underDeliveryTotal < 0 ? underDeliveryTotal * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerShareAmount * Math.Floor(-(decimal)underDeliveryTotal / (season.MinKgPerShare ?? 0)) ?? 0) : 0) +
(underDeliveryRed < 0 ? underDeliveryRed * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerShareAmount * Math.Floor(-(decimal)underDeliveryRed / (season.MinKgPerShareRed ?? season.MinKgPerShare ?? 0)) ?? 0) : 0) +
(underDeliveryWhite < 0 ? underDeliveryWhite * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerShareAmount * Math.Floor(-(decimal)underDeliveryWhite / (season.MinKgPerShareWhite ?? season.MinKgPerShare ?? 0)) ?? 0) : 0);
if (considerTotalPenalty) {
var total = data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value);
var totalUnderDelivery = total - p.Member.BusinessShares * season.MinKgPerBusinessShare;
MemberTotalUnderDelivery = totalUnderDelivery < 0 ? totalUnderDelivery * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerBsAmount * Math.Floor(-(decimal)totalUnderDelivery / season.MinKgPerBusinessShare) ?? 0) : 0;
if (total == 0)
MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerShareNone * (Member.Shares + Member.SharesRed + Member.SharesWhite) ?? 0);
MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerBsNone * p.Member.BusinessShares ?? 0);
}
if (ConsiderAutoBusinessShares) {
if (considerAutoBusinessShares) {
var fromDate = $"{season.Year}-01-01";
var toDate = $"{season.Year}-12-31";
MemberAutoBusinessShares = await ctx.MemberHistory
.Where(h => h.ToMgNr == Member.MgNr && h.Reason == "auto")
MemberAutoBusinessShares = ctx.MemberHistory
.Where(h => h.MgNr == p.Member.MgNr && h.Type == "auto")
.Where(h => h.DateString.CompareTo(fromDate) >= 0 && h.DateString.CompareTo(toDate) <= 0)
.SumAsync(h => h.Shares);
.Sum(h => h.BusinessShares);
MemberAutoBusinessSharesAmount = MemberAutoBusinessShares * (-season.BusinessShareValue ?? 0);
}
if (ConsiderContractPenalties) {
var varieties = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v);
var attributes = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.AttrId, a => a);
var comTypes = await ctx.AreaCommitmentTypes.ToDictionaryAsync(t => t.VtrgId, t => t);
MemberUnderDeliveries = _underDeliveries?
if (considerContractPenalties) {
var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t);
MemberUnderDeliveries = underDeliveries?
.OrderBy(u => u.Key)
.Select(u => (
varieties[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""),
@@ -125,226 +96,4 @@ namespace Elwig.Documents {
.ToList();
}
}
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf);
Aside?.AddCell(NewAsideCell("Gutschrift", 2))
.AddCell(NewAsideCell("TG-Nr.:", isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-"))
.AddCell(NewAsideCell("Datum:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}"))
.AddCell(NewAsideCell("Überw. am:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}"));
}
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
if (_data == null) throw new Exception("Call LoadData before RenderBody");
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 {
var idx = i - (rows - p.Modifiers.Length);
if (idx >= 0 && idx < p.Modifiers.Length) {
sub.AddCell(NewCell(colspan: 2))
.AddCell(NewTd(p.Modifiers[idx], 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).SetPaddingLeft(0).SetPaddingRight(0))
.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;
}
}
}
}}
+197
View File
@@ -0,0 +1,197 @@
@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&shy;lenden Pönalen werden mit
@Model.Payment?.Variant.Season.Precision Nach&shy;komma&shy;stellen berechnent,
erst das Ergebnis wird kauf&shy;männisch auf 2 Nach&shy;komma&shy;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>
+48
View File
@@ -0,0 +1,48 @@
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;
}
+3 -50
View File
@@ -1,9 +1,5 @@
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 {
@@ -11,58 +7,15 @@ namespace Elwig.Documents {
public new static string Name => "Anmeldeliste";
public string Filter;
public List<DeliveryAncmtListRow> Announcements;
public IEnumerable<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;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
@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>
+13
View File
@@ -0,0 +1,13 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-bottom: 2mm;
}
h2 {
text-align: center;
font-size: 14pt;
margin-top: 2mm;
}
+10 -157
View File
@@ -1,176 +1,29 @@
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 Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Documents {
public class DeliveryConfirmation : BusinessDocument {
public new static string Name => "Anlieferungsbestätigung";
private readonly int _year;
public Season? Season;
public int MemberDeliveredWeightRed;
public int MemberDeliveredWeightWhite;
public DeliveryConfirmationDeliveryData? Data;
public Season Season;
public DeliveryConfirmationDeliveryData Data;
public string? Text = App.Client.TextDeliveryConfirmation;
public Dictionary<string, MemberBucket> MemberBuckets = [];
public List<MemberStat> MemberStats = [];
public Dictionary<string, MemberBucket> MemberBuckets;
public List<MemberStat> MemberStats;
public DeliveryConfirmation(int year, Member m, DateOnly? dateFrom, DeliveryConfirmationDeliveryData? data = null) :
base($"{Name} {year}", m, dateFrom) {
_year = year;
public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationDeliveryData data) :
base($"{Name} {year}", m) {
Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season");
ShowDateAndLocation = true;
UseBillingAddress = true;
DocumentId = $"Anl.-Best. {_year}/{m.MgNr}";
DocumentId = $"Anl.-Best. {Season.Year}/{m.MgNr}";
Data = data;
}
protected override async Task LoadData(AppDbContext ctx) {
await base.LoadData(ctx);
Season = await ctx.FetchSeasons(_year).SingleOrDefaultAsync() ?? throw new ArgumentException("Invalid season");
var weights = await ctx.Deliveries
.Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr)
.SelectMany(d => d.Parts)
.GroupBy(p => p.Variety.Type)
.ToDictionaryAsync(g => g.Key, g => g.Sum(p => p.Weight));
MemberDeliveredWeightRed = weights.GetValueOrDefault("R", 0);
MemberDeliveredWeightWhite = weights.GetValueOrDefault("W", 0);
MemberHistory = await ctx.GetMemberHistory(Season.Year, Member.MgNr);
MemberBuckets = await ctx.GetMemberBuckets(Season.Year, Member.MgNr);
MemberStats = await AppDbContext.GetMemberStats(Season.Year, Member.MgNr);
Data ??= await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, Season.Year, Member);
}
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
if (Data == null) throw new Exception("Call LoadData before BeforeRenderBody");
base.BeforeRenderBody(doc, pdf);
var firstDay = Data.Rows.MinBy(r => r.Date)?.Date;
var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date;
Aside?.AddCell(NewAsideCell("Saison", 2))
.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) {
if (Season == null || Data == null) throw new Exception("Call LoadData before RenderBody");
base.RenderBody(doc, pdf);
doc.Add(NewDeliveryListTable(Data));
doc.Add(NewWeightsTable(MemberStats)
.SetMarginTopMM(10).SetKeepTogether(true));
doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, 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)
.SetPaddingTop(0))
.AddCell(NewCell(colspan: 2));
} else {
sub.AddCell(NewCell(colspan: 2));
var idx = i - (rows - p.Modifiers.Length);
if (idx >= 0 && idx < p.Modifiers.Length) {
sub.AddCell(NewTd(p.Modifiers[idx], 8, colspan: 2)
.SetPaddingTop(0).SetPaddingLeftMM(5));
} else {
sub.AddCell(NewCell(colspan: 2));
}
sub.AddCell(NewCell(colspan: 2));
}
if (i < p.Buckets.Length) {
var bucket = p.Buckets[i];
var pad = i == 0 ? 0.5f : 0;
sub.AddCell(NewTd($"{bucket.Name}:", 8).SetHeight(10).SetPaddingTopMM(pad));
sub.AddCell(NewTd($"{bucket.Value:N0}", right: true).SetPaddingTopMM(pad));
} else {
sub.AddCell(NewCell(colspan: 2));
}
if (i == p.Buckets.Length - 1) {
sub.AddCell(NewTd($"{p.Weight:N0}", right: true).SetPaddingTop(0));
sub.AddCell(NewTd(p.IsNetWeight ? "\u2611" : "\u2610", 7, right: 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;
MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult();
MemberStats = AppDbContext.GetMemberStats(Season.Year, m.MgNr).GetAwaiter().GetResult();
}
}
}
+112
View File
@@ -0,0 +1,112 @@
@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>
+32
View File
@@ -0,0 +1,32 @@
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;
}
+2 -88
View File
@@ -1,9 +1,5 @@
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 {
@@ -11,98 +7,16 @@ namespace Elwig.Documents {
public new static string Name => "Abwertungsliste";
public string Filter;
public List<DeliveryJournalRow> Deliveries;
public IEnumerable<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;
}
}
}
@@ -0,0 +1,104 @@
@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>
@@ -0,0 +1,13 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-bottom: 2mm;
}
h2 {
text-align: center;
font-size: 14pt;
margin-top: 2mm;
}
+2 -74
View File
@@ -1,9 +1,5 @@
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 {
@@ -11,84 +7,16 @@ namespace Elwig.Documents {
public new static string Name => "Lieferjournal";
public string Filter;
public List<DeliveryJournalRow> Deliveries;
public IEnumerable<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;
}
}
}
+87
View File
@@ -0,0 +1,87 @@
@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>
+13
View File
@@ -0,0 +1,13 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-bottom: 2mm;
}
h2 {
text-align: center;
font-size: 14pt;
margin-top: 2mm;
}
+9 -215
View File
@@ -1,16 +1,6 @@
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 Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Elwig.Documents {
public class DeliveryNote : BusinessDocument {
@@ -19,9 +9,7 @@ namespace Elwig.Documents {
public Delivery Delivery;
public string? Text;
public int MemberDeliveredWeightRed;
public int MemberDeliveredWeightWhite;
public Dictionary<string, MemberBucket> MemberBuckets = [];
public Dictionary<string, MemberBucket> MemberBuckets;
// 0 - none
// 1 - GA only
@@ -29,213 +17,19 @@ namespace Elwig.Documents {
// 3 - full
public int DisplayStats = App.Client.ModeDeliveryNoteStats;
public DeliveryNote(Delivery d) :
base($"{Name} Nr. {d.LsNr}", d.Member, DateOnly.FromDateTime(d.ModifiedAt)) {
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;
}
public static async Task<DeliveryNote> Initialize(int year, int did) {
using var ctx = new AppDbContext();
await ctx.WineOrigins.LoadAsync();
var d = await ctx.Deliveries
.Where(d => d.Year == year && d.DId == did)
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
.SingleAsync();
return new DeliveryNote(d);
}
public static async Task<DeliveryNote> Initialize(string lsnr) {
using var ctx = new AppDbContext();
await ctx.WineOrigins.LoadAsync();
var d = await ctx.Deliveries
.Where(d => d.LsNr == lsnr)
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
.SingleAsync();
return new DeliveryNote(d);
}
protected override async Task LoadData(AppDbContext ctx) {
await base.LoadData(ctx);
var weights = await ctx.DeliveryParts
.Where(d => d.Year == Delivery.Year && d.Delivery.MgNr == Member.MgNr)
.GroupBy(p => p.Variety.Type)
.ToDictionaryAsync(g => g.Key, g => g.Sum(p => p.Weight));
MemberDeliveredWeightRed = weights.GetValueOrDefault("R", 0);
MemberDeliveredWeightWhite = weights.GetValueOrDefault("W", 0);
MemberHistory = await ctx.GetMemberHistory(Delivery.Year, Member.MgNr);
MemberBuckets = await ctx.GetMemberBuckets(Delivery.Year, Member.MgNr) ?? [];
}
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf);
Aside?.AddCell(NewAsideCell("Lieferung", 2))
.AddCell(NewAsideCell("LS-Nr.:", isName: true)).AddCell(NewAsideCell(Delivery.LsNr))
.AddCell(NewAsideCell("Datum/Zeit:", isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}"))
.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, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, 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;
MemberBuckets = ctx?.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult() ?? [];
}
}
}
+115
View File
@@ -0,0 +1,115 @@
@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.PublicValueStr</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>
}
+46
View File
@@ -0,0 +1,46 @@
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;
}
+43
View File
@@ -0,0 +1,43 @@
.page-break {
break-before: page;
}
hr.page-break {
display: none;
}
@page {
size: A4;
margin: 25mm 20mm 25mm 25mm;
}
@page :first {
margin: 25mm 20mm 35mm 25mm;
}
@media screen {
body, header {
width: 210mm;
}
header, .address-wrapper, aside, main {
border: 1px solid lightgray;
}
header {
top: 0;
}
.spacing {
height: 45mm;
}
.main-wrapper {
margin: 0 20mm 40mm 25mm;
}
}
a {
text-decoration: inherit;
color: inherit;
}
+178
View File
@@ -0,0 +1,178 @@
main table {
border-collapse: collapse;
margin-bottom: 0mm;
font-size: 10pt;
}
main table.large {font-size: 12pt;}
main table.tiny {
font-size: 8pt;
margin-bottom: 0mm;
}
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: 0mm 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;
}
+86 -403
View File
@@ -1,42 +1,19 @@
using Elwig.Helpers;
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;
using System.IO;
using Elwig.Helpers;
using System.Collections.Generic;
using System.Linq;
using Elwig.Helpers.Printing;
using MimeKit;
using System.Threading;
namespace Elwig.Documents {
public class Document : IDisposable {
public abstract partial class Document : IDisposable {
public static string Name => "Dokument";
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 static readonly double GenerationProportion = 0.125;
protected TempFile? _pdfFile = null;
protected string? _pdfPath;
@@ -47,16 +24,22 @@ namespace Elwig.Documents {
public bool ShowFoldMarks = App.Config.Debug;
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 DateOnly Date;
public Document(string title) {
var c = App.Client;
DocumentsPath = App.DocumentsPath;
CurrentNextSeason = Utils.CurrentNextSeason;
Title = title;
Author = App.Client.NameFull;
Author = c.NameFull;
Header = "";
Date = DateOnly.FromDateTime(Utils.Today);
}
@@ -75,172 +58,99 @@ namespace Elwig.Documents {
}
public static Document FromPdf(string path) {
return new RawPdfDocument(path);
return new PdfDocument(path);
}
private class RawPdfDocument : Document {
public RawPdfDocument(string pdfPath) :
base(Path.GetFileNameWithoutExtension(pdfPath)) {
_pdfPath = pdfPath;
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");
}
return await Render(name);
}
public int Render(string path) {
using (var writer = new PdfWriter(path)) {
Render(writer);
}
using var reader = new PdfReader(path);
using var pdf = new PdfDocument(reader);
return pdf.GetNumberOfPages();
private async Task<string> Render(string name) {
return await Html.CompileRenderAsync(name, this); ;
}
private void Render(PdfWriter writer) {
NF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\times.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
BF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesbd.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
IF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesi.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
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);
BeforeRenderBody(_doc, pdf);
RenderBody(_doc, pdf);
} finally {
_doc.Close();
}
}
protected virtual async Task LoadData(AppDbContext ctx) { }
protected virtual void BeforeRenderBody(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(AppDbContext ctx, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
if (_pdfFile != null)
return;
progress?.Report(0.0);
if (this is RawPdfDocument) {
if (this is PdfDocument) {
// 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>();
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 Page, int InsertIndex, int DocIndex)? letterhead = null;
int p = mergedPdf.GetNumberOfPages();
for (int i = 0; i < m.Documents.Count; i++) {
var n = m.Documents.Count();
int i = 0;
foreach (var doc in m.Documents) {
if (doc is PdfDocument) {
tmpFiles.Add(doc.PdfPath!);
continue;
}
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var doc = m.Documents[i];
int p0 = p;
if (letterhead != null && doc is Letterhead) {
if (p0 <= letterhead.Value.InsertIndex) {
mergedPdf.AddPage(letterhead.Value.Page);
mergedPdf.AddNewPage();
} else {
mergedPdf.AddPage(letterhead.Value.InsertIndex + 1, letterhead.Value.Page);
mergedPdf.AddNewPage(letterhead.Value.InsertIndex + 2);
var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8);
tmpHtmls.Add(tmpHtml);
tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FilePath);
i++;
progress?.Report(GenerationProportion * 100 * i / n);
}
pageNums[letterhead.Value.DocIndex] = 1;
letterhead = null;
p += 2;
}
if (doc is RawPdfDocument) {
using var reader = new PdfReader(doc.PdfPath);
using var src = new PdfDocument(reader);
merger.Merge(src, 1, src.GetNumberOfPages());
p += src.GetNumberOfPages();
} else {
await doc.LoadData(ctx);
int pageNum = doc.Render(tmpPdf.FilePath);
if (IsDoublePaged && doc is Letterhead) {
using var reader = new PdfReader(tmpPdf.FilePath);
using var src = new PdfDocument(reader);
letterhead = (src.GetPage(1).CopyTo(mergedPdf), p0, i);
} else {
using var reader = new PdfReader(tmpPdf.FilePath);
using var src = new PdfDocument(reader);
merger.Merge(src, 1, pageNum);
p += pageNum;
}
}
int p1 = p;
pageNums.Add(p1 - p0);
if (IsDoublePaged && doc is not Letterhead && p % 2 != 0) {
if (letterhead != null) {
mergedPdf.AddPage(letterhead.Value.Page);
letterhead = null;
} else {
mergedPdf.AddNewPage();
}
p++;
}
progress?.Report(100.0 * (i + 1) / (m.Documents.Count + 1));
}
if (letterhead != null) {
if (p <= letterhead.Value.InsertIndex) {
mergedPdf.AddPage(letterhead.Value.Page);
mergedPdf.AddNewPage();
} else {
mergedPdf.AddPage(letterhead.Value.InsertIndex + 1, letterhead.Value.Page);
mergedPdf.AddNewPage(letterhead.Value.InsertIndex + 2);
}
pageNums[letterhead.Value.DocIndex] = 1;
p += 2;
}
TotalPages = p;
progress?.Report(GenerationProportion * 100);
var pages = Pdf.Convert(tmpFiles, pdf.FilePath, IsDoublePaged, m.Documents, cancelToken, new Progress<double>(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion))));
TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
} finally {
foreach (var tmp in tmpHtmls) {
tmp.Dispose();
}
}
_pdfFile = pdf;
} else {
if (cancelToken?.IsCancellationRequested ?? false)
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
var pdf = new TempFile("pdf");
try {
await LoadData(ctx);
TotalPages = Render(pdf.FilePath);
using var tmpHtml = new TempFile("html");
await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
progress?.Report(50.0);
var pages = Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, this);
TotalPages = pages.Pages;
_pdfFile = pdf;
} catch {
pdf.Dispose();
throw;
}
_pdfFile = pdf;
}
progress?.Report(100.0);
}
@@ -270,241 +180,14 @@ namespace Elwig.Documents {
};
}
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 MergedDocument(IEnumerable<Document> docs) : Document("Mehrere Dokumente") {
public IEnumerable<Document> Documents = docs;
}
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(0.5 * Math.PI);
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) {
if (_doc is Letterhead) return;
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);
}
private class PdfDocument : Document {
public PdfDocument(string pdfPath) :
base(Path.GetFileNameWithoutExtension(pdfPath)) {
_pdfPath = pdfPath;
}
}
}
+28
View File
@@ -0,0 +1,28 @@
@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.IsDoublePaged) {
<style>
@@page :left {
margin: 25mm 25mm 25mm 20mm;
}
</style>
}
</head>
<body>
<header>@Raw(Model.Header)</header>
<div class="spacing"></div>
<div class="main-wrapper">
@RenderBody()
</div>
</body>
</html>
+66
View File
@@ -0,0 +1,66 @@
: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;
}
.hidden {
visibility: hidden;
}
hr {
border: none;
border-top: var(--border-thickness) solid black;
margin: 5mm 0;
}
-93
View File
@@ -1,93 +0,0 @@
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
View File
@@ -1,82 +0,0 @@
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 -14
View File
@@ -1,21 +1,9 @@
using Elwig.Models.Entities;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Layout;
namespace Elwig.Documents {
public class Letterhead : BusinessDocument {
public Letterhead(Member m) :
base($"Briefkopf {m.FullName}", m, null, includeSender: true) {
}
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
// do not render anything except this
var page = pdf.AddNewPage();
var pageSize = page.GetPageSize();
var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdf);
using var canvas = new Canvas(pdfCanvas, pageSize);
RenderAddress(canvas, pageSize);
public Letterhead(Member m) : base($"Briefkopf {m.FullName}", m, true) {
Aside = "";
}
}
}
+9
View File
@@ -0,0 +1,9 @@
@using RazorLight
@inherits TemplatePage<Elwig.Documents.Letterhead>
@model Elwig.Documents.Letterhead
@{ Layout = "BusinessDocument"; }
<style>
header, .footer-wrapper {
visibility: hidden;
}
</style>
+7 -197
View File
@@ -1,213 +1,23 @@
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 Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Elwig.Documents {
public class MemberDataSheet : BusinessDocument {
public new static string Name => "Stammdatenblatt";
public Season? Season;
public int MemberDeliveredWeightRed;
public int MemberDeliveredWeightWhite;
public Dictionary<string, MemberBucket> MemberBuckets = [];
public List<AreaCom> ActiveAreaCommitments = [];
public Season Season;
public Dictionary<string, MemberBucket> MemberBuckets;
public IEnumerable<AreaCom> ActiveAreaCommitments;
public MemberDataSheet(Member m) :
base($"{Name} {m.AdministrativeName}", m, DateOnly.FromDateTime(m.ModifiedAt)) {
ShowDateAndLocation = true;
public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) {
DocumentId = $"{Name} {m.MgNr}";
}
public static async Task<MemberDataSheet> Initialize(int mgnr) {
using var ctx = new AppDbContext();
return new MemberDataSheet(await ctx.FetchMembers(mgnr, includeContactInfo: true).SingleAsync());
}
protected override async Task LoadData(AppDbContext ctx) {
await base.LoadData(ctx);
Season = await ctx.FetchSeasons().FirstOrDefaultAsync() ?? throw new ArgumentException("Invalid season");
MemberBuckets = await ctx.GetMemberBuckets(Utils.CurrentYear, Member.MgNr);
ActiveAreaCommitments = await Member.ActiveAreaCommitments(ctx)
.Include(c => c.Contract).ThenInclude(c => c.Revisions)
.ToListAsync();
var weights = await ctx.Deliveries
.Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr)
.SelectMany(d => d.Parts)
.GroupBy(p => p.Variety.Type)
.ToDictionaryAsync(g => g.Key, g => g.Sum(p => p.Weight));
MemberDeliveredWeightRed = weights.GetValueOrDefault("R", 0);
MemberDeliveredWeightWhite = weights.GetValueOrDefault("W", 0);
}
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
if (Season == null) throw new Exception("Call LoadData before RenderBody");
base.RenderBody(doc, pdf);
doc.Add(NewMemberData(Season).SetMarginBottomMM(5));
doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, 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(ActiveAreaCommitments));
}
}
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(Season season) {
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));
}
var shares = (Member.Shares != 0 || (Member.SharesRed == 0 && Member.SharesWhite == 0) ? $"{Member.Shares:N0}" : "") +
(Member.SharesRed != 0 || Member.SharesWhite != 0 ? (Member.Shares != 0 ? " / " : "") + $"{Member.SharesRed:N0} (rot) / {Member.SharesWhite:N0} (weiß)" : "") +
(Member.SharesDormant != 0 ? $" / {Member.SharesDormant:N0} (ruhend)" : "");
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(shares, 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(IEnumerable<AreaCom> activeAreaComs) {
var areaComs = activeAreaComs.GroupBy(a => a.AreaComType).Select(group => new {
Type = group.Key,
AreaComs = group.OrderBy(c => c.Contract.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) {
var c = areaCom.Contract;
tbl.AddCell(NewTd(new KernedParagraph(10).Add(Normal($"{c.Kg.AtKg.Name} ")).Add(Normal($"({c.Kg.AtKg.KgNr:00000})", 8))))
.AddCell(NewTd(c.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(c.YearTo == null ? (c.YearFrom == null ? "unbefristet" : $"ab {c.YearFrom}") : (c.YearFrom == null ? $"bis {c.YearTo}" : $"{c.YearFrom}{c.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($"{activeAreaComs.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;
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);
}
}
}
+221
View File
@@ -0,0 +1,221 @@
@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) {
<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>
+25
View File
@@ -0,0 +1,25 @@
h2 {
margin-top: 0;
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;
}
+3 -84
View File
@@ -1,10 +1,4 @@
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;
@@ -14,15 +8,14 @@ namespace Elwig.Documents {
public new static string Name => "Mitgliederliste";
public string Filter;
public List<MemberListRow> Members;
public IEnumerable<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)
@@ -33,79 +26,5 @@ 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.SharesTotal: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;
}
}
}
+109
View File
@@ -0,0 +1,109 @@
@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>
+13
View File
@@ -0,0 +1,13 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
margin-bottom: 2mm;
}
h2 {
text-align: center;
font-size: 14pt;
margin-top: 2mm;
}
+9 -288
View File
@@ -2,315 +2,36 @@ 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 Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Documents {
public class PaymentVariantSummary : Document {
public new static string Name => "Auszahlungsvariante";
public PaymentVariantSummaryData? Data;
public PaymentVariantSummaryData Data;
public PaymentVar Variant;
public BillingData BillingData;
public string CurrencySymbol;
public int MemberNum;
public int DeliveryNum;
public int DeliveryPartNum;
public List<ModifierStat>? ModifierStat;
public Dictionary<string, Modifier>? Modifiers;
public List<ModifierStat> ModifierStat;
public Dictionary<string, Modifier> Modifiers;
private List<Credit> _credits = [];
private List<PaymentDeliveryPart> _parts = [];
public PaymentVariantSummary(PaymentVar v, PaymentVariantSummaryData? data = null) :
public PaymentVariantSummary(PaymentVar v, PaymentVariantSummaryData data) :
base($"{Name} {v.Year} - {v.Name}") {
Variant = v;
BillingData = BillingData.FromJson(v.Data);
Data = data;
CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code;
}
public static async Task<PaymentVariantSummary> Initialize(int year, int avnr, PaymentVariantSummaryData? data = null) {
using var ctx = new AppDbContext();
var v = await ctx.PaymentVariants
.Where(v => v.Year == year && v.AvNr == avnr)
.SingleAsync();
return new PaymentVariantSummary(v, data);
}
protected override async Task LoadData(AppDbContext ctx) {
_credits = await ctx.Credits.Where(c => c.Year == Variant.Year && c.AvNr == Variant.AvNr).ToListAsync();
_parts = await ctx.PaymentDeliveryParts.Where(p => p.Year == Variant.Year && p.AvNr == Variant.AvNr).ToListAsync();
MemberNum = _credits.Count;
MemberNum = v.Credits.Count;
IsPreview = MemberNum == 0;
DeliveryNum = await ctx.Deliveries.Where(d => d.Year == Variant.Year).CountAsync();
DeliveryPartNum = await ctx.DeliveryParts.Where(d => d.Year == Variant.Year).CountAsync();
Data ??= await PaymentVariantSummaryData.ForPaymentVariant(Variant, ctx.PaymentVariantSummaryRows);
ModifierStat = await AppDbContext.GetModifierStats(Variant.Year, Variant.AvNr);
Modifiers = await ctx.FetchModifiers(Variant.Year).ToDictionaryAsync(m => m.ModId);
}
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
if (Data == null || Modifiers == null || ModifierStat == null) throw new Exception("Call LoadData before RenderBody");
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(Data).SetMarginBottomMM(10));
doc.Add(NewModifierStatTable(Modifiers, ModifierStat));
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
doc.Add(NewPriceTable(Data));
}
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(PaymentVariantSummaryData data) {
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 = _parts.Sum(p => p.NetAmount);
//var sum2 = _credits.Sum(p => p.); //Variant.MemberPayments.Sum(p => p.Amount);
var deliveryModifiers = _parts.Sum(p => p.Amount - p.NetAmount);
var memberModifiers = _credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount);
var sum2 = _credits.Sum(p => p.NetAmount);
var sum1 = sum2 - deliveryModifiers - memberModifiers;
var payed = -_credits.Sum(p => p.PrevNetAmount ?? 0m);
var netSum = _credits.Sum(p => p.NetAmount) - _credits.Sum(p => p.PrevNetAmount ?? 0m);
var vat = _credits.Sum(p => p.VatAmount);
var grossSum = _credits.Sum(p => p.GrossAmount);
var totalMods = _credits.Sum(p => p.Modifiers ?? 0m);
var considered = -_credits.Sum(p => p.PrevModifiers ?? 0m);
var totalSum = _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 == null && maxWei == null ? "-" : (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 == null && maxPrice == null ? "-" : (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 == null && maxGeb == null ? "-" : 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(Dictionary<string, Modifier> modifiers, IEnumerable<ModifierStat> modStat) {
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 modStat) {
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(PaymentVariantSummaryData data) {
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;
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);
}
}
}
@@ -0,0 +1,288 @@
@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 + r.LowGeb.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.LowGeb.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>
+21
View File
@@ -0,0 +1,21 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
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 -99
View File
@@ -1,13 +1,5 @@
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 {
@@ -27,99 +19,9 @@ 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;
}
}
}
@@ -0,0 +1,81 @@
@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>
+97
View File
@@ -0,0 +1,97 @@
h1 {
text-align: center;
font-size: 24pt;
margin-top: 0;
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; }
+20 -16
View File
@@ -9,7 +9,7 @@
<UseWindowsForms>true</UseWindowsForms>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.0.5.6</Version>
<Version>1.0.3.3</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
@@ -23,21 +23,25 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="bblanchon.PDFium.Win32" Version="151.0.7906" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="itext" Version="9.6.0" />
<PackageReference Include="itext.bouncy-castle-adapter" Version="9.6.0" />
<PackageReference Include="LinqKit" Version="1.3.11" />
<PackageReference Include="MailKit" Version="4.17.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.9" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.4022.49" />
<PackageReference Include="NJsonSchema" Version="11.6.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.1.59" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.3" />
<PackageReference Include="System.IO.Hashing" Version="10.0.9" />
<PackageReference Include="System.IO.Ports" Version="10.0.9" />
<PackageReference Include="System.Management" Version="10.0.9" />
<PackageReference Include="bblanchon.PDFium.Win32" Version="147.0.7690" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="itext" Version="9.5.0" />
<PackageReference Include="itext.bouncy-castle-adapter" Version="9.5.0" />
<PackageReference Include="itext.pdfhtml" Version="6.3.1" />
<PackageReference Include="LinqKit" Version="1.3.9" />
<PackageReference Include="MailKit" Version="4.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.2" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
<PackageReference Include="NJsonSchema" Version="11.5.2" />
<PackageReference Include="RazorLight" Version="2.3.1" />
<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.2" />
<PackageReference Include="System.IO.Ports" Version="10.0.2" />
<PackageReference Include="System.Management" Version="10.0.2" />
</ItemGroup>
</Project>
+55 -220
View File
@@ -1,20 +1,19 @@
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.Services;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using Elwig.Models.Entities;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.IO;
using System;
using System.Windows;
using Microsoft.Extensions.Logging;
using Microsoft.Data.Sqlite;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Elwig.Models.Dtos;
using System.Data;
namespace Elwig.Helpers {
public record struct MemberHistoryPoint(int Shares, int SharesRed, int SharesWhite, int SharesDormant);
public record struct AreaComBucket(int Area, int Obligation, int Right);
public record struct UnderDelivery(int Weight, int Diff);
public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int DeliveryTotal, int Payment);
@@ -49,7 +48,6 @@ namespace Elwig.Helpers {
public DbSet<MemberTelNr> MemberTelephoneNrs { get; private set; }
public DbSet<MemberEmailAddr> MemberEmailAddrs { get; private set; }
public DbSet<MemberHistory> MemberHistory { get; private set; }
public DbSet<AreaComContract> AreaCommitmentContracts { get; private set; }
public DbSet<AreaCom> AreaCommitments { get; private set; }
public DbSet<Season> Seasons { get; private set; }
public DbSet<DeliverySchedule> DeliverySchedules { get; private set; }
@@ -83,71 +81,6 @@ namespace Elwig.Helpers {
public static string? ConnectionStringOverride { get; set; } = null;
public static string ConnectionString => ConnectionStringOverride ?? $"Data Source=\"{App.Config.DatabaseFile}\"; Mode=ReadWrite; Foreign Keys=True; Cache=Default; Pooling=False";
private static readonly Func<AppDbContext, string?, bool, IAsyncEnumerable<Branch>> _compiledQueryBranches =
EF.CompileAsyncQuery<AppDbContext, string?, bool, Branch>((ctx, zwstid, includeWithoutMembers) => ctx.Branches
.Where(b => includeWithoutMembers || b.Members.Count > 0)
.Where(b => zwstid == null || b.ZwstId == zwstid)
.Include(b => b.PostalDest)
.OrderBy(b => b.Name));
private static readonly Func<AppDbContext, string?, IAsyncEnumerable<WineVar>> _compiledQueryWineVarieties =
EF.CompileAsyncQuery<AppDbContext, string?, WineVar>((ctx, sortid) => ctx.WineVarieties
.Where(v => sortid == null || v.SortId == sortid)
.OrderBy(v => v.Name));
private static readonly Func<AppDbContext, string?, bool, IAsyncEnumerable<WineAttr>> _compiledQueryWineAttributes =
EF.CompileAsyncQuery<AppDbContext, string?, bool, WineAttr>((ctx, attrid, includeNotActive) => ctx.WineAttributes
.Where(a => includeNotActive || a.IsActive)
.Where(a => attrid == null || a.AttrId == attrid)
.OrderBy(a => a.Name));
private static readonly Func<AppDbContext, string?, IAsyncEnumerable<WineCult>> _compiledQueryWineCultivations =
EF.CompileAsyncQuery<AppDbContext, string?, WineCult>((ctx, cultid) => ctx.WineCultivations
.Where(c => cultid == null || c.CultId == cultid)
.OrderBy(v => v.Name));
private static readonly Func<AppDbContext, bool, IAsyncEnumerable<WineQualLevel>> _compiledQueryWineQualityLevels =
EF.CompileAsyncQuery<AppDbContext, bool, WineQualLevel>((ctx, includePredicate) => ctx.WineQualityLevels
.Where(l => includePredicate || !l.IsPredicate)
.OrderBy(l => l.MinKmw));
private static readonly Func<AppDbContext, int?, bool, IAsyncEnumerable<Modifier>> _compiledQueryModifiers =
EF.CompileAsyncQuery<AppDbContext, int?, bool, Modifier>((ctx, year, incudeNotActive) => ctx.Modifiers
.Where(m => (year == null || m.Year == year) && (incudeNotActive || m.IsActive))
.OrderBy(m => m.Year).ThenBy(m => m.Ordering).ThenBy(m => m.Name));
private static readonly Func<AppDbContext, int?, bool, IAsyncEnumerable<Member>> _compiledQueryMembers =
EF.CompileAsyncQuery<AppDbContext, int?, bool, Member>((ctx, mgnr, includeNotActive) => ctx.Members
.Where(m => includeNotActive || m.IsActive)
.Where(m => mgnr == null || m.MgNr == mgnr)
.OrderBy(m => m.Name).ThenBy(m => m.GivenName).ThenBy(m => m.MgNr));
private static readonly Func<AppDbContext, int?, bool, IAsyncEnumerable<Member>> _compiledQueryMembersContactInfo =
EF.CompileAsyncQuery<AppDbContext, int?, bool, Member>((ctx, mgnr, includeNotActive) => ctx.Members
.Where(m => includeNotActive || m.IsActive)
.Where(m => mgnr == null || m.MgNr == mgnr)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.OrderBy(m => m.Name).ThenBy(m => m.GivenName).ThenBy(m => m.MgNr)
.AsSplitQuery());
private static readonly Func<AppDbContext, int?, IAsyncEnumerable<AreaCom>> _compiledQueryAreaCommitments =
EF.CompileAsyncQuery<AppDbContext, int?, AreaCom>((ctx, fbnr) => ctx.AreaCommitments
.Where(c => fbnr == null || c.FbNr == fbnr)
.OrderBy(c => c.FbNr).ThenBy(c => c.RevNr));
private static readonly Func<AppDbContext, int?, IAsyncEnumerable<Season>> _compiledQuerySeasons =
EF.CompileAsyncQuery<AppDbContext, int?, Season>((ctx, year) => ctx.Seasons
.Where(s => year == null || s.Year == year)
.OrderByDescending(s => s.Year));
private static readonly Func<AppDbContext, int?, IAsyncEnumerable<Season>> _compiledQuerySeasonsModifiers =
EF.CompileAsyncQuery<AppDbContext, int?, Season>((ctx, year) => ctx.Seasons
.Where(s => year == null || s.Year == year)
.Include(s => s.Modifiers)
.OrderByDescending(s => s.Year));
private readonly Dictionary<int, Dictionary<int, MemberHistoryPoint>> _memberHistory = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, AreaComBucket>>> _memberAreaCommitmentBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBucketsStrict = [];
@@ -161,8 +94,8 @@ namespace Elwig.Helpers {
LogFile = new(file) {
AutoFlush = true
};
} catch (Exception exc) {
InteractionService.ShowException("Database Log", $"Unable to open database log file", exc);
} catch (Exception e) {
MessageBox.Show($"Unable to open database log file:\n\n{e.Message}", "Database Log", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
SavedLastWriteTime = LastWriteTime;
@@ -185,59 +118,11 @@ namespace Elwig.Helpers {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlite(ConnectionString);
optionsBuilder.UseLazyLoadingProxies();
optionsBuilder.LogTo(Log, LogLevel.Information);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<WbKg>().Navigation(k => k.AtKg).AutoInclude();
modelBuilder.Entity<WbKg>().Navigation(k => k.Gl).AutoInclude();
modelBuilder.Entity<AT_Kg>().Navigation(k => k.Gem).AutoInclude();
modelBuilder.Entity<PostalDest>().Navigation(p => p.Country).AutoInclude();
modelBuilder.Entity<PostalDest>().Navigation(p => p.AtPlz).AutoInclude();
modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.AtPlz).AutoInclude();
modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.Ort).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.Branch).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.DefaultWbKg).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.Country).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.PostalDest).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.BillingAddress).AutoInclude();
modelBuilder.Entity<BillingAddr>().Navigation(a => a.Country).AutoInclude();
modelBuilder.Entity<BillingAddr>().Navigation(a => a.PostalDest).AutoInclude();
modelBuilder.Entity<Modifier>().Navigation(m => m.Season).AutoInclude();
modelBuilder.Entity<Season>().Navigation(s => s.Currency).AutoInclude();
modelBuilder.Entity<PaymentVar>().Navigation(v => v.Season).AutoInclude();
modelBuilder.Entity<PaymentDeliveryPart>().Navigation(p => p.Variant).AutoInclude();
modelBuilder.Entity<Credit>().Navigation(c => c.Payment).AutoInclude();
modelBuilder.Entity<Delivery>().Navigation(d => d.Member).AutoInclude();
modelBuilder.Entity<Delivery>().Navigation(d => d.Season).AutoInclude();
modelBuilder.Entity<Delivery>().Navigation(d => d.Branch).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Quality).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Variety).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Attribute).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Cultivation).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Kg).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Rd).AutoInclude();
modelBuilder.Entity<DeliveryPartModifier>().Navigation(m => m.Modifier).AutoInclude();
modelBuilder.Entity<AreaComContract>().Navigation(c => c.Kg).AutoInclude();
modelBuilder.Entity<AreaComContract>().Navigation(c => c.Rd).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.Contract).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.WineCult).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.AreaComType).AutoInclude();
modelBuilder.Entity<AreaComType>().Navigation(c => c.WineVar).AutoInclude();
modelBuilder.Entity<AreaComType>().Navigation(c => c.WineAttr).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Credit).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Member).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Variant).AutoInclude();
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Member).AutoInclude();
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Schedule).AutoInclude();
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Variety).AutoInclude();
modelBuilder.Entity<DeliverySchedule>().Navigation(s => s.Branch).AutoInclude();
modelBuilder.Entity<MemberHistory>().Navigation(s => s.FromMember).AutoInclude();
modelBuilder.Entity<MemberHistory>().Navigation(s => s.ToMember).AutoInclude();
modelBuilder.Entity<MemberHistory>().Navigation(s => s.Currency).AutoInclude();
}
public override void Dispose() {
base.Dispose();
LogFile?.Dispose();
@@ -253,23 +138,23 @@ namespace Elwig.Helpers {
}
public async Task<bool> MgNrExists(int mgnr) {
return await _compiledQueryMembers.Invoke(this, mgnr, true).AnyAsync();
return await Members.FindAsync(mgnr) != null;
}
public async Task<bool> FbNrExists(int fbnr) {
return await _compiledQueryAreaCommitments.Invoke(this, fbnr).AnyAsync();
return await AreaCommitments.FindAsync(fbnr) != null;
}
public async Task<bool> SortIdExists(string sortId) {
return await _compiledQueryWineVarieties.Invoke(this, sortId).AnyAsync();
return await WineVarieties.FindAsync(sortId) != null;
}
public async Task<bool> AttrIdExists(string attrId) {
return await _compiledQueryWineAttributes.Invoke(this, attrId, true).AnyAsync();
return await WineAttributes.FindAsync(attrId) != null;
}
public async Task<bool> CultIdExists(string cultId) {
return await _compiledQueryWineCultivations.Invoke(this, cultId).AnyAsync();
return await WineCultivations.FindAsync(cultId) != null;
}
public async Task<int> NextMgNr() {
@@ -281,113 +166,87 @@ namespace Elwig.Helpers {
public async Task<int> NextFbNr() {
int c = 0;
(await AreaCommitmentContracts.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync())
(await AreaCommitments.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync())
.ForEach(a => { if (a <= c + 10000) c = a; });
return c + 1;
}
public async Task<int> NextRevNr(int fbnr) {
return (await AreaCommitments.Where(c => c.FbNr == fbnr).Select(c => (int?)c.RevNr).MaxAsync() ?? 0) + 1;
}
public async Task<int> NextLNr(DateOnly date, string zwstid) {
var dateStr = date.ToString("yyyy-MM-dd");
return (await Deliveries.Where(d => d.DateString == dateStr && d.ZwstId == zwstid).Select(d => (int?)d.LNr).MaxAsync() ?? 0) + 1;
int c = 0;
(await Deliveries.Where(d => d.DateString == dateStr && d.ZwstId == zwstid).Select(d => d.LNr).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
public async Task<int> NextDId(int year) {
return (await Deliveries.Where(d => d.Year == year).Select(d => (int?)d.DId).MaxAsync() ?? 0) + 1;
int c = 0;
(await Deliveries.Where(d => d.Year == year).Select(d => d.DId).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
public async Task<int> NextDPNr(int year, int did) {
return (await DeliveryParts.Where(p => p.Year == year && p.DId == did).Select(p => (int?)p.DPNr).MaxAsync() ?? 0) + 1;
int c = 0;
(await DeliveryParts.Where(p => p.Year == year && p.DId == did).Select(d => d.DPNr).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
public async Task<int> NextRdNr(int kgnr) {
return (await WbRde.Where(r => r.KgNr == kgnr).Select(r => (int?)r.RdNr).MaxAsync() ?? 0) + 1;
int c = 0;
(await WbRde.Where(r => r.KgNr == kgnr).Select(r => r.RdNr).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
public async Task<int> NextAvNr(int year) {
return (await PaymentVariants.Where(v => v.Year == year).Select(v => (int?)v.AvNr).MaxAsync() ?? 0) + 1;
int c = 0;
(await PaymentVariants.Where(v => v.Year == year).Select(v => v.AvNr).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
public async Task<int> NextDsNr(int year) {
return (await DeliverySchedules.Where(s => s.Year == year).Select(v => (int?)v.DsNr).MaxAsync() ?? 0) + 1;
int c = 0;
(await DeliverySchedules.Where(s => s.Year == year).Select(s => s.DsNr).ToListAsync())
.ForEach(a => { if (a <= c + 100) c = a; });
return c + 1;
}
public IAsyncEnumerable<Branch> FetchBranches(string? zwstid = null, bool includeWithoutMembers = true) {
return _compiledQueryBranches.Invoke(this, zwstid, includeWithoutMembers);
}
public IAsyncEnumerable<WineVar> FetchWineVarieties() {
return _compiledQueryWineVarieties.Invoke(this, null);
}
public IAsyncEnumerable<WineAttr> FetchWineAttributes(bool incudeNotActive = true) {
return _compiledQueryWineAttributes.Invoke(this, null, incudeNotActive);
}
public IAsyncEnumerable<WineCult> FetchWineCultivations() {
return _compiledQueryWineCultivations.Invoke(this, null);
}
public IAsyncEnumerable<WineQualLevel> FetchWineQualityLevels(bool includePredicate = true) {
return _compiledQueryWineQualityLevels.Invoke(this, includePredicate);
}
public IAsyncEnumerable<Modifier> FetchModifiers(int? year, bool incudeNotActive = true) {
return _compiledQueryModifiers.Invoke(this, year, incudeNotActive);
}
public IAsyncEnumerable<Member> FetchMembers(int? mgnr = null, bool includeNotActive = false, bool includeContactInfo = false) {
if (includeContactInfo) {
return _compiledQueryMembersContactInfo.Invoke(this, mgnr, mgnr != null || includeNotActive);
} else {
return _compiledQueryMembers.Invoke(this, mgnr, mgnr != null || includeNotActive);
}
}
public IAsyncEnumerable<Season> FetchSeasons(int? year = null, bool includeModifiers = false) {
if (includeModifiers) {
return _compiledQuerySeasonsModifiers.Invoke(this, year);
} else {
return _compiledQuerySeasons.Invoke(this, year);
}
}
public async Task UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<string> oldModIds, IEnumerable<string> newModIds) {
foreach (var m in await FetchModifiers(part.Year).ToListAsync()) {
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 {
Year = part.Year,
DId = part.DId,
DPNr = part.DPNr,
ModId = m.ModId,
};
var old = oldModIds.Contains(m.ModId);
if (newModIds.Contains(m.ModId)) {
if (!old) {
var old = oldModifiers.Where(pa => pa.ModId == m.ModId).FirstOrDefault();
if (newModifiers.Any(md => md.ModId == m.ModId)) {
if (old == null) {
Add(mod);
} else {
Update(mod);
}
} else {
if (old) {
if (old != null) {
Remove(mod);
}
}
}
}
public async Task UpdateDeliveryScheduleWineVarieties(DeliverySchedule schedule, IEnumerable<(string, int)> oldVarieties, IEnumerable<(string, int)> newVarieties) {
foreach (var v in await FetchWineVarieties().ToArrayAsync()) {
public void UpdateDeliveryScheduleWineVarieties(DeliverySchedule schedule, IEnumerable<(WineVar, int)> oldVarieties, IEnumerable<(WineVar, int)> newVarieties) {
foreach (var v in WineVarieties) {
var e = new DeliveryScheduleWineVar {
Year = schedule.Year,
DsNr = schedule.DsNr,
SortId = v.SortId,
Priority = 1,
};
var o = oldVarieties.Where(x => x.Item1 == e.SortId).Select(x => x.Item2).FirstOrDefault(-1);
var n = newVarieties.Where(x => x.Item1 == e.SortId).Select(x => x.Item2).FirstOrDefault(-1);
var o = oldVarieties.Where(x => x.Item1.SortId == e.SortId).Select(x => x.Item2).FirstOrDefault(-1);
var n = newVarieties.Where(x => x.Item1.SortId == e.SortId).Select(x => x.Item2).FirstOrDefault(-1);
if (n != -1) {
e.Priority = n;
if (o == -1) {
@@ -403,22 +262,6 @@ namespace Elwig.Helpers {
}
}
private async Task FetchMemberHistory(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();
var history = new Dictionary<int, MemberHistoryPoint>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"SELECT mgnr, shares, shares_red, shares_white, shares_dormant FROM v_member_history WHERE year = {year}";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0);
history[mgnr] = new(reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3), reader.GetInt32(4));
}
}
if (ownCnx) await cnx.DisposeAsync();
_memberHistory[year] = history;
}
private async Task FetchMemberAreaCommitmentBuckets(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();
@@ -509,12 +352,6 @@ namespace Elwig.Helpers {
_memberUnderDelivery[year] = buckets;
}
public async Task<MemberHistoryPoint> GetMemberHistory(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberHistory.ContainsKey(year))
await FetchMemberHistory(year, cnx);
return _memberHistory[year].GetValueOrDefault(mgnr, new());
}
public async Task<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberAreaCommitmentBuckets.ContainsKey(year))
await FetchMemberAreaCommitmentBuckets(year, cnx);
@@ -554,12 +391,10 @@ namespace Elwig.Helpers {
var paymentBuckets = await GetMemberPaymentBuckets(year, mgnr, cnx);
if (ownCnx) await cnx.DisposeAsync();
var varieties = await WineVarieties.ToDictionaryAsync(v => v.SortId);
var attributes = await WineAttributes.ToDictionaryAsync(a => a.AttrId);
var buckets = new Dictionary<string, MemberBucket>();
foreach (var id in rightsAndObligations.Keys.Union(deliveryBuckets.Keys).Union(paymentBuckets.Keys)) {
var variety = varieties.GetValueOrDefault(id[..2]);
var attribute = attributes.GetValueOrDefault(id[2..]);
var variety = await WineVarieties.FindAsync(id[..2]);
var attribute = await WineAttributes.FindAsync(id[2..]);
var name = (variety?.Name ?? "") + (id[2..] == "_" ? " (kein Qual.Wein)" : attribute != null ? $" ({attribute})" : "");
buckets[id] = new(
name,
+22 -32
View File
@@ -1,6 +1,6 @@
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -9,41 +9,36 @@ namespace Elwig.Helpers {
public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 40;
public static readonly int RequiredSchemaVersion = 36;
private static int VersionOffset = 0;
public static async Task<Version> CheckDb() {
long? applId, schemaVers;
using (var cnx = await AppDbContext.ConnectAsync()) {
applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
using var cnx = AppDbContext.Connect();
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
schemaVers = (long?)await cnx.ExecuteScalar("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
// TODO issue warning
}
}
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
await UpdateDbSchema((int)(schemaVers / 100), RequiredSchemaVersion);
Version v;
using (var cnx = await AppDbContext.ConnectAsync()) {
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
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 cnx.ExecuteBatch($"PRAGMA user_version = {vers}");
}
}
return v;
}
private static async Task UpdateDbSchema(int fromVersion, int toVersion) {
private static async Task UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) {
if (fromVersion == toVersion) {
return;
} else if (fromVersion > toVersion) {
@@ -53,48 +48,43 @@ namespace Elwig.Helpers {
}
var asm = Assembly.GetExecutingAssembly();
(int From, int To, string Name)[] scripts = [.. asm.GetManifestResourceNames()
(int From, int To, string Name)[] scripts = asm.GetManifestResourceNames()
.Where(n => n.StartsWith("Elwig.Resources.Sql."))
.Select(n => {
var p = n.Split(".")[^2].Split("-");
return (int.Parse(p[0]), int.Parse(p[1]), n);
})
.OrderBy(s => s.Item1).ThenBy(s => s.Item2)];
.OrderBy(s => s.Item1).ThenBy(s => s.Item2)
.ToArray();
List<string> toExecute = [];
var vers = fromVersion;
while (vers < toVersion) {
var (_, to, name) = scripts.Last(s => s.From == vers);
var (_, to, name) = scripts.Where(s => s.From == vers).Last();
toExecute.Add(name);
vers = to;
}
if (toExecute.Count == 0)
return;
var backup = Path.ChangeExtension(App.Config.DatabaseFile, $".v{fromVersion}.sqlite3");
File.Copy(App.Config.DatabaseFile, backup, true);
try {
using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch("PRAGMA locking_mode = EXCLUSIVE");
await cnx.ExecuteBatch("""
PRAGMA locking_mode = EXCLUSIVE;
BEGIN EXCLUSIVE;
""");
foreach (var script in toExecute) {
await cnx.ExecuteEmbeddedScript(asm, script);
}
var violations = await cnx.ForeignKeyCheck();
if (violations.Length > 0) {
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
.Take(50)
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
}
await cnx.ExecuteBatch("VACUUM");
await cnx.ExecuteBatch($"PRAGMA schema_version = {toVersion * 100 + VersionOffset}");
} catch (Exception) {
File.Move(backup, App.Config.DatabaseFile, true);
throw;
} finally {
File.Delete(backup);
}
await cnx.ExecuteBatch($"""
COMMIT;
VACUUM;
PRAGMA schema_version = {toVersion * 100 + VersionOffset};
""");
}
}
}
+34 -34
View File
@@ -1,5 +1,6 @@
using Elwig.Models.Entities;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -15,30 +16,13 @@ namespace Elwig.Helpers.Billing {
protected readonly Dictionary<string, (decimal?, decimal?)> Modifiers;
protected readonly Dictionary<string, (string, string?, string?, int?, decimal?)> AreaComTypes;
protected Billing(int year, Season season,
Dictionary<string, string> attributes,
Dictionary<string, (decimal?, decimal?)> modifiers,
Dictionary<string, (string, string?, string?, int?, decimal?)> areaComTypes
) {
public Billing(int year) {
Year = year;
Season = season;
Attributes = attributes;
Modifiers = modifiers;
AreaComTypes = areaComTypes;
}
protected static async Task<(Season, Dictionary<string, string>, Dictionary<string, (decimal?, decimal?)>, Dictionary<string, (string, string?, string?, int?, decimal?)>)> LoadData(AppDbContext ctx, int year) {
var season = await ctx.FetchSeasons(year).SingleOrDefaultAsync() ?? throw new ArgumentException("Invalid season");
var attributes = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.AttrId, a => a.Name);
var modifiers = await ctx.FetchModifiers(year).ToDictionaryAsync(m => m.ModId, m => (m.Abs, m.Rel));
var areaComTypes = ctx.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount));
return (season, attributes, modifiers, areaComTypes);
}
public static async Task<Billing> Create(int year) {
using var ctx = new AppDbContext();
var (season, attributes, modifiers, areaComTypes) = await LoadData(ctx, year);
return new Billing(year, season, attributes, modifiers, areaComTypes);
Season = ctx.Seasons.Find(Year)!;
Attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a.Name);
Modifiers = ctx.Modifiers.Where(m => m.Year == Year).Include(m => m.Season).ToDictionary(m => m.ModId, m => (m.Abs, m.Rel));
AreaComTypes = ctx.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount));
}
public async Task FinishSeason() {
@@ -50,29 +34,45 @@ namespace Elwig.Helpers.Billing {
""");
}
public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceShares = 0, int allowanceKgPerShare = 0, double allowanceRel = 0, int addMinShares = 1) {
if (addMinShares < 1) addMinShares = 1;
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 cnx.ExecuteBatch($"""
DELETE FROM member_history WHERE source = 'elwig' AND reason = 'auto' AND SUBSTR(date, 1, 4) = '{Year}';
INSERT INTO member_history (histnr, from_mgnr, from_type, to_mgnr, to_type, date, reason, source, shares, value_per_share, currency)
SELECT COALESCE((SELECT MAX(histnr) AS histnr FROM member_history), 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr),
NULL, NULL, u.mgnr, 1, '{date:yyyy-MM-dd}', 'auto', 'elwig',
CEIL((u.diff - {allowanceKg}.0 - {allowanceKgPerShare}.0 * u.shares) / s.max_kg_per_share
- {allowanceShares.ToString(CultureInfo.InvariantCulture)}
- {allowanceRel.ToString(CultureInfo.InvariantCulture)} * u.shares) AS adjust_shares,
s.share_value / POW(10, s.precision - 2), s.currency
UPDATE member
SET business_shares = member.business_shares - h.business_shares
FROM member_history h
WHERE h.date = '{Year}-11-30' AND h.type = 'auto' AND h.mgnr = member.mgnr AND member.active;
INSERT INTO member_history (mgnr, date, type, business_shares)
SELECT u.mgnr,
'{date:yyyy-MM-dd}',
'auto',
CEIL((u.diff - {allowanceKg}.0 - {allowanceKgPerBs}.0 * u.business_shares) / s.max_kg_per_bs
- {allowanceBs.ToString(CultureInfo.InvariantCulture)}
- {allowanceRel.ToString(CultureInfo.InvariantCulture)} * u.business_shares) AS bs
FROM v_total_under_delivery u
JOIN season s ON s.year = u.year
JOIN member m ON m.mgnr = u.mgnr
WHERE s.year = {Year} AND adjust_shares >= {addMinShares} AND m.active
WHERE s.year = {Year} AND bs >= {addMinBs} AND m.active
ON CONFLICT DO UPDATE
SET business_shares = excluded.business_shares;
UPDATE member
SET business_shares = member.business_shares + h.business_shares
FROM member_history h
WHERE h.date = '{Year}-11-30' AND h.type = 'auto' AND h.mgnr = member.mgnr;
""");
}
public async Task UnAdjustBusinessShares() {
using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch($"""
DELETE FROM member_history WHERE source = 'elwig' AND reason = 'auto' AND SUBSTR(date, 1, 4) = '{Year}';
UPDATE member
SET business_shares = member.business_shares - h.business_shares
FROM member_history h
WHERE h.date = '{Year}-11-30' AND h.type = 'auto' AND h.mgnr = member.mgnr AND member.active;
DELETE FROM member_history WHERE date = '{Year}-11-30' AND type = 'auto';
""");
}
+23 -25
View File
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
@@ -93,14 +92,6 @@ namespace Elwig.Helpers.Billing {
Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster;
}
public BillingData() {
Data = new JsonObject {
["mode"] = "elwig",
["version"] = 1,
};
Mode = CalculationMode.Elwig;
}
protected static JsonObject ParseJson(string json) {
if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first");
try {
@@ -116,10 +107,6 @@ namespace Elwig.Helpers.Billing {
return new(ParseJson(json));
}
public string ToJsonString(JsonSerializerOptions? options = null) {
return Data.ToJsonString(options);
}
protected JsonArray GetCurvesEntry() {
return Data[Mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException();
}
@@ -425,7 +412,7 @@ namespace Elwig.Helpers.Billing {
}
}
public static BillingData FromGraphEntries(
public static JsonObject FromGraphEntries(
IEnumerable<GraphEntry> graphEntries,
BillingData? origData = null,
IEnumerable<RawVaribute>? vaributes = null,
@@ -459,29 +446,40 @@ namespace Elwig.Helpers.Billing {
}
}
CollapsePaymentData(payment, payment.DeepClone().AsObject(), vaributes ?? [.. payment.Select(e => new RawVaribute(e.Key))], useDefaultPayment);
CollapsePaymentData(qualityWei, qualityWei.DeepClone().AsObject(), vaributes ?? [.. qualityWei.Select(e => new RawVaribute(e.Key))], useDefaultQuality);
CollapsePaymentData(payment, payment.DeepClone().AsObject(), vaributes ?? payment.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultPayment);
CollapsePaymentData(qualityWei, qualityWei.DeepClone().AsObject(), vaributes ?? qualityWei.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultQuality);
var data = new JsonObject {
["mode"] = "elwig",
["version"] = 1,
};
if (origData?.ConsiderDelieryModifiers == true)
data["consider_delivery_modifiers"] = true;
if (origData?.ConsiderContractPenalties == true)
data["consider_contract_penalties"] = true;
if (origData?.ConsiderTotalPenalty == true)
data["consider_total_penalty"] = true;
if (origData?.ConsiderAutoBusinessShares == true)
data["consider_auto_business_shares"] = true;
BillingData data = origData != null && origData.Mode == CalculationMode.Elwig ? new BillingData((JsonObject)origData.Data.DeepClone()) : new BillingData();
if (payment.Count == 0) {
data.Data["payment"] = 0;
data["payment"] = 0;
} else if (payment.Count == 1 && payment.First().Key == "default") {
data.Data["payment"] = payment.Single().Value?.DeepClone();
data["payment"] = payment.Single().Value?.DeepClone();
} else {
data.Data["payment"] = payment;
data["payment"] = payment;
}
if (qualityWei.Count == 1 && qualityWei.First().Key == "default") {
data.Data["quality"] = new JsonObject() {
data["quality"] = new JsonObject() {
["WEI"] = qualityWei.Single().Value?.DeepClone()
};
} else if (qualityWei.Count >= 1) {
data.Data["quality"] = new JsonObject() {
data["quality"] = new JsonObject() {
["WEI"] = qualityWei
};
} else {
data.Data.Remove("quality");
}
data.Data["curves"] = curves;
data["curves"] = curves;
return data;
}
+12 -30
View File
@@ -10,35 +10,17 @@ namespace Elwig.Helpers.Billing {
public class BillingVariant : Billing {
protected readonly int AvNr;
protected PaymentVar PaymentVariant;
protected PaymentBillingData Data;
protected readonly PaymentVar PaymentVariant;
protected readonly PaymentBillingData Data;
protected BillingVariant(int year, int avnr, Season season,
Dictionary<string, string> attributes,
Dictionary<string, (decimal?, decimal?)> modifiers,
Dictionary<string, (string, string?, string?, int?, decimal?)> areaComTypes,
PaymentVar paymentVar, PaymentBillingData data) :
base(year, season, attributes, modifiers, areaComTypes) {
public BillingVariant(int year, int avnr) : base(year) {
AvNr = avnr;
PaymentVariant = paymentVar;
Data = data;
}
protected static async Task<(PaymentVar, PaymentBillingData)> LoadData(AppDbContext ctx, int year, int avnr) {
var paymentVar = await ctx.PaymentVariants.Where(v => v.Year == year && v.AvNr == avnr).SingleAsync();
var data = PaymentBillingData.FromJson(paymentVar.Data, await Utils.GetVaributes(ctx, year, onlyDelivered: false));
return (paymentVar, data);
}
public static async Task<BillingVariant> Create(int year, int avnr) {
using var ctx = new AppDbContext();
var (season, attributes, modifiers, areaComTypes) = await LoadData(ctx, year);
var (paymentVar, data) = await LoadData(ctx, year, avnr);
return new BillingVariant(year, avnr, season, attributes, modifiers, areaComTypes, paymentVar, data);
PaymentVariant = ctx.PaymentVariants.Include(v => v.Season).Where(v => v.Year == Year && v.AvNr == AvNr).Single() ?? throw new ArgumentException("PaymentVar not found");
Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(ctx, Year, onlyDelivered: false));
}
public async Task Calculate(bool strictPrices = true, bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
if (PaymentVariant == null || Data == null) throw new Exception("Call Load before Calculate");
using var cnx = await AppDbContext.ConnectAsync();
using var tx = await cnx.BeginTransactionAsync();
await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
@@ -76,7 +58,7 @@ namespace Elwig.Helpers.Billing {
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)) +
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, 0, lc.modifiers) AS prev_modifiers
@@ -98,7 +80,7 @@ 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} AND p.amount != COALESCE(lp.amount, 0);
WHERE s.year = {Year} AND v.avnr = {AvNr};
""");
await cnx.ExecuteBatch($"""
UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
@@ -137,11 +119,11 @@ namespace Elwig.Helpers.Billing {
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.weight_total * COALESCE(m.abs, 0)),
ROUND(s.sum * COALESCE(m.abs, 0)),
COALESCE(m.rel, 0)
FROM (SELECT {Year} AS year, m.mgnr,
ROUND(AVG(COALESCE(a.weight_total, b.weight_total)) * {multiplier}) AS baseline,
COUNT(*) = {lastYears} AND MIN(COALESCE(a.weight_total, b.weight_total)) > 0 AS allowed
ROUND(AVG(COALESCE(a.sum, b.sum)) * {multiplier}) AS baseline,
COUNT(*) = {lastYears} AND MIN(COALESCE(a.sum, b.sum)) > 0 AS allowed
FROM member m
LEFT JOIN v_stat_member a ON a.mgnr = m.mgnr
FULL OUTER JOIN v_stat_member b ON b.mgnr = m.predecessor_mgnr AND b.year = a.year AND {(includePredecessor ? "TRUE" : "FALSE")}
@@ -150,7 +132,7 @@ namespace Elwig.Helpers.Billing {
HAVING allowed) c
JOIN v_stat_member s ON (s.year, s.mgnr) = (c.year, c.mgnr)
LEFT JOIN modifier m ON m.year = c.year AND m.name LIKE '{modName}'
WHERE weight_total >= baseline
WHERE sum >= baseline
ON CONFLICT DO UPDATE
SET mod_abs = mod_abs + excluded.mod_abs,
mod_rel = mod_rel + excluded.mod_rel
@@ -232,7 +214,7 @@ namespace Elwig.Helpers.Billing {
SET mod_rel = mod_rel + excluded.mod_rel;
INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0) * d.weight, COALESCE(m.rel, 0)
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0), COALESCE(m.rel, 0)
FROM delivery_part d
LEFT JOIN delivery_part_modifier p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
LEFT JOIN modifier m ON (m.year, m.modid) = (d.year, p.modid)
+8 -9
View File
@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Elwig.Helpers.Billing {
public class EditBillingData : BillingData {
@@ -71,14 +70,14 @@ namespace Elwig.Helpers.Billing {
return (curves, dict3);
}
private static async Task<List<GraphEntry>> CreateGraphEntries(
private static List<GraphEntry> CreateGraphEntries(
AppDbContext ctx, int precision,
Dictionary<int, Curve> curves,
Dictionary<int, List<RawVaribute>> entries
) {
var vars = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v);
var attrs = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.AttrId, a => a);
var cults = await ctx.FetchWineCultivations().ToDictionaryAsync(c => c.CultId, c => c);
var vars = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attrs = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
var cults = ctx.WineCultivations.ToDictionary(c => c.CultId, c => c);
return entries
.Select(e => new GraphEntry(e.Key, precision, curves[e.Key], e.Value
.Select(s => new Varibute(s, vars, attrs, cults))
@@ -86,18 +85,18 @@ namespace Elwig.Helpers.Billing {
.ToList();
}
public async Task<IEnumerable<GraphEntry>> GetPaymentGraphEntries(AppDbContext ctx, Season season) {
public IEnumerable<GraphEntry> GetPaymentGraphEntries(AppDbContext ctx, Season season) {
var root = GetPaymentEntry();
var (curves, entries) = GetGraphEntries(root);
return (await CreateGraphEntries(ctx, season.Precision, curves, entries)).Where(e => e.Vaributes.Count > 0);
return CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
}
public async Task<IEnumerable<GraphEntry>> GetQualityGraphEntries(AppDbContext ctx, Season season, int idOffset = 0) {
public IEnumerable<GraphEntry> GetQualityGraphEntries(AppDbContext ctx, Season season, int idOffset = 0) {
var root = GetQualityEntry();
if (root == null || root["WEI"] is not JsonNode qualityWei)
return [];
var (curves, entries) = GetGraphEntries(qualityWei);
var list = (await CreateGraphEntries(ctx, season.Precision, curves, entries)).Where(e => e.Vaributes.Count > 0);
var list = CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
foreach (var e in list) {
e.Id += idOffset;
e.Abgewertet = true;
+6 -30
View File
@@ -8,18 +8,16 @@ using System.Threading.Tasks;
namespace Elwig.Helpers {
public class ClientParameters {
public enum Type { Matzen, Winzerkeller, Weinland, Baden, Seewinkel };
public enum Type { Matzen, Winzerkeller, Weinland, Baden };
public bool IsMatzen => Client == Type.Matzen;
public bool IsWinzerkeller => Client == Type.Winzerkeller;
public bool IsWeinland => Client == Type.Weinland;
public bool IsBaden => Client == Type.Baden;
public bool IsSeewinkel => Client == Type.Seewinkel;
public bool IsWolkersdorf => IsWinzerkeller && App.ZwstId == "W";
public bool IsHaugsdorf => IsWinzerkeller && App.ZwstId == "H";
public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
public bool IsGrInzersdorf => IsWeinland;
public bool IsPamhagen => IsSeewinkel;
public string NameToken;
public string NameShort;
@@ -56,10 +54,6 @@ namespace Elwig.Helpers {
public string? EmailAddress;
public string? Website;
public bool EnableMemberHistory;
public int ModeBusinessShares;
public bool HasRedWhite => ModeBusinessShares == 1;
public int ModeDeliveryNoteStats;
public int ModeWineQualityStatistics;
public int OrderingMemberList;
@@ -79,7 +73,7 @@ namespace Elwig.Helpers {
public int ExportEbicsVersion;
public int ExportEbicsAddress;
public (int? AllowanceKg, double? AllowanceShares, int? AllowanceKgPerShare, double? AllowancePercent, int? MinShares) AutoAdjustShares;
public (int? AllowanceKg, double? AllowanceBs, int? AllowanceKgPerBs, double? AllowancePercent, int? MinBs) AutoAdjustBs;
public ClientParameters(AppDbContext ctx) : this(ctx.ClientParameters.ToDictionary(e => e.Param, e => e.Value)) { }
@@ -99,8 +93,6 @@ namespace Elwig.Helpers {
Client = Type.Weinland; break;
case "Winzergenossenschaft Baden - Bad Vöslau":
Client = Type.Baden; break;
case "Winzerkeller Seewinkel":
Client = Type.Seewinkel; break;
};
Plz = int.Parse(parameters["CLIENT_PLZ"] ?? "");
@@ -116,15 +108,6 @@ namespace Elwig.Helpers {
Iban = parameters.GetValueOrDefault("CLIENT_IBAN");
OrganicAuthority = parameters.GetValueOrDefault("CLIENT_ORGANIC_AUTHORITY");
EnableMemberHistory = (parameters.GetValueOrDefault("ENABLE_MEMBERHISTORY")?.ToUpper()) switch {
"1" or "TRUE" or "YES" or "JA" => true,
_ => false,
};
switch (parameters.GetValueOrDefault("MODE_BUSINESSSHARES", "SIMPLE")?.ToUpper()) {
case "SIMPLE": ModeBusinessShares = 0; break;
case "RED_WHITE": ModeBusinessShares = 1; break;
}
switch (parameters.GetValueOrDefault("MODE_DELIVERYNOTE_STATS", "SHORT")?.ToUpper()) {
case "NONE": ModeDeliveryNoteStats = 0; break;
case "GA_ONLY": ModeDeliveryNoteStats = 1; break;
@@ -189,7 +172,7 @@ namespace Elwig.Helpers {
}
var autoAdjust = (parameters.GetValueOrDefault("AUTOADJUST_BUSINESSSHARES") ?? "").Split(';');
AutoAdjustShares = autoAdjust.Length == 5 ? (
AutoAdjustBs = autoAdjust.Length == 5 ? (
int.TryParse(autoAdjust[0], out var v1) ? v1 : null,
double.TryParse(autoAdjust[1], out var v2) ? v2 : null,
int.TryParse(autoAdjust[2], out var v3) ? v3 : null,
@@ -202,11 +185,6 @@ namespace Elwig.Helpers {
}
private IEnumerable<(string, string?)> GetParamValues() {
string businessShares = "SIMPLE";
switch (ModeBusinessShares) {
case 0: businessShares = "SIMPLE"; break;
case 1: businessShares = "RED_WHITE"; break;
}
string deliveryNoteStats = "SHORT";
switch (ModeDeliveryNoteStats) {
case 0: deliveryNoteStats = "NONE"; break;
@@ -253,9 +231,9 @@ namespace Elwig.Helpers {
case 1: exportEbicsAddress = "LINES"; break;
case 2: exportEbicsAddress = "FULL"; break;
}
string autoAdjust = $"{AutoAdjustShares.AllowanceKg};{AutoAdjustShares.AllowanceShares?.ToString(CultureInfo.InvariantCulture)};" +
$"{AutoAdjustShares.AllowanceKgPerShare};{AutoAdjustShares.AllowancePercent?.ToString(CultureInfo.InvariantCulture)};" +
$"{AutoAdjustShares.MinShares}";
string autoAdjust = $"{AutoAdjustBs.AllowanceKg};{AutoAdjustBs.AllowanceBs?.ToString(CultureInfo.InvariantCulture)};" +
$"{AutoAdjustBs.AllowanceKgPerBs};{AutoAdjustBs.AllowancePercent?.ToString(CultureInfo.InvariantCulture)};" +
$"{AutoAdjustBs.MinBs}";
return [
("CLIENT_NAME_TOKEN", NameToken),
("CLIENT_NAME_SHORT", NameShort),
@@ -274,8 +252,6 @@ namespace Elwig.Helpers {
("CLIENT_BIC", Bic),
("CLIENT_IBAN", Iban),
("CLIENT_ORGANIC_AUTHORITY", OrganicAuthority),
("ENABLE_MEMBERHISTORY", EnableMemberHistory ? "YES" : "NO"),
("MODE_BUSINESSSHARES", businessShares),
("MODE_DELIVERYNOTE_STATS", deliveryNoteStats),
("MODE_WINEQUALITYSTATISTICS", modeWineQualityStatistics),
("ORDERING_MEMBERLIST", orderingMemberList),
+99 -291
View File
@@ -1,5 +1,4 @@
using Elwig.Models.Entities;
using Elwig.Services;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
@@ -9,6 +8,7 @@ using System.IO.Compression;
using System.Linq;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Export {
public static class ElwigData {
@@ -41,7 +41,7 @@ namespace Elwig.Helpers.Export {
List<WbGl> currentWbGls;
using (var ctx = new AppDbContext()) {
branches = await ctx.FetchBranches().ToDictionaryAsync(b => b.ZwstId);
branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId);
currentDids = await ctx.Deliveries
.GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));
@@ -59,16 +59,14 @@ namespace Elwig.Helpers.Export {
List<BillingAddr> BillingAddresses,
List<MemberTelNr> TelephoneNumbers,
List<MemberEmailAddr> EmailAddresses,
List<MemberHistory> MemberHistory,
List<AreaCom> AreaCommitments,
List<AreaComContract> Contracts,
List<WbRd> Riede,
List<WbKg> WbKgs,
List<WbGl> WbGls,
List<Delivery> Deliveries,
List<DeliveryPart> DeliveryParts,
List<DeliveryPartModifier> Modifiers,
Dictionary<string, List<(int Id1, int Id2, int Id3, DateTime CreatedAt, DateTime ModifiedAt)>> Timestamps)>();
Dictionary<string, List<(int Id1, int Id2, DateTime CreatedAt, DateTime ModifiedAt)>> Timestamps)>();
var metaData = new List<(string FileName, string ZwstId, string Device,
int? MemberNum, string? MemberFilters,
@@ -77,12 +75,10 @@ namespace Elwig.Helpers.Export {
foreach (var filename in filenames) {
try {
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], [], [], new() {
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], new() {
["member"] = [],
["area_commitment_contract"] = [],
["area_commitment"] = [],
["delivery"] = [],
["delivery_part"] = [],
})));
var r = data[^1];
@@ -90,11 +86,9 @@ namespace Elwig.Helpers.Export {
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
await zip.CheckIntegrity();
string[] acceptableVersions = ["1", "2"];
var version = zip.GetEntry("version");
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
var v = await reader.ReadToEndAsync();
if (!v.StartsWith("elwig:") || !acceptableVersions.Contains(v[6..]))
if (await reader.ReadToEndAsync() != "elwig:1")
throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})");
}
@@ -102,8 +96,8 @@ namespace Elwig.Helpers.Export {
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_commitment_contracts"]?["count"]?.AsValue().GetValue<int>();
var areaComFilters = meta!["area_commitment_contracts"]?["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),
@@ -139,59 +133,23 @@ namespace Elwig.Helpers.Export {
r.TelephoneNumbers.AddRange(telNrs);
r.EmailAddresses.AddRange(emailAddrs);
if (timestamps.HasValue)
r.Timestamps["member"].Add((m.MgNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
var historyJson = zip.GetEntry("member_history.json");
if (historyJson != null) {
using var reader = new StreamReader(historyJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var h = obj.ToMemberHistory();
r.MemberHistory.Add(h);
}
}
// legacy area commitments
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 (contract, areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
r.Contracts.Add(contract);
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_contract"].Add((contract.FbNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
r.Timestamps["area_commitment"].Add((areaCom.FbNr, areaCom.RevNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
}
var contractsJson = zip.GetEntry("area_commitment_contracts.json");
if (contractsJson != null) {
using var reader = new StreamReader(contractsJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (contract, areaComs, wbrd, timestamps) = obj.ToAreaComContract(currentWbRde);
r.Contracts.Add(contract);
r.AreaCommitments.AddRange(areaComs.Select(v => v.Item1));
if (wbrd != null) {
r.Riede.Add(wbrd);
}
if (timestamps.HasValue)
r.Timestamps["area_commitment_contract"].Add((contract.FbNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
foreach (var (areaCom, ts) in areaComs) {
if (!ts.HasValue) continue;
r.Timestamps["area_commitment"].Add((areaCom.FbNr, areaCom.RevNr, 0, ts.Value.CreatedAt, ts.Value.ModifiedAt));
}
r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
@@ -203,15 +161,11 @@ namespace Elwig.Helpers.Export {
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.Select(p => p.Item1));
r.DeliveryParts.AddRange(parts);
r.Modifiers.AddRange(mods);
r.Riede.AddRange(rde);
if (timestamps.HasValue)
r.Timestamps["delivery"].Add((d.Year, d.DId, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
foreach (var (part, ts) in parts) {
if (!ts.HasValue) continue;
r.Timestamps["area_commitment"].Add((part.Year, part.DId, part.DPNr, ts.Value.CreatedAt, ts.Value.ModifiedAt));
}
r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
}
}
} catch (Exception exc) when (
@@ -220,21 +174,26 @@ namespace Elwig.Helpers.Export {
exc is FileNotFoundException ||
exc is IOException) {
data.RemoveAt(data.Count - 1);
InteractionService.ShowException("Fehler beim Importieren", $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen", exc);
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);
if (InteractionService.AskException("Fehler beim Importieren", $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?", exc)) {
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));
}
}
}
var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>();
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>();
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>();
var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
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, history, areaCommitments, contracts, riede, wbKgs, wbGls, 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;
@@ -246,12 +205,6 @@ namespace Elwig.Helpers.Export {
.Select(k => k.KgNr)
.ToListAsync();
var histNrs = history.Select(h => h.HistNr).ToList();
var duplicateHistNrs = await ctx.MemberHistory
.Where(h => histNrs.Contains(h.HistNr))
.Select(h => h.HistNr)
.ToListAsync();
var mgnrs = members.Select(m => m.MgNr).ToList();
var duplicateMgNrs = await ctx.Members
.Where(m => mgnrs.Contains(m.MgNr))
@@ -267,20 +220,11 @@ namespace Elwig.Helpers.Export {
if (duplicateMgNrs.Count > 0)
importDuplicateMembers = ImportQuestion(branch.Name, device, "Mitglieder", true, duplicateMgNrs.Count);
var fbnrs = contracts.Select(c => c.FbNr).ToList();
var duplicateFbNrs = await ctx.AreaCommitmentContracts
var fbnrs = areaCommitments.Select(c => c.FbNr).ToList();
var duplicateFbNrs = await ctx.AreaCommitments
.Where(c => fbnrs.Contains(c.FbNr))
.Select(c => c.FbNr)
.ToListAsync();
bool importNewContracts = false, importDuplicateContracts = false;
if (mode == ImportMode.Interactively) {
if (fbnrs.Count - duplicateFbNrs.Count > 0)
importNewContracts = ImportQuestion(branch.Name, device, "Flächenbindungsverträge", false, fbnrs.Count - duplicateFbNrs.Count);
} else {
importNewContracts = true;
}
if (duplicateFbNrs.Count > 0)
importDuplicateContracts = ImportQuestion(branch.Name, device, "Flächenbindungsverträge", true, duplicateFbNrs.Count);
var lsnrs = deliveries.Select(d => d.LsNr).ToList();
var duplicateLsNrs = await ctx.Deliveries
@@ -314,52 +258,40 @@ namespace Elwig.Helpers.Export {
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
}
if (importDuplicateMembers || importNewMembers || importDuplicateContracts || importNewContracts || importDuplicateDeliveries || importNewDeliveries) {
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.IgnoreAutoIncludes().Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.RemoveRange(ctx.MemberTelephoneNrs.IgnoreAutoIncludes().Where(n => duplicateMgNrs.Contains(n.MgNr)));
ctx.RemoveRange(ctx.MemberEmailAddrs.IgnoreAutoIncludes().Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr)));
ctx.RemoveRange(ctx.MemberEmailAddrs.Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.UpdateRange(members.Where(m => duplicateMgNrs.Contains(m.MgNr)));
ctx.AddRange(billingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.AddRange(telephoneNumbers.Where(n => duplicateMgNrs.Contains(n.MgNr)));
ctx.AddRange(emailAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
ctx.UpdateRange(areaCommitments.Where(c => duplicateMgNrs.Contains(c.MgNr) && duplicateFbNrs.Contains(c.FbNr)));
ctx.AddRange(areaCommitments.Where(c => duplicateMgNrs.Contains(c.MgNr) && !duplicateFbNrs.Contains(c.FbNr)));
}
if (importNewMembers) {
ctx.AddRange(members.Where(m => !duplicateMgNrs.Contains(m.MgNr)));
ctx.AddRange(billingAddresses.Where(a => !duplicateMgNrs.Contains(a.MgNr)));
ctx.AddRange(telephoneNumbers.Where(n => !duplicateMgNrs.Contains(n.MgNr)));
ctx.AddRange(emailAddresses.Where(a => !duplicateMgNrs.Contains(a.MgNr)));
ctx.UpdateRange(areaCommitments.Where(c => !duplicateMgNrs.Contains(c.MgNr) && duplicateFbNrs.Contains(c.FbNr)));
ctx.AddRange(areaCommitments.Where(c => !duplicateMgNrs.Contains(c.MgNr) && !duplicateFbNrs.Contains(c.FbNr)));
}
if (members.Count > 0) {
var n = importNewMembers ? members.Count - duplicateMgNrs.Count : 0;
var o = importDuplicateMembers ? duplicateMgNrs.Count : 0;
importedMembers.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, members.Count - n - o, meta.MemberFilters));
}
if (importDuplicateMembers || importNewMembers) {
ctx.UpdateRange(history.Where(h => duplicateHistNrs.Contains(h.HistNr)));
ctx.AddRange(history.Where(h => !duplicateHistNrs.Contains(h.HistNr)));
}
if (importDuplicateContracts) {
ctx.RemoveRange(ctx.AreaCommitments.IgnoreAutoIncludes().Where(c => duplicateFbNrs.Contains(c.FbNr)));
ctx.UpdateRange(contracts.Where(c => duplicateFbNrs.Contains(c.FbNr)));
ctx.AddRange(areaCommitments.Where(c => duplicateFbNrs.Contains(c.FbNr)));
}
if (importNewContracts) {
ctx.AddRange(contracts.Where(c => !duplicateFbNrs.Contains(c.FbNr)));
ctx.AddRange(areaCommitments.Where(c => !duplicateFbNrs.Contains(c.FbNr)));
}
if (contracts.Count > 0) {
if (areaCommitments.Count > 0) {
ctx.AddRange(riede);
var n = importNewContracts ? contracts.Count - duplicateFbNrs.Count : 0;
var o = importDuplicateContracts ? duplicateFbNrs.Count : 0;
importedAreaComs.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, contracts.Count - n - o, meta.AreaComFilters));
var imported = areaCommitments.Where(c => (importNewMembers && !duplicateMgNrs.Contains(c.MgNr)) || (importDuplicateMembers && duplicateMgNrs.Contains(c.MgNr))).ToList();
importedAreaComs.Add((meta.FileName, meta.ZwstId, meta.Device, imported.Count, areaCommitments.Count - imported.Count, meta.AreaComFilters));
}
if (allowedDuplicateLsNrs.Count > 0) {
@@ -368,10 +300,9 @@ namespace Elwig.Helpers.Export {
.Select(d => (d.Year, d.DId))
.ToList();
ctx.RemoveRange(ctx.DeliveryParts
.IgnoreAutoIncludes()
.Where(p => allowedDuplicateLsNrs.Contains(p.Delivery.LsNr))
.SelectMany(p => p.PartModifiers));
ctx.RemoveRange(ctx.DeliveryParts.IgnoreAutoIncludes().Where(p => allowedDuplicateLsNrs.Contains(p.Delivery.LsNr)));
ctx.RemoveRange(ctx.DeliveryParts.Where(p => allowedDuplicateLsNrs.Contains(p.Delivery.LsNr)));
ctx.UpdateRange(deliveries.Where(d => dids.Contains((d.Year, d.DId))));
ctx.AddRange(deliveryParts.Where(p => dids.Contains((p.Year, p.DId))));
ctx.AddRange(modifiers.Where(m => dids.Contains((m.Year, m.DId))));
@@ -383,10 +314,9 @@ namespace Elwig.Helpers.Export {
.Select(d => (d.Year, d.DId))
.ToList();
ctx.RemoveRange(ctx.DeliveryParts
.IgnoreAutoIncludes()
.Where(p => l.Contains(p.Delivery.LsNr))
.SelectMany(p => p.PartModifiers));
ctx.RemoveRange(ctx.DeliveryParts.IgnoreAutoIncludes().Where(p => l.Contains(p.Delivery.LsNr)));
ctx.RemoveRange(ctx.DeliveryParts.Where(p => l.Contains(p.Delivery.LsNr)));
ctx.UpdateRange(deliveries.Where(d => dids.Contains((d.Year, d.DId))));
ctx.AddRange(deliveryParts.Where(p => dids.Contains((p.Year, p.DId))));
ctx.AddRange(modifiers.Where(m => dids.Contains((m.Year, m.DId))));
@@ -402,22 +332,18 @@ namespace Elwig.Helpers.Export {
importedDeliveries.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, deliveries.Count - n - o, meta.DeliveryFilters));
}
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
await ctx.SaveChangesAsync();
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
var primaryKeys = new Dictionary<string, string>() {
["member"] = "mgnr, 0, 0",
["area_commitment_contract"] = "fbnr, 0, 0",
["area_commitment"] = "fbnr, revnr, 0",
["delivery"] = "year, did, 0",
["delivery_part"] = "year, did, dpnr",
["member"] = "mgnr, 0",
["area_commitment"] = "fbnr, 0",
["delivery"] = "year, did",
};
var updateStmts = timestamps
.SelectMany(e => e.Value.Select(m => $"UPDATE {e.Key} " +
$"SET ctime = {((DateTimeOffset)m.CreatedAt.ToUniversalTime()).ToUnixTimeSeconds()}, " +
$"mtime = {((DateTimeOffset)m.ModifiedAt.ToUniversalTime()).ToUnixTimeSeconds()} " +
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2}, {m.Id3});"));
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2});"));
using var cnx = AppDbContext.Connect();
await cnx.ExecuteBatch($"""
BEGIN;
@@ -431,7 +357,7 @@ namespace Elwig.Helpers.Export {
}
App.HintContextChange();
InteractionService.ShowInformation("Importieren erfolgreich",
MessageBox.Show(
$"Das importieren der Daten war erfolgreich!\n" +
$"Folgendes wurde importiert:\n" +
string.Join("\n", [
@@ -441,10 +367,10 @@ namespace Elwig.Helpers.Export {
$" ({d.New} neu, {d.Overwritten} überschrieben, {d.NotImported} nicht importiert)\n" +
$" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" +
$" Filter: {d.Filters}"),
$"Flächenbindungen: {importedAreaComs.Sum(d => d.New + d.Overwritten)}",
$"Flächenbindungen: {importedAreaComs.Sum(d => d.Imported)}",
..importedAreaComs.Select(d =>
$" {d.FileName} ({d.New + d.Overwritten})\n" +
$" ({d.New} importiert, {d.Overwritten} überschreiben, {d.NotImported} nicht importiert)\n" +
$" {d.FileName} ({d.Imported})\n" +
$" ({d.Imported} importiert, {d.NotImported} nicht importiert)\n" +
$" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" +
$" Filter: {d.Filters}"),
$"Lieferungen: {importedDeliveries.Sum(d => d.New + d.Overwritten)}",
@@ -453,32 +379,37 @@ namespace Elwig.Helpers.Export {
$" ({d.New} neu, {d.Overwritten} überschr., {d.NotImported} nicht importiert)\n" +
$" Zwst.: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" +
$" Filter: {d.Filters}")
]));
]),
"Importieren erfolgreich",
MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception exc) {
InteractionService.ShowException("Fehler beim Importieren", "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\nEvtl. muss die Datenbank manuell auf dieses Gerät kopieren werden", exc);
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\nEvtl. muss die Datenbank manuell auf dieses Gerät kopieren werden.\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
private static bool ImportQuestion(string branch, string device, string subject, bool duplicate, int number) {
return InteractionService.AskQuestion($"{subject} importieren",
return MessageBox.Show(
$"Sollen {number} {(duplicate ? "" : "neue ")}{subject} durch die Zweigstelle\n" +
$"{branch} (Gerät {device}) {(duplicate ? "überschrieben" : "importiert")} werden?", true);
$"{branch} (Gerät {device}) {(duplicate ? "überschrieben" : "importiert")} werden?",
$"{subject} importieren",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes
) == MessageBoxResult.Yes;
}
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<MemberHistory> history, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
return new ElwigExport {
Members = (members, filters),
History = (history, ["von exportierten Mitgliedern"]),
WbKgs = (wbKgs, ["von exportierten Mitgliedern"]),
}.Export(filename);
}
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<MemberHistory> history, IEnumerable<AreaComContract> areaComs, IEnumerable<WbKg> wbKgs, 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),
History = (history, ["von exportierten Mitgliedern"]),
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]),
}.Export(filename);
@@ -494,8 +425,7 @@ namespace Elwig.Helpers.Export {
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<MemberHistory> History, IEnumerable<string> Filters)? History { get; set; }
public (IEnumerable<AreaComContract> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
public (IEnumerable<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
public async Task Export(string filename) {
@@ -504,7 +434,7 @@ namespace Elwig.Helpers.Export {
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
await writer.WriteAsync("elwig:2");
await writer.WriteAsync("elwig:1");
}
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
@@ -514,25 +444,20 @@ namespace Elwig.Helpers.Export {
["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName,
};
if (WbKgs != null)
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(),
["filters"] = new JsonArray(Members.Value.Filters.Select(f => (JsonNode)f).ToArray()),
};
if (History != null)
obj["member_history"] = new JsonObject {
["count"] = History.Value.History.Count(),
["filters"] = new JsonArray(History.Value.Filters.Select(f => (JsonNode)f).ToArray()),
};
if (AreaComs != null)
obj["area_commitment_contracts"] = new JsonObject {
obj["area_commitments"] = new JsonObject {
["count"] = AreaComs.Value.AreaComs.Count(),
["revisions"] = AreaComs.Value.AreaComs.Sum(c => c.Revisions.Count),
["filters"] = new JsonArray(AreaComs.Value.Filters.Select(f => (JsonNode)f).ToArray()),
};
if (Deliveries != null)
@@ -559,15 +484,8 @@ namespace Elwig.Helpers.Export {
await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts));
}
}
if (History != null) {
var json = zip.CreateEntry("member_history.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var h in History.Value.History) {
await writer.WriteLineAsync(h.ToJson().ToJsonString(Utils.JsonOpts));
}
}
if (AreaComs != null) {
var json = zip.CreateEntry("area_commitment_contracts.json", CompressionLevel.SmallestSize);
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(Utils.JsonOpts));
@@ -624,10 +542,7 @@ namespace Elwig.Helpers.Export {
["birthday"] = m.Birthday,
["entry_date"] = m.EntryDate != null ? $"{m.EntryDate:yyyy-MM-dd}" : null,
["exit_date"] = m.ExitDate != null ? $"{m.ExitDate:yyyy-MM-dd}" : null,
["shares"] = m.Shares,
["shares_red"] = m.SharesRed,
["shares_white"] = m.SharesWhite,
["shares_dormant"] = m.SharesDormant,
["business_shares"] = m.BusinessShares,
["accounting_nr"] = m.AccountingNr,
["zwstid"] = m.ZwstId,
["lfbis_nr"] = m.LfbisNr,
@@ -696,10 +611,7 @@ namespace Elwig.Helpers.Export {
Birthday = json["birthday"]?.AsValue().GetValue<string>(),
EntryDateString = json["entry_date"]?.AsValue().GetValue<string>(),
ExitDateString = json["exit_date"]?.AsValue().GetValue<string>(),
Shares = json["shares"]?.AsValue().GetValue<int>() ?? json["business_shares"]?.AsValue().GetValue<int>() ?? 0,
SharesRed = json["shares_red"]?.AsValue().GetValue<int>() ?? 0,
SharesWhite = json["shares_white"]?.AsValue().GetValue<int>() ?? 0,
SharesDormant = json["shares_dormant"]?.AsValue().GetValue<int>() ?? 0,
BusinessShares = json["business_shares"]?.AsValue().GetValue<int>() ?? 0,
AccountingNr = json["accounting_nr"]?.AsValue().GetValue<string>(),
ZwstId = json["zwstid"]?.AsValue().GetValue<string>(),
LfbisNr = json["lfbis_nr"]?.AsValue().GetValue<string>(),
@@ -727,88 +639,44 @@ namespace Elwig.Helpers.Export {
CountryNum = a["country"]!.AsValue().GetValue<int>(),
PostalDestId = a["postal_dest"]!.AsValue().GetValue<string>(),
Address = a["address"]!.AsValue().GetValue<string>(),
} : null, [.. json["telephone_numbers"]!.AsArray().Select(n => n!.AsObject()).Select((n, i) => new MemberTelNr {
} : null, json["telephone_numbers"]!.AsArray().Select(n => n!.AsObject()).Select((n, i) => new MemberTelNr {
MgNr = mgnr,
Nr = i + 1,
Type = n["type"]!.AsValue().GetValue<string>(),
Number = n["number"]!.AsValue().GetValue<string>(),
Comment = n["comment"]?.AsValue().GetValue<string>(),
})], [.. json["email_addresses"]!.AsArray().Select(a => a!.AsObject()).Select((a, i) => new MemberEmailAddr {
}).ToList(), json["email_addresses"]!.AsArray().Select(a => a!.AsObject()).Select((a, i) => new MemberEmailAddr {
MgNr = mgnr,
Nr = i + 1,
Address = a["address"]!.AsValue().GetValue<string>(),
Comment = a["comment"]?.AsValue().GetValue<string>(),
})],
}).ToList(),
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)));
}
public static JsonObject ToJson(this MemberHistory h) {
return new JsonObject {
["histnr"] = h.HistNr,
["from_mgnr"] = h.FromMgNr,
["from_type"] = h.FromType,
["to_mgnr"] = h.ToMgNr,
["to_type"] = h.ToType,
["date"] = h.DateString,
["reason"] = h.Reason,
["source"] = h.Source,
["shares"] = h.Shares,
["value_per_share"] = h.ValuePerShare,
["currency"] = h.CurrencyCode,
["comment"] = h.Comment,
};
}
public static MemberHistory ToMemberHistory(this JsonNode json) {
return new MemberHistory {
HistNr = json["histnr"]!.AsValue().GetValue<int>(),
FromMgNr = json["from_mgnr"]?.AsValue().GetValue<int>(),
FromType = json["from_type"]?.AsValue().GetValue<int>(),
ToMgNr = json["to_mgnr"]?.AsValue().GetValue<int>(),
ToType = json["to_type"]?.AsValue().GetValue<int>(),
DateString = json["date"]!.AsValue().GetValue<string>(),
Reason = json["reason"]!.AsValue().GetValue<string>(),
Source = json["source"]!.AsValue().GetValue<string>(),
Shares = json["shares"]!.AsValue().GetValue<int>(),
ValuePerShare = json["value_per_share"]?.AsValue().GetValue<decimal>(),
CurrencyCode = json["currency"]?.AsValue().GetValue<string>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
};
}
public static JsonObject ToJson(this AreaComContract c) {
public static JsonObject ToJson(this AreaCom c) {
return new JsonObject {
["fbnr"] = c.FbNr,
["mgnr"] = c.MgNr,
["vtrgid"] = c.VtrgId,
["cultid"] = c.CultId,
["area"] = c.Area,
["kgnr"] = c.KgNr,
["gstnr"] = c.GstNr,
["ried"] = c.Rd?.Name,
["revisions"] = new JsonArray(c.Revisions.OrderBy(r => r.RevNr).Select(r => r.ToJson()).ToArray()),
["year_from"] = c.YearFrom,
["year_to"] = c.YearTo,
["comment"] = c.Comment,
["created_at"] = $"{c.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{c.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
private static JsonObject ToJson(this AreaCom c) {
return new JsonObject {
["revnr"] = c.RevNr,
["mgnr"] = c.MgNr,
["vtrgid"] = c.VtrgId,
["cultid"] = c.CultId,
["area"] = c.Area,
["gstnr"] = c.GstNr,
["year_from"] = c.YearFrom,
["year_to"] = c.YearTo,
["created_at"] = $"{c.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{c.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (AreaComContract, List<(AreaCom, (DateTime CreatedAt, DateTime ModifiedAt)?)>, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaComContract(this JsonNode json, 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>();
var fbnr = json["fbnr"]!.AsValue()!.GetValue<int>();
WbRd? rd = null;
bool newRd = false;
if (ried != null) {
@@ -825,74 +693,20 @@ namespace Elwig.Helpers.Export {
riede[rd.KgNr] = rde;
}
}
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaComContract {
FbNr = fbnr,
KgNr = kgnr,
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now,
}, [.. json["revisions"]!.AsArray().Select(r => r!.AsObject()).Select<JsonObject, (AreaCom, (DateTime, DateTime)?)>(r => {
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaCom {
FbNr = fbnr,
RevNr = r["revnr"]!.AsValue().GetValue<int>(),
MgNr = r["mgnr"]!.AsValue().GetValue<int>(),
VtrgId = r["vtrgid"]!.AsValue().GetValue<string>(),
CultId = r["cultid"]?.AsValue().GetValue<string>(),
Area = r["area"]!.AsValue().GetValue<int>(),
GstNr = r["gstnr"]?.AsValue().GetValue<string>() ?? "-",
YearFrom = r["year_from"]?.AsValue().GetValue<int>(),
YearTo = r["year_to"]?.AsValue().GetValue<int>(),
ImportedAt = DateTime.Now,
}, 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)));
})], newRd ? rd : null,
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)));
}
public static (AreaComContract, 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.GetValueOrDefault(kgnr, []);
rd = rde.FirstOrDefault(r => r.Name == ried);
if (rd == null) {
newRd = true;
rd = new WbRd {
KgNr = kgnr,
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>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaComContract {
FbNr = json["fbnr"]!.AsValue().GetValue<int>(),
KgNr = kgnr,
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now,
}, new AreaCom {
FbNr = json["fbnr"]!.AsValue().GetValue<int>(),
RevNr = 1,
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
VtrgId = json["vtrgid"]!.AsValue().GetValue<string>(),
CultId = json["cultid"]?.AsValue().GetValue<string>(),
Area = json["area"]!.AsValue().GetValue<int>(),
KgNr = kgnr,
GstNr = json["gstnr"]?.AsValue().GetValue<string>() ?? "-",
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>(),
ImportedAt = DateTime.Now,
}, newRd ? rd : null,
createdAt == null || modifiedAt == null ? null :
@@ -909,14 +723,7 @@ namespace Elwig.Helpers.Export {
["lnr"] = d.LNr,
["time"] = d.Time != null ? $"{d.Time:HH:mm:ss}" : null,
["mgnr"] = d.MgNr,
["parts"] = new JsonArray(d.Parts.OrderBy(p => p.DPNr).Select(p => p.ToJson()).ToArray()),
["comment"] = d.Comment,
["created_at"] = $"{d.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{d.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
private static JsonObject ToJson(this DeliveryPart p) {
["parts"] = new JsonArray(d.Parts.OrderBy(p => p.DPNr).Select(p => {
var obj = new JsonObject {
["dpnr"] = p.DPNr,
["sortid"] = p.SortId,
@@ -930,7 +737,7 @@ namespace Elwig.Helpers.Export {
["ried"] = p.Rd?.Name,
["net_weight"] = p.IsNetWeight,
["manual_weighing"] = p.IsManualWeighing,
["modids"] = new JsonArray(p.PartModifiers.Select(m => (JsonNode)m.ModId).ToArray()),
["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
["comment"] = p.Comment,
["created_at"] = $"{p.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{p.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
@@ -945,9 +752,14 @@ namespace Elwig.Helpers.Export {
if (p.WeighingData != null) obj["weighing_data"] = JsonNode.Parse(p.WeighingData);
if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason;
return obj;
}).ToArray()),
["comment"] = d.Comment,
["created_at"] = $"{d.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
["modified_at"] = $"{d.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
};
}
public static (Delivery, List<(DeliveryPart, (DateTime CreatedAt, DateTime ModifiedAt)?)>, 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) {
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);
@@ -970,16 +782,16 @@ 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<JsonObject, (DeliveryPart, (DateTime, DateTime)?)>(p => {
var kgnr = p["kgnr"]?.AsValue().GetValue<int>();
}, 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, []);
if (ried != null) {
var rde = riede.GetValueOrDefault(kgnr, []);
rd = rde.FirstOrDefault(r => r.Name == ried);
if (rd == null) {
rd = new WbRd {
KgNr = kgnr.Value,
KgNr = kgnr,
RdNr = (rde.Count == 0 ? 1 : rde.Max(r => r.RdNr)) + 1,
Name = ried,
};
@@ -988,9 +800,7 @@ namespace Elwig.Helpers.Export {
wbRde.Add(rd);
}
}
var createdAt = p["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = p["modified_at"]?.AsValue().GetValue<string>();
return (new DeliveryPart {
return new DeliveryPart {
Year = year,
DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
@@ -1015,15 +825,13 @@ namespace Elwig.Helpers.Export {
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
}, 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)));
})], [.. json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
};
}).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),
@@ -0,0 +1,176 @@
using Elwig.Documents;
using iText.IO.Font.Constants;
using iText.Kernel.Font;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Action;
using iText.Kernel.Pdf.Canvas;
using iText.Kernel.Pdf.Event;
using iText.Kernel.Pdf.Xobject;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using System;
using System.Collections.Generic;
namespace Elwig.Helpers.Printing {
public class FooterEventHandler : AbstractPdfDocumentEventHandler {
private const float _fontSize = 10;
private const float _ptInMm = 2.8346456693f;
private const float _placeholderWidth = 50 * _ptInMm;
private readonly string _date;
private readonly string? _center;
private readonly bool _doublePaged;
private readonly bool _isPreview;
private readonly bool _isBusiness;
private readonly bool _showFoldMarks;
private readonly PdfFont _font;
private readonly PdfFont _fontBold;
private readonly PdfFont _fontItalic;
private readonly List<PdfFormXObject> _pageNumPlaceholders;
public int NumberOfPages { get; private set; }
public FooterEventHandler(Documents.Document? doc = null) {
_date = $"{doc?.Date ?? DateOnly.FromDateTime(Utils.Today):dddd, d. MMMM yyyy}";
_center = doc?.DocumentId;
_doublePaged = doc?.IsDoublePaged ?? false;
_isPreview = doc?.IsPreview ?? false;
_isBusiness = doc is BusinessDocument;
_showFoldMarks = doc?.ShowFoldMarks ?? false;
_font = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN);
_fontBold = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLD);
_fontItalic = PdfFontFactory.CreateFont(StandardFonts.TIMES_ITALIC);
_pageNumPlaceholders = [];
}
protected override void OnAcceptedEvent(AbstractPdfDocumentEvent evt) {
if (evt.GetType() == PdfDocumentEvent.END_PAGE) {
OnPageEnd((PdfDocumentEvent)evt);
} else if (evt.GetType() == PdfDocumentEvent.START_DOCUMENT_CLOSING) {
OnDocumentClose((PdfDocumentEvent)evt);
}
}
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 y = footerY + _fontSize;
float footerWidth = 165 * _ptInMm;
float footerHeight = 25 * _ptInMm;
var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdf);
using var canvas = new Canvas(pdfCanvas, pageSize);
var placeholder = new PdfFormXObject(new Rectangle(0, 0, _placeholderWidth, _fontSize));
_pageNumPlaceholders.Add(placeholder);
var c = App.Client;
var dateP = new Paragraph(_date).SetFont(_font).SetFontSize(_fontSize);
var centerP = new Paragraph(_center ?? "").SetFont(_fontItalic).SetFontSize(_fontSize);
var pageNumP = new Paragraph().Add(new Image(placeholder)).SetFont(_font).SetFontSize(_fontSize);
if (pageNum == 1) {
// First page
canvas.ShowTextAligned(dateP, leftX1, y, TextAlignment.LEFT);
canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, y, TextAlignment.CENTER);
canvas.ShowTextAligned(pageNumP, rightX1, y, TextAlignment.RIGHT);
var footerP = new Paragraph().SetFont(_font).SetFontSize(_fontSize)
.SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.TOP)
.SetMargin(0).SetMultipliedLeading(1)
.SetWidth(footerWidth).SetHeight(footerHeight).SetPaddingTop(1 * _ptInMm).SetBorderTop(new SolidBorder(0.5f))
.Add(c.NameFull);
if (_isBusiness) {
footerP.Add("\n");
footerP.AddAll(Utils.GenerateFooter("\n", " \u00b7 ")
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
.Item(c.EmailAddress != null ? new Link(c.EmailAddress, PdfAction.CreateURI($"mailto:{Uri.EscapeDataString(c.Name)}%20<{c.EmailAddress}>")) : null)
.Item(c.Website != null ? new Link(c.Website, PdfAction.CreateURI($"http://{c.Website}/")) : 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());
}
// FIXME links are drawn on next page - move footer into "normal" document creation
canvas.ShowTextAligned(footerP, (leftX1 + rightX1) / 2, footerY, TextAlignment.CENTER, VerticalAlignment.TOP);
} else if (_doublePaged && (pageNum % 2 == 0)) {
// Swap side
canvas.ShowTextAligned(pageNumP, leftX2, footerY, TextAlignment.LEFT, VerticalAlignment.TOP);
canvas.ShowTextAligned(centerP, (leftX2 + rightX2) / 2, footerY, TextAlignment.CENTER, VerticalAlignment.TOP);
canvas.ShowTextAligned(dateP, rightX2, footerY, TextAlignment.RIGHT, VerticalAlignment.TOP);
} else {
canvas.ShowTextAligned(dateP, leftX1, footerY, TextAlignment.LEFT, VerticalAlignment.TOP);
canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, footerY, TextAlignment.CENTER, VerticalAlignment.TOP);
canvas.ShowTextAligned(pageNumP, rightX1, footerY, TextAlignment.RIGHT, VerticalAlignment.TOP);
}
if (_showFoldMarks) {
var m1 = pageSize.GetTop() - 105 * _ptInMm;
var m2 = pageSize.GetTop() - 148.5 * _ptInMm;
var m3 = pageSize.GetTop() - 210 * _ptInMm;
pdfCanvas.SetLineWidth(0.5f);
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) {
FillPlaceholders(evt.GetDocument());
}
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 (_doublePaged && (pageNum % 2 == 0)) {
// swap
var p = new Paragraph().SetFont(_font).SetFontSize(_fontSize);
if (_isPreview) p.Add(new Text("(vorläufig) ").SetFont(_fontBold));
p.Add($"Seite {pageNum:N0} von {NumberOfPages:N0} ");
canvas.ShowTextAligned(p, 0, 0, TextAlignment.LEFT);
} else {
var p = new Paragraph().SetFont(_font).SetFontSize(_fontSize)
.Add($"Seite {pageNum:N0} von {NumberOfPages:N0}");
if (_isPreview) p.Add(new Text(" (vorläufig)").SetFont(_fontBold));
canvas.ShowTextAligned(p, _placeholderWidth, 0, TextAlignment.RIGHT);
}
}
}
}
+44
View File
@@ -0,0 +1,44 @@
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 Task.Delay(500);
await e.CompileTemplateAsync("Document");
await e.CompileTemplateAsync("BusinessDocument");
await e.CompileTemplateAsync("BusinessLetter");
await e.CompileTemplateAsync("CreditNote");
await e.CompileTemplateAsync("DeliveryAncmtList");
await e.CompileTemplateAsync("DeliveryConfirmation");
await e.CompileTemplateAsync("DeliveryDepreciationList");
await e.CompileTemplateAsync("DeliveryJournal");
await e.CompileTemplateAsync("DeliveryNote");
await e.CompileTemplateAsync("Letterhead");
await e.CompileTemplateAsync("MemberDataSheet");
await e.CompileTemplateAsync("MemberList");
await e.CompileTemplateAsync("PaymentVariantSummary");
await e.CompileTemplateAsync("WineQualityStatistics");
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);
}
}
}
+131 -4
View File
@@ -1,12 +1,19 @@
using Elwig.Services;
using Elwig.Windows;
using iText.Html2pdf;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Event;
using iText.Kernel.Utils;
using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Printing {
public static class Pdf {
public static Task Init(Action? evtHandler = null) {
PdfiumNative.FPDF_InitLibrary();
evtHandler?.Invoke();
@@ -18,6 +25,126 @@ namespace Elwig.Helpers.Printing {
return Task.CompletedTask;
}
public static (int Pages, IEnumerable<int> PerDoc) Convert(string htmlPath, string pdfPath, Documents.Document? doc = null) {
int nPages;
using var html = File.Open(htmlPath, FileMode.Open);
using var writer = new PdfWriter(pdfPath);
using var pdf = new PdfDocument(writer);
var footerHandler = new FooterEventHandler(doc);
pdf.AddEventHandler(PdfDocumentEvent.END_PAGE, footerHandler);
pdf.AddEventHandler(PdfDocumentEvent.START_DOCUMENT_CLOSING, footerHandler);
// TODO embed fonts?
//pdf.AddFont(PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN));
HtmlConverter.ConvertToPdf(html, pdf);
nPages = footerHandler.NumberOfPages;
return (nPages, [nPages]);
}
public static (int Pages, IEnumerable<int> PerDoc) Convert(IEnumerable<string> inputFiles, string pdfPath, bool doublePaged = false, IEnumerable<Documents.Document>? docs = null, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
var tmpFileNames = new List<string>();
var pageNums = new List<int>();
var tmpPageNums = new List<int>();
var htmlFiles = inputFiles
.Where(f => !f.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
.Select(f => f.TrimStart('!', '#'))
.ToList();
try {
for (int i = 0; i < htmlFiles.Count; i++) {
string tmpFile = $"{pdfPath}.{i:0000}.part";
tmpFileNames.Add(tmpFile);
var (pages, _) = Convert(htmlFiles[i], tmpFile, docs?.ElementAt(i));
tmpPageNums.Add(pages);
}
using var writer = new PdfWriter(pdfPath);
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 < inputFiles.Count(); i++) {
var fileName = inputFiles.ElementAt(i);
int p0 = mergedPdf.GetNumberOfPages();
if (letterheadPage != null && fileName.StartsWith('#')) {
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;
letterheadPage = null;
}
if (fileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)) {
var cleanName = fileName.TrimStart('!', '#');
if (doublePaged && fileName.StartsWith('#')) {
using var reader = new PdfReader(cleanName);
using var src = new PdfDocument(reader);
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
letterheadInsertIndex = p0;
letterheadDocIndex = i;
} else {
using var reader = new PdfReader(cleanName);
using var src = new PdfDocument(reader);
merger.Merge(src, 1, src.GetNumberOfPages());
}
} else {
string tmpFile = tmpFileNames[i];
if (doublePaged && fileName.StartsWith('#')) {
using var reader = new PdfReader(tmpFile);
using var src = new PdfDocument(reader);
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
letterheadInsertIndex = p0;
letterheadDocIndex = i;
} else {
using var reader = new PdfReader(tmpFile);
using var src = new PdfDocument(reader);
merger.Merge(src, 1, tmpPageNums[i]);
}
}
int p1 = mergedPdf.GetNumberOfPages();
pageNums.Add(p1 - p0);
if (doublePaged && fileName[0] != '!' && fileName[0] != '#' && mergedPdf.GetNumberOfPages() % 2 != 0) {
if (letterheadPage != null) {
mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf));
letterheadPage = null;
} else {
mergedPdf.AddNewPage();
}
}
}
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;
}
} finally {
foreach (var tmp in tmpFileNames) {
if (File.Exists(tmp)) File.Delete(tmp);
}
}
return (pageNums.Sum(), pageNums);
}
public static void Show(TempFile file, string title) {
App.MainDispatcher.BeginInvoke(() => {
var w = new DocumentViewerWindow(title, file);
@@ -46,8 +173,8 @@ namespace Elwig.Helpers.Printing {
PrinterSettings = settings,
};
printDoc.Print();
} catch (Exception exc) {
InteractionService.ShowException("Fehler beim Drucken", "Beim Drucken ist ein Fehler aufgetreten", exc);
} catch (Exception e) {
MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken", MessageBoxButton.OK, MessageBoxImage.Error);
}
return Task.CompletedTask;
}
+40 -51
View File
@@ -3,12 +3,12 @@ using Elwig.Documents;
using Elwig.Helpers.Billing;
using Elwig.Models;
using Elwig.Models.Entities;
using Elwig.Services;
using iText.Layout.Element;
using LinqKit;
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using MimeKit;
using System;
using System.Collections;
@@ -31,7 +31,6 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;
namespace Elwig.Helpers {
@@ -210,29 +209,16 @@ namespace Elwig.Helpers {
return Regex.Replace(iban.Trim(), ".{4}", "$0 ").Trim();
}
public static void RunBackground(string title, Func<Task> function) {
public static void RunBackground(string title, Func<Task> a) {
Task.Run(async () => {
try {
await function();
} catch (Exception exc) {
InteractionService.ShowException(title, exc);
await a();
} catch (Exception e) {
MessageBox.Show(e.ToString(), title, MessageBoxButton.OK, MessageBoxImage.Error);
}
});
}
public static async Task RunForeground(Func<Task> function) {
var isSTA = Thread.CurrentThread.GetApartmentState() == ApartmentState.STA;
if (isSTA) Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
await function();
} catch (Exception exc) {
InteractionService.ShowException(exc);
}
});
if (isSTA) Mouse.OverrideCursor = null;
}
public static void MailTo(string emailAddress) {
MailTo([emailAddress]);
}
@@ -314,8 +300,8 @@ namespace Elwig.Helpers {
return d.ShowDialog() == true ? d.Price : null;
}
public static Footer GenerateFooter(string lineBreak, string separator) {
return new Footer(lineBreak, separator);
public static Footer GenerateFooter(string lineBreak, string seperator) {
return new Footer(lineBreak, seperator);
}
public class Footer {
@@ -355,7 +341,7 @@ namespace Elwig.Helpers {
var first2 = true;
foreach (var item in line) {
if (!first2) l.Add(new Text(Separator));
l.Add(item as ILeafElement ?? new Text(item.ToString() ?? ""));
l.Add(item as ILeafElement ?? new Text(item.ToString()));
first2 = false;
}
first1 = false;
@@ -427,8 +413,8 @@ namespace Elwig.Helpers {
return output.OrderByDescending(l => l.Count());
}
public static async Task<List<RawVaribute>> GetVaributes(AppDbContext ctx, int year, bool onlyDelivered = true) {
var varieties = await ctx.FetchWineVarieties().Select(v => new RawVaribute(v.SortId, "", null)).ToListAsync();
public static List<RawVaribute> GetVaributes(AppDbContext ctx, int year, bool onlyDelivered = true) {
var varieties = ctx.WineVarieties.Select(v => new RawVaribute(v.SortId, "", null)).ToList();
var delivered = ctx.DeliveryParts
.Where(d => d.Year == year)
.Select(d => new RawVaribute(d.SortId, d.AttrId ?? "", d.CultId ?? ""))
@@ -437,11 +423,13 @@ namespace Elwig.Helpers {
return [.. (onlyDelivered ? delivered : delivered.Union(varieties)).Order()];
}
public static async Task<List<Varibute>> GetVaributeList(AppDbContext ctx, int year, bool onlyDelivered = true) {
var varieties = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v);
var attributes = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.AttrId, a => a);
var cultivations = await ctx.FetchWineCultivations().ToDictionaryAsync(c => c.CultId, c => c);
return [.. (await GetVaributes(ctx, year, onlyDelivered)).Select(s => new Varibute(s, varieties, attributes, cultivations))];
public static List<Varibute> GetVaributeList(AppDbContext ctx, int year, bool onlyDelivered = true) {
var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
var cultivations = ctx.WineCultivations.ToDictionary(c => c.CultId, c => c);
return GetVaributes(ctx, year, onlyDelivered)
.Select(s => new Varibute(s, varieties, attributes, cultivations))
.ToList();
}
[LibraryImport("wininet.dll")]
@@ -457,7 +445,7 @@ namespace Elwig.Helpers {
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.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));
@@ -551,7 +539,7 @@ namespace Elwig.Helpers {
subject, docs.Select(d => d.Title).ToArray()
)]);
} catch (Exception exc) {
InteractionService.ShowException(exc);
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
} finally {
if (client != null)
@@ -565,40 +553,41 @@ namespace Elwig.Helpers {
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) {
InteractionService.ShowError("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!");
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!",
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
using (var ctx = new AppDbContext()) {
await doc.Generate(ctx);
}
await doc.Generate();
await doc.Print();
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
if (doc.IsPreview) {
InteractionService.ShowError("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!");
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!",
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
using (var ctx = new AppDbContext()) {
await doc.Generate(ctx);
}
await doc.Generate();
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
if (success)
InteractionService.ShowInformation("E-Mail verschickt", "Die E-Mail wurde erfolgreich verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mail im Posteingang des Empfängers aufscheint.");
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) {
InteractionService.ShowWarning("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!");
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!",
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Warning);
}
filename = InteractionService.SaveFile(doc.Title, NormalizeFileName(filename ?? doc.Title), "pdf");
if (filename != null) {
using (var ctx = new AppDbContext()) {
await doc.Generate(ctx);
}
doc.SaveTo(filename);
if (!App.Config.Debug) Process.Start("explorer.exe", filename);
var d = new SaveFileDialog() {
FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf",
DefaultExt = "pdf",
Filter = "PDF-Datei (*.pdf)|*.pdf",
Title = $"{doc.Title} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
await doc.Generate();
doc.SaveTo(d.FileName);
Process.Start("explorer.exe", d.FileName);
}
} else {
using (var ctx = new AppDbContext()) {
await doc.Generate(ctx);
}
await doc.Generate();
doc.Show();
}
}
+1 -1
View File
@@ -580,7 +580,7 @@ namespace Elwig.Helpers {
}
}
public static ValidationResult CheckFbNr(TextBox input, bool required, AreaComContract? c) {
public static ValidationResult CheckFbNr(TextBox input, bool required, AreaCom? c) {
var res = CheckInteger(input, required);
if (!res.IsValid) {
return res;
+4 -3
View File
@@ -1,9 +1,9 @@
using Elwig.Services;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Weighing {
public class AveryEventScale : Scale, IEventScale, IDisposable {
@@ -60,8 +60,9 @@ namespace Elwig.Helpers.Weighing {
} catch (TimeoutException) {
await Task.Delay(500);
await Reconnect();
} catch (Exception exc) {
InteractionService.ShowException("Waagenfehler", "Beim Wiegen ist ein Fehler Aufgetreten", exc, showExcType: true);
} catch (Exception ex) {
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
+2 -9
View File
@@ -53,16 +53,9 @@ namespace Elwig.Helpers.Weighing {
return line[1..^1];
}
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) {
string record;
try {
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
await SendCommand(incIdentNr ? '\x05' : '?');
record = await ReceiveResponse();
} catch (IOException) {
if (!retry || Tcp == null) throw;
ReconnectTcp();
return await Weigh(incIdentNr, false);
}
string record = await ReceiveResponse();
if (record.Length != 45)
throw new FormatException("Invalid response from scale: Received record has invalid size");
var line = record[2..];
+4 -13
View File
@@ -1,16 +1,15 @@
using Elwig.Services;
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;
@@ -38,14 +37,14 @@ namespace Elwig.Helpers.Weighing {
}
protected Scale(string cnx, string? empty, string? filling, int? limit, string? log, bool softFail = false, bool failSilent = false) {
Connection = cnx;
if (cnx.StartsWith("serial:")) {
try {
Serial = Utils.OpenSerialConnection(cnx);
} catch (Exception exc) {
} catch (Exception e) {
if (!softFail) throw;
if (!failSilent)
InteractionService.ShowException("Waagenfehler", "Verbindung zu Waage konnte nicht hergestellt werden", exc, isError: false);
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:")) {
@@ -96,14 +95,6 @@ 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,
+3 -12
View File
@@ -33,9 +33,7 @@ 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) {
throw new IOException("Verbindung zu Waage verloren");
} else if (line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
throw new FormatException("Invalid response from scale");
}
@@ -74,16 +72,9 @@ namespace Elwig.Helpers.Weighing {
return line[1..^3];
}
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) {
string record;
try {
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}");
record = await ReceiveResponse();
} catch (IOException) {
if (!retry || Tcp == null) throw;
ReconnectTcp();
return await Weigh(incIdentNr, false);
}
string record = await ReceiveResponse();
if (record.Length != 62)
throw new FormatException("Invalid response from scale: Received record has invalid size");
var line = record[2..];
+15 -15
View File
@@ -29,7 +29,7 @@ namespace Elwig.Models.Dtos {
("Gross", "Brutto", "€", 20),
("Penalties", "Pönalen FB", "€", 20),
("Penalty", "Unterl. GA", "€", 20),
("AutoShares", "GA Nachz.", "€", 20),
("AutoBs", "GA Nachz.", "€", 20),
("Custom", "Weitere", "€", 20),
("Others", "Sonstige", "€", 20),
("Considered", "Berückstgt.", "€", 20),
@@ -56,9 +56,9 @@ namespace Elwig.Models.Dtos {
p.plz, o.name AS ort, m.address, m.iban, c.tgnr, s.year, s.precision,
p.amount - p.net_amount AS surcharge,
c.net_amount, c.prev_net_amount, c.vat, c.vat_amount, c.gross_amount, c.modifiers, c.prev_modifiers, c.amount,
ROUND(b.total_penalty / POW(10, s.precision - 2)) AS shares_penalty,
ROUND(u.total_penalty / POW(10, 4 - 2)) AS ac_penalty,
-a.total_amount AS auto_shares,
ROUND(b.total_penalty / POW(10, s.precision - 2)) AS bs_penalty,
ROUND(u.total_penalty / POW(10, 4 - 2)) AS fb_penalty,
ROUND(-a.total_amount / POW(10, s.precision - 2)) AS auto_bs,
x.amount AS custom_mod
FROM credit c
LEFT JOIN member m ON m.mgnr = c.mgnr
@@ -94,7 +94,7 @@ namespace Elwig.Models.Dtos {
public decimal Gross;
public decimal? Penalties;
public decimal? Penalty;
public decimal? AutoShares;
public decimal? AutoBs;
public decimal? Custom;
public decimal? Others;
public decimal? Considered;
@@ -122,14 +122,14 @@ namespace Elwig.Models.Dtos {
}
decimal mod = (row.Modifiers == null) ? 0 : Utils.DecFromDb((long)row.Modifiers, prec1);
if (data.ConsiderContractPenalties)
Penalties = (row.AcPenalty == null) ? null : Utils.DecFromDb((long)row.AcPenalty, prec1);
Penalties = (row.FbPenalty == null) ? null : Utils.DecFromDb((long)row.FbPenalty, prec1);
if (data.ConsiderTotalPenalty)
Penalty = (row.SharesPenalty == null) ? null : Utils.DecFromDb((long)row.SharesPenalty, prec1);
Penalty = (row.BsPenalty == null) ? null : Utils.DecFromDb((long)row.BsPenalty, prec1);
if (data.ConsiderAutoBusinessShares)
AutoShares = (row.AutoShares == null) ? null : Utils.DecFromDb((long)row.AutoShares, prec1);
AutoBs = (row.AutoBs == null) ? null : Utils.DecFromDb((long)row.AutoBs, prec1);
if (data.ConsiderCustomModifiers)
Custom = (row.CustomMod == null) ? null : Utils.DecFromDb((long)row.CustomMod, prec1);
mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoShares ?? 0) + (Custom ?? 0);
mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoBs ?? 0) + (Custom ?? 0);
Others = (mod == 0) ? null : mod;
Gross = Utils.DecFromDb(row.GrossAmount, prec1);
Considered = (row.PrevModifiers == null || row.PrevModifiers == 0) ? null : -Utils.DecFromDb((long)row.PrevModifiers, prec1);
@@ -179,12 +179,12 @@ namespace Elwig.Models.Dtos {
public long? PrevModifiers { get; set; }
[Column("amount")]
public long Amount { get; set; }
[Column("shares_penalty")]
public long? SharesPenalty { get; set; }
[Column("ac_penalty")]
public long? AcPenalty { get; set; }
[Column("auto_shares")]
public long? AutoShares { get; set; }
[Column("bs_penalty")]
public long? BsPenalty { get; set; }
[Column("fb_penalty")]
public long? FbPenalty { get; set; }
[Column("auto_bs")]
public long? AutoBs { get; set; }
[Column("custom_mod")]
public long? CustomMod { get; set; }
}
+13 -13
View File
@@ -28,9 +28,9 @@ namespace Elwig.Models.Dtos {
}
public static async Task<IDictionary<int, CreditNoteDeliveryData>> ForPaymentVariant(DbSet<CreditNoteDeliveryRowSingle> table, DbSet<PaymentVar> paymentVariants, int year, int avnr) {
var variant = await paymentVariants.Include(v => v.Season.Modifiers).Where(v => v.Year == year && v.AvNr == avnr).SingleAsync();
var variant = await paymentVariants.FindAsync(year, avnr);
BillingData? varData = null;
try { varData = variant.Data != null ? BillingData.FromJson(variant.Data) : null; } catch { }
try { varData = variant != null ? BillingData.FromJson(variant.Data) : null; } catch { }
return (await FromDbSet(table, year, avnr))
.GroupBy(
r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
@@ -42,10 +42,13 @@ namespace Elwig.Models.Dtos {
}
private static async Task<IEnumerable<CreditNoteDeliveryRowSingle>> FromDbSet(DbSet<CreditNoteDeliveryRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) {
return await table.FromSql($"""
var y = year?.ToString() ?? "NULL";
var v = avnr?.ToString() ?? "NULL";
var m = mgnr?.ToString() ?? "NULL";
return await table.FromSqlRaw($"""
SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers,
b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount,
s.name AS variety, s.type AS type, a.name AS attribute, c.name AS cultivation, q.qualid AS qualid, q.name AS quality_level, d.oe, d.kmw, d.net_weight
s.name AS variety, a.name AS attribute, c.name AS cultivation, q.qualid AS qualid, q.name AS quality_level, d.oe, d.kmw, d.net_weight
FROM v_delivery d
JOIN wine_variety s ON s.sortid = d.sortid
LEFT JOIN wine_attribute a ON a.attrid = d.attrid
@@ -56,7 +59,7 @@ namespace Elwig.Models.Dtos {
LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr)
LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr, pb.avnr) = (b.year, b.did, b.dpnr, b.bktnr, v.avnr)
LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr)
WHERE b.value > 0 AND (d.year = {year} OR {year} IS NULL) AND (v.avnr = {avnr} OR {avnr} IS NULL) AND (d.mgnr = {mgnr} OR {mgnr} IS NULL)
WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL)
ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
""").ToListAsync();
}
@@ -72,7 +75,6 @@ namespace Elwig.Models.Dtos {
public string LsNr;
public int DPNr;
public string Variety;
public string Type;
public string? Attribute;
public string? Cultivation;
public string[] Modifiers;
@@ -95,24 +97,24 @@ namespace Elwig.Models.Dtos {
LsNr = f.LsNr;
DPNr = f.DPNr;
Variety = f.Variety;
Type = f.Type;
Attribute = f.Attribute;
Cultivation = f.Cultivation;
var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',')
.Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m))
.Where(m => m != null)
.OrderBy(m => m!.Ordering)
.OrderBy(m => m.Ordering)
.ToList();
Modifiers = [.. modifiers.Select(m => m.Name)];
Modifiers = modifiers.Select(m => m.Name).ToArray();
QualId = f.QualId;
QualityLevel = f.QualityLevel;
Gradation = (f.Oe, f.Kmw);
Buckets = [.. rows
Buckets = rows
.Where(b => b.Value > 0)
.OrderByDescending(b => b.BktNr)
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
b.Price != null ? season?.DecFromDb((long)b.Price) : null,
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))];
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
.ToArray();
WeighingModifier = (varData == null || !varData.ConsiderDelieryModifiers) ? 0 : f.NetWeight ? varData.NetWeightModifier : varData.GrossWeightModifier;
Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
@@ -159,8 +161,6 @@ namespace Elwig.Models.Dtos {
public long? TotalAmount { get; set; }
[Column("variety")]
public required string Variety { get; set; }
[Column("type")]
public required string Type { get; set; }
[Column("attribute")]
public string? Attribute { get; set; }
[Column("cultivation")]
+6 -1
View File
@@ -28,7 +28,12 @@ namespace Elwig.Models.Dtos {
}
public static async Task<DeliveryAncmtListData> FromQuery(IQueryable<DeliveryAncmt> query, List<string> filterNames) {
return new((await query.ToListAsync()).Select(d => new DeliveryAncmtListRow(d)), filterNames);
return new((await query
.Include(a => a.Schedule.Branch)
.Include(a => a.Member)
.Include(a => a.Variety)
.AsSplitQuery()
.ToListAsync()).Select(d => new DeliveryAncmtListRow(d)), filterNames);
}
}
@@ -1,6 +1,5 @@
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -52,8 +51,12 @@ namespace Elwig.Models.Dtos {
if (mgnr != null) q = q.Where(p => p.Delivery.MgNr == mgnr);
await q
.Include(p => p.Delivery)
.Include(p => p.Variety)
.Include(p => p.Attribute)
.Include(p => p.Quality)
.Include(p => p.Buckets)
.Include(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.Include(p => p.PartModifiers)
.ThenInclude(m => m.Modifier)
.LoadAsync();
return await table.FromSqlRaw($"""
SELECT p.*
@@ -61,20 +64,17 @@ namespace Elwig.Models.Dtos {
JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (v.year, v.did, v.dpnr)
WHERE (p.year = {y} OR {y} IS NULL) AND (v.mgnr = {m} OR {m} IS NULL)
ORDER BY p.year, v.mgnr, v.sortid, v.abgewertet ASC, v.attribute_prio DESC, COALESCE(v.attrid, '~'), v.kmw DESC, v.lsnr, v.dpnr
""").IgnoreAutoIncludes().ToListAsync();
""").ToListAsync();
}
}
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 +83,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)];
Modifiers = p.Modifiers
.Select(m => m.Name)
.ToArray();
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))];
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {p.SortId}{b.Discr}", b.Value))
.ToArray();
}
}
}
+5
View File
@@ -40,7 +40,12 @@ namespace Elwig.Models.Dtos {
.Include(p => p.Delivery.Member.Branch)
.Include(p => p.Delivery.Branch)
.Include(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.Include(p => p.Variety)
.Include(p => p.Attribute)
.Include(p => p.Cultivation)
.Include(p => p.Origin)
.Include(p => p.Quality)
.AsSplitQuery()
.ToListAsync()).Select(d => new DeliveryJournalRow(d)), filterNames);
}
}
+8 -12
View File
@@ -19,7 +19,7 @@ namespace Elwig.Models.Dtos {
("Locality", "Ort", null, 60),
("DefaultKg", "Stammgemeinde", null, 60),
("Branch", "Zweigstelle", null, 40),
("SharesTotal", "GA", null, 10),
("BusinessShares", "GA", null, 10),
("BillingName", "Rechnungsname", null, 60),
("BillingAddress", "Rechnungsadresse", null, 60),
("BillingPlz", "PLZ", null, 10),
@@ -47,11 +47,15 @@ namespace Elwig.Models.Dtos {
}
public static async Task<MemberListData> FromQuery(IQueryable<Member> query, List<string> filterNames, IEnumerable<string> filterAreaCom) {
var areaComs = await query.Include(m => m.AreaCommitments).ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments));
var areaComs = await query.ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments));
return new((await query
.Include(m => m.DefaultWbKg!.AtKg)
.Include(m => m.Branch)
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.BillingAddress!.PostalDest.AtPlz!.Ort)
.Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses)
.AsSplitQuery()
.ToListAsync()).Select(m => new MemberListRow(m,
areaComs[m.MgNr].Sum(c => c.Area),
areaComs[m.MgNr].Where(c => filterAreaCom.Contains(c.VtrgId)).GroupBy(c => c.VtrgId).ToDictionary(g => g.Key, g => g.Sum(c => c.Area)))),
@@ -66,11 +70,7 @@ namespace Elwig.Models.Dtos {
public string? Name2;
public string? DefaultKg;
public string? Branch;
public int Shares;
public int SharesRed;
public int SharesWhite;
public int SharesDormant;
public int SharesTotal;
public int BusinessShares;
public string Address;
public int Plz;
public string Locality;
@@ -102,11 +102,7 @@ namespace Elwig.Models.Dtos {
Name2 = m.AdministrativeName2;
DefaultKg = m.DefaultKg?.Name;
Branch = m.Branch?.Name;
Shares = m.Shares;
SharesRed = m.SharesRed;
SharesWhite = m.SharesWhite;
SharesDormant = m.SharesDormant;
SharesTotal = Shares + SharesRed + SharesWhite + SharesDormant;
BusinessShares = m.BusinessShares;
Address = m.Address;
Plz = m.PostalDest.AtPlz!.Plz;
Locality = m.PostalDest.AtPlz!.Ort.Name;
+18 -81
View File
@@ -13,74 +13,35 @@ namespace Elwig.Models.Dtos {
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("Shares", "GA", null, 10),
("BusinessShares", "GA", null, 10),
("DeliveryObligation", "Lieferpflicht", "kg", 22),
("DeliveryRight", "Lieferrecht", "kg", 22),
("WeightTotal", "Geliefert", "kg", 22),
("Weight", "Geliefert", "kg", 22),
("OverUnderDelivery", "Über-/Unterliefert", "kg|%", 34),
];
private static readonly (string, string, string?, int)[] FieldNamesRed = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SharesRed", "GA", null, 10),
("DeliveryObligationRed", "Lieferpflicht", "kg", 22),
("DeliveryRightRed", "Lieferrecht", "kg", 22),
("WeightRed", "Geliefert", "kg", 22),
("OverUnderDeliveryRed", "Über-/Unterliefert", "kg|%", 34),
];
private static readonly (string, string, string?, int)[] FieldNamesWhite = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SharesWhite", "GA", null, 10),
("DeliveryObligationWhite", "Lieferpflicht", "kg", 22),
("DeliveryRightWhite", "Lieferrecht", "kg", 22),
("WeightWhite", "Geliefert", "kg", 22),
("OverUnderDeliveryWhite", "Über-/Unterliefert", "kg|%", 34),
];
public OverUnderDeliveryData(IEnumerable<OverUnderDeliveryRow> rows, int year) :
base($"Über-Unterlieferungen", $"Über- und Unterlieferungen laut gezeichneten Geschäftsanteilen {year}", rows, FieldNames) {
}
public OverUnderDeliveryData(IEnumerable<OverUnderDeliveryRow> rows, int year, string mode) :
base($"Über-Unterlieferungen-{(mode == "R" ? "rot" : "weiß")}", $"Über- und Unterlieferungen laut gezeichneten Geschäftsanteilen {(mode == "R" ? "rot" : "weiß")} {year}", rows,
mode == "R" ? FieldNamesRed : FieldNamesWhite) {
}
public static async Task<(OverUnderDeliveryData Total, OverUnderDeliveryData Red, OverUnderDeliveryData White)> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) {
public static async Task<OverUnderDeliveryData> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) {
var rows = await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address, h.shares, h.shares_red, h.shares_white,
h.shares * COALESCE(s.min_kg_per_share, 0) AS min_kg,
h.shares * COALESCE(s.max_kg_per_share, 0) AS max_kg,
h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0) AS min_kg_red,
h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0) AS max_kg_red,
h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0) AS min_kg_white,
h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0) AS max_kg_white,
COALESCE(d.weight_total, 0) AS weight_total,
COALESCE(d.weight_red, 0) AS weight_red,
COALESCE(d.weight_white, 0) AS weight_white
p.plz, o.name AS ort, m.address, m.business_shares,
m.business_shares * s.min_kg_per_bs AS min_kg,
m.business_shares * s.max_kg_per_bs AS max_kg,
COALESCE(SUM(d.weight), 0) AS sum
FROM season s, member m
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz
LEFT JOIN v_member_history h ON (h.year, h.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_stat_member d ON (d.year, d.mgnr) = (s.year, m.mgnr)
WHERE s.year = {year} AND (m.active = TRUE OR d.weight_total > 0)
ORDER BY 100.0 * weight_total / (max_kg + max_kg_red + max_kg_white), m.mgnr
LEFT JOIN v_delivery d ON (d.year, d.mgnr) = (s.year, m.mgnr)
WHERE s.year = {year} AND (m.active = TRUE OR d.weight > 0)
GROUP BY s.year, m.mgnr
ORDER BY 100.0 * sum / max_kg, m.mgnr
""").ToListAsync();
return (new OverUnderDeliveryData(rows, year), new OverUnderDeliveryData(rows, year, "R"), new OverUnderDeliveryData(rows, year, "W"));
return new OverUnderDeliveryData(rows, year);
}
}
@@ -100,41 +61,17 @@ namespace Elwig.Models.Dtos {
public required string LocalityFull { get; set; }
[NotMapped]
public string Locality => LocalityFull.Split(",")[0];
[Column("shares")]
public int Shares { get; set; }
[Column("shares_red")]
public int SharesRed { get; set; }
[Column("shares_white")]
public int SharesWhite { get; set; }
[Column("business_shares")]
public int BusinessShares { get; set; }
[Column("min_kg")]
public int DeliveryObligation { get; set; }
[Column("max_kg")]
public int DeliveryRight { get; set; }
[Column("sum")]
public int Weight { get; set; }
[NotMapped]
public (int? Kg, double? Percent) OverUnderDelivery =>
WeightTotal < DeliveryObligation ? (WeightTotal - DeliveryObligation, WeightTotal * 100.0 / DeliveryObligation - 100.0) :
WeightTotal > DeliveryRight ? (WeightTotal - DeliveryRight, WeightTotal * 100.0 / DeliveryRight - 100.0) : (null, null);
[Column("min_kg_red")]
public int DeliveryObligationRed { get; set; }
[Column("max_kg_red")]
public int DeliveryRightRed { get; set; }
[NotMapped]
public (int? Kg, double? Percent) OverUnderDeliveryRed =>
WeightRed < DeliveryObligationRed ? (WeightRed - DeliveryObligationRed, WeightRed * 100.0 / DeliveryObligationRed - 100.0) :
WeightRed > DeliveryRightRed ? (WeightRed - DeliveryRightRed, WeightRed * 100.0 / DeliveryRightRed - 100.0) : (null, null);
[Column("min_kg_white")]
public int DeliveryObligationWhite { get; set; }
[Column("max_kg_white")]
public int DeliveryRightWhite { get; set; }
[NotMapped]
public (int? Kg, double? Percent) OverUnderDeliveryWhite =>
WeightWhite < DeliveryObligationWhite ? (WeightWhite - DeliveryObligationWhite, WeightWhite * 100.0 / DeliveryObligationWhite - 100.0) :
WeightWhite > DeliveryRightWhite ? (WeightWhite - DeliveryRightWhite, WeightWhite * 100.0 / DeliveryRightWhite - 100.0) : (null, null);
[Column("weight_total")]
public int WeightTotal { get; set; }
[Column("weight_red")]
public int WeightRed { get; set; }
[Column("weight_white")]
public int WeightWhite { get; set; }
Weight < DeliveryObligation ? (Weight - DeliveryObligation, Weight * 100.0 / DeliveryObligation - 100.0) :
Weight > DeliveryRight ? (Weight - DeliveryRight, Weight * 100.0 / DeliveryRight - 100.0) : (null, null);
}
}
+24 -7
View File
@@ -1,16 +1,15 @@
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace Elwig.Models.Entities {
[Table("area_commitment"), PrimaryKey("FbNr", "RevNr")]
[Table("area_commitment"), PrimaryKey("FbNr")]
public class AreaCom {
[Column("fbnr")]
public int FbNr { get; set; }
[Column("revnr")]
public int RevNr { get; set; }
[Column("mgnr")]
public int MgNr { get; set; }
@@ -23,15 +22,24 @@ namespace Elwig.Models.Entities {
[Column("area")]
public int Area { get; set; }
[Column("kgnr")]
public int KgNr { get; set; }
[Column("gstnr")]
public required string GstNr { get; set; }
[Column("rdnr")]
public int? RdNr { get; set; }
[Column("year_from")]
public int? YearFrom { get; set; }
[Column("year_to")]
public int? YearTo { get; set; }
[Column("comment")]
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
[NotMapped]
@@ -64,9 +72,6 @@ namespace Elwig.Models.Entities {
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("FbNr")]
public virtual AreaComContract Contract { get; private set; } = null!;
[ForeignKey("MgNr")]
public virtual Member Member { get; private set; } = null!;
@@ -75,5 +80,17 @@ namespace Elwig.Models.Entities {
[ForeignKey("CultId")]
public virtual WineCult? WineCult { get; private set; }
[ForeignKey("KgNr")]
public virtual WbKg Kg { get; private set; } = null!;
[ForeignKey("KgNr, RdNr")]
public virtual WbRd? Rd { get; private set; }
public int SearchScore(IEnumerable<string> keywords) {
return Utils.GetSearchScore([
WineCult?.Name, Kg.AtKg.Name, Rd?.Name, GstNr, Comment,
], keywords);
}
}
}
-78
View File
@@ -1,78 +0,0 @@
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
namespace Elwig.Models.Entities {
[Table("area_commitment_contract"), PrimaryKey("FbNr")]
public class AreaComContract {
[Column("fbnr")]
public int FbNr { get; set; }
[Column("kgnr")]
public int KgNr { get; set; }
[Column("rdnr")]
public int? RdNr { get; set; }
[Column("comment")]
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; set; }
[NotMapped]
public DateTime CreatedAt {
get => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
set => CTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; set; }
[NotMapped]
public DateTime ModifiedAt {
get => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
set => MTime = ((DateTimeOffset)value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("xtime")]
public long? XTime { get; set; }
[NotMapped]
public DateTime? ExportedAt {
get => XTime == null ? null : DateTimeOffset.FromUnixTimeSeconds(XTime.Value).LocalDateTime;
set => XTime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[Column("itime")]
public long? ITime { get; set; }
[NotMapped]
public DateTime? ImportedAt {
get => ITime == null ? null : DateTimeOffset.FromUnixTimeSeconds(ITime.Value).LocalDateTime;
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
}
[ForeignKey("KgNr")]
public virtual WbKg Kg { get; private set; } = null!;
[ForeignKey("KgNr, RdNr")]
public virtual WbRd? Rd { get; private set; }
[InverseProperty(nameof(AreaCom.Contract))]
public virtual ICollection<AreaCom> Revisions { get; private set; } = null!;
[NotMapped]
public AreaCom? Latest => Revisions.OrderBy(r => r.RevNr).LastOrDefault();
[NotMapped]
public int? YearFrom => Revisions.Any(r => r.YearFrom == null) ? null : Revisions.Min(r => r.YearFrom);
[NotMapped]
public int? YearTo => Revisions.Any(r => r.YearTo == null) ? null : Revisions.Max(r => r.YearTo);
public int SearchScore(IEnumerable<string> keywords) {
return Utils.GetSearchScore([
..Revisions.Select(r => r.WineCult?.Name), Kg.AtKg.Name, Rd?.Name, ..Revisions.Select(r => r.GstNr), Comment,
], keywords);
}
}
}
+5 -5
View File
@@ -111,16 +111,16 @@ namespace Elwig.Models.Entities {
[NotMapped]
public Predicate<DeliveryPart>? PartFilter { get; set; }
public int Weight => Parts.Sum(p => p.Weight);
public int FilteredWeight => FilteredParts.Sum(p => p.Weight);
public int Weight => Parts.Select(p => p.Weight).Sum();
public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
public IEnumerable<RawVaribute> Vaributes => Parts
.GroupBy(p => (p.SortId, p.AttrId, p.CultId))
.OrderByDescending(g => g.Sum(p => p.Weight))
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
.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.Sum(p => p.Weight))
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
.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);
@@ -153,7 +153,7 @@ namespace Elwig.Models.Entities {
Member.Name, Member.MiddleName, Member.GivenName, Member.BillingAddress?.FullName,
Comment
}.ToList();
list.AddRange(FilteredParts.Select(p => p.Comment).Distinct());
list.AddRange(Parts.Select(p => p.Comment).Distinct());
return Utils.GetSearchScore(list, keywords);
}
}
+3 -22
View File
@@ -67,22 +67,8 @@ namespace Elwig.Models.Entities {
set => ExitDateString = value?.ToString("yyyy-MM-dd");
}
[Column("shares")]
public int Shares { get; set; }
[Column("shares_red")]
public int SharesRed { get; set; }
[Column("shares_white")]
public int SharesWhite { get; set; }
[Column("shares_dormant")]
public int SharesDormant { get; set; }
[NotMapped]
public int SharesTotal => Shares + SharesRed + SharesWhite + SharesDormant;
[NotMapped]
public int SharesActive => Shares + SharesRed + SharesWhite;
[Column("business_shares")]
public int BusinessShares { get; set; }
[Column("accounting_nr")]
public string? AccountingNr { get; set; }
@@ -203,14 +189,9 @@ namespace Elwig.Models.Entities {
[InverseProperty(nameof(BillingAddr.Member))]
public virtual BillingAddr? BillingAddress { get; private set; }
[InverseProperty(nameof(DeliveryAncmt.Member))]
[InverseProperty(nameof(Delivery.Member))]
public virtual ICollection<DeliveryAncmt> Announcements { get; private set; } = null!;
[InverseProperty(nameof(MemberHistory.FromMember))]
public virtual ICollection<MemberHistory> HistoryFrom { get; private set; } = null!;
[InverseProperty(nameof(MemberHistory.ToMember))]
public virtual ICollection<MemberHistory> HistoryTo { get; private set; } = null!;
[InverseProperty(nameof(Delivery.Member))]
public virtual ICollection<Delivery> Deliveries { get; private set; } = null!;
+9 -43
View File
@@ -1,23 +1,12 @@
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Elwig.Models.Entities {
[Table("member_history"), PrimaryKey("HistNr")]
[Table("member_history"), PrimaryKey("MgNr", "DateString", "Type")]
public class MemberHistory {
[Column("histnr")]
public int HistNr { get; set; }
[Column("from_mgnr")]
public int? FromMgNr { get; set; }
[Column("from_type")]
public int? FromType { get; set; }
[Column("to_mgnr")]
public int? ToMgNr { get; set; }
[Column("to_type")]
public int? ToType { get; set; }
[Column("mgnr")]
public int MgNr { get; set; }
[Column("date")]
public required string DateString { get; set; }
@@ -27,39 +16,16 @@ namespace Elwig.Models.Entities {
set => value.ToString("yyyy-MM-dd");
}
[Column("reason")]
public required string Reason { get; set; }
[Column("type")]
public required string Type { get; set; }
[Column("source")]
public required string Source { get; set; }
[Column("shares")]
public int Shares { get; set; }
[Column("value_per_share")]
public long? ValuePerShareValue { get; set; }
[NotMapped]
public decimal? ValuePerShare {
get => ValuePerShareValue != null ? Utils.DecFromDb(ValuePerShareValue.Value, 2) : null;
set => ValuePerShareValue = value != null ? Utils.DecToDb(value.Value, 2) : null;
}
[NotMapped]
public decimal? TotalValue => Shares * ValuePerShare;
[Column("currency")]
public string? CurrencyCode { get; set; }
[Column("business_shares")]
public int BusinessShares { get; set; }
[Column("comment")]
public string? Comment { get; set; }
[ForeignKey("FromMgNr")]
public virtual Member FromMember { get; private set; } = null!;
[ForeignKey("ToMgNr")]
public virtual Member ToMember { get; private set; } = null!;
[ForeignKey("CurrencyCode")]
public virtual Currency Currency { get; private set; } = null!;
[ForeignKey("MgNr")]
public virtual Member Member { get; private set; } = null!;
}
}
+15 -24
View File
@@ -25,20 +25,11 @@ namespace Elwig.Models.Entities {
[Column("vat_flatrate")]
public double VatFlatrate { get; set; }
[Column("min_kg_per_share")]
public int? MinKgPerShare { get; set; }
[Column("max_kg_per_share")]
public int? MaxKgPerShare { get; set; }
[Column("min_kg_per_bs")]
public int MinKgPerBusinessShare { get; set; }
[Column("min_kg_per_share_red")]
public int? MinKgPerShareRed { get; set; }
[Column("max_kg_per_share_red")]
public int? MaxKgPerShareRed { get; set; }
[Column("min_kg_per_share_white")]
public int? MinKgPerShareWhite { get; set; }
[Column("max_kg_per_share_white")]
public int? MaxKgPerShareWhite { get; set; }
[Column("max_kg_per_bs")]
public int MaxKgPerBusinessShare { get; set; }
[Column("penalty_per_kg")]
public long? PenaltyPerKgValue { get; set; }
@@ -64,23 +55,23 @@ namespace Elwig.Models.Entities {
set => PenaltyNoneValue = value != null ? DecToDb(value.Value) : null;
}
[Column("penalty_per_share_amount")]
public long? PenaltyPerShareAmountValue { get; set; }
[Column("penalty_per_bs_amount")]
public long? PenaltyPerBsAmountValue { get; set; }
[NotMapped]
public decimal? PenaltyPerShareAmount {
get => PenaltyPerShareAmountValue != null ? DecFromDb(PenaltyPerShareAmountValue.Value) : null;
set => PenaltyPerShareAmountValue = value != null ? DecToDb(value.Value) : null;
public decimal? PenaltyPerBsAmount {
get => PenaltyPerBsAmountValue != null ? DecFromDb(PenaltyPerBsAmountValue.Value) : null;
set => PenaltyPerBsAmountValue = value != null ? DecToDb(value.Value) : null;
}
[Column("penalty_per_share_none")]
public long? PenaltyPerShareNoneValue { get; set; }
[Column("penalty_per_bs_none")]
public long? PenaltyPerBsNoneValue { get; set; }
[NotMapped]
public decimal? PenaltyPerShareNone {
get => PenaltyPerShareNoneValue != null ? DecFromDb(PenaltyPerShareNoneValue.Value) : null;
set => PenaltyPerShareNoneValue = value != null ? DecToDb(value.Value) : null;
public decimal? PenaltyPerBsNone {
get => PenaltyPerBsNoneValue != null ? DecFromDb(PenaltyPerBsNoneValue.Value) : null;
set => PenaltyPerBsNoneValue = value != null ? DecToDb(value.Value) : null;
}
[Column("share_value")]
[Column("bs_value")]
public long? BusinessShareValueValue { get; set; }
[NotMapped]
public decimal? BusinessShareValue {
+2 -4
View File
@@ -27,15 +27,13 @@ namespace Elwig.Models.Entities {
public virtual ICollection<WbGem> Gems { get; private set; } = null!;
[InverseProperty(nameof(Parent))]
public virtual ICollection<WineOrigin> RealChildren { get; private set; } = null!;
[NotMapped]
public List<WineOrigin> Children { get; private set; } = [];
public virtual ICollection<WineOrigin> Children { get; private set; } = null!;
public int Level => (Parent?.Level + 1) ?? 0;
public string HkIdLevel => $"{new string(' ', Level * 2)}{HkId}";
public int TotalChildNum => 1 + Children.Sum(c => c.TotalChildNum);
public int TotalChildNum => 1 + Children.Select(c => c.TotalChildNum).Sum();
private int SortKey1 => (Parent?.SortKey1 ?? 0) | (TotalChildNum << ((3 - Level) * 8));
public int SortKey => SortKey1 | ((Level < 3) ? (-1 >>> (Level * 8 + 8)) : 0);
-2
View File
@@ -1,8 +1,6 @@
-- 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';
-12
View File
@@ -1,12 +0,0 @@
-- 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';
-239
View File
@@ -1,239 +0,0 @@
-- schema version 37 to 38
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
DROP TRIGGER t_area_commitment_i_ctime;
DROP TRIGGER t_area_commitment_u_ctime;
DROP TRIGGER t_area_commitment_i_mtime;
DROP TRIGGER t_area_commitment_u_mtime;
CREATE TABLE area_commitment_contract (
fbnr INTEGER NOT NULL,
kgnr INTEGER NOT NULL,
rdnr INTEGER,
comment TEXT DEFAULT NULL,
ctime INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
mtime INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
xtime INTEGER DEFAULT NULL,
itime INTEGER DEFAULT NULL,
CONSTRAINT area_commitment_contract PRIMARY KEY (fbnr),
CONSTRAINT fk_area_commitment_contract_wb_kg FOREIGN KEY (kgnr) REFERENCES wb_kg (kgnr)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_area_commitment_contract_wb_rd FOREIGN KEY (kgnr, rdnr) REFERENCES wb_rd (kgnr, rdnr)
ON UPDATE CASCADE
ON DELETE RESTRICT
) STRICT;
CREATE TABLE area_commitment_new (
fbnr INTEGER NOT NULL,
revnr INTEGER NOT NULL,
mgnr INTEGER NOT NULL,
vtrgid TEXT NOT NULL,
cultid TEXT DEFAULT NULL,
area INTEGER NOT NULL,
gstnr TEXT NOT NULL,
year_from INTEGER CHECK (year_from >= 1000 AND year_from <= 9999) DEFAULT NULL,
year_to INTEGER CHECK (year_to >= 1000 AND year_to <= 9999) DEFAULT NULL,
ctime INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
mtime INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
xtime INTEGER DEFAULT NULL,
itime INTEGER DEFAULT NULL,
CONSTRAINT pk_area_commitment PRIMARY KEY (fbnr, revnr),
CONSTRAINT fk_area_commitment_area_commitment_contract FOREIGN KEY (fbnr) REFERENCES area_commitment_contract (fbnr)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT fk_area_commitment_member FOREIGN KEY (mgnr) REFERENCES member (mgnr)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_area_commitment_area_commitment_type FOREIGN KEY (vtrgid) REFERENCES area_commitment_type (vtrgid)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_area_commitment_wine_cultivation FOREIGN KEY (cultid) REFERENCES wine_cultivation (cultid)
ON UPDATE CASCADE
ON DELETE RESTRICT
) STRICT;
INSERT INTO area_commitment_contract (fbnr, kgnr, rdnr, comment, ctime, mtime, xtime, itime)
SELECT fbnr, kgnr, rdnr, comment, ctime, mtime, xtime, itime
FROM area_commitment;
INSERT INTO area_commitment_new (fbnr, revnr, mgnr, vtrgid, cultid, area, gstnr, year_from, year_to, ctime, mtime, xtime, itime)
SELECT fbnr, 1, mgnr, vtrgid, cultid, area, gstnr, year_from, year_to, ctime, mtime, xtime, itime
FROM area_commitment;
PRAGMA foreign_keys = OFF;
PRAGMA writable_schema = ON;
DROP TABLE area_commitment;
ALTER TABLE area_commitment_new RENAME TO area_commitment;
PRAGMA writable_schema = OFF;
PRAGMA foreign_keys = ON;
CREATE TRIGGER t_area_commitment_contract_i_ctime
AFTER INSERT ON area_commitment_contract FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND NEW.ctime != UNIXEPOCH()
BEGIN
UPDATE area_commitment_contract SET ctime = UNIXEPOCH() WHERE fbnr = NEW.fbnr;
END;
CREATE TRIGGER t_area_commitment_contract_u_ctime
BEFORE UPDATE ON area_commitment_contract FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND OLD.ctime != NEW.ctime
BEGIN
SELECT RAISE(ABORT, 'It is not allowed to change ctime');
END;
CREATE TRIGGER t_area_commitment_contract_i_mtime
AFTER INSERT ON area_commitment_contract FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND NEW.mtime != UNIXEPOCH()
BEGIN
UPDATE area_commitment_contract SET mtime = UNIXEPOCH() WHERE fbnr = NEW.fbnr;
END;
CREATE TRIGGER t_area_commitment_contract_u_mtime
AFTER UPDATE ON area_commitment_contract FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND NEW.mtime != UNIXEPOCH()
BEGIN
UPDATE area_commitment_contract SET mtime = UNIXEPOCH() WHERE fbnr = NEW.fbnr;
END;
CREATE TRIGGER t_area_commitment_i_ctime
AFTER INSERT ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND NEW.ctime != UNIXEPOCH()
BEGIN
UPDATE area_commitment SET ctime = UNIXEPOCH() WHERE (fbnr, revnr) = (NEW.fbnr, NEW.revnr);
END;
CREATE TRIGGER t_area_commitment_u_ctime
BEFORE UPDATE ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND OLD.ctime != NEW.ctime
BEGIN
SELECT RAISE(ABORT, 'It is not allowed to change ctime');
END;
CREATE TRIGGER t_area_commitment_i_mtime
AFTER INSERT ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND NEW.mtime != UNIXEPOCH()
BEGIN
UPDATE area_commitment SET mtime = UNIXEPOCH() WHERE (fbnr, revnr) = (NEW.fbnr, NEW.revnr);
END;
CREATE TRIGGER t_area_commitment_u_mtime
AFTER UPDATE ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1 AND NEW.mtime != UNIXEPOCH()
BEGIN
UPDATE area_commitment SET mtime = UNIXEPOCH() WHERE (fbnr, revnr) = (NEW.fbnr, NEW.revnr);
END;
CREATE TRIGGER t_area_commitment_i_mtime_contract
AFTER INSERT ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1
BEGIN
UPDATE area_commitment_contract SET mtime = UNIXEPOCH() WHERE fbnr = NEW.fbnr;
END;
CREATE TRIGGER t_area_commitment_u_mtime_contract
AFTER UPDATE ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1
BEGIN
UPDATE area_commitment_contract SET mtime = UNIXEPOCH() WHERE fbnr = NEW.fbnr OR fbnr = OLD.fbnr;
END;
CREATE TRIGGER t_area_commitment_d_mtime_contract
AFTER DELETE ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1
BEGIN
UPDATE area_commitment_contract SET mtime = UNIXEPOCH() WHERE fbnr = OLD.fbnr;
END;
CREATE TRIGGER t_area_commitment_i_mtime_member
AFTER INSERT ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1
BEGIN
UPDATE member SET mtime = UNIXEPOCH() WHERE mgnr = NEW.mgnr;
END;
CREATE TRIGGER t_area_commitment_u_mtime_member
AFTER UPDATE ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1
BEGIN
UPDATE member SET mtime = UNIXEPOCH() WHERE mgnr = NEW.mgnr OR mgnr = OLD.mgnr;
END;
CREATE TRIGGER t_area_commitment_d_mtime_member
AFTER DELETE ON area_commitment FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_TIME_TRIGGERS') = 1
BEGIN
UPDATE member SET mtime = UNIXEPOCH() WHERE mgnr = OLD.mgnr;
END;
-- fix user fiddling - set actual year_from
UPDATE area_commitment AS b
SET fbnr = a.fbnr, revnr = b.fbnr, year_from = a.year_to + 1
FROM area_commitment AS a
WHERE a.fbnr < b.fbnr
AND COALESCE(a.year_from <= a.year_to, TRUE)
AND COALESCE(b.year_from <= b.year_to, TRUE)
AND SUBSTR(a.vtrgid, 0, 2) = SUBSTR(b.vtrgid, 0, 2)
AND (SELECT c.kgnr = d.kgnr AND COALESCE(c.rdnr = d.rdnr, TRUE) FROM area_commitment_contract c, area_commitment_contract d WHERE c.fbnr = a.fbnr AND d.fbnr = b.fbnr)
AND ((IIF(INSTR(a.gstnr, b.gstnr) > 0, 1, 0) + IIF(INSTR(b.gstnr, a.gstnr) > 0, 1, 0) + IIF(a.area = b.area, 2, 0) + (a.mgnr = b.mgnr)) > 1)
AND ((a.year_from = b.year_from AND b.year_to IS NULL AND a.year_to IS NOT NULL));
-- simple
UPDATE area_commitment AS b
SET fbnr = a.fbnr, revnr = b.fbnr
FROM area_commitment AS a
WHERE a.fbnr < b.fbnr
AND COALESCE(a.year_from <= a.year_to, TRUE)
AND COALESCE(b.year_from <= b.year_to, TRUE)
AND SUBSTR(a.vtrgid, 0, 2) = SUBSTR(b.vtrgid, 0, 2)
AND (SELECT c.kgnr = d.kgnr AND COALESCE(c.rdnr = d.rdnr, TRUE) FROM area_commitment_contract c, area_commitment_contract d WHERE c.fbnr = a.fbnr AND d.fbnr = b.fbnr)
AND (a.gstnr = b.gstnr OR a.gstnr IN ('offen', '', '-')) AND a.area = b.area
AND a.year_to + 1 = b.year_from;
-- copy comments
UPDATE area_commitment_contract AS b
SET comment = IIF(b.comment IS NULL, a.comment, CONCAT(b.comment, '; ', a.comment))
FROM area_commitment_contract AS a
WHERE a.comment IS NOT NULL AND a.fbnr IN (SELECT revnr FROM area_commitment WHERE revnr > 1 AND fbnr = b.fbnr);
-- fix revnr
UPDATE area_commitment AS b
SET revnr = a.revnr + 1
FROM area_commitment AS a
WHERE a.fbnr = b.fbnr AND a.revnr < b.revnr;
UPDATE area_commitment AS b
SET revnr = a.revnr + 1
FROM area_commitment AS a
WHERE a.fbnr = b.fbnr AND a.revnr < b.revnr;
UPDATE area_commitment AS b
SET revnr = a.revnr + 1
FROM area_commitment AS a
WHERE a.fbnr = b.fbnr AND a.revnr < b.revnr;
-- fix year_from
UPDATE area_commitment AS b
SET year_from = a.year_to + 1
FROM area_commitment AS a
WHERE a.fbnr = b.fbnr AND a.revnr = b.revnr - 1
AND a.year_to = b.year_from;
UPDATE area_commitment AS b
SET year_from = a.year_to + 1
FROM area_commitment AS a
WHERE a.fbnr = b.fbnr AND a.revnr = b.revnr - 1
AND a.year_to = b.year_from;
-- delete unreferenced contracts
DELETE FROM area_commitment_contract
WHERE fbnr IN (SELECT c.fbnr FROM area_commitment_contract c
LEFT JOIN area_commitment a ON a.fbnr = c.fbnr
WHERE a.fbnr IS NULL);
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
-8
View File
@@ -1,8 +0,0 @@
-- schema version 38 to 39
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = REPLACE(sql, '{2,}', '{2,64}')
WHERE type = 'table' AND name = 'member_email_address';
PRAGMA writable_schema = OFF;
UPDATE wb_gem SET hkid = 'WLLB' WHERE gkz IN (10710, 10723);
-318
View File
@@ -1,318 +0,0 @@
-- schema version 39 to 40
PRAGMA writable_schema = ON;
ALTER TABLE member RENAME COLUMN business_shares TO shares;
ALTER TABLE member ADD COLUMN shares_red INTEGER NOT NULL DEFAULT 0;
ALTER TABLE member ADD COLUMN shares_white INTEGER NOT NULL DEFAULT 0;
ALTER TABLE member ADD COLUMN shares_dormant INTEGER NOT NULL DEFAULT 0;
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
UPDATE member
SET shares_dormant = shares + shares_red + shares_white,
shares = 0, shares_red = 0, shares_white = 0
WHERE NOT active;
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
CREATE TABLE member_history_new (
histnr INTEGER NOT NULL,
from_mgnr INTEGER,
from_type INTEGER,
to_mgnr INTEGER,
to_type INTEGER,
date TEXT NOT NULL CHECK (date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$') DEFAULT CURRENT_DATE,
reason TEXT NOT NULL CHECK (reason REGEXP '^[a-z_]+$'),
source TEXT NOT NULL CHECK (source REGEXP '^[a-z_]+$'),
shares INTEGER NOT NULL,
value_per_share INTEGER DEFAULT NULL,
currency TEXT DEFAULT NULL,
comment TEXT DEFAULT NULL,
CONSTRAINT pk_member_history PRIMARY KEY (histnr),
CONSTRAINT fk_member_history_member_from FOREIGN KEY (from_mgnr) REFERENCES member (mgnr)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_member_history_member_to FOREIGN KEY (to_mgnr) REFERENCES member (mgnr)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_member_history_currency FOREIGN KEY (currency) REFERENCES currency (code)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT c_member_history CHECK ((from_mgnr IS NOT NULL OR to_mgnr IS NOT NULL) AND
((from_mgnr IS NULL AND from_type IS NULL) OR (from_mgnr IS NOT NULL AND from_type IS NOT NULL)) AND
((to_mgnr IS NULL AND to_type IS NULL) OR (to_mgnr IS NOT NULL AND to_type IS NOT NULL)))
) STRICT;
INSERT INTO member_history_new (histnr, from_mgnr, from_type, to_mgnr, to_type, date, reason, source, shares, value_per_share, currency, comment)
SELECT ROW_NUMBER() OVER(ORDER BY h.date, h.mgnr), NULL, NULL, h.mgnr, 1, h.date, h.type, 'elwig', h.business_shares, s.bs_value / POW(10, s.precision - 2), s.currency, comment
FROM member_history h
JOIN season s ON s.year = SUBSTR(h.date, 1, 4);
PRAGMA foreign_keys = OFF;
DROP TABLE member_history;
ALTER TABLE member_history_new RENAME TO member_history;
PRAGMA foreign_keys = ON;
CREATE TRIGGER t_member_history_i_member
AFTER INSERT ON member_history FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS') = 1
BEGIN
UPDATE member SET shares = shares - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 1;
UPDATE member SET shares_red = shares_red - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 2;
UPDATE member SET shares_white = shares_white - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 9;
UPDATE member SET shares = shares + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 1;
UPDATE member SET shares_red = shares_red + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 2;
UPDATE member SET shares_white = shares_white + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 9;
END;
CREATE TRIGGER t_member_history_u_member
AFTER UPDATE ON member_history FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS') = 1
BEGIN
UPDATE member SET shares = shares + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 1;
UPDATE member SET shares_red = shares_red + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 2;
UPDATE member SET shares_white = shares_white + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 9;
UPDATE member SET shares = shares - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 1;
UPDATE member SET shares_red = shares_red - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 2;
UPDATE member SET shares_white = shares_white - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 9;
UPDATE member SET shares = shares - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 1;
UPDATE member SET shares_red = shares_red - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 2;
UPDATE member SET shares_white = shares_white - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 9;
UPDATE member SET shares = shares + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 1;
UPDATE member SET shares_red = shares_red + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 2;
UPDATE member SET shares_white = shares_white + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 9;
END;
CREATE TRIGGER t_member_history_d_member
AFTER DELETE ON member_history FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS') = 1
BEGIN
UPDATE member SET shares = shares + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 1;
UPDATE member SET shares_red = shares_red + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 2;
UPDATE member SET shares_white = shares_white + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 9;
UPDATE member SET shares = shares - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 1;
UPDATE member SET shares_red = shares_red - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 2;
UPDATE member SET shares_white = shares_white - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 9;
END;
INSERT INTO client_parameter (param, value) VALUES ('ENABLE_MEMBER_HISTORY_TRIGGERS', '1');
CREATE TABLE season_new (
year INTEGER NOT NULL CHECK (year >= 1000 AND year <= 9999),
currency TEXT NOT NULL,
precision INTEGER NOT NULL DEFAULT 4,
max_kg_per_ha INTEGER NOT NULL DEFAULT 10000,
vat_normal REAL NOT NULL DEFAULT 0.10,
vat_flatrate REAL NOT NULL DEFAULT 0.13,
min_kg_per_share INTEGER DEFAULT NULL,
max_kg_per_share INTEGER DEFAULT NULL,
min_kg_per_share_red INTEGER DEFAULT NULL,
max_kg_per_share_red INTEGER DEFAULT NULL,
min_kg_per_share_white INTEGER DEFAULT NULL,
max_kg_per_share_white INTEGER DEFAULT NULL,
penalty_per_kg INTEGER DEFAULT NULL,
penalty_amount INTEGER DEFAULT NULL,
penalty_none INTEGER DEFAULT NULL,
penalty_per_share_amount INTEGER DEFAULT NULL,
penalty_per_share_none INTEGER DEFAULT NULL,
share_value INTEGER,
start_date TEXT CHECK (start_date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$'),
end_date TEXT CHECK (end_date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$'),
calc_mode INTEGER NOT NULL DEFAULT 0,
CONSTRAINT pk_season PRIMARY KEY (year),
CONSTRAINT fk_season_currency FOREIGN KEY (currency) REFERENCES currency (code)
ON UPDATE CASCADE
ON DELETE RESTRICT
) STRICT;
INSERT INTO season_new (year, currency, precision, max_kg_per_ha, vat_normal, vat_flatrate, min_kg_per_share, max_kg_per_share, penalty_per_kg, penalty_amount, penalty_none, penalty_per_share_amount, penalty_per_share_none, share_value, start_date, end_date, calc_mode)
SELECT year, currency, precision, max_kg_per_ha, vat_normal, vat_flatrate, min_kg_per_bs, max_kg_per_bs, penalty_per_kg, penalty_amount, penalty_none, penalty_per_bs_amount, penalty_per_bs_none, bs_value, start_date, end_date, calc_mode
FROM season;
PRAGMA foreign_keys = OFF;
DROP TABLE season;
ALTER TABLE season_new RENAME TO season;
PRAGMA foreign_keys = ON;
DROP VIEW v_delivery;
CREATE VIEW v_delivery AS
SELECT p.year, p.did, p.dpnr,
d.date, d.time, d.zwstid, d.lnr, d.lsnr,
m.mgnr, m.name, m.given_name,
v.sortid, v.type, a.attrid, p.cultid,
p.weight, p.kmw, ROUND(p.kmw * (4.54 + 0.022 * p.kmw), 0) AS oe, p.qualid,
p.hkid, p.kgnr, p.rdnr,
p.net_weight, p.gebunden,
p.qualid IN (SELECT l.qualid FROM wine_quality_level l WHERE NOT l.predicate AND (p.kmw >= l.min_kmw OR l.min_kmw IS NULL) ORDER BY l.min_kmw DESC LIMIT 1,100) AS abgewertet,
p.qualid NOT IN ('WEI', 'RSW', 'LDW') AS min_quw,
IIF(a.strict, COALESCE(a.fill_lower, 0), 0) AS attribute_prio,
GROUP_CONCAT(o.modid) AS modifiers,
d.comment, p.comment AS part_comment
FROM delivery_part p
JOIN delivery d ON (d.year, d.did) = (p.year, p.did)
JOIN member m ON m.mgnr = d.mgnr
LEFT JOIN wine_variety v ON v.sortid = p.sortid
LEFT JOIN wine_attribute a ON a.attrid = p.attrid
LEFT JOIN delivery_part_modifier o ON (o.year, o.did, o.dpnr) = (p.year, p.did, p.dpnr)
GROUP BY p.year, p.did, p.dpnr
ORDER BY p.year, p.did, p.dpnr, o.modid;
DROP VIEW v_stat_season;
CREATE VIEW v_stat_season AS
SELECT year,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year
ORDER BY year;
DROP VIEW v_stat_sort;
CREATE VIEW v_stat_variety AS
SELECT year, sortid,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year, sortid
ORDER BY year, sortid;
DROP VIEW v_stat_attr;
CREATE VIEW v_stat_attr AS
SELECT year, attrid,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year, attrid
ORDER BY year, attrid;
DROP VIEW v_stat_sort_attr;
CREATE VIEW v_stat_variety_attr AS
SELECT year, sortid, attrid,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year, sortid, attrid
ORDER BY year, sortid, attrid;
DROP VIEW v_stat_member;
CREATE VIEW v_stat_member AS
SELECT year, mgnr,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen
FROM v_delivery
GROUP BY year, mgnr
ORDER BY year, mgnr;
CREATE VIEW v_member_history AS
SELECT m.mgnr, s.year,
m.shares + SUM(IIF(h1.from_type = 1, h1.shares, 0)) - SUM(IIF(h2.to_type = 1, h2.shares, 0)) AS shares,
m.shares_red + SUM(IIF(h1.from_type = 2, h1.shares, 0)) - SUM(IIF(h2.to_type = 2, h2.shares, 0)) AS shares_red,
m.shares_white + SUM(IIF(h1.from_type = 3, h1.shares, 0)) - SUM(IIF(h2.to_type = 3, h2.shares, 0)) AS shares_white,
m.shares_dormant + SUM(IIF(h1.from_type = 9, h1.shares, 0)) - SUM(IIF(h2.to_type = 9, h2.shares, 0)) AS shares_dormant
FROM member m, v_virtual_season s
LEFT JOIN member_history h1 ON (SUBSTR(h1.date, 1, 4) + 0) > s.year AND h1.from_mgnr = m.mgnr
LEFT JOIN member_history h2 ON (SUBSTR(h2.date, 1, 4) + 0) > s.year AND h2.to_mgnr = m.mgnr
GROUP BY m.mgnr, s.year
ORDER BY m.mgnr, s.year;
DROP VIEW v_total_under_delivery;
CREATE VIEW v_total_under_delivery AS
SELECT s.year, m.mgnr,
h.shares,
h.shares * COALESCE(s.min_kg_per_share, 0) AS min_kg,
h.shares * COALESCE(s.max_kg_per_share, 0) AS max_kg,
COALESCE(d.weight_total, 0) AS weight_total,
IIF(COALESCE(d.weight_total, 0) < h.shares * COALESCE(s.min_kg_per_share, 0),
COALESCE(d.weight_total, 0) - h.shares * COALESCE(s.min_kg_per_share, 0),
IIF(COALESCE(d.weight_total, 0) > h.shares * COALESCE(s.max_kg_per_share, 0),
COALESCE(d.weight_total, 0) - h.shares * COALESCE(s.max_kg_per_share, 0),
0)) AS diff,
h.shares_red,
h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0) AS min_kg_red,
h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0) AS max_kg_red,
COALESCE(d.weight_red, 0) AS weight_red,
IIF(COALESCE(d.weight_red, 0) < h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0),
COALESCE(d.weight_red, 0) - h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0),
IIF(COALESCE(d.weight_red, 0) > h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0),
COALESCE(d.weight_red, 0) - h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0),
0)) AS diff_red,
h.shares_white,
h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0) AS min_kg_white,
h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0) AS max_kg_white,
COALESCE(d.weight_white, 0) AS weight_white,
IIF(COALESCE(d.weight_white, 0) < h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0),
COALESCE(d.weight_white, 0) - h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0),
IIF(COALESCE(d.weight_white, 0) > h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0),
COALESCE(d.weight_white, 0) - h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0),
0)) AS diff_white
FROM member m, season s
LEFT JOIN v_member_history h ON (h.year, h.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_stat_member d ON (d.year, d.mgnr) = (s.year, m.mgnr)
ORDER BY s.year, m.mgnr;
DROP VIEW v_penalty_business_shares;
CREATE VIEW v_penalty_business_shares AS
SELECT u.year, u.mgnr,
SUM(IIF(u.weight_total = 0, COALESCE(-s.penalty_none, 0) + COALESCE(-(u.shares + u.shares_red + u.shares_white) * s.penalty_per_share_none, 0), 0) +
IIF(u.diff < 0 OR u.diff_red < 0 OR u.diff_white < 0, COALESCE(-s.penalty_amount, 0), 0) +
COALESCE((u.diff + u.diff_red + u.diff_white) * s.penalty_per_kg, 0) +
COALESCE(CEIL(CAST(u.diff AS REAL) / s.min_kg_per_share) * s.penalty_per_share_amount, 0) +
COALESCE(CEIL(CAST(u.diff_red AS REAL) / s.min_kg_per_share_red) * s.penalty_per_share_amount, 0) +
COALESCE(CEIL(CAST(u.diff_white AS REAL) / s.min_kg_per_share_white) * s.penalty_per_share_amount, 0)
) AS total_penalty
FROM v_total_under_delivery u
JOIN season s ON u.year = s.year
JOIN member m ON m.mgnr = u.mgnr
WHERE m.active
GROUP BY u.year, u.mgnr
HAVING total_penalty < 0
ORDER BY u.year, u.mgnr;
DROP VIEW v_auto_business_shares;
CREATE VIEW v_auto_business_shares AS
SELECT (SUBSTR(h.date, 1, 4) + 0) AS year,
h.to_mgnr AS mgnr,
SUM(h.shares) AS shares,
SUM(h.shares * COALESCE(h.value_per_share, 0)) AS total_amount
FROM member_history h
WHERE h.reason = 'auto' AND h.source = 'elwig'
GROUP BY year, h.to_mgnr
ORDER BY year, h.to_mgnr;
PRAGMA writable_schema = OFF;
+27 -48
View File
@@ -20,45 +20,41 @@ namespace Elwig.Services {
}
public static void ClearInputs(this AreaComAdminViewModel vm) {
vm.Period = null;
}
public static void FillInputs(this AreaComAdminViewModel vm, AreaComContract c) {
vm.FbNr = c.FbNr;
vm.Period = c.YearTo == null ? $"ab {c.YearFrom}" : $"{c.YearFrom}\u2013{c.YearTo}";
vm.Comment = c.Comment;
vm.Kg = ControlUtils.GetItemFromSourceWithPk(vm.KgSource, c.KgNr) as AT_Kg;
vm.Rd = ControlUtils.GetItemFromSourceWithPk(vm.RdSource, c.KgNr, c.RdNr) as WbRd;
}
public static void FillInputs(this AreaComAdminViewModel vm, AreaCom a) {
vm.FbNr = a.FbNr;
vm.MgNr = a.MgNr;
vm.YearFrom = a.YearFrom;
vm.YearTo = a.YearTo;
vm.AreaComType = ControlUtils.GetItemFromSourceWithPk(vm.AreaComTypeSource, a.VtrgId) as AreaComType;
vm.WineCult = ControlUtils.GetItemFromSourceWithPk(vm.WineCultSource, a.CultId) as WineCult;
vm.Comment = a.Comment;
vm.Kg = ControlUtils.GetItemFromSourceWithPk(vm.KgSource, a.KgNr) as AT_Kg;
vm.Rd = ControlUtils.GetItemFromSourceWithPk(vm.RdSource, a.KgNr, a.RdNr) as WbRd;
vm.GstNr = a.GstNr;
vm.Area = a.Area;
}
public static async Task<(List<string>, IQueryable<AreaComContract>, IQueryable<AreaCom>, List<string>)> GetFilters(this AreaComAdminViewModel vm, AppDbContext ctx) {
public static async Task<(List<string>, IQueryable<AreaCom>, List<string>)> GetFilters(this AreaComAdminViewModel vm, AppDbContext ctx) {
List<string> filterNames = [];
IQueryable<AreaCom> areaComQuery = ctx.AreaCommitments.Where(a => a.MgNr == vm.FilterMember.MgNr).OrderBy(a => a.FbNr);
if (vm.FilterSeason is int season) {
areaComQuery = Utils.ActiveAreaCommitments(areaComQuery, season);
filterNames.Add($"laufend {season}");
if (vm.ShowOnlyActiveAreaComs) {
areaComQuery = Utils.ActiveAreaCommitments(areaComQuery, Utils.CurrentLastSeason);
filterNames.Add($"laufend {Utils.CurrentLastSeason}");
}
var filterVar = new List<string>();
var filterNotVar = new List<string>();
var filterAttr = new List<string>();
var filterNotAttr = new List<string>();
var filterSeasons = new List<int>();
var filter = vm.TextFilter;
if (filter.Count > 0) {
var var = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v);
var attrId = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.AttrId, a => a);
var attr = attrId.Values.ToDictionary(a => a.Name.ToLower().Split(" ")[0], a => a);
var var = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v);
var attr = await ctx.WineAttributes.ToDictionaryAsync(a => a.Name.ToLower().Split(" ")[0], a => a);
var attrId = await ctx.WineAttributes.ToDictionaryAsync(a => a.AttrId, a => a);
for (int i = 0; i < filter.Count; i++) {
var e = filter[i];
@@ -92,6 +88,10 @@ namespace Elwig.Services {
filter.RemoveAt(i--);
filterNames.Add($"ohne {var[e[1..3].ToUpper()].Name}");
filterNames.Add($"ohne Attribut {attrId[e[3..].ToUpper()].Name}");
} else if (e.Length == 4 && int.TryParse(e, out var year)) {
filterSeasons.Add(year);
filter.RemoveAt(i--);
filterNames.Add($"laufend {e}");
}
}
@@ -99,50 +99,38 @@ namespace Elwig.Services {
if (filterNotVar.Count > 0) areaComQuery = areaComQuery.Where(a => !filterNotVar.Contains(a.AreaComType.WineVar.SortId));
if (filterAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId != null && filterAttr.Contains(a.AreaComType.WineAttr.AttrId));
if (filterNotAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId == null || !filterNotAttr.Contains(a.AreaComType.WineAttr.AttrId));
foreach (var year in filterSeasons) areaComQuery = Utils.ActiveAreaCommitments(areaComQuery, year);
}
IQueryable<AreaComContract> contracts = areaComQuery
.Select(c => c.Contract).Distinct()
.OrderBy(c => c.FbNr);
return (filterNames, contracts, areaComQuery, filter);
return (filterNames, areaComQuery, filter);
}
public static async Task<(int FbNr, int RevNr)> UpdateAreaCommitment(this AreaComAdminViewModel vm, int? oldFbNr, int? revNr) {
public static async Task<int> UpdateAreaCommitment(this AreaComAdminViewModel vm, int? oldFbNr) {
int newFbNr = vm.FbNr!.Value;
return await Task.Run(async () => {
using var ctx = new AppDbContext();
var c = new AreaComContract {
FbNr = oldFbNr ?? newFbNr,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
KgNr = vm.Kg!.KgNr,
RdNr = vm.Rd?.RdNr,
};
var a = new AreaCom {
FbNr = oldFbNr ?? newFbNr,
RevNr = revNr ?? await ctx.NextRevNr(oldFbNr ?? newFbNr),
MgNr = vm.MgNr!.Value,
YearFrom = vm.YearFrom,
YearTo = vm.YearTo,
VtrgId = vm.AreaComType!.VtrgId,
CultId = vm.WineCult?.CultId,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment,
KgNr = vm.Kg!.KgNr,
RdNr = vm.Rd?.RdNr,
GstNr = vm.GstNr?.Trim() ?? "-",
Area = vm.Area!.Value,
};
if (vm.Rd?.RdNr == 0) {
vm.Rd.RdNr = await ctx.NextRdNr(c.KgNr);
c.RdNr = vm.Rd.RdNr;
vm.Rd.RdNr = await ctx.NextRdNr(a.KgNr);
a.RdNr = vm.Rd.RdNr;
ctx.Add(vm.Rd);
}
if (oldFbNr != null) {
ctx.Update(c);
} else {
ctx.Add(c);
}
if (revNr != null) {
ctx.Update(a);
} else {
ctx.Add(a);
@@ -151,10 +139,10 @@ namespace Elwig.Services {
await ctx.SaveChangesAsync();
if (newFbNr != a.FbNr) {
await ctx.Database.ExecuteSqlAsync($"UPDATE area_commitment_contract SET fbnr = {newFbNr} WHERE fbnr = {oldFbNr}");
await ctx.Database.ExecuteSqlAsync($"UPDATE area_commitment SET fbnr = {newFbNr} WHERE fbnr = {oldFbNr}");
}
return (newFbNr, a.RevNr);
return newFbNr;
});
}
@@ -268,16 +256,7 @@ namespace Elwig.Services {
public static async Task DeleteAreaCom(int fbnr) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
var l = (await ctx.AreaCommitmentContracts.FindAsync(fbnr))!;
ctx.Remove(l);
await ctx.SaveChangesAsync();
});
}
public static async Task DeleteAreaComRevision(int fbnr, int revnr) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
var l = (await ctx.AreaCommitments.FindAsync(fbnr, revnr))!;
var l = (await ctx.AreaCommitments.FindAsync(fbnr))!;
ctx.Remove(l);
await ctx.SaveChangesAsync();
});
+42 -31
View File
@@ -8,6 +8,8 @@ using Microsoft.EntityFrameworkCore;
using Elwig.Documents;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos;
using Microsoft.Win32;
using System.Windows.Input;
using System.Windows;
using System;
using LinqKit;
@@ -63,11 +65,11 @@ namespace Elwig.Services {
var filter = vm.TextFilter;
if (filter.Count > 0) {
var var = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v);
var mgnr = await ctx.FetchMembers(includeNotActive: true).ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var zwst = await ctx.FetchBranches().ToDictionaryAsync(b => b.Name.ToLower().Split(' ')[0], b => b);
var attr = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.Name.ToLower().Split(' ')[0], a => a);
var cult = await ctx.FetchWineCultivations().ToDictionaryAsync(c => c.Name.ToLower().Split(' ')[0], c => c);
var var = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v);
var mgnr = await ctx.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var zwst = await ctx.Branches.ToDictionaryAsync(b => b.Name.ToLower().Split(' ')[0], b => b);
var attr = await ctx.WineAttributes.ToDictionaryAsync(a => a.Name.ToLower().Split(' ')[0], a => a);
var cult = await ctx.WineCultivations.ToDictionaryAsync(c => c.Name.ToLower().Split(' ')[0], c => c);
for (int i = 0; i < filter.Count; i++) {
var e = filter[i];
@@ -259,20 +261,37 @@ namespace Elwig.Services {
.ThenBy(a => a.Member.MgNr);
if (mode == ExportMode.SaveList) {
var filename = InteractionService.SaveFile(DeliveryAncmtList.Name, DeliveryAncmtList.Name, "ods");
if (filename != null) {
await Utils.RunForeground(async () => {
var d = new SaveFileDialog() {
FileName = $"{DeliveryAncmtList.Name}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{DeliveryAncmtList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryAncmtListData.FromQuery(query, filterNames);
using var ods = new OdsFile(filename);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else {
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryAncmtListData.FromQuery(query, filterNames);
using var doc = new DeliveryAncmtList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
@@ -299,13 +318,18 @@ namespace Elwig.Services {
AddToolTipCell(grid, $"{weight * 100.0 / total2:N1} %", row, 4, 1, bold, true);
}
public static async Task<(string Text, (string?, string?, int, int?, int)[] Grid)> GenerateToolTipData(IQueryable<DeliveryAncmt> deliveryAncmts) {
var grid = new List<(string?, string?, int, int?, int)>();
public static async Task<(string, Grid)> GenerateToolTip(IQueryable<DeliveryAncmt> deliveryAncmts) {
var grid = new Grid();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
var text = "-";
var weight = await deliveryAncmts.SumAsync(p => p.Weight);
text = $"{weight:N0} kg";
grid.Add(("Menge", null, weight, null, weight));
AddToolTipRow(grid, 0, "Menge", null, weight, null, weight);
if (await deliveryAncmts.AnyAsync()) {
var attrGroups = await deliveryAncmts
@@ -346,11 +370,13 @@ namespace Elwig.Services {
.ThenBy(g => g.SortId)
.ToListAsync();
int rowNum = 1;
foreach (var attrG in attrGroups) {
rowNum++;
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
grid.Add((name, null, attrG.Weight, attrG.Weight, weight));
AddToolTipRow(grid, rowNum++, name, null, attrG.Weight, attrG.Weight, weight);
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
grid.Add((null, g.SortId, g.Weight, attrG.Weight, weight));
AddToolTipRow(grid, rowNum++, null, g.SortId, g.Weight, attrG.Weight, weight);
}
}
@@ -369,22 +395,7 @@ namespace Elwig.Services {
}
}
return (text, grid.ToArray());
}
public static Grid GenerateToolTip((string?, string?, int, int?, int)[] data) {
var grid = new Grid();
grid.ColumnDefinitions.Add(new() { Width = new(10) });
grid.ColumnDefinitions.Add(new() { Width = new(60) });
grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
grid.ColumnDefinitions.Add(new() { Width = new(50) });
int rowNum = 0;
foreach (var row in data) {
if (rowNum != 0 && row.Item2 == null) rowNum++;
AddToolTipRow(grid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5);
}
return grid;
return (text, grid);
}
}
}
+5 -5
View File
@@ -60,8 +60,8 @@ namespace Elwig.Services {
var filter = vm.TextFilter;
if (filter.Count > 0) {
var var = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v);
var zwst = await ctx.FetchBranches().ToDictionaryAsync(b => b.Name.ToLower().Split(" ")[0], b => b);
var var = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v);
var zwst = await ctx.Branches.ToDictionaryAsync(b => b.Name.ToLower().Split(" ")[0], b => b);
for (int i = 0; i < filter.Count; i++) {
var e = filter[i];
@@ -174,12 +174,12 @@ namespace Elwig.Services {
ctx.Add(s);
}
await ctx.UpdateDeliveryScheduleWineVarieties(s, (await ctx.DeliveryScheduleWineVarieties
ctx.UpdateDeliveryScheduleWineVarieties(s, (await ctx.DeliveryScheduleWineVarieties
.Where(v => v.Year == s.Year && v.DsNr == s.DsNr)
.Select(v => new { v.Variety, v.Priority })
.ToListAsync())
.Select(v => (v.Variety.SortId, v.Priority))
.ToList(), vm.MainVarieties.Select(v => (v.SortId, 1)).Union(vm.OtherVarieties.Select(v => (v.SortId, 2))).ToList());
.Select(v => (v.Variety, v.Priority))
.ToList(), vm.MainVarieties.Select(v => (v, 1)).Union(vm.OtherVarieties.Select(v => (v, 2))).ToList());
await ctx.SaveChangesAsync();
});
+157 -184
View File
@@ -3,9 +3,11 @@ using Elwig.Helpers.Export;
using Elwig.Helpers;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows;
using System;
using Elwig.ViewModels;
@@ -25,7 +27,10 @@ namespace Elwig.Services {
public static async Task<Member?> GetMemberAsync(int mgnr) {
using var ctx = new AppDbContext();
return await ctx.FetchMembers(mgnr).SingleOrDefaultAsync();
return await ctx.Members
.Include(m => m.PostalDest.AtPlz!.Ort)
.Include(m => m.DefaultWbKg!.AtKg)
.FirstOrDefaultAsync(m => m.MgNr == mgnr);
}
public static Member? GetMember(int mgnr) {
@@ -66,7 +71,7 @@ namespace Elwig.Services {
vm.IsNetWeight = p.IsNetWeight;
vm.Modifiers.Clear();
foreach (var m in p.PartModifiers) {
foreach (var m in p.Modifiers) {
vm.Modifiers.Add((Modifier)ControlUtils.GetItemFromSourceWithPk(vm.ModifiersSource, m.Year, m.ModId)!);
}
@@ -124,12 +129,12 @@ namespace Elwig.Services {
var filter = vm.TextFilter;
if (filter.Count > 0) {
var var = await ctx.FetchWineVarieties().ToDictionaryAsync(v => v.SortId, v => v);
var qual = await ctx.FetchWineQualityLevels(false).ToDictionaryAsync(q => q.QualId, q => q);
var mgnr = await ctx.FetchMembers(includeNotActive: true).ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var zwst = await ctx.FetchBranches().ToDictionaryAsync(b => b.Name.ToLower().Split(' ')[0], b => b);
var attr = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.Name.ToLower().Split(' ')[0], a => a);
var cult = await ctx.FetchWineCultivations().ToDictionaryAsync(c => c.Name.ToLower().Split(' ')[0], c => c);
var var = await ctx.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v);
var qual = await ctx.WineQualityLevels.Where(q => !q.IsPredicate).ToDictionaryAsync(q => q.QualId, q => q);
var mgnr = await ctx.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var zwst = await ctx.Branches.ToDictionaryAsync(b => b.Name.ToLower().Split(' ')[0], b => b);
var attr = await ctx.WineAttributes.ToDictionaryAsync(a => a.Name.ToLower().Split(' ')[0], a => a);
var cult = await ctx.WineCultivations.ToDictionaryAsync(c => c.Name.ToLower().Split(' ')[0], c => c);
for (int i = 0; i < filter.Count; i++) {
var e = filter[i];
@@ -467,7 +472,6 @@ namespace Elwig.Services {
DeliveryPart p;
using var ctx = new AppDbContext();
using var tx = await ctx.Database.BeginTransactionAsync();
int year = oldYear ?? Utils.CurrentYear;
int did = oldDid ?? await ctx.NextDId(year);
int dpnr = oldDpnr ?? await ctx.NextDPNr(year, did);
@@ -544,21 +548,21 @@ namespace Elwig.Services {
ctx.Add(p);
}
await ctx.UpdateDeliveryPartModifiers(p, await ctx.DeliveryPartModifiers
ctx.UpdateDeliveryPartModifiers(p, await ctx.DeliveryPartModifiers
.Where(m => m.Year == p.Year && m.DId == p.DId && m.DPNr == p.DPNr)
.Select(m => m.ModId)
.ToListAsync(), vm.Modifiers.Select(m => m.ModId).ToList());
.Select(m => m.Modifier)
.ToListAsync(), vm.Modifiers);
if (originalMgNr != null && originalMgNr.Value != d.MgNr) {
// update origin (KgNr), if default is selected
var newKgNr = (await ctx.FetchMembers(d.MgNr).SingleOrDefaultAsync())?.DefaultKgNr;
await ctx.DeliveryParts
.Where(p => p.Year == d.Year && p.DId == d.DId && p.DPNr != dpnr && p.KgNr == originalMemberKgNr)
.ExecuteUpdateAsync(u => u.SetProperty(p => p.KgNr, newKgNr));
var newKgNr = (await ctx.Members.FindAsync(d.MgNr))?.DefaultKgNr;
foreach (var part in d.Parts.Where(part => part.DPNr != dpnr && part.KgNr == originalMemberKgNr)) {
part.KgNr = newKgNr;
ctx.Update(part);
}
}
await ctx.SaveChangesAsync();
await tx.CommitAsync();
return p;
});
@@ -570,10 +574,7 @@ namespace Elwig.Services {
using var ctx = new AppDbContext();
bool anyLeft = false;
var d = await ctx.Deliveries
.Where(d => d.Year == year && d.DId == did)
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
.SingleAsync();
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var lnr = await ctx.NextLNr(d.Date, d.ZwstId);
n = new Delivery {
Year = year,
@@ -600,11 +601,7 @@ namespace Elwig.Services {
anyLeft = true;
p.Weight -= w;
ctx.Update(p);
var s = new DeliveryPart {
SortId = null!,
QualId = null!,
HkId = null!,
};
var s = ctx.CreateProxy<DeliveryPart>();
var values = ctx.Entry(p).CurrentValues;
ctx.Entry(s).CurrentValues.SetValues(values);
s.Year = n.Year;
@@ -635,11 +632,8 @@ namespace Elwig.Services {
Delivery n;
using var ctx = new AppDbContext();
var anyLeft = false;
n = (await ctx.Deliveries.Where(d => d.LsNr == lsnr).FirstAsync())!;
var d = await ctx.Deliveries
.Where(d => d.Year == year && d.DId == did)
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
.SingleAsync();
n = (await ctx.Deliveries.FirstAsync(d => d.LsNr == lsnr))!;
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var dpnr = await ctx.NextDPNr(n.Year, n.DId);
foreach (var (p, w) in d.Parts.ToList().Zip(weights)) {
if (w <= 0) {
@@ -651,11 +645,7 @@ namespace Elwig.Services {
anyLeft = true;
p.Weight -= w;
ctx.Update(p);
var s = new DeliveryPart {
SortId = null!,
QualId = null!,
HkId = null!,
};
var s = ctx.CreateProxy<DeliveryPart>();
var values = ctx.Entry(p).CurrentValues;
ctx.Entry(s).CurrentValues.SetValues(values);
s.Year = n.Year;
@@ -684,10 +674,7 @@ namespace Elwig.Services {
public static async Task DepreciateDelivery(int year, int did, int[] weights) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
var d = await ctx.Deliveries
.Where(d => d.Year == year && d.DId == did)
.Include(d => d.Parts).ThenInclude(p => p.PartModifiers)
.SingleAsync();
var d = (await ctx.Deliveries.FindAsync(year, did))!;
var dpnr = await ctx.NextDPNr(year, did);
foreach (var (p, w) in d.Parts.ToList().Zip(weights)) {
if (w <= 0) {
@@ -699,11 +686,7 @@ namespace Elwig.Services {
} else {
p.Weight -= w;
ctx.Update(p);
var n = new DeliveryPart {
SortId = null!,
QualId = null!,
HkId = null!,
};
var n = ctx.CreateProxy<DeliveryPart>();
var values = ctx.Entry(p).CurrentValues;
ctx.Entry(n).CurrentValues.SetValues(values);
n.DPNr = dpnr++;
@@ -725,10 +708,18 @@ namespace Elwig.Services {
}
public static async Task GenerateDeliveryNote(int year, int did, ExportMode mode) {
await Utils.RunForeground(async () => {
using var doc = await DeliveryNote.Initialize(year, did);
await Utils.ExportDocument(doc, mode, doc.Delivery.LsNr, (doc.Member, $"{DeliveryNote.Name} Nr. {doc.Delivery.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {doc.Delivery.LsNr}"));
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var d = (await ctx.Deliveries.FindAsync(year, did))!;
using var doc = new DeliveryNote(d, ctx);
await Utils.ExportDocument(doc, mode, d.LsNr, (d.Member, $"{DeliveryNote.Name} Nr. {d.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {d.LsNr}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
public static async Task GenerateDeliveryJournal(this DeliveryAdminViewModel vm, ExportSubject subject, ExportMode mode) {
@@ -765,23 +756,45 @@ namespace Elwig.Services {
.ThenBy(p => p.DPNr);
if (mode == ExportMode.SaveList) {
var filename = InteractionService.SaveFile(DeliveryJournal.Name, DeliveryJournal.Name, "ods");
if (filename != null) {
await Utils.RunForeground(async () => {
var d = new SaveFileDialog() {
FileName = $"{DeliveryJournal.Name}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var ods = new OdsFile(filename);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Export) {
var filename = InteractionService.SaveFile(DeliveryJournal.Name, subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}", "elwig.zip");
if (filename != null) {
if (!filename.EndsWith(".elwig.zip")) filename += ".elwig.zip";
await Utils.RunForeground(async () => {
var d = new SaveFileDialog() {
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",
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 list = await query
.Select(p => p.Delivery)
.Distinct()
.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();
var wbKgs = list
.SelectMany(d => d.Parts)
@@ -790,19 +803,31 @@ namespace Elwig.Services {
.DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr)
.ToList();
await ElwigData.Export(filename, list, wbKgs, filterNames);
await ElwigData.Export(d.FileName, list, wbKgs, filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
});
Mouse.OverrideCursor = null;
} else {
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryJournal(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
@@ -823,11 +848,17 @@ namespace Elwig.Services {
throw new ArgumentException("Invalid value for ExportSubject");
}
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await WineQualityStatisticsData.FromQuery(query, App.Client.OrderingMemberList);
using var doc = new WineQualityStatistics(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
public static async Task GenerateLocalityStatistics(this DeliveryAdminViewModel vm, ExportSubject subject) {
@@ -843,13 +874,24 @@ namespace Elwig.Services {
throw new ArgumentException("Invalid value for ExportSubject");
}
var filename = InteractionService.SaveFile("Lieferstatistik pro Ort", $"Lieferstatistik-{vm.FilterSeason ?? Utils.CurrentLastSeason}", "ods");
if (filename != null) {
await Utils.RunForeground(async () => {
using var ods = new OdsFile(filename);
var d = new SaveFileDialog() {
FileName = $"Lieferstatistik-{vm.FilterSeason ?? Utils.CurrentLastSeason}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Lieferstatistik pro Ort speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ods = new OdsFile(d.FileName);
var tbl = await WineLocalityStatisticsData.FromQuery(query, filterNames);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
@@ -880,28 +922,45 @@ namespace Elwig.Services {
filterNames.Remove("abgewertet");
if (mode == ExportMode.SaveList) {
var filename = InteractionService.SaveFile(DeliveryDepreciationList.Name, $"{DeliveryDepreciationList.Name}-{vm.FilterSeason ?? Utils.CurrentLastSeason}", "ods");
if (filename != null) {
await Utils.RunForeground(async () => {
using var ods = new OdsFile(filename);
var d = new SaveFileDialog() {
FileName = $"{DeliveryDepreciationList.Name}-{vm.FilterSeason ?? Utils.CurrentLastSeason}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{DeliveryDepreciationList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ods = new OdsFile(d.FileName);
var tblTotal = await DeliveryJournalData.FromQuery(query, filterNames);
tblTotal.FullName = DeliveryDepreciationList.Name;
tblTotal.Name = "Gesamt";
await ods.AddTable(tblTotal);
foreach (var branch in await ctx.FetchBranches().ToListAsync()) {
foreach (var branch in await ctx.Branches.OrderBy(b => b.Name).ToListAsync()) {
var tbl = await DeliveryJournalData.FromQuery(query.Where(p => p.Delivery.ZwstId == branch.ZwstId), filterNames);
tbl.FullName = DeliveryDepreciationList.Name;
tbl.Name = branch.Name;
await ods.AddTable(tbl);
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else {
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryDepreciationList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
@@ -928,15 +987,26 @@ namespace Elwig.Services {
.ThenBy(p => p.AttrId)
.ThenBy(p => p.CultId);
var filename = InteractionService.SaveFile("Liefermengen", "Liefermengen", "ods");
if (filename != null) {
await Utils.RunForeground(async () => {
using var ods = new OdsFile(filename);
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;
}
}
@@ -978,23 +1048,16 @@ namespace Elwig.Services {
var gGrid = new List<(string?, string?, double, double, double)>();
var gText = "-";
var stat = (await deliveryParts.GroupBy(p => 0)
.Select(g => new {
Weight = g.Sum(p => p.Weight),
Min = g.Select(p => (double?)p.Kmw).DefaultIfEmpty().Min(),
Avg = g.Sum(p => p.Kmw * p.Weight) / g.Sum(p => p.Weight),
Max = g.Select(p => (double?)p.Kmw).DefaultIfEmpty().Max(),
})
.ToListAsync())
.DefaultIfEmpty(new { Weight = 0, Min = (double?)null, Avg = (double)0, Max = (double?)null })
.Single();
var weight = await deliveryParts.SumAsync(p => p.Weight);
wText = $"{weight:N0} kg";
wGrid.Add(("Menge", null, weight, null, weight));
wText = $"{stat.Weight:N0} kg";
wGrid.Add(("Menge", null, stat.Weight, null, stat.Weight));
if (stat.Min != null && stat.Max != null) {
gText = $"{stat.Min:N1}° / {stat.Avg:N1}° / {stat.Max:N1}°";
gGrid.Add(("Gradation", null, stat.Min.Value, stat.Avg, stat.Max.Value));
if (await deliveryParts.AnyAsync()) {
var kmwMin = await deliveryParts.MinAsync(p => p.Kmw);
var kmwAvg = Utils.AggregateDeliveryPartsKmw(deliveryParts);
var kmwMax = await deliveryParts.MaxAsync(p => p.Kmw);
gText = $"{kmwMin:N1}° / {kmwAvg:N1}° / {kmwMax:N1}°";
gGrid.Add(("Gradation", null, kmwMin, kmwAvg, kmwMax));
var attrGroups = await deliveryParts
.GroupBy(p => new { Attr = p.Attribute!.Name, Cult = p.Cultivation!.Name })
@@ -1045,9 +1108,9 @@ namespace Elwig.Services {
foreach (var attrG in attrGroups) {
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
wGrid.Add((name, null, attrG.Weight, attrG.Weight, stat.Weight));
wGrid.Add((name, null, attrG.Weight, attrG.Weight, weight));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
wGrid.Add((null, g.SortId, g.Weight, attrG.Weight, stat.Weight));
wGrid.Add((null, g.SortId, g.Weight, attrG.Weight, weight));
}
}
foreach (var attrG in attrGroups) {
@@ -1066,12 +1129,12 @@ namespace Elwig.Services {
gText += $" [{name}]";
}
if (sortGroups.Count > 1 && sortGroups.Count <= 4) {
wText += $" = {string.Join(" + ", sortGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / stat.Weight:0%})" + (g.SortId == null ? "" : $" [{g.SortId}]")))}";
wText += $" = {string.Join(" + ", sortGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / weight:0%})" + (g.SortId == null ? "" : $" [{g.SortId}]")))}";
gText += $" = {string.Join(" + ", sortGroups.Select(g => $"{g.Min:N1}/{g.Avg:N1}/{g.Max:N1}" + (g.SortId == null ? "" : $" [{g.SortId}]")))}";
}
} else if (attrGroups.Count <= 4) {
wText += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / stat.Weight:0%})" + (g.Attr == null && g.Cult == null ? "" : $" [{g.Attr}{(g.Attr != null && g.Cult != null ? " / " : "")}{g.Cult}]")))}";
wText += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / weight:0%})" + (g.Attr == null && g.Cult == null ? "" : $" [{g.Attr}{(g.Attr != null && g.Cult != null ? " / " : "")}{g.Cult}]")))}";
gText += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Min:N1}/{g.Avg:N1}/{g.Max:N1}" + (g.Attr == null && g.Cult == null ? "" : $" [{g.Attr}{(g.Attr != null && g.Cult != null ? " / " : "")}{g.Cult}]")))}";
}
}
@@ -1117,95 +1180,5 @@ 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();
if (!InteractionService.AskContinue("Massenaktion: Attribut setzen",
$"Soll wirklich für {dids.Length:N0} Teillieferung(en) das Attribut\n'{attributeName}' gesetz werden?"))
return;
await Utils.RunForeground(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) {
InteractionService.ShowException(exc);
}
}
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();
if (!InteractionService.AskContinue("Massenaktion: Zu-/Abschlag hinzufügen",
$"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' hinzugefügt werden?"))
return;
await Utils.RunForeground(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) {
InteractionService.ShowException(exc);
}
}
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();
if (!InteractionService.AskContinue("Massenaktion: Zu-/Abschlag entfernen",
$"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' entfernt werden?"))
return;
await Utils.RunForeground(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) {
InteractionService.ShowException(exc);
}
}
}
}
-150
View File
@@ -1,150 +0,0 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Windows;
namespace Elwig.Services {
public static class InteractionService {
public static Func<string, string, string, string?>? Override;
public static Action<string, string>? NextInformation;
public static Action<string, string>? NextWarning;
public static Action<string, string>? NextError;
public static Func<string, string, bool>? NextContinue;
public static Func<string, string, bool>? NextConfirmation;
public static Func<string, string, bool>? NextQuestion;
public static Func<string, string, string?>? NextSave;
public static readonly Dictionary<string, string> ExtensionFilters = new() {
["pdf"] = "PDF-Datei (*.pdf)|*.pdf",
["ods"] = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
["vcf"] = "vCard-Datei (*.vcf)|*.vcf",
["xml"] = "EBICS-Datei (*.xml)|*.xml",
["csv"] = "CSV-Datei (*.csv)|*.csv",
["sql.zip"] = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip",
["elwig.zip"] = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
};
public static void ShowInformation(string title, string text) {
if (NextInformation != null) {
NextInformation(title, text);
NextInformation = null;
} else if (Override != null) {
Override("information", title, text);
} else {
MessageBox.Show(text, title, MessageBoxButton.OK, MessageBoxImage.Information);
}
}
public static bool AskContinue(string title, string text) {
if (NextContinue != null) {
var r = NextContinue(title, text);
NextContinue = null;
return r;
} else if (Override != null) {
return Override("continue", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel) == MessageBoxResult.OK;
}
}
public static bool AskConfirmation(string title, string text) {
if (NextConfirmation != null) {
var r = NextConfirmation(title, text);
NextConfirmation = null;
return r;
} else if (Override != null) {
return Override("confirm", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No) == MessageBoxResult.Yes;
}
}
public static bool AskQuestion(string title, string text, bool defaultResult) {
if (NextQuestion != null) {
var r = NextQuestion(title, text);
NextQuestion = null;
return r;
} else if (Override != null) {
return Override("question", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.YesNo, MessageBoxImage.Question, defaultResult ? MessageBoxResult.Yes : MessageBoxResult.No) == MessageBoxResult.Yes;
}
}
public static void ShowWarning(string title, string text) {
if (NextWarning != null) {
NextWarning(title, text);
NextWarning = null;
} else if (Override != null) {
Override("warning", title, text);
} else {
MessageBox.Show(text, title, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
public static void ShowError(string title, string text) {
if (NextError != null) {
NextError(title, text);
NextError = null;
} else if (Override != null) {
Override("error", title, text);
} else {
MessageBox.Show(text, title, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public static void ShowException(Exception exc, bool showExcType = false, bool isError = true) {
ShowException("Fehler", exc, showExcType, isError);
}
public static void ShowException(string title, Exception exc, bool showExcType = false, bool isError = true) {
ShowException(title, null, exc, showExcType, isError);
}
public static void ShowException(string title, string? text, Exception exc, bool showExcType = false, bool isError = true) {
text = text == null ? "" : text + (text.EndsWith('.') || text.EndsWith('!') || text.EndsWith('?') ? "" : ":") + "\n\n";
text += exc.Message + (showExcType ? $" ({exc.GetType().Name})" : "");
if (exc.InnerException != null) text += "\n\n" + exc.InnerException.Message;
if (isError) {
ShowError(title, text);
} else {
ShowWarning(title, text);
}
}
public static void ShowDbException(string title, Exception exc) {
ShowException(title, "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!", exc);
}
public static bool AskException(string title, string? text, Exception exc) {
text = text == null ? "" : text + (text.EndsWith('.') || text.EndsWith('!') || text.EndsWith('?') ? "" : ":") + "\n\n";
text += exc.Message;
if (exc.InnerException != null) text += "\n\n" + exc.InnerException.Message;
if (Override != null) {
return Override("error", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No) == MessageBoxResult.Yes;
}
}
public static string? SaveFile(string title, string defaultFileName, string extension) {
if (NextSave != null) {
var r = NextSave(title, $"{defaultFileName}.{extension}");
NextSave = null;
return r;
} else if (Override != null) {
return Override("save", title, $"{defaultFileName}.{extension}");
} else {
var d = new SaveFileDialog() {
FileName = $"{defaultFileName}.{extension}",
DefaultExt = extension,
Filter = ExtensionFilters.GetValueOrDefault(extension, ""),
Title = $"{title} speichern unter - Elwig",
AddExtension = !extension.Contains('.'),
};
return d.ShowDialog() == true ? d.FileName : null;
}
}
}
}
+120 -59
View File
@@ -6,10 +6,13 @@ using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Elwig.Services {
public static class MemberService {
@@ -126,15 +129,7 @@ namespace Elwig.Services {
vm.EntryDate = (m.EntryDateString != null) ? string.Join(".", m.EntryDateString.Split("-").Reverse()) : null;
vm.ExitDate = (m.ExitDateString != null) ? string.Join(".", m.ExitDateString.Split("-").Reverse()) : null;
if (App.Client.HasRedWhite) {
vm.BusinessShares1 = m.SharesRed;
vm.BusinessShares2 = m.SharesWhite;
vm.BusinessShares3 = m.SharesDormant + m.Shares;
} else {
vm.BusinessShares1 = m.Shares;
vm.BusinessShares2 = m.SharesDormant + m.SharesRed + m.SharesWhite;
vm.BusinessShares3 = 0;
}
vm.BusinessShares = m.BusinessShares;
vm.AccountingNr = m.AccountingNr;
vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, m.ZwstId);
vm.DefaultKg = (AT_Kg?)ControlUtils.GetItemFromSourceWithPk(vm.DefaultKgSource, m.DefaultKgNr);
@@ -177,7 +172,7 @@ namespace Elwig.Services {
var c = m.ActiveAreaCommitments(ctx, Utils.CurrentLastSeason);
int maxKgPerHa = 10_000;
try {
var s = await ctx.FetchSeasons().FirstOrDefaultAsync();
var s = await ctx.Seasons.FindAsync(await ctx.Seasons.MaxAsync(s => s.Year));
if (s != null) maxKgPerHa = s.MaxKgPerHa;
} catch { }
var (text, gridData) = await AreaComService.GenerateToolTipData(c, maxKgPerHa);
@@ -230,8 +225,8 @@ namespace Elwig.Services {
var filter = vm.TextFilter;
if (filter.Count > 0) {
var branches = await ctx.FetchBranches().ToListAsync();
var mgnr = await ctx.FetchMembers(includeNotActive: true).ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var branches = await ctx.Branches.ToListAsync();
var mgnr = await ctx.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var kgs = await ctx.WbKgs.ToDictionaryAsync(k => k.AtKg.Name.ToLower(), k => k.AtKg);
var areaComs = await ctx.AreaCommitmentTypes.ToDictionaryAsync(t => $"{t.SortId}{t.AttrId}", t => t);
@@ -397,29 +392,58 @@ namespace Elwig.Services {
}
public static async Task GenerateMemberDataSheet(Member m, ExportMode mode) {
await Utils.RunForeground(async () => {
using var doc = new MemberDataSheet(m);
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
using var doc = new MemberDataSheet(m, ctx);
await Utils.ExportDocument(doc, mode, emailData: (m, MemberDataSheet.Name, "Im Anhang finden Sie das aktuelle Stammdatenblatt"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
public static async Task GenerateDeliveryConfirmation(Member m, int year, ExportMode mode) {
await Utils.RunForeground(async () => {
var b = await Billing.Create(year);
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
App.HintContextChange();
using var doc = new DeliveryConfirmation(year, m, null);
using var ctx = new AppDbContext();
var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, year, m);
using var doc = new DeliveryConfirmation(ctx, year, m, data);
await Utils.ExportDocument(doc, mode, emailData: (m, $"{DeliveryConfirmation.Name} {year}", $"Im Anhang finden Sie die Anlieferungsbestätigung {year}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
public static async Task GenerateCreditNote(Member m, int year, int avnr, ExportMode mode) {
await Utils.RunForeground(async () => {
using var doc = await CreditNote.Initialize(year, avnr, m.MgNr, null);
await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {doc.Payment.Variant.Name}", $"Im Anhang finden Sie die Traubengutschrift {doc.Payment.Variant.Name}"));
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr);
var p = (await ctx.MemberPayments.FindAsync(year, avnr, m.MgNr))!;
var b = BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data);
using var doc = new CreditNote(ctx, p, data[m.MgNr],
b.ConsiderContractPenalties, b.ConsiderTotalPenalty, b.ConsiderAutoBusinessShares, b.ConsiderCustomModifiers,
await ctx.GetMemberUnderDelivery(year, m.MgNr));
await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {v.Name}", $"Im Anhang finden Sie die Traubengutschrift {v.Name}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
public static async Task GenerateMemberList(this MemberAdminViewModel vm, ExportSubject subject, ExportMode mode) {
@@ -464,48 +488,76 @@ namespace Elwig.Services {
}
if (mode == ExportMode.SaveList) {
var filename = InteractionService.SaveFile(MemberList.Name, MemberList.Name, "ods");
if (filename != null) {
await Utils.RunForeground(async () => {
var d = new SaveFileDialog() {
FileName = $"{MemberList.Name}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{MemberList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var ods = new OdsFile(filename);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Vcf) {
var filename = InteractionService.SaveFile("Kontakte", "Mitglieder", "vcf");
if (filename != null) {
await Utils.RunForeground(async () => {
var d = new SaveFileDialog() {
FileName = "Mitglieder.vcf",
DefaultExt = "vcf",
Filter = "vCard-Datei (*.vcf)|*.vcf",
Title = "Kontakte speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
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)
.AsSplitQuery()
.ToListAsync();
using var exporter = new VCard(filename);
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 filename = InteractionService.SaveFile(MemberList.Name, subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}", "elwig.zip");
if (filename != null) {
if (!filename.EndsWith(".elwig.zip")) filename += ".elwig.zip";
await Utils.RunForeground(async () => {
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 history1 = await query.IgnoreAutoIncludes()
.SelectMany(m => m.HistoryFrom)
.ToListAsync();
var history2 = await query.IgnoreAutoIncludes()
.SelectMany(m => m.HistoryTo)
.ToListAsync();
var history = history1.Union(history2).DistinctBy(h => h.HistNr).OrderBy(h => h.HistNr).ToList();
var areaComs = await query
.SelectMany(m => m.AreaCommitments)
.Select(c => c.Contract).Distinct()
.Include(c => c.Revisions)
.Include(c => c.Rd)
.Include(c => c.Kg.Gl)
.ToListAsync();
var wbKgs = members
.Where(m => m.DefaultWbKg != null)
@@ -514,19 +566,31 @@ namespace Elwig.Services {
.Distinct()
.OrderBy(k => k.KgNr)
.ToList();
await ElwigData.Export(filename, members, history, areaComs, wbKgs, filterNames);
await ElwigData.Export(d.FileName, members, areaComs, wbKgs, filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
});
Mouse.OverrideCursor = null;
} else {
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var doc = new MemberList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
@@ -557,10 +621,7 @@ namespace Elwig.Services {
EntryDateString = string.IsNullOrEmpty(vm.EntryDate) ? null : string.Join("-", vm.EntryDate.Split(".").Reverse()),
ExitDateString = string.IsNullOrEmpty(vm.ExitDate) ? null : string.Join("-", vm.ExitDate.Split(".").Reverse()),
Shares = App.Client.HasRedWhite ? 0 : vm.BusinessShares1 ?? 0,
SharesRed = App.Client.HasRedWhite ? vm.BusinessShares1 ?? 0 : 0,
SharesWhite = App.Client.HasRedWhite ? vm.BusinessShares2 ?? 0 : 0,
SharesDormant = App.Client.HasRedWhite ? vm.BusinessShares3 ?? 0 : vm.BusinessShares2 ?? 0,
BusinessShares = (int)vm.BusinessShares!,
AccountingNr = string.IsNullOrEmpty(vm.AccountingNr) ? null : vm.AccountingNr,
IsActive = vm.IsActive,
IsVollLieferant = vm.IsVollLieferant,
@@ -620,17 +681,19 @@ namespace Elwig.Services {
.Where(c => c.MgNr == predecessor && (c.YearTo == null || c.YearTo >= year))
.ToListAsync();
ctx.AddRange(await Task.WhenAll(areaComs.Select(async (c, i) => new AreaCom {
FbNr = c.FbNr,
RevNr = await ctx.NextRevNr(c.FbNr),
var fbNr = await ctx.NextFbNr();
ctx.AddRange(areaComs.Select((c, i) => new AreaCom {
FbNr = fbNr + i,
MgNr = m.MgNr,
VtrgId = c.VtrgId,
CultId = c.CultId,
Area = c.Area,
KgNr = c.KgNr,
GstNr = c.GstNr,
YearFrom = year,
RdNr = c.RdNr,
YearFrom = vm.MaintainAreaComYearFrom ? c.YearFrom : year,
YearTo = c.YearTo,
})));
}));
foreach (var ac in areaComs)
ac.YearTo = year - 1;
@@ -662,20 +725,18 @@ namespace Elwig.Services {
public static async Task DeleteMember(int mgnr, bool deletePaymentData, bool deleteDeliveries, bool deleteAreaComs) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
using var tx = await ctx.Database.BeginTransactionAsync();
var l = await ctx.FetchMembers(mgnr).SingleAsync();
var l = (await ctx.Members.FindAsync(mgnr))!;
if (deletePaymentData) {
await ctx.Credits.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync();
ctx.RemoveRange(l.Credits);
}
if (deleteDeliveries) {
await ctx.Deliveries.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync();
ctx.RemoveRange(l.Deliveries);
}
if (deleteAreaComs) {
await ctx.AreaCommitments.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync();
ctx.RemoveRange(l.AreaCommitments);
}
ctx.Remove(l);
await ctx.SaveChangesAsync();
await tx.CommitAsync();
});
}
}
+72 -59
View File
@@ -6,12 +6,13 @@ using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Elwig.Services {
public static class PaymentVariantService {
@@ -141,7 +142,7 @@ namespace Elwig.Services {
});
var credits = ctx.Credits.Where(c => c.Year == v.Year && c.AvNr == v.AvNr);
if (!(await credits.AnyAsync())) {
if (!credits.Any()) {
long lastTotalSum = 0;
decimal vatSum = 0;
var currentPayments = await ctx.MemberPayments
@@ -149,7 +150,8 @@ namespace Elwig.Services {
.ToDictionaryAsync(p => p.MgNr);
var lastV = await ctx.PaymentVariants
.Where(l => l.Year == v.Year && !l.TestVariant)
.OrderByDescending(l => l.TransferDateString ?? l.DateString).ThenByDescending(l => l.AvNr)
.OrderByDescending(l => l.TransferDateString ?? l.DateString)
.ThenByDescending(l => l.AvNr)
.FirstOrDefaultAsync();
if (lastV != null) {
var lastPayments = await ctx.MemberPayments
@@ -158,8 +160,8 @@ namespace Elwig.Services {
lastTotalSum = lastPayments.Sum(e => e.Value.AmountValue);
foreach (int mgnr in currentPayments.Keys) {
var c = currentPayments[mgnr];
var l = lastPayments.GetValueOrDefault(mgnr);
vatSum += (c.Amount - (l?.Amount ?? 0)) * (decimal)(c.Member.IsBuchführend ? v.Season.VatNormal : v.Season.VatFlatrate);
var l = lastPayments[mgnr];
vatSum += (c.Amount - l.Amount) * (decimal)(c.Member.IsBuchführend ? v.Season.VatNormal : v.Season.VatFlatrate);
}
} else {
vatSum = currentPayments.Sum(e => e.Value.Amount * (decimal)(e.Value.Member.IsBuchführend ? v.Season.VatNormal : v.Season.VatFlatrate));
@@ -188,58 +190,99 @@ namespace Elwig.Services {
public static async Task GenerateSummary(PaymentVar v, ExportMode mode) {
if (mode == ExportMode.SaveList) {
var filename = InteractionService.SaveFile($"Variantendaten {v.Name}", $"Variantendaten-{v.Name.Trim().Replace(' ', '-')}", "ods");
if (filename == null)
var d = new SaveFileDialog() {
FileName = $"Variantendaten-{v.Name.Trim().Replace(' ', '-')}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Variantendaten {v.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == false)
return;
await Utils.RunForeground(async () => {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
using var ods = new OdsFile(filename);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
} else {
await Utils.RunForeground(async () => {
using var doc = await PaymentVariantSummary.Initialize(v.Year, v.AvNr);
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var data = await PaymentVariantSummaryData.ForPaymentVariant(v, ctx.PaymentVariantSummaryRows);
using var doc = new PaymentVariantSummary((await ctx.PaymentVariants.FindAsync(v.Year, v.AvNr))!, data);
await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
public static async Task GenerateEbics(int year, int avnr) {
using var ctx = new AppDbContext();
var v = await ctx.PaymentVariants.Include(v => v.Credits).Where(v => v.Year == year && v.AvNr == avnr).SingleAsync();
var v = (await ctx.PaymentVariants.FindAsync(year, avnr))!;
var withoutIban = v.Credits.Count(c => c.Member.Iban == null);
if (withoutIban > 0) {
if (!InteractionService.AskContinue("Mitglieder ohne IBAN",
$"Achtung: Für {withoutIban:N0} Mitglieder ist kein IBAN hinterlegt.\n\nDiese werden NICHT exportiert."))
return;
var r = MessageBox.Show($"Achtung: Für {withoutIban:N0} Mitglieder ist kein IBAN hinterlegt.\n\nDiese werden NICHT exportiert.",
"Mitglieder ohne IBAN", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (r != MessageBoxResult.OK) return;
}
var withNegAmount = v.Credits.Count(c => c.Amount <= 0);
if (withNegAmount > 0) {
if (!InteractionService.AskContinue("Traubengutschriften mit negativem Betrag",
$"Achtung: Es gibt {withNegAmount:N0} Traubengutschriften mit negativem Betrag.\n\nDiese werden NICHT exportiert."))
return;
var r = MessageBox.Show($"Achtung: Es gibt {withNegAmount:N0} Traubengutschriften mit negativem Betrag.\n\nDiese werden NICHT exportiert.",
"Traubengutschriften mit negativem Betrag", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.OK);
if (r != MessageBoxResult.OK) return;
}
var filename = InteractionService.SaveFile("Überweisungsdaten", $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}", Ebics.FileExtension);
if (filename != null) {
await Utils.RunForeground(async () => {
using var e = new Ebics(v, filename, App.Client.ExportEbicsVersion, (Ebics.AddressMode)App.Client.ExportEbicsAddress);
var d = new SaveFileDialog() {
FileName = $"{App.Client.NameToken}-Überweisungsdaten-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.{Ebics.FileExtension}",
DefaultExt = Ebics.FileExtension,
Filter = "EBICS-Datei (*.xml)|*.xml",
Title = $"Überweisungsdaten speichern unter - Elwig",
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var e = new Ebics(v, d.FileName, App.Client.ExportEbicsVersion, (Ebics.AddressMode)App.Client.ExportEbicsAddress);
await e.ExportAsync(Transaction.FromPaymentVariant(v));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
public static async Task GenerateAccountingList(int year, int avnr, string name) {
var filename = InteractionService.SaveFile("Buchungsliste", $"{App.Client.NameToken}-Buchungsliste-{year}-{name.Trim().Replace(' ', '-')}", "ods");
if (filename != null) {
await Utils.RunForeground(async () => {
var d = new SaveFileDialog() {
FileName = $"{App.Client.NameToken}-Buchungsliste-{year}-{name.Trim().Replace(' ', '-')}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Buchungsliste speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ctx = new AppDbContext();
var tbl = await CreditNoteData.ForPaymentVariant(ctx, year, avnr);
using var ods = new OdsFile(filename);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
Mouse.OverrideCursor = null;
}
}
@@ -322,51 +365,21 @@ namespace Elwig.Services {
public static async Task Calculate(int year, int avnr) {
await Task.Run(async () => {
var b = await BillingVariant.Create(year, avnr);
var b = new BillingVariant(year, avnr);
await b.Calculate();
});
}
public static async Task Commit(int year, int avnr) {
List<Member> membersLess = [];
using (var ctx = new AppDbContext()) {
var lastAvNr = await ctx.PaymentVariants
.Where(v => v.Year == year && !v.TestVariant)
.OrderByDescending(v => v.TransferDateString ?? v.DateString).ThenByDescending(v => v.AvNr)
.Select(v => v.AvNr)
.FirstOrDefaultAsync();
if (lastAvNr != 0) {
var cur = await ctx.MemberPayments.Include(p => p.Member).Where(p => p.Year == year && p.AvNr == avnr).ToDictionaryAsync(p => p.Member.MgNr, p => p);
var last = await ctx.MemberPayments.Include(p => p.Member).Where(p => p.Year == year && p.AvNr == lastAvNr).ToDictionaryAsync(p => p.Member.MgNr, p => p);
membersLess = [.. last.Where(p => (cur.TryGetValue(p.Key, out var c) ? c.Amount : 0) < p.Value.Amount).Select(p => p.Value.Member)];
}
}
if (membersLess.Count > 0) {
if (!InteractionService.AskContinue("Kleinerer Gesamtbetrag",
$"Achtung: Bei {membersLess.Count:N0} Mitglied(ern) ist der Soll-Betrag der Auszahlung (\"Gesamtbetrag\") kleiner als bei der letzten Auszahlung. Das sollte nicht vorkommen!\n\n" +
$"{string.Join(", ", membersLess.Select(m => $"{m.AdministrativeName} ({m.MgNr})"))}\n\n" +
"Soll trotzdem forgefahren werden?"))
return;
}
await Task.Run(async () => {
var b = await BillingVariant.Create(year, avnr);
var b = new BillingVariant(year, avnr);
await b.Commit();
});
List<Member> membersNeg = [];
using (var ctx = new AppDbContext()) {
membersNeg = await ctx.Credits.Where(c => c.Year == year && c.AvNr == avnr && c.AmountValue < 0).Select(c => c.Member).ToListAsync();
}
if (membersNeg.Count > 0) {
InteractionService.ShowWarning("Negativer Auszahlungsbetrag", $"Achtung: Bei {membersNeg.Count:N0} Mitglied(ern) ist der Auszahlungsbetrag negativ!\n\n" +
$"{string.Join(", ", membersNeg.Select(m => $"{m.AdministrativeName} ({m.MgNr})"))}");
}
}
public static async Task Revert(int year, int avnr) {
await Task.Run(async () => {
var b = await BillingVariant.Create(year, avnr);
var b = new BillingVariant(year, avnr);
await b.Revert();
});
}
+69 -57
View File
@@ -10,12 +10,12 @@ 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<AreaComContract, bool>> ChangedAreaComContracts = (c) => ((c.XTime == null && c.MTime > 1751328000) || c.MTime > c.XTime) && (c.ITime == null || c.MTime > c.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) {
@@ -24,21 +24,17 @@ namespace Elwig.Services {
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 history1 = await query.IgnoreAutoIncludes()
.SelectMany(m => m.HistoryFrom)
.ToListAsync();
var history2 = await query.IgnoreAutoIncludes()
.SelectMany(m => m.HistoryTo)
.ToListAsync();
var history = history1.Union(history2).DistinctBy(h => h.HistNr).OrderBy(h => h.HistNr).ToList();
var areaComs = await query
.SelectMany(m => m.AreaCommitments)
.Select(c => c.Contract).Distinct()
.OrderBy(c => c.FbNr)
.Include(c => c.Revisions)
.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)
@@ -48,20 +44,22 @@ namespace Elwig.Services {
.OrderBy(k => k.KgNr)
.ToList();
if (members.Count == 0) {
InteractionService.ShowError("Mitglieder hochladen", "Es wurden keine Mitglieder zum Hochladen ausgewählt!");
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, history, areaComs, wbKgs, filterNames);
await ElwigData.Export(path, members, areaComs, wbKgs, filterNames);
await Utils.UploadExportData(path, url, username, password);
await UpdateExportedAt(members, areaComs, [], exportedAt);
InteractionService.ShowInformation("Mitglieder hochgeladen", $"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!");
await UpdateExportedAt(members, [], exportedAt);
MessageBox.Show($"Hochladen von {members.Count:N0} Mitgliedern erfolgreich!", "Mitglieder hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
InteractionService.ShowException("Mitglieder hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
InteractionService.ShowException("Mitglieder hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
InteractionService.ShowException("Mitglieder hochladen", exc);
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -72,7 +70,10 @@ namespace Elwig.Services {
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).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)
@@ -82,20 +83,22 @@ namespace Elwig.Services {
.OrderBy(k => k.KgNr)
.ToList();
if (list.Count == 0) {
InteractionService.ShowError("Lieferungen hochladen", "Es wurden keine Lieferungen zum Hochladen ausgewählt!");
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);
InteractionService.ShowInformation("Lieferungen hochgeladen", $"Hochladen von {list.Count:N0} Lieferungen erfolgreich!");
await UpdateExportedAt([], list, exportedAt);
MessageBox.Show($"Hochladen von {list.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
InteractionService.ShowException("Lieferungen hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
InteractionService.ShowException("Lieferungen hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
InteractionService.ShowException("Lieferungen hochladen", exc);
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -103,24 +106,32 @@ namespace Elwig.Services {
try {
var path = Path.Combine(App.TempPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip");
List<Member> members;
List<AreaComContract> areaComs;
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.AreaCommitmentContracts
.Where(ChangedAreaComContracts)
.Include(c => c.Revisions)
.OrderBy(c => c.FbNr)
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)
.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
@@ -134,25 +145,27 @@ namespace Elwig.Services {
.OrderBy(k => k.KgNr)
.ToList();
if (members.Count == 0 && deliveries.Count == 0) {
InteractionService.ShowInformation("Mitglieder und Lieferungen hochladen", "Es gibt keine geänderten Mitglieder oder Lieferungen, die hochgeladen werden könnten!");
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, ["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, areaComs, deliveries, exportedAt);
InteractionService.ShowInformation("Mitglieder und Lieferungen hochladen", $"Hochladen von {members.Count:N0} Mitgliedern, {areaComs.Count:N0} Flächenbindungsverträgen, und {deliveries.Count:N0} Lieferungen erfolgreich!");
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) {
InteractionService.ShowException("Mitglieder und Lieferungen hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
InteractionService.ShowException("Mitglieder und Lieferungen hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
InteractionService.ShowException("Mitglieder und Lieferungen hochladen", exc);
MessageBox.Show(exc.Message, "Mitglieder und Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -163,8 +176,11 @@ namespace Elwig.Services {
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)
.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)
@@ -173,20 +189,22 @@ namespace Elwig.Services {
.DistinctBy(k => k.KgNr)
.ToList();
if (deliveries.Count == 0) {
InteractionService.ShowError("Lieferungen hochladen", "Es gibt keine Lieferungen, die hochgeladen werden können!");
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);
InteractionService.ShowInformation("Lieferungen hochladen", $"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!");
await UpdateExportedAt([], deliveries, exportedAt);
MessageBox.Show($"Hochladen von {deliveries.Count:N0} Lieferungen erfolgreich!", "Lieferungen hochladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (HttpRequestException exc) {
InteractionService.ShowException("Lieferungen hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
InteractionService.ShowException("Lieferungen hochladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
InteractionService.ShowException("Lieferungen hochladen", exc);
MessageBox.Show(exc.Message, "Lieferungen hochladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -225,26 +243,24 @@ namespace Elwig.Services {
}
await ElwigData.Import(paths, ElwigData.ImportMode.FromBranches);
} catch (HttpRequestException exc) {
InteractionService.ShowException("Daten herunterladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (TaskCanceledException exc) {
InteractionService.ShowException("Daten herunterladen", "Eventuell Internetverbindung prüfen!", exc);
MessageBox.Show("Eventuell Internetverbindung prüfen!\n\n" + exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
} catch (Exception exc) {
InteractionService.ShowException("Daten herunterladen", exc);
MessageBox.Show(exc.Message, "Daten herunterladen", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private static async Task UpdateExportedAt(IEnumerable<Member> member, IEnumerable<AreaComContract> contracts, IEnumerable<Delivery> deliveries, DateTime dateTime) {
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 fbnrs = string.Join(",", contracts.Select(c => $"{c.FbNr}").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_contract SET xtime = {timestamp} WHERE fbnr IN ({fbnrs});
UPDATE area_commitment SET xtime = {timestamp} WHERE fbnr IN ({fbnrs});
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';
@@ -255,11 +271,7 @@ namespace Elwig.Services {
}
public static async Task<bool> ChangesAvailable(AppDbContext ctx, string url, string username, string password) {
try {
return await ctx.Members.Where(ChangedMembers).AnyAsync() || await ctx.AreaCommitmentContracts.Where(ChangedAreaComContracts).AnyAsync() || await ctx.Deliveries.Where(ChangedDeliveries).AnyAsync() || (Utils.HasInternetConnectivity() && (await GetFilesToImport(url, username, password)).Count > 0);
} catch {
return false;
}
return await ctx.Members.AnyAsync(ChangedMembers) || await ctx.Deliveries.AnyAsync(ChangedDeliveries) || (Utils.HasInternetConnectivity() && (await GetFilesToImport(url, username, password)).Count > 0);
}
}
}
+1 -7
View File
@@ -20,11 +20,7 @@ namespace Elwig.ViewModels {
}
[ObservableProperty]
private string? _filterSeasonString;
public int? FilterSeason {
get => int.TryParse(FilterSeasonString, out var year) ? year : null;
set => FilterSeasonString = $"{value}";
}
private bool _showOnlyActiveAreaComs;
[ObservableProperty]
private string? _fbNrString;
@@ -50,8 +46,6 @@ namespace Elwig.ViewModels {
get => int.TryParse(YearToString, out var year) ? year : null;
set => YearToString = $"{value}";
}
[ObservableProperty]
private string? _period;
[ObservableProperty]
private AreaComType? _areaComType;

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