Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91e8c7e99 | ||
|
|
09642e643c | ||
|
|
b262e17293 | ||
|
|
4f4d026b7b | ||
|
|
abb9e5a577 | ||
|
|
0e586cc438 | ||
|
|
829ad106cf | ||
|
|
ba7c37fd70 | ||
|
|
66a1185dc3 | ||
|
|
db8afa59b7 | ||
|
|
3d96c31a70 | ||
|
|
a18d687f18 | ||
|
|
5f2788eb9e | ||
|
|
4988081bfd | ||
|
|
03872ca5c2 | ||
|
|
b886abc632 | ||
|
|
cba949d2eb | ||
|
|
c2bc5b221a | ||
|
|
5845693f17 | ||
|
|
aea7c39734 | ||
|
|
c54a998c96 | ||
|
|
5bb84944bc | ||
|
|
34b40535a6 | ||
|
|
3dca099248 | ||
|
|
155a599878 | ||
|
|
fda337c3cb | ||
|
|
d8126e254d | ||
|
|
96441a6701 | ||
|
|
03867bcd74 | ||
|
|
83dfde9203 | ||
|
|
805c5efda8 | ||
|
|
e86edc080a | ||
|
|
0d5d1d4d3d | ||
|
|
ffd3dc9631 | ||
|
|
d592fef352 | ||
|
|
f2b84053e6 | ||
|
|
7fef895491 | ||
|
|
0e9121c462 | ||
|
|
15e191d74e | ||
|
|
abf06c558f | ||
|
|
6436762e8d | ||
|
|
6441fa4f50 | ||
|
|
6bed370eba | ||
|
|
4cc33b56c8 | ||
|
|
f4cc443521 | ||
|
|
8ba051198f | ||
|
|
fea779bd95 | ||
|
|
e66780ccd0 | ||
|
|
cc01f92005 | ||
|
|
0a93167ea7 | ||
|
|
848a6d1ce0 | ||
|
|
f0745eae4e | ||
|
|
8471ac9319 | ||
|
|
7add96dfd5 | ||
|
|
2360402124 | ||
|
|
cadac4302d | ||
|
|
c1690bf104 | ||
|
|
ca5b719630 | ||
|
|
163fe0fac8 | ||
|
|
edf3678154 | ||
|
|
f450dc35b1 | ||
|
|
0c4611024f | ||
|
|
46e596ae23 | ||
|
|
3366cc25eb | ||
|
|
ff71e9fb7f | ||
|
|
7d5d7d9307 | ||
|
|
b468bbce31 | ||
|
|
a79631c586 | ||
|
|
20b6e11b7f | ||
|
|
77e812f5e8 | ||
|
|
58bfb4f56d | ||
|
|
e6e140dc08 | ||
|
|
eaddf0c3cb | ||
|
|
fc5ad82686 | ||
|
|
b76367f73a | ||
|
|
76ea3513ac | ||
|
|
07b197958d | ||
|
|
470c6e09e5 | ||
|
|
20b19775b1 | ||
|
|
f13a836ebd | ||
|
|
2bf926c84c | ||
|
|
a8cc3c9cb9 | ||
|
|
88616b2c0b | ||
|
|
e6712732cd | ||
|
|
cfabf99c99 | ||
|
|
668030fbff | ||
|
|
b445928c4d | ||
|
|
2a3999f300 | ||
|
|
309f118739 | ||
|
|
0d8cdd5d6e | ||
|
|
2350b55cea | ||
|
|
e8789aff37 | ||
|
|
918555f2b8 | ||
|
|
1169ba5101 | ||
|
|
76da392f5f | ||
|
|
000117dc67 | ||
|
|
314a0d37a8 | ||
|
|
765edbdae6 | ||
|
|
9a0a422c8a | ||
|
|
a71c41af5c | ||
|
|
62346a63a8 | ||
|
|
feee6ad1ec | ||
|
|
4ebe07f579 | ||
|
|
beacba6bd9 | ||
|
|
69efca1cc3 | ||
|
|
fcd0555e4d | ||
|
|
b93e987685 | ||
|
|
1ad97a78ff | ||
|
|
1261be001c | ||
|
|
15de07a4c3 | ||
|
|
5cbad9a195 | ||
|
|
4e027a9add | ||
|
|
f32ff945ec | ||
|
|
ba3f66591e | ||
|
|
3f65b2350b | ||
|
|
7a6a77419b | ||
|
|
7a993585c3 | ||
|
|
d0ce377d92 | ||
|
|
f8b3147c72 | ||
|
|
b4ab8349b4 | ||
|
|
5db5876905 | ||
|
|
0617726139 | ||
|
|
9dfe71d6d0 | ||
|
|
72155fc54e | ||
|
|
61bdb2f3b5 | ||
|
|
2915f08a6b | ||
|
|
5c2fae1855 | ||
|
|
95fabe3633 | ||
|
|
7861a15e2e | ||
|
|
20fd5d5826 | ||
|
|
2b3c293730 | ||
|
|
a852dbb242 | ||
|
|
7edf395497 | ||
|
|
ea2b6db1fc | ||
|
|
cf48f005e2 | ||
|
|
4eac8cd629 | ||
|
|
360856585a | ||
|
|
cc6e31a006 | ||
|
|
ce1a55df86 | ||
|
|
07d93dd384 | ||
|
|
f6e9d429d5 | ||
|
|
7b78f9d6b9 | ||
|
|
22fbb0772f | ||
|
|
0a9c800116 | ||
|
|
278d79429b | ||
|
|
e5e5e10cd7 | ||
|
|
d051a2bfcf | ||
|
|
9c39a2f820 | ||
|
|
4460de9975 | ||
|
|
f96ebdcf60 | ||
|
|
e593175e72 | ||
|
|
1f4fe2129d | ||
|
|
cdb4b0a2bd | ||
|
|
ddfc86197d | ||
|
|
ae0a082421 | ||
|
|
3c52156b7e | ||
|
|
982a6616e1 | ||
|
|
b1075d1e69 | ||
|
|
cc018ded10 | ||
|
|
0aefab5d63 |
@@ -2,7 +2,7 @@ name: Test
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
paths: ["Elwig/**", "Tests/**", "Installer/Files/*.exe", ".gitea/workflows/test.yaml"]
|
||||
paths: ["Elwig/**", "Tests/**", ".gitea/workflows/test.yaml"]
|
||||
jobs:
|
||||
test:
|
||||
name: Run tests
|
||||
@@ -23,6 +23,16 @@ 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
|
||||
|
||||
@@ -7,3 +7,4 @@ Tests/Resources/Sql/Create.sql
|
||||
*.exe
|
||||
!WinziPrint.exe
|
||||
*.sqlite3
|
||||
*.zip
|
||||
|
||||
+696
-335
File diff suppressed because it is too large
Load Diff
@@ -16,14 +16,7 @@
|
||||
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
+1
-8
@@ -2,18 +2,11 @@
|
||||
x:Class="Elwig.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ctrl="clr-namespace:Elwig.Controls"
|
||||
Exit="Application_Exit">
|
||||
xmlns:ctrl="clr-namespace:Elwig.Controls">
|
||||
<Application.Resources>
|
||||
<ctrl:BoolToStringConverter x:Key="BoolToStarConverter" FalseValue="" TrueValue="*"/>
|
||||
<ctrl:WidthToMarginConverter x:Key="WidthToMarginConverter"/>
|
||||
|
||||
<DataTemplate x:Key="PostalDestTemplate">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Dest}"/>
|
||||
<TextBlock Text="{Binding Ort.Name, StringFormat='{} ({0})'}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="KgNrTemplate">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
|
||||
+90
-42
@@ -5,7 +5,9 @@ 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;
|
||||
@@ -16,16 +18,18 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Elwig {
|
||||
public partial class App : Application {
|
||||
|
||||
protected static App CurrentApp;
|
||||
public static App CurrentApp;
|
||||
public static int NumWindows => CurrentApp.Windows.Count;
|
||||
public static bool ForceShutdown { get; private set; } = false;
|
||||
public static bool ForceShutdown { get; set; } = false;
|
||||
|
||||
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
|
||||
private readonly DispatcherTimer _autoBackupTimer = new() { Interval = TimeSpan.FromMinutes(10) };
|
||||
public readonly SerialPortWatcher SerialPortWatcher = new();
|
||||
|
||||
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig");
|
||||
@@ -72,7 +76,7 @@ namespace Elwig {
|
||||
Config = new(Path.GetFullPath(args[1]));
|
||||
}
|
||||
|
||||
ContextTimer.Tick += (object? sender, EventArgs evt) => {
|
||||
ContextTimer.Tick += (sender, evt) => {
|
||||
var ch = CurrentLastWrite;
|
||||
if (ch > LastChanged) {
|
||||
LastChanged = ch;
|
||||
@@ -90,12 +94,16 @@ namespace Elwig {
|
||||
|
||||
try {
|
||||
await AppDbUpdater.CheckDb();
|
||||
} catch (Exception e) {
|
||||
} catch (FileNotFoundException exc) {
|
||||
InteractionService.ShowException("Datenbank nicht gefunden", "Die Datenbank konnte nicht gefunden werden", exc);
|
||||
} catch (Exception exc) {
|
||||
if (Config.UpdateUrl != null && Utils.HasInternetConnectivity()) {
|
||||
await CheckForUpdates();
|
||||
}
|
||||
MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Shutdown();
|
||||
InteractionService.ShowException("Fehlerhafte Datenbank", "Schwerwiegender Fehler in der Datenbank, unbedingt den Programmierern melden!", exc);
|
||||
if (InteractionService.AskQuestion("Backup wiederherstellen", "Soll die Datenbank aus einem Backup wiederhergestellt werden?\n\nAchtung: Das kann zu Datenverlust führen!", false))
|
||||
await BackupService.RestoreDatabase();
|
||||
ShutdownNow();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,12 +112,15 @@ namespace Elwig {
|
||||
|
||||
Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = [];
|
||||
using (var ctx = new AppDbContext()) {
|
||||
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));
|
||||
branches = await 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));
|
||||
try {
|
||||
Client = new(ctx);
|
||||
} catch (Exception e) {
|
||||
MessageBox.Show($"Fehler beim Laden der Mandantendaten:\n\n{e.Message}", "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Shutdown();
|
||||
} catch (Exception exc) {
|
||||
InteractionService.ShowException("Fehler", "Fehler beim Laden der Mandantendaten, unbedingt den Programmierern melden!", exc);
|
||||
if (InteractionService.AskQuestion("Backup wiederherstellen", "Soll die Datenbank aus einem Backup wiederhergestellt werden?\n\nAchtung: Das kann zu Datenverlust führen!", false))
|
||||
await BackupService.RestoreDatabase();
|
||||
ShutdownNow();
|
||||
return;
|
||||
}
|
||||
BranchNum = branches.Count;
|
||||
@@ -120,9 +131,23 @@ namespace Elwig {
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
Utils.RunBackground("PDF Initialization", () => Pdf.Init());
|
||||
Utils.RunBackground("PDF Initialization", async () => Pdf.Init());
|
||||
Utils.RunBackground("JSON Schema Initialization", BillingData.Init);
|
||||
|
||||
if (Config.BackupAuto && !Config.Debug) {
|
||||
Utils.RunBackground("Automatisches Backup", async () => {
|
||||
Directory.CreateDirectory(Config.BackupPath);
|
||||
await Task.Delay(500);
|
||||
await BackupService.TryDatabaseBackup();
|
||||
});
|
||||
_autoBackupTimer.Tick += new EventHandler((sender, evt) => {
|
||||
Utils.RunBackground("Automatisches Backup", async () => {
|
||||
await BackupService.TryDatabaseBackup();
|
||||
});
|
||||
});
|
||||
_autoBackupTimer.Start();
|
||||
}
|
||||
|
||||
if (Config.UpdateAuto && Config.UpdateUrl != null) {
|
||||
if (Utils.HasInternetConnectivity()) {
|
||||
Utils.RunBackground("Auto Updater", async () => {
|
||||
@@ -141,28 +166,33 @@ namespace Elwig {
|
||||
var list = new List<IScale>();
|
||||
foreach (var s in Config.Scales) {
|
||||
try {
|
||||
list.Add(Scale.FromConfig(s));
|
||||
} catch (Exception e) {
|
||||
var scale = Scale.FromConfig(s);
|
||||
if (s.SyncTime && scale is ICommandScale cmd) {
|
||||
try {
|
||||
await cmd.SetDateAndTime(DateTime.Now);
|
||||
} catch { }
|
||||
}
|
||||
list.Add(scale);
|
||||
} catch (Exception exc) {
|
||||
list.Add(new InvalidScale(s.Id));
|
||||
if (s.Required)
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowException("Waagenfehler", $"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden", exc);
|
||||
}
|
||||
}
|
||||
Scales = list;
|
||||
|
||||
if (Config.Branch != null) {
|
||||
if (!branches.ContainsKey(Config.Branch.ToLower())) {
|
||||
MessageBox.Show("Ungültige Zweigstelle in Konfigurationsdatei!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Shutdown();
|
||||
InteractionService.ShowError("Ungültige Zweigstelle", "Ungültige Zweigstelle in Konfigurationsdatei!");
|
||||
ShutdownNow();
|
||||
} else {
|
||||
SetBranch(branches[Config.Branch.ToLower()]);
|
||||
}
|
||||
} else if (branches.Count == 1) {
|
||||
SetBranch(branches.First().Value);
|
||||
} else {
|
||||
MessageBox.Show("Erkennen der lokalen Zweigstelle nicht möglich!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Shutdown();
|
||||
InteractionService.ShowError("Ungültige Zweigstelle", "Erkennen der lokalen Zweigstelle nicht möglich!");
|
||||
ShutdownNow();
|
||||
}
|
||||
|
||||
if (Config.WeighingMode == null) {
|
||||
@@ -170,7 +200,7 @@ namespace Elwig {
|
||||
Config.WeighingMode = WeighingMode.Net;
|
||||
} else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
|
||||
Config.WeighingMode = WeighingMode.Box;
|
||||
} else if (Client.IsBaden || Client.IsGrInzersdorf) {
|
||||
} else if (Client.IsBaden || Client.IsGrInzersdorf || Client.IsPamhagen) {
|
||||
Config.WeighingMode = WeighingMode.Gross;
|
||||
}
|
||||
}
|
||||
@@ -181,12 +211,29 @@ namespace Elwig {
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private async void Application_Exit(object sender, ExitEventArgs evt) {
|
||||
public static void ShutdownNow() {
|
||||
ForceShutdown = true;
|
||||
Current.Shutdown();
|
||||
}
|
||||
|
||||
public async void ShutdownGracefully() {
|
||||
Mouse.OverrideCursor = Cursors.AppStarting;
|
||||
|
||||
if (Config.BackupAuto && !Config.Debug) {
|
||||
try {
|
||||
await BackupService.TryDatabaseBackup();
|
||||
} catch (Exception exc) {
|
||||
InteractionService.ShowException("Automatisches Backup", exc);
|
||||
}
|
||||
}
|
||||
|
||||
SerialPortWatcher.Dispose();
|
||||
foreach (var s in EventScales) {
|
||||
s.Dispose();
|
||||
}
|
||||
await Pdf.Cleanup();
|
||||
Pdf.Cleanup();
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
public static void SetBranch(Branch b) {
|
||||
@@ -198,6 +245,7 @@ namespace Elwig {
|
||||
BranchName = entry.Item2;
|
||||
BranchPlz = entry.Item3;
|
||||
BranchLocation = entry.Item4?
|
||||
.Split(",")[0]
|
||||
.Split(" in ")[0]
|
||||
.Split(" im ")[0]
|
||||
.Split(" an ")[0]
|
||||
@@ -218,7 +266,8 @@ namespace Elwig {
|
||||
MainDispatcher.Invoke(() => {
|
||||
foreach (Window w in CurrentApp.Windows) {
|
||||
if (w is not ContextWindow c) continue;
|
||||
MainDispatcher.BeginInvoke(c.HintContextChange);
|
||||
MainDispatcher.Invoke(c.HintContextChange);
|
||||
MainDispatcher.BeginInvoke(c.TryContextReload);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -249,14 +298,13 @@ namespace Elwig {
|
||||
if (Scales[i] is InvalidScale) {
|
||||
try {
|
||||
Scales[i] = Scale.FromConfig(s);
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
} catch (Exception e) {
|
||||
InteractionService.ShowInformation($"Waage {s.Id}", $"Verbindung zu Waage {s.Id} wieder hergestellt!");
|
||||
} catch (Exception exc) {
|
||||
Scales[i] = new InvalidScale(s.Id);
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowException("Waagenfehler", $"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden", exc);
|
||||
}
|
||||
} else if (Scales[i] is IEventScale) {
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
InteractionService.ShowInformation($"Waage {s.Id}", $"Verbindung zu Waage {s.Id} wieder hergestellt!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +315,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) {
|
||||
MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
InteractionService.ShowWarning($"Waagen {s.Id}", $"Verbindung zu Waage {s.Id} unterbrochen!");
|
||||
if (Scales[i] is ICommandScale) {
|
||||
try {
|
||||
Scales[i].Dispose();
|
||||
@@ -296,17 +344,14 @@ namespace Elwig {
|
||||
await MainDispatcher.BeginInvoke(() => {
|
||||
var d = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size);
|
||||
if (d.ShowDialog() == true) {
|
||||
ForceShutdown = true;
|
||||
Current.Shutdown();
|
||||
ShutdownNow();
|
||||
}
|
||||
});
|
||||
} else if (showAlert) {
|
||||
if (latest == null) {
|
||||
MessageBox.Show("Informationen konnten nicht abgerufen werden!", "Nach Updates suchen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowError("Nach Updates suchen", "Informationen konnten nicht abgerufen werden!");
|
||||
} else {
|
||||
MessageBox.Show($"Elwig ist auf dem aktuellsten Stand! (Version: {latest.Value.Version})", "Nach Updates suchen",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
InteractionService.ShowInformation("Nach Updates suchen", $"Elwig ist auf dem aktuellsten Stand! (Version: {latest.Value.Version})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,11 +361,10 @@ namespace Elwig {
|
||||
await Task.Run(async () => {
|
||||
await Database.Import(filename);
|
||||
});
|
||||
MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
ForceShutdown = true;
|
||||
Current.Shutdown();
|
||||
InteractionService.ShowInformation("Datenbank ersetzen", "Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!");
|
||||
ShutdownNow();
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowException("Datenbank ersetzen", "Fehler beim Ersetzen", exc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,19 +386,23 @@ namespace Elwig {
|
||||
return FocusWindow<DeliveryAdminWindow>(() => new(true), w => w.ViewModel.IsReceipt);
|
||||
}
|
||||
|
||||
public static MemberBusinessSharesAdminWindow FocusMemberBusinessSharesWindow(int mgnr) {
|
||||
return FocusWindow<MemberBusinessSharesAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember?.MgNr == mgnr);
|
||||
}
|
||||
|
||||
public static DeliveryAdminWindow FocusMemberDeliveries(int mgnr) {
|
||||
return FocusWindow<DeliveryAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember?.MgNr == mgnr);
|
||||
}
|
||||
|
||||
public static AreaComAdminWindow FocusMemberAreaComs(int mgnr) {
|
||||
return FocusWindow<AreaComAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember.MgNr == mgnr);
|
||||
public static AreaCmtAdminWindow FocusMemberAreaCmts(int mgnr) {
|
||||
return FocusWindow<AreaCmtAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember.MgNr == mgnr);
|
||||
}
|
||||
|
||||
public static BaseDataWindow FocusBaseData() {
|
||||
return FocusWindow<BaseDataWindow>(() => new());
|
||||
}
|
||||
|
||||
public static BaseDataWindow FocusBaseDataAreaComType() {
|
||||
public static BaseDataWindow FocusBaseDataAreaCmtType() {
|
||||
var w = FocusBaseData();
|
||||
w.AreaCommitmentTypes.Focus();
|
||||
return w;
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace Elwig.Controls {
|
||||
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs evt) {
|
||||
SelectItemsReverse();
|
||||
var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null;
|
||||
var dmp = !string.IsNullOrWhiteSpace(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrWhiteSpace(DisplayMemberPath) ? DisplayMemberPath : null;
|
||||
if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
|
||||
AllItemsSelected = true;
|
||||
} else if (SelectedItems.Count == 0) {
|
||||
|
||||
@@ -41,13 +41,21 @@ 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).ToArray());
|
||||
Text = new string([.. Text.Where(char.IsAsciiDigit).Take(4)]);
|
||||
CaretIndex = idx;
|
||||
evt.Handled = !(Value >= Minimum && Value <= Maximum);
|
||||
evt.Handled = !((!Minimum.HasValue || Value >= Minimum) && (!Maximum.HasValue || Value <= Maximum));
|
||||
if (idx >= 4) {
|
||||
if (Value < Minimum) {
|
||||
Value = Minimum;
|
||||
@@ -56,6 +64,7 @@ namespace Elwig.Controls {
|
||||
}
|
||||
CaretIndex = 4;
|
||||
}
|
||||
UpdateButtons();
|
||||
}
|
||||
|
||||
private void IntegerUpDown_LostFocus(object sender, RoutedEventArgs evt) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Window x:Class="Elwig.Dialogs.AreaComModifyDialog"
|
||||
<Window x:Class="Elwig.Dialogs.AreaCmtModifyDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
+10
-5
@@ -1,9 +1,10 @@
|
||||
using Elwig.Helpers;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Elwig.Dialogs {
|
||||
public partial class AreaComModifyDialog : Window {
|
||||
public partial class AreaCmtModifyDialog : Window {
|
||||
|
||||
public int? YearTo { get; set; }
|
||||
public bool ModifyRetroactively { get; set; }
|
||||
@@ -12,23 +13,27 @@ namespace Elwig.Dialogs {
|
||||
public string OrigYearTo { get; set; }
|
||||
public string Area { get; set; }
|
||||
|
||||
public AreaComModifyDialog(int? yearFrom, int? yearTo, int area, bool delete) {
|
||||
public AreaCmtModifyDialog(int? yearFrom, int? yearTo, int area, bool delete, bool forceRetroactive = false) {
|
||||
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;
|
||||
}
|
||||
if ((yearTo.HasValue && yearTo < Utils.CurrentNextSeason) || (yearFrom.HasValue && yearFrom >= Utils.CurrentNextSeason)) {
|
||||
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)) {
|
||||
RetroactiveInput.IsChecked = true;
|
||||
} else {
|
||||
SeasonInput.Text = $"{Utils.CurrentNextSeason}";
|
||||
SeasonInput.Text = $"{Utils.CurrentYear}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +53,7 @@ namespace Elwig.Dialogs {
|
||||
DescBlock2.Visibility = Visibility.Hidden;
|
||||
} else {
|
||||
SeasonInput.IsEnabled = true;
|
||||
SeasonInput.Text = $"{Utils.CurrentNextSeason}";
|
||||
SeasonInput.Text = $"{Math.Max(SeasonInput.Minimum ?? Utils.CurrentYear, Utils.CurrentYear)}";
|
||||
DescBlock1.Visibility = QuestionBlock1.Visibility;
|
||||
DescBlock2.Visibility = QuestionBlock2.Visibility;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Window x:Class="Elwig.Dialogs.AreaComTransferDialog"
|
||||
<Window x:Class="Elwig.Dialogs.AreaCmtTransferDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -8,7 +8,7 @@
|
||||
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||
Title="Flächenbindungen übertragen" Height="260" Width="450">
|
||||
Title="Flächenbindungen übertragen" Height="240" Width="450">
|
||||
<Window.Resources>
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
@@ -35,11 +35,11 @@
|
||||
<TextBlock x:Name="QuestionBlock1" TextAlignment="Center" Margin="0,10,0,0"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top">
|
||||
Sollen die aktiven Flächenbindungen des angegebenen Vorgängers<LineBreak/>
|
||||
übernommen werden? (<Run Text="{Binding AreaComNum}"/> FB, <Run Text="{Binding Area}"/> m²)
|
||||
übernommen werden? (<Run Text="{Binding AreaCmtNum}"/> FB, <Run Text="{Binding Area}"/> m²)
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="QuestionBlock2" TextAlignment="Center" Margin="0,10,0,0" Visibility="Hidden"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top">
|
||||
Sollen die aktiven Flächenbindungen gekündigt werden? (<Run Text="{Binding AreaComNum}"/> FB, <Run Text="{Binding Area}"/> m²)
|
||||
Sollen die aktiven Flächenbindungen gekündigt werden? (<Run Text="{Binding AreaCmtNum}"/> FB, <Run Text="{Binding Area}"/> m²)
|
||||
</TextBlock>
|
||||
|
||||
<Label x:Name="SeasonLabel" Content="Saison:" Margin="0,50,100,0"
|
||||
@@ -48,12 +48,8 @@
|
||||
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,105,0,0" TextAlignment="Center"
|
||||
<TextBlock x:Name="DescBlock1" Margin="0,90,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.
|
||||
+19
-19
@@ -3,53 +3,53 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Elwig.Dialogs {
|
||||
public partial class AreaComTransferDialog : Window {
|
||||
public partial class AreaCmtTransferDialog : Window {
|
||||
|
||||
public int CancelSeason { get; set; }
|
||||
public int SuccessorSeason => CancelSeason + 1;
|
||||
public bool MaintainYearFrom { get; set; }
|
||||
public int CancelSeason => SuccessorSeason - 1;
|
||||
public int SuccessorSeason { get; set; }
|
||||
|
||||
public string AreaComNum { get; set; }
|
||||
public string AreaCmtNum { get; set; }
|
||||
public string Area { get; set; }
|
||||
|
||||
public AreaComTransferDialog(string name, int areaComNum, int area) {
|
||||
CancelSeason = Utils.FollowingSeason - 1;
|
||||
AreaComNum = $"{areaComNum:N0}";
|
||||
public AreaCmtTransferDialog(string name, int areaCmtNum, int area) {
|
||||
SuccessorSeason = Utils.FollowingSeason;
|
||||
AreaCmtNum = $"{areaCmtNum:N0}";
|
||||
Area = $"{area:N0}";
|
||||
InitializeComponent();
|
||||
SeasonInput.Text = $"{CancelSeason}";
|
||||
SeasonInput.Text = $"{SuccessorSeason}";
|
||||
SeasonInput.Minimum = Utils.CurrentLastSeason;
|
||||
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) {
|
||||
CancelSeason = Utils.FollowingSeason - 1;
|
||||
AreaComNum = $"{areaComNum:N0}";
|
||||
public AreaCmtTransferDialog(string name, string successorName, int areaCmtNum, int area) {
|
||||
SuccessorSeason = Utils.FollowingSeason;
|
||||
AreaCmtNum = $"{areaCmtNum:N0}";
|
||||
Area = $"{area:N0}";
|
||||
InitializeComponent();
|
||||
SeasonInput.Text = $"{CancelSeason}";
|
||||
SeasonInput.Text = $"{SuccessorSeason}";
|
||||
SeasonInput.Minimum = Utils.CurrentLastSeason;
|
||||
Title = $"Aktive Flächenbindungen übertragen - {name} - {successorName}";
|
||||
InfoBlock.Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void SeasonInput_TextChanged(object sender, TextChangedEventArgs evt) {
|
||||
CancelSeason = (int)SeasonInput.Value!;
|
||||
SuccessorSeason = (int)SeasonInput.Value!;
|
||||
CancelSeason1.Text = $"{CancelSeason}";
|
||||
CancelSeason2.Text = $"{CancelSeason}";
|
||||
TransferSeason.Text = MaintainYearFrom ? "" : $"{SuccessorSeason}";
|
||||
DescBlock1Season.Text = MaintainYearFrom ? "dem originalen Beginn der FB" : "inkl. Saison ";
|
||||
TransferSeason.Text = $"{SuccessorSeason}";
|
||||
DescBlock1Season.Text = "inkl. Saison ";
|
||||
}
|
||||
|
||||
private void CopyYearToInput_Changed(object sender, RoutedEventArgs evt) {
|
||||
TransferSeason.Text = MaintainYearFrom ? "" : $"{SuccessorSeason}";
|
||||
DescBlock1Season.Text = MaintainYearFrom ? "dem originalen Beginn der FB" : "inkl. Saison ";
|
||||
TransferSeason.Text = $"{SuccessorSeason}";
|
||||
DescBlock1Season.Text = "inkl. Saison ";
|
||||
}
|
||||
|
||||
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
|
||||
@@ -47,9 +47,9 @@
|
||||
TextChanged="NameInput_TextChanged"/>
|
||||
|
||||
<Label Content="Beim Löschen müssen folgende Daten auch gelöscht werden:" Margin="10,120,10,10"/>
|
||||
<CheckBox x:Name="AreaComInput" Content="Flächenbindungen" Margin="40,145,0,0"
|
||||
<CheckBox x:Name="AreaCmtInput" Content="Flächenbindungen" Margin="40,145,0,0"
|
||||
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
|
||||
IsChecked="{Binding DeleteAreaComs}"/>
|
||||
IsChecked="{Binding DeleteAreaCmts}"/>
|
||||
<CheckBox x:Name="DeliveryInput" Content="Lieferungen" Margin="40,165,0,0"
|
||||
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
|
||||
IsChecked="{Binding DeleteDeliveries}"/>
|
||||
|
||||
@@ -8,16 +8,16 @@ namespace Elwig.Dialogs {
|
||||
|
||||
protected string[] NameParts;
|
||||
|
||||
public bool DeleteAreaComs { get; set; }
|
||||
public bool DeleteAreaCmts { get; set; }
|
||||
public bool DeleteDeliveries { get; set; }
|
||||
public bool DeletePaymentData { get; set; }
|
||||
|
||||
public DeleteMemberDialog(int mgnr, string name, int numAreaComs, int numDeliveries, int numCredits) {
|
||||
public DeleteMemberDialog(int mgnr, string name, int numAreaCmts, int numDeliveries, int numCredits) {
|
||||
NameParts = name.ToLower().Split(' ').Where(p => p.Length > 0).Append($"{mgnr}").ToArray();
|
||||
InitializeComponent();
|
||||
Title += " - " + name;
|
||||
AreaComInput.IsEnabled = numAreaComs != 0;
|
||||
AreaComInput.Content += $" ({numAreaComs:N0})";
|
||||
AreaCmtInput.IsEnabled = numAreaCmts != 0;
|
||||
AreaCmtInput.Content += $" ({numAreaCmts:N0})";
|
||||
DeliveryInput.IsEnabled = numDeliveries != 0;
|
||||
DeliveryInput.Content += $" ({numDeliveries:N0})";
|
||||
PaymentInput.IsEnabled = numCredits != 0;
|
||||
@@ -43,7 +43,7 @@ namespace Elwig.Dialogs {
|
||||
private void Update() {
|
||||
var t = NameInput.Text.ToLower().Split(' ');
|
||||
var nameValid = NameParts.All(t.Contains);
|
||||
UpdateCheckBox(AreaComInput);
|
||||
UpdateCheckBox(AreaCmtInput);
|
||||
UpdateCheckBox(DeliveryInput);
|
||||
UpdateCheckBox(PaymentInput);
|
||||
if (!nameValid) {
|
||||
@@ -52,7 +52,7 @@ namespace Elwig.Dialogs {
|
||||
ControlUtils.ClearInputState(NameInput);
|
||||
}
|
||||
ConfirmButton.IsEnabled =
|
||||
(!AreaComInput.IsEnabled || DeleteAreaComs) &&
|
||||
(!AreaCmtInput.IsEnabled || DeleteAreaCmts) &&
|
||||
(!DeliveryInput.IsEnabled || DeleteDeliveries) &&
|
||||
(!PaymentInput.IsEnabled || DeletePaymentData) &&
|
||||
nameValid;
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Part.DPNr}" Width="20" TextAlignment="Right"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0" FontSize="14"/>
|
||||
<TextBlock Text="{Binding Part.SortId}" Width="40" TextAlignment="Center"
|
||||
<TextBlock Text="{Binding Part.SortId}" Foreground="{Binding Part.Variety.Color}" Width="40" TextAlignment="Center"
|
||||
VerticalAlignment="Center" Margin="0,0,0,0" FontSize="14"/>
|
||||
<TextBlock Text="{Binding Part.Kmw, StringFormat='{}{0:N1}°'}" Width="40" TextAlignment="Right" Padding="0,0,10,0"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
@@ -2,7 +2,6 @@ using Elwig.Helpers;
|
||||
using Elwig.Models.Entities;
|
||||
using Elwig.Windows;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -44,15 +43,10 @@ namespace Elwig.Dialogs {
|
||||
}
|
||||
|
||||
protected override async Task OnRenewContext(AppDbContext ctx) {
|
||||
ControlUtils.RenewItemsSource(MemberInput, await ctx.Members
|
||||
.Where(m => m.IsActive)
|
||||
.OrderBy(m => m.Name)
|
||||
.ThenBy(m => m.GivenName)
|
||||
.ToListAsync());
|
||||
ControlUtils.RenewItemsSource(MemberInput, await ctx.FetchMembers().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,9 +1,11 @@
|
||||
<Window x:Class="Elwig.Dialogs.ManualWeighingDialog"
|
||||
AutomationProperties.AutomationId="ManualWeighingDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Elwig.Dialogs"
|
||||
xmlns:ctrl="clr-namespace:Elwig.Controls"
|
||||
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
FocusManager.FocusedElement="{Binding ElementName=WeightInput}"
|
||||
@@ -37,11 +39,9 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Content="Gewicht:" Margin="10,20,10,10"/>
|
||||
<Grid Grid.Column="1" Width="70" Height="25" Margin="0,20,10,10" HorizontalAlignment="Left" VerticalAlignment="Top">
|
||||
<TextBox x:Name="WeightInput" TextAlignment="Right" Padding="2,2,17,2"
|
||||
TextChanged="WeightInput_TextChanged"/>
|
||||
<Label Content="kg" Margin="0,4,3,0" HorizontalAlignment="Right" FontSize="10" Padding="2,4,2,4"/>
|
||||
</Grid>
|
||||
<ctrl:UnitTextBox x:Name="WeightInput" Grid.Column="1" Width="62" Height="25" Unit="kg"
|
||||
Margin="0,20,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="14" Padding="2"
|
||||
TextChanged="WeightInput_TextChanged"/>
|
||||
|
||||
<Label Content="Grund:" Margin="10,50,10,10"/>
|
||||
<TextBox x:Name="ReasonInput" Grid.Column="1" Margin="0,50,10,10"
|
||||
|
||||
@@ -6,8 +6,8 @@ using System.Windows.Controls;
|
||||
namespace Elwig.Dialogs {
|
||||
public partial class ManualWeighingDialog : Window {
|
||||
|
||||
public int Weight = 0;
|
||||
public string? Reason = null;
|
||||
public int Weight { get; private set; }
|
||||
public string? Reason { get; private set; }
|
||||
|
||||
public ManualWeighingDialog(string? reason = null) {
|
||||
InitializeComponent();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<Window x:Class="Elwig.Dialogs.TareCorrectionDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Elwig.Dialogs"
|
||||
xmlns:ctrl="clr-namespace:Elwig.Controls"
|
||||
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
FocusManager.FocusedElement="{Binding ElementName=TargetTareInput}"
|
||||
Title="Tara korrigieren" Height="170" Width="300">
|
||||
<Window.Resources>
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
<Setter Property="Padding" Value="2,4,2,4"/>
|
||||
<Setter Property="Height" Value="25"/>
|
||||
</Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="Height" Value="25"/>
|
||||
<Setter Property="TextWrapping" Value="NoWrap"/>
|
||||
</Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalAlignment" Value="Bottom"/>
|
||||
<Setter Property="Width" Value="100"/>
|
||||
<Setter Property="Height" Value="25"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Content="Ist-Tara:" Margin="10,20,0,10" Grid.Column="1"/>
|
||||
<ctrl:UnitTextBox x:Name="ActualTareInput" Grid.Column="2" Width="56" Height="25" Unit="kg" IsReadOnly="True"
|
||||
Margin="0,20,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="14" Padding="2"
|
||||
TextChanged="TareInput_TextChanged"/>
|
||||
|
||||
<Label Content="Soll-Tara:" Margin="10,50,0,10" Grid.Column="1"/>
|
||||
<ctrl:UnitTextBox x:Name="TargetTareInput" Grid.Column="2" Width="56" Height="25" Unit="kg"
|
||||
Margin="0,50,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="14" Padding="2"
|
||||
TextChanged="TareInput_TextChanged"/>
|
||||
|
||||
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" Grid.ColumnSpan="3" IsEnabled="False" IsDefault="True"
|
||||
Click="ConfirmButton_Click"/>
|
||||
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" Grid.ColumnSpan="3" IsCancel="True"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,38 @@
|
||||
using Elwig.Helpers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Elwig.Dialogs {
|
||||
public partial class TareCorrectionDialog : Window {
|
||||
|
||||
public int ActualTare { get; private set; }
|
||||
public int TargetTare { get; private set; }
|
||||
|
||||
public TareCorrectionDialog(int? actualTare, int? targetTare = null) {
|
||||
InitializeComponent();
|
||||
ActualTare = actualTare ?? 0;
|
||||
TargetTare = targetTare ?? actualTare ?? 0;
|
||||
ActualTareInput.IsReadOnly = actualTare != null;
|
||||
ActualTareInput.Text = ActualTare.ToString();
|
||||
TargetTareInput.Text = TargetTare.ToString();
|
||||
TargetTareInput.SelectAll();
|
||||
}
|
||||
|
||||
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
|
||||
DialogResult = true;
|
||||
ActualTare = int.Parse(ActualTareInput.Text);
|
||||
TargetTare = int.Parse(TargetTareInput.Text);
|
||||
Close();
|
||||
}
|
||||
|
||||
private void UpdateButtons() {
|
||||
ConfirmButton.IsEnabled = ActualTareInput.Text.Length > 0 && TargetTareInput.Text.Length > 0;
|
||||
}
|
||||
|
||||
private void TareInput_TextChanged(object sender, TextChangedEventArgs evt) {
|
||||
if (sender is not TextBox b) return;
|
||||
Validator.CheckInteger(b, true, 4);
|
||||
UpdateButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Services;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -51,7 +52,7 @@ namespace Elwig.Dialogs {
|
||||
File.Delete(fileName);
|
||||
return;
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowException(exc);
|
||||
}
|
||||
Process.Start(fileName);
|
||||
DialogResult = true;
|
||||
|
||||
@@ -2,6 +2,7 @@ using Elwig.Helpers;
|
||||
using Elwig.Models;
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Colors;
|
||||
using iText.Kernel.Geom;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Kernel.Pdf.Action;
|
||||
using iText.Kernel.Pdf.Canvas;
|
||||
@@ -17,55 +18,87 @@ namespace Elwig.Documents {
|
||||
public class BusinessDocument : Document {
|
||||
|
||||
public Member Member;
|
||||
public MemberHistoryPoint MemberShares;
|
||||
public string? Location;
|
||||
public bool IncludeSender = false;
|
||||
public bool UseBillingAddress = false;
|
||||
public bool ShowDateAndLocation = false;
|
||||
public DateOnly? DateFrom;
|
||||
protected Table? Aside;
|
||||
|
||||
public string Address {
|
||||
get {
|
||||
IAddress addr = (Member.BillingAddress != null && UseBillingAddress) ? Member.BillingAddress : Member;
|
||||
var plz = addr.PostalDest.AtPlz;
|
||||
return string.Join("\n", ((string?[])[Member.BillingAddress?.FullName, Member.AdministrativeName, Member.ForTheAttentionOf, addr.Address, $"{plz?.Plz} {plz?.Ort.Name.Split(",")[0]}", addr.PostalDest.Country.Name]).Where(s => !string.IsNullOrWhiteSpace(s)));
|
||||
return string.Join("\n", ((string?[])[addr is BillingAddr ? addr.FullName : null, Member.AdministrativeName, Member.ForTheAttentionOf, addr.Address, $"{plz?.Plz} {plz?.Ort.Name.Split(",")[0]}", addr.PostalDest.Country.Name]).Where(s => !string.IsNullOrWhiteSpace(s)));
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
protected Cell NewAsideCell(Paragraph text, int colspan = 1, bool isName = false, bool right = false, bool center = false) {
|
||||
var cell = NewCell(text, colspan: colspan).SetPaddingsMM(0.25f, center ? 0 : right ? 1 : 0.5f, 0.25f, isName ? 1 : 0);
|
||||
if (colspan == 26) {
|
||||
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);
|
||||
} else if (isName) {
|
||||
cell.SetBorderLeft(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness));
|
||||
}
|
||||
if (right) cell.SetTextAlignment(TextAlignment.RIGHT);
|
||||
if (center) cell.SetTextAlignment(TextAlignment.CENTER);
|
||||
return cell;
|
||||
}
|
||||
|
||||
protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false) {
|
||||
return NewAsideCell(new KernedParagraph(text, 10), colspan, isName);
|
||||
protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false, bool right = false, bool center = false) {
|
||||
return NewAsideCell(new KernedParagraph(text, 10), colspan, isName, right, center);
|
||||
}
|
||||
|
||||
public BusinessDocument(string title, Member m, bool includeSender = false) :
|
||||
public BusinessDocument(string title, Member m, DateOnly? dateFrom, bool includeSender = false) :
|
||||
base(title) {
|
||||
Member = m;
|
||||
MemberShares = new(m.Shares, m.SharesRed, m.SharesWhite, m.SharesDormant);
|
||||
Location = App.BranchLocation;
|
||||
IncludeSender = includeSender;
|
||||
DateFrom = dateFrom;
|
||||
}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
|
||||
var uid = new KernedParagraph(Member.UstIdNr ?? "-", 10);
|
||||
if (!Member.IsBuchführend) uid.Add(Normal(" ")).Add(Italic("(pauschaliert)"));
|
||||
Aside = new Table(ColsMM(22.5, 42.5))
|
||||
.SetFont(NF).SetFontSize(10)
|
||||
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.BeforeRenderBody(doc, pdf);
|
||||
Aside = new Table(ColsMM(2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5))
|
||||
.SetWidth(UnitValue.CreatePointValue(65 * PtInMM)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetFont(NF).SetFontSize(10)
|
||||
.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));
|
||||
.AddCell(NewAsideCell("Mitglied", 26));
|
||||
if (Member.IsOrganic) {
|
||||
Aside
|
||||
.AddCell(NewAsideCell("Mitgl.-Nr.:", 8, isName: true)).AddCell(NewAsideCell($"{Member.MgNr}", 4, right: true))
|
||||
.AddCell(NewAsideCell("Bio-KSt.:", 6, isName: true)).AddCell(NewAsideCell(Member.OrganicAuthorityCode ?? "-", 8, right: true, center: Member.OrganicAuthorityCode == null))
|
||||
.AddCell(NewAsideCell("Betr.-Nr.:", 6, isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "-", 6, right: true, center: Member.LfbisNr == null))
|
||||
.AddCell(NewAsideCell("OOC-ID:", 6, isName: true)).AddCell(NewAsideCell(Member.OrganicOperatorId ?? "-", 8, right: true, center: Member.OrganicOperatorId == null))
|
||||
.AddCell(NewAsideCell("UID:", 6, isName: true)).AddCell(NewAsideCell(Member.UstIdNr ?? "-", 10, right: true, center: Member.UstIdNr == null))
|
||||
.AddCell(NewAsideCell(new KernedParagraph("", 10).Add(Italic(Member.IsAccounting ? "(buchführend)" : "(pauschaliert)")), 10, center: true));
|
||||
} else {
|
||||
Aside
|
||||
.AddCell(NewAsideCell("Mitglieds-Nr.:", 12, isName: true)).AddCell(NewAsideCell($"{Member.MgNr}", 4, right: true)).AddCell(NewAsideCell("", 10))
|
||||
.AddCell(NewAsideCell("Betriebs-Nr.:", 10, isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "-", 6, right: true, center: Member.LfbisNr == null)).AddCell(NewAsideCell("", 10))
|
||||
.AddCell(NewAsideCell("UID:", 6, isName: true)).AddCell(NewAsideCell(Member.UstIdNr ?? "-", 10, right: true, center: Member.UstIdNr == null))
|
||||
.AddCell(NewAsideCell(new KernedParagraph("", 10).Add(Italic(Member.IsAccounting ? "(buchführend)" : "(pauschaliert)")), 10, center: true));
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -84,20 +117,14 @@ namespace Elwig.Documents {
|
||||
header.Add(new KernedParagraph(App.Client.NameTypeFull, 12).SetFont(NF).SetMargin(0));
|
||||
canvas.Add(header);
|
||||
// address
|
||||
canvas.Add(new Div()
|
||||
.SetFixedPositionMM(25, 50, 80, 45, pageSize)
|
||||
.SetFont(NF)
|
||||
.Add(new KernedParagraph(IncludeSender ? $"{App.Client.Sender1}\n{App.Client.Sender2}" : "", 8).SetMargin(0).SetHeight(16).SetPaddings(8, 0, 8, 0))
|
||||
.Add(new KernedParagraph(Address, 12).SetMargin(0).SetHeight(12 * 5)));
|
||||
RenderAddress(canvas, pageSize);
|
||||
// aside
|
||||
if (Aside != null) {
|
||||
canvas.Add(new Div()
|
||||
.SetFixedPositionMM(125, 50, 65, 50, pageSize)
|
||||
.Add(Aside));
|
||||
RenderAside(Aside, canvas, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
doc.Add(new KernedParagraph(ShowDateAndLocation ? $"{Location}, am {Date:dd.MM.yyyy}" : "", 12).SetTextAlignment(TextAlignment.RIGHT).SetVerticalAlignment(VerticalAlignment.MIDDLE).SetHeight(24).SetMargin(0));
|
||||
doc.Add(new KernedParagraph(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));
|
||||
}
|
||||
|
||||
@@ -110,11 +137,15 @@ namespace Elwig.Documents {
|
||||
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(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").NextGroup()
|
||||
.Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextGroup()
|
||||
.Item(c.EmailAddress != null ? link1 : null).NextLine()
|
||||
.Item(c.Website != null ? link2 : null).NextGroup()
|
||||
.Item("Betriebs-Nr.", c.LfbisNr).NextGroup()
|
||||
.Item("Bio-KSt.", c.OrganicAuthorityCode).NextGroup()
|
||||
.Item("OOC-ID", c.OrganicOperatorId).NextLine()
|
||||
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
|
||||
.RebalanceLines()
|
||||
.ToLeafElements());
|
||||
}
|
||||
|
||||
@@ -253,7 +284,7 @@ namespace Elwig.Documents {
|
||||
}
|
||||
|
||||
protected Table NewBucketTable(
|
||||
Season season, Dictionary<string, MemberBucket> buckets,
|
||||
Season season, Dictionary<string, MemberBucket> buckets, int deliveredWeightRed, int deliveredWeightWhite,
|
||||
bool includeDelivery = true, bool includePayment = false,
|
||||
bool isTiny = false, IEnumerable<string>? filter = null
|
||||
) {
|
||||
@@ -306,11 +337,21 @@ namespace Elwig.Documents {
|
||||
.Where(b => !fbVars.Contains(b.Key))
|
||||
.OrderBy(b => b.Value.Name);
|
||||
|
||||
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny));
|
||||
tbl.AddCells(FormatRow(Member.BusinessShares * season.MinKgPerBusinessShare,
|
||||
Member.BusinessShares * season.MaxKgPerBusinessShare,
|
||||
season.Deliveries.Where(d => d.MgNr == Member.MgNr).Sum(d => d.Weight),
|
||||
isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
|
||||
if (MemberShares.Shares != 0 || (MemberShares.SharesRed == 0 && MemberShares.SharesWhite == 0)) {
|
||||
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny));
|
||||
tbl.AddCells(FormatRow(MemberShares.Shares * (season.MinKgPerShare ?? 0), MemberShares.Shares * (season.MaxKgPerShare ?? 0),
|
||||
deliveredWeightRed + deliveredWeightWhite, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
|
||||
}
|
||||
|
||||
if (MemberShares.SharesRed != 0 || MemberShares.SharesWhite != 0) {
|
||||
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA (rot)", isTiny: isTiny));
|
||||
tbl.AddCells(FormatRow(MemberShares.SharesRed * (season.MinKgPerShareRed ?? season.MinKgPerShare ?? 0), MemberShares.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(MemberShares.SharesWhite * (season.MinKgPerShareWhite ?? season.MinKgPerShare ?? 0), MemberShares.SharesWhite * (season.MaxKgPerShareWhite ?? season.MaxKgPerShare ?? 0),
|
||||
deliveredWeightWhite, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
|
||||
}
|
||||
|
||||
if (fbs.Any()) {
|
||||
tbl.AddCell(NewBucketSubHdr("Flächenbindungen" + (vtr.Any() ? " (inkl. Verträge)" : "") + ":", includePayment ? 8 : 7, isTiny: isTiny));
|
||||
|
||||
@@ -1,55 +1,82 @@
|
||||
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 string? MemberModifier;
|
||||
public List<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries;
|
||||
public decimal MemberTotalUnderDelivery;
|
||||
public int MemberAutoBusinessShares;
|
||||
public decimal MemberAutoBusinessSharesAmount;
|
||||
public PaymentCustom? CustomPayment;
|
||||
|
||||
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) {
|
||||
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) {
|
||||
UseBillingAddress = true;
|
||||
ShowDateAndLocation = true;
|
||||
Data = data;
|
||||
Payment = p;
|
||||
Credit = p.Credit;
|
||||
IsPreview = Payment == null || Credit == null;
|
||||
var season = p.Variant.Season;
|
||||
if (considerCustomModifiers) {
|
||||
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
var mod = App.Client.IsMatzen ? ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefault() : null;
|
||||
_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;
|
||||
if (CustomPayment?.ModComment != null) {
|
||||
MemberModifier = CustomPayment.ModComment;
|
||||
} else if (mod != null) {
|
||||
@@ -57,32 +84,33 @@ namespace Elwig.Documents {
|
||||
} else {
|
||||
MemberModifier = "Sonstige Zu-/Abschläge";
|
||||
}
|
||||
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 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 (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 (total == 0)
|
||||
MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerBsNone * p.Member.BusinessShares ?? 0);
|
||||
MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerShareNone * (Member.Shares + Member.SharesRed + Member.SharesWhite) ?? 0);
|
||||
}
|
||||
if (considerAutoBusinessShares) {
|
||||
var fromDate = $"{season.Year}-01-01";
|
||||
var toDate = $"{season.Year}-12-31";
|
||||
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)
|
||||
.Sum(h => h.BusinessShares);
|
||||
if (ConsiderAutoBusinessShares) {
|
||||
MemberAutoBusinessShares = await ctx.MemberHistory
|
||||
.Where(h => h.ToMgNr == Member.MgNr && h.DeductYear == season.Year)
|
||||
.SumAsync(h => h.Shares);
|
||||
MemberAutoBusinessSharesAmount = MemberAutoBusinessShares * (-season.BusinessShareValue ?? 0);
|
||||
}
|
||||
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?
|
||||
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?
|
||||
.OrderBy(u => u.Key)
|
||||
.Select(u => (
|
||||
varieties[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""),
|
||||
@@ -95,17 +123,18 @@ namespace Elwig.Documents {
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
Aside?.AddCell(NewAsideCell("Gutschrift", 2))
|
||||
.AddCell(NewAsideCell("TG-Nr.:", isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-"))
|
||||
.AddCell(NewAsideCell("Datum:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}"))
|
||||
.AddCell(NewAsideCell("Überw. am:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}"));
|
||||
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.BeforeRenderBody(doc, pdf);
|
||||
Aside?.AddCell(NewAsideCell("Gutschrift", 26))
|
||||
.AddCell(NewAsideCell("Tr.-Gutschr.-Nr.:", 12, isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-", 14))
|
||||
.AddCell(NewAsideCell("Datum:", 12, isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}", 14))
|
||||
.AddCell(NewAsideCell("Überwiesen am:", 12, isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}", 14));
|
||||
}
|
||||
|
||||
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));
|
||||
doc.Add(NewCreditTable(_data));
|
||||
|
||||
var div = new Table(ColsMM(60, 105))
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
@@ -131,7 +160,7 @@ namespace Elwig.Documents {
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetKeepTogether(true);
|
||||
|
||||
var sum = Data.Rows.Sum(p => p.Amount);
|
||||
var sum = _data.Rows.Sum(p => p.Amount);
|
||||
if (Payment == null) {
|
||||
tbl1.AddCells(FormatRow("Gesamt", sum, bold: true, noTopBorder: true));
|
||||
} else {
|
||||
@@ -139,7 +168,7 @@ namespace Elwig.Documents {
|
||||
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));
|
||||
tbl1.AddCells(FormatRow(MemberModifier ?? "", Payment.Amount - Payment.NetAmount, add: true));
|
||||
}
|
||||
if (Credit == null) {
|
||||
tbl1.AddCells(FormatRow("Gesamtbetrag", Payment.Amount, bold: true, noTopBorder: noBorder));
|
||||
@@ -173,7 +202,7 @@ namespace Elwig.Documents {
|
||||
penalty += MemberTotalUnderDelivery;
|
||||
}
|
||||
if (MemberAutoBusinessSharesAmount != 0) {
|
||||
tbl2.AddCells(FormatRow($"Autom. Nachz. von GA ({MemberAutoBusinessShares})", MemberAutoBusinessSharesAmount, add: true));
|
||||
tbl2.AddCells(FormatRow($"Nachzeichnung von GA ({MemberAutoBusinessShares})", MemberAutoBusinessSharesAmount, add: true));
|
||||
penalty += MemberAutoBusinessSharesAmount;
|
||||
}
|
||||
if (CustomPayment?.Amount != null) {
|
||||
@@ -272,11 +301,14 @@ namespace Elwig.Documents {
|
||||
if (p.QualId == "WEI") varibute.Add(Italic("abgew."));
|
||||
sub.AddCell(NewCell(colspan: 2))
|
||||
.AddCell(NewTd(varibute, colspan: 3).SetPaddingTop(0));
|
||||
} else if (i - (rows - p.Modifiers.Length) < p.Modifiers.Length) {
|
||||
sub.AddCell(NewCell(colspan: 2))
|
||||
.AddCell(NewTd(p.Modifiers[i - (rows - p.Modifiers.Length)], 8, colspan: 3).SetPaddingTop(0).SetPaddingLeftMM(5));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 5));
|
||||
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) {
|
||||
@@ -297,7 +329,7 @@ namespace Elwig.Documents {
|
||||
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))
|
||||
.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)
|
||||
|
||||
@@ -5,49 +5,70 @@ 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";
|
||||
|
||||
public Season Season;
|
||||
public DeliveryConfirmationDeliveryData Data;
|
||||
private readonly int _year;
|
||||
public Season? Season;
|
||||
public int MemberDeliveredWeightRed;
|
||||
public int MemberDeliveredWeightWhite;
|
||||
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(AppDbContext ctx, int year, Member m, DeliveryConfirmationDeliveryData data) :
|
||||
base($"{Name} {year}", m) {
|
||||
Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season");
|
||||
public DeliveryConfirmation(int year, Member m, DateOnly? dateFrom, DeliveryConfirmationDeliveryData? data = null) :
|
||||
base($"{Name} {year}", m, dateFrom) {
|
||||
_year = year;
|
||||
ShowDateAndLocation = true;
|
||||
UseBillingAddress = true;
|
||||
DocumentId = $"Anl.-Best. {Season.Year}/{m.MgNr}";
|
||||
DocumentId = $"Anlfrg.-Bstng. {_year}/{m.MgNr}";
|
||||
Data = data;
|
||||
MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult();
|
||||
MemberStats = AppDbContext.GetMemberStats(Season.Year, m.MgNr).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
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);
|
||||
MemberShares = 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"));
|
||||
Aside?.AddCell(NewAsideCell("Saison", 26))
|
||||
.AddCell(NewAsideCell("Lieferungen:", 12, isName: true)).AddCell(NewAsideCell($"{Data.Rows.DistinctBy(r => r.LsNr).Count():N0} (Teil-Lfrg.: {Data.RowNum:N0})", 14))
|
||||
.AddCell(NewAsideCell("Zeitraum:", 8, 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)", 18, center: true))
|
||||
.AddCell(NewAsideCell("Menge:", 12, isName: true)).AddCell(NewAsideCell($"{MemberStats.Sum(s => s.Weight):N0} kg", 14));
|
||||
}
|
||||
|
||||
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, includePayment: true)
|
||||
doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, includePayment: true)
|
||||
.SetMarginTopMM(10).SetKeepTogether(true));
|
||||
|
||||
if (Text != null) {
|
||||
@@ -98,13 +119,14 @@ namespace Elwig.Documents {
|
||||
} else if (i == 1 && attr) {
|
||||
sub.AddCell(NewCell(colspan: 2))
|
||||
.AddCell(NewTd($"{p.Attribute}{(p.Attribute != null && p.Cultivation != null ? " / " : "")}{p.Cultivation}", 8, colspan: 2)
|
||||
.SetPaddingsMM(0.125f, 1, 0.125f, 1))
|
||||
.SetPaddingTop(0))
|
||||
.AddCell(NewCell(colspan: 2));
|
||||
} else {
|
||||
sub.AddCell(NewCell(colspan: 2));
|
||||
if (i - (rows - p.Modifiers.Length) < p.Modifiers.Length) {
|
||||
sub.AddCell(NewTd(p.Modifiers[i - (rows - p.Modifiers.Length)], 8, colspan: 2)
|
||||
.SetPaddingsMM(0.125f, 0, 0.125f, 5));
|
||||
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));
|
||||
}
|
||||
@@ -113,15 +135,16 @@ namespace Elwig.Documents {
|
||||
|
||||
if (i < p.Buckets.Length) {
|
||||
var bucket = p.Buckets[i];
|
||||
sub.AddCell(NewTd($"{bucket.Name}:", 8).SetHeight(10).SetPaddingsMM(0.125f, 0, 0.125f, 0));
|
||||
sub.AddCell(NewTd($"{bucket.Value:N0}", right: true));
|
||||
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}"));
|
||||
sub.AddCell(NewTd(p.IsNetWeight ? "\u2611" : "\u2610", 7, center: true).SetFont(SF).SetPadding(0));
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ 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 {
|
||||
@@ -17,7 +19,9 @@ namespace Elwig.Documents {
|
||||
|
||||
public Delivery Delivery;
|
||||
public string? Text;
|
||||
public Dictionary<string, MemberBucket> MemberBuckets;
|
||||
public int MemberDeliveredWeightRed;
|
||||
public int MemberDeliveredWeightWhite;
|
||||
public Dictionary<string, MemberBucket> MemberBuckets = [];
|
||||
|
||||
// 0 - none
|
||||
// 1 - GA only
|
||||
@@ -25,23 +29,53 @@ namespace Elwig.Documents {
|
||||
// 3 - full
|
||||
public int DisplayStats = App.Client.ModeDeliveryNoteStats;
|
||||
|
||||
public DeliveryNote(Delivery d, AppDbContext? ctx = null) :
|
||||
base($"{Name} Nr. {d.LsNr}", d.Member) {
|
||||
public DeliveryNote(Delivery d) :
|
||||
base($"{Name} Nr. {d.LsNr}", d.Member, DateOnly.FromDateTime(d.ModifiedAt)) {
|
||||
UseBillingAddress = true;
|
||||
ShowDateAndLocation = true;
|
||||
Delivery = d;
|
||||
Text = App.Client.TextDeliveryNote;
|
||||
DocumentId = d.LsNr;
|
||||
IsDoublePaged = true;
|
||||
MemberBuckets = ctx?.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult() ?? [];
|
||||
}
|
||||
|
||||
protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
base.RenderHeader(doc, pdf);
|
||||
Aside?.AddCell(NewAsideCell("Lieferung", 2))
|
||||
.AddCell(NewAsideCell("LS-Nr.:", isName: true)).AddCell(NewAsideCell(Delivery.LsNr))
|
||||
.AddCell(NewAsideCell("Datum/Zeit:", isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}"))
|
||||
.AddCell(NewAsideCell("Zweigstelle:", isName: true)).AddCell(NewAsideCell(Delivery.Branch.Name));
|
||||
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);
|
||||
MemberShares = 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", 26))
|
||||
.AddCell(NewAsideCell("Lieferschein-Nr.:", 12, isName: true)).AddCell(NewAsideCell(Delivery.LsNr, 14))
|
||||
.AddCell(NewAsideCell("Datum/Zeit:", 12, isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}", 14))
|
||||
.AddCell(NewAsideCell("Zweigstelle:", 12, isName: true)).AddCell(NewAsideCell(Delivery.Branch.Name, 14));
|
||||
}
|
||||
|
||||
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
|
||||
@@ -52,7 +86,7 @@ namespace Elwig.Documents {
|
||||
doc.Add(new KernedParagraph($"Anmerkung zur Lieferung: {Delivery.Comment}", 10).SetMarginsMM(5, 0, 0, 0));
|
||||
}
|
||||
if (DisplayStats > 0) {
|
||||
doc.Add(NewBucketTable(Delivery.Season, MemberBuckets, isTiny: true,
|
||||
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));
|
||||
@@ -87,7 +121,7 @@ namespace Elwig.Documents {
|
||||
}
|
||||
|
||||
protected Table NewDeliveryTable() {
|
||||
var tbl = new Table(ColsMM(10, 21, 25, 19.5, 19.5, 30, 12.5, 12.5, 15), true)
|
||||
var tbl = new Table(ColsMM(10, 21, 25, 20, 20, 30, 12.5, 12.5, 14), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorder(Border.NO_BORDER).SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
|
||||
@@ -96,15 +130,16 @@ namespace Elwig.Documents {
|
||||
.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("Menge").SetPaddingRight(0))
|
||||
.AddHeaderCell(NewTh("[°Oe]", 8))
|
||||
.AddHeaderCell(NewTh("[°KMW]", 8))
|
||||
.AddHeaderCell(NewTh("[kg]", 8));
|
||||
.AddHeaderCell(NewTh("[kg]", 8).SetPaddingRight(0));
|
||||
|
||||
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)
|
||||
var sub = new Table(ColsMM(10, 21, 25, 20, 20, 30, 12.5, 12.5, 14), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetKeepTogether(true);
|
||||
|
||||
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))
|
||||
@@ -113,11 +148,14 @@ namespace Elwig.Documents {
|
||||
.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));
|
||||
.AddCell(NewDeliveryMainTd($"{part.Weight:N0}", right: true).SetPaddingRight(0));
|
||||
|
||||
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})" : "")));
|
||||
if (part.Cultivation.IsOrganic) {
|
||||
cult.Add(" \u2013 ").Add(Italic("Kontrollstelle:")).Add(Normal($" {Member.OrganicAuthorityCode ?? "?"}"));
|
||||
}
|
||||
sub.AddCell(NewTd())
|
||||
.AddCell(NewTd(cult, colspan: 5))
|
||||
.AddCell(NewTd(colspan: 3));
|
||||
@@ -147,8 +185,8 @@ namespace Elwig.Documents {
|
||||
}
|
||||
} else {
|
||||
var info = part.WeighingInfo;
|
||||
weighing.Add(Italic("Waage:"))
|
||||
.Add(Normal(" " + (part.ScaleId ?? "?") + ", "))
|
||||
weighing.Add(Italic("Waage/Terminal:"))
|
||||
.Add(Normal(" " + (info.ScaleId ?? "?") + "/" + (part.ScaleTerminalId ?? "?") + ", "))
|
||||
.Add(Italic("ID:"))
|
||||
.Add(Normal(" " + (info.Id ?? "?")));
|
||||
if (info.Date != null || info.Time != null)
|
||||
@@ -162,9 +200,17 @@ namespace Elwig.Documents {
|
||||
.Add(Italic("Brutto:"))
|
||||
.Add(Normal($" {info.Gross:N0} kg \u2013 "))
|
||||
.Add(Italic("Tara:"))
|
||||
.Add(Normal($" {info.Tare:N0} kg \u2013 "))
|
||||
.Add(Normal($" {info.Tare:N0} kg "));
|
||||
if (info.TareCorrection != null && info.TareCorrection != 0)
|
||||
weighing.Add(Bold($"(Korr.: {Utils.GetSign(info.TareCorrection.Value)}{Math.Abs(info.TareCorrection.Value)} kg)")).Add(" ");
|
||||
weighing
|
||||
.Add(Normal("\u2013 "))
|
||||
.Add(Italic("Netto:"))
|
||||
.Add(Normal($" {info.Net:N0} kg \u2013 "))
|
||||
.Add(Normal($" {info.Net:N0} kg "));
|
||||
if (info.TareCorrection != null && info.TareCorrection != 0)
|
||||
weighing.Add(Normal($"({Utils.GetSign(-info.TareCorrection.Value)}{Math.Abs(info.TareCorrection.Value)} kg) "));
|
||||
weighing
|
||||
.Add(Normal("\u2013 "))
|
||||
.Add(Italic(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen"));
|
||||
} else {
|
||||
weighing.Add(" ")
|
||||
@@ -176,7 +222,7 @@ namespace Elwig.Documents {
|
||||
.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(new KernedParagraph(8).Add(Italic("Anmerkung:")).Add(Normal(" " + part.Comment)), colspan: 5))
|
||||
.AddCell(NewCell(colspan: 3));
|
||||
}
|
||||
if (part.Temperature != null || part.Acid != null) {
|
||||
@@ -186,7 +232,7 @@ namespace Elwig.Documents {
|
||||
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"));
|
||||
p.Add(Italic("Säure:")).Add(Normal($" {part.Acid:N1} g/l"));
|
||||
sub.AddCell(NewCell())
|
||||
.AddCell(NewCell(p, colspan: 5))
|
||||
.AddCell(NewCell(colspan: 3));
|
||||
@@ -195,10 +241,10 @@ namespace Elwig.Documents {
|
||||
}
|
||||
|
||||
if (Delivery.Parts.Count > 1) {
|
||||
tbl.AddCell(NewTd("Gesamt:", 12, bold: true, borderTop: true, colspan: 6).SetPaddingsMM(1, 1, 1, 1))
|
||||
tbl.AddCell(NewTd("Gesamt:", 12, bold: true, borderTop: true, colspan: 6).SetPaddingsMM(1, 1, 1, 0))
|
||||
.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));
|
||||
.AddCell(NewTd($"{Delivery.Weight:N0}", 12, bold: true, right: true, borderTop: true).SetPaddingsMM(1, 0, 1, 1));
|
||||
}
|
||||
|
||||
return tbl;
|
||||
|
||||
+50
-51
@@ -49,17 +49,15 @@ namespace Elwig.Documents {
|
||||
public bool IsPreview = false;
|
||||
private iText.Layout.Document? _doc;
|
||||
|
||||
public int CurrentNextSeason;
|
||||
public string? DocumentId;
|
||||
public string Title;
|
||||
public string Author;
|
||||
public DateOnly Date;
|
||||
|
||||
public Document(string title) {
|
||||
CurrentNextSeason = Utils.CurrentNextSeason;
|
||||
Title = title;
|
||||
Author = App.Client.NameFull;
|
||||
Date = DateOnly.FromDateTime(Utils.Today);
|
||||
Date = DateOnly.FromDateTime(DateTime.Today);
|
||||
}
|
||||
|
||||
~Document() {
|
||||
@@ -88,11 +86,15 @@ namespace Elwig.Documents {
|
||||
}
|
||||
|
||||
public int Render(string path) {
|
||||
using var writer = new PdfWriter(path);
|
||||
return Render(writer);
|
||||
using (var writer = new PdfWriter(path)) {
|
||||
Render(writer);
|
||||
}
|
||||
using var reader = new PdfReader(path);
|
||||
using var pdf = new PdfDocument(reader);
|
||||
return pdf.GetNumberOfPages();
|
||||
}
|
||||
|
||||
private int Render(PdfWriter writer) {
|
||||
private void Render(PdfWriter writer) {
|
||||
NF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\times.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
BF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesbd.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
IF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesi.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
|
||||
@@ -117,16 +119,16 @@ namespace Elwig.Documents {
|
||||
_doc = new iText.Layout.Document(pdf, iText.Kernel.Geom.PageSize.A4);
|
||||
try {
|
||||
_doc.SetFont(NF).SetFontSize(12);
|
||||
RenderHeader(_doc, pdf);
|
||||
BeforeRenderBody(_doc, pdf);
|
||||
RenderBody(_doc, pdf);
|
||||
var pageNum = pdf.GetNumberOfPages();
|
||||
return pageNum;
|
||||
} finally {
|
||||
_doc.Close();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { }
|
||||
protected virtual 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) { }
|
||||
|
||||
@@ -134,7 +136,7 @@ namespace Elwig.Documents {
|
||||
return new KernedParagraph(App.Client.NameFull, 10);
|
||||
}
|
||||
|
||||
public async Task Generate(CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
|
||||
public async Task Generate(AppDbContext ctx, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
|
||||
if (_pdfFile != null)
|
||||
return;
|
||||
progress?.Report(0.0);
|
||||
@@ -150,84 +152,78 @@ namespace Elwig.Documents {
|
||||
using var mergedPdf = new PdfDocument(writer);
|
||||
var merger = new PdfMerger(mergedPdf);
|
||||
|
||||
PdfPage? letterheadPage = null;
|
||||
int letterheadInsertIndex = 0;
|
||||
int letterheadDocIndex = 0;
|
||||
(PdfPage Page, int InsertIndex, int DocIndex)? letterhead = null;
|
||||
|
||||
int p = mergedPdf.GetNumberOfPages();
|
||||
for (int i = 0; i < m.Documents.Count; i++) {
|
||||
if (cancelToken?.IsCancellationRequested ?? false)
|
||||
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
|
||||
var doc = m.Documents[i];
|
||||
int p0 = mergedPdf.GetNumberOfPages();
|
||||
int p0 = p;
|
||||
|
||||
if (letterheadPage != null && doc is Letterhead) {
|
||||
if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) {
|
||||
mergedPdf.AddPage(letterheadPage);
|
||||
if (letterhead != null && doc is Letterhead) {
|
||||
if (p0 <= letterhead.Value.InsertIndex) {
|
||||
mergedPdf.AddPage(letterhead.Value.Page);
|
||||
mergedPdf.AddNewPage();
|
||||
} else {
|
||||
mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage);
|
||||
mergedPdf.AddNewPage(letterheadInsertIndex + 2);
|
||||
mergedPdf.AddPage(letterhead.Value.InsertIndex + 1, letterhead.Value.Page);
|
||||
mergedPdf.AddNewPage(letterhead.Value.InsertIndex + 2);
|
||||
}
|
||||
|
||||
pageNums[letterheadDocIndex] = 1;
|
||||
letterheadPage = null;
|
||||
pageNums[letterhead.Value.DocIndex] = 1;
|
||||
letterhead = null;
|
||||
p += 2;
|
||||
}
|
||||
|
||||
if (doc is RawPdfDocument) {
|
||||
if (IsDoublePaged && doc is Letterhead) {
|
||||
using var reader = new PdfReader(doc.PdfPath);
|
||||
using var src = new PdfDocument(reader);
|
||||
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
|
||||
letterheadInsertIndex = p0;
|
||||
letterheadDocIndex = i;
|
||||
} else {
|
||||
using var reader = new PdfReader(doc.PdfPath);
|
||||
using var src = new PdfDocument(reader);
|
||||
merger.Merge(src, 1, src.GetNumberOfPages());
|
||||
}
|
||||
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);
|
||||
letterheadPage = src.GetPage(1).CopyTo(mergedPdf);
|
||||
letterheadInsertIndex = p0;
|
||||
letterheadDocIndex = i;
|
||||
letterhead = (src.GetPage(1).CopyTo(mergedPdf), p0, i);
|
||||
} else {
|
||||
using var reader = new PdfReader(tmpPdf.FilePath);
|
||||
using var src = new PdfDocument(reader);
|
||||
merger.Merge(src, 1, pageNum);
|
||||
p += pageNum;
|
||||
}
|
||||
}
|
||||
|
||||
int p1 = mergedPdf.GetNumberOfPages();
|
||||
int p1 = p;
|
||||
pageNums.Add(p1 - p0);
|
||||
|
||||
if (IsDoublePaged && doc is not Letterhead && mergedPdf.GetNumberOfPages() % 2 != 0) {
|
||||
if (letterheadPage != null) {
|
||||
mergedPdf.AddPage(letterheadPage);
|
||||
letterheadPage = null;
|
||||
if (IsDoublePaged && doc is not Letterhead && p % 2 != 0) {
|
||||
if (letterhead != null) {
|
||||
mergedPdf.AddPage(letterhead.Value.Page);
|
||||
letterhead = null;
|
||||
} else {
|
||||
mergedPdf.AddNewPage();
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
progress?.Report(100.0 * (i + 1) / (m.Documents.Count + 1));
|
||||
}
|
||||
|
||||
if (letterheadPage != null) {
|
||||
if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) {
|
||||
mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf));
|
||||
if (letterhead != null) {
|
||||
if (p <= letterhead.Value.InsertIndex) {
|
||||
mergedPdf.AddPage(letterhead.Value.Page);
|
||||
mergedPdf.AddNewPage();
|
||||
} else {
|
||||
mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage.CopyTo(mergedPdf));
|
||||
mergedPdf.AddNewPage(letterheadInsertIndex + 2);
|
||||
mergedPdf.AddPage(letterhead.Value.InsertIndex + 1, letterhead.Value.Page);
|
||||
mergedPdf.AddNewPage(letterhead.Value.InsertIndex + 2);
|
||||
}
|
||||
|
||||
pageNums[letterheadDocIndex] = 1;
|
||||
pageNums[letterhead.Value.DocIndex] = 1;
|
||||
p += 2;
|
||||
}
|
||||
|
||||
TotalPages = pageNums.Sum();
|
||||
TotalPages = p;
|
||||
} catch {
|
||||
pdf.Dispose();
|
||||
throw;
|
||||
@@ -238,6 +234,7 @@ namespace Elwig.Documents {
|
||||
throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
|
||||
var pdf = new TempFile("pdf");
|
||||
try {
|
||||
await LoadData(ctx);
|
||||
TotalPages = Render(pdf.FilePath);
|
||||
} catch {
|
||||
pdf.Dispose();
|
||||
@@ -255,7 +252,7 @@ namespace Elwig.Documents {
|
||||
|
||||
public async Task Print(int copies = 1) {
|
||||
if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
|
||||
await Pdf.Print(PdfPath, copies, IsDoublePaged);
|
||||
Pdf.Print(PdfPath, copies, IsDoublePaged);
|
||||
}
|
||||
|
||||
public void Show() {
|
||||
@@ -297,7 +294,7 @@ namespace Elwig.Documents {
|
||||
|
||||
protected Cell NewTh(string? text, float fontSize = 8, int rowspan = 1, int colspan = 1, bool left = false, bool rotated = false) {
|
||||
var p = new KernedParagraph(text ?? "", fontSize);
|
||||
if (rotated) p.SetRotationAngle(rotated ? 0.5 * Math.PI : 0);
|
||||
if (rotated) p.SetRotationAngle(0.5 * Math.PI);
|
||||
var cell = NewCell(p, rowspan: rowspan, colspan: colspan)
|
||||
.SetTextAlignment(left ? TextAlignment.LEFT : TextAlignment.CENTER)
|
||||
.SetVerticalAlignment(VerticalAlignment.MIDDLE)
|
||||
@@ -394,6 +391,8 @@ namespace Elwig.Documents {
|
||||
}
|
||||
|
||||
private void OnPageEnd(PdfDocumentEvent evt) {
|
||||
if (_doc is Letterhead) return;
|
||||
|
||||
var pdf = evt.GetDocument();
|
||||
var page = evt.GetPage();
|
||||
var pageNum = pdf.GetPageNumber(page);
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
using Elwig.Models.Entities;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Kernel.Pdf.Canvas;
|
||||
using iText.Layout;
|
||||
|
||||
namespace Elwig.Documents {
|
||||
public class Letterhead : BusinessDocument {
|
||||
public Letterhead(Member m) :
|
||||
base($"Briefkopf {m.FullName}", m, true) {
|
||||
Aside = null;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,83 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models;
|
||||
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 Dictionary<string, MemberBucket> MemberBuckets;
|
||||
public List<AreaCom> ActiveAreaCommitments;
|
||||
public Season? Season;
|
||||
public int MemberDeliveredWeightRed;
|
||||
public int MemberDeliveredWeightWhite;
|
||||
public List<MemberHistory> MemberHistory = [];
|
||||
public Dictionary<string, MemberBucket> MemberBuckets = [];
|
||||
public List<AreaCmt> ActiveAreaCommitments = [];
|
||||
|
||||
public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) {
|
||||
public MemberDataSheet(Member m) :
|
||||
base($"{Name} {m.AdministrativeName}", m, DateOnly.FromDateTime(m.ModifiedAt)) {
|
||||
ShowDateAndLocation = true;
|
||||
DocumentId = $"{Name} {m.MgNr}";
|
||||
Season = ctx.Seasons.ToList().MaxBy(s => s.Year) ?? throw new ArgumentException("invalid season");
|
||||
MemberBuckets = ctx.GetMemberBuckets(Utils.CurrentYear, m.MgNr).GetAwaiter().GetResult();
|
||||
ActiveAreaCommitments = [.. m.ActiveAreaCommitments(ctx)];
|
||||
}
|
||||
|
||||
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");
|
||||
MemberHistory = await ctx.MemberHistory
|
||||
.Where(h => h.FromMgNr == Member.MgNr || h.ToMgNr == Member.MgNr)
|
||||
.OrderBy(h => h.DateNoticeString).ThenBy(h => h.HistNr)
|
||||
.ToListAsync();
|
||||
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().SetMarginBottomMM(5));
|
||||
doc.Add(NewBucketTable(Season, MemberBuckets, includeDelivery: false));
|
||||
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());
|
||||
doc.Add(new KernedParagraph(12).Add(Bold($"Flächenbindungen per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 6, 0));
|
||||
doc.Add(NewAreaCmtTable(ActiveAreaCommitments));
|
||||
}
|
||||
if (App.Client.EnableMemberHistory && App.Client.ModeMemberDataSheetShares > 0) {
|
||||
bool firstOnPage = false;
|
||||
if (pdf.GetNumberOfPages() == 1) {
|
||||
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
|
||||
firstOnPage = true;
|
||||
}
|
||||
doc.Add(new KernedParagraph(12).Add(Bold($"Verlauf der Geschäftsanteile per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 6, 0));
|
||||
doc.Add(NewMemberBusinessSharesTable(MemberHistory));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +91,7 @@ namespace Elwig.Documents {
|
||||
.SetPaddingRightMM(0);
|
||||
}
|
||||
|
||||
protected Table NewMemberData() {
|
||||
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)
|
||||
@@ -111,40 +151,44 @@ namespace Elwig.Documents {
|
||||
.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("Buchführend:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsAccounting ? "Ja " : "Nein ", 10)
|
||||
.Add(Normal($"({(Member.IsAccounting ? 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(NewDataTh("Bio:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsOrganic ? "Ja" : "Nein", 10)
|
||||
.Add(Normal(Member.OrganicAuthorityCode != null || Member.OrganicOperatorId != null ? $" ({Member.OrganicAuthorityCode}{(Member.OrganicAuthorityCode != null && Member.OrganicOperatorId != null ? ", " : "")}{Member.OrganicOperatorId})" : "", 8)), colspan: 2))
|
||||
.AddCell(NewDataHdr("Genossenschaft", colspan: 6))
|
||||
.AddCell(NewDataTh("Status:")).AddCell(NewTd(new KernedParagraph(Member.IsActive ? "Aktiv " : "Nicht aktiv ", 10)
|
||||
.Add(Normal("(" + (Member.ExitDate != null ? $"{Member.EntryDate:dd.MM.yyyy}\u2013{Member.ExitDate:dd.MM.yyyy}" : $"seit {Member.EntryDate:dd.MM.yyyy}") + ")", 8))))
|
||||
.AddCell(NewDataTh("Geschäftsanteile:", colspan: 2)).AddCell(NewTd($"{Member.BusinessShares:N0}", colspan: 2))
|
||||
.AddCell(NewDataTh("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("Vollablieferer:", colspan: 2)).AddCell(NewTd(Member.IsFullDelivery ? "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));
|
||||
.AddCell(NewDataTh("Funktionär:", colspan: 2)).AddCell(NewTd(Member.IsOfficer ? "Ja" : "Nein", colspan: 2));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
protected Table NewAreaComTable() {
|
||||
var areaComs = ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new {
|
||||
protected Table NewAreaCmtTable(IEnumerable<AreaCmt> activeAreaCmts) {
|
||||
var areaCmts = activeAreaCmts.GroupBy(a => a.AreaCmtType).Select(group => new {
|
||||
Type = group.Key,
|
||||
AreaComs = group.OrderBy(c => c.Kg.AtKg.Name).ToList(),
|
||||
AreaCmts = 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);
|
||||
.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))
|
||||
@@ -155,7 +199,7 @@ namespace Elwig.Documents {
|
||||
.AddHeaderCell(NewTh("[m²]"));
|
||||
|
||||
var lastContract = "";
|
||||
foreach (var contractType in areaComs) {
|
||||
foreach (var contractType in areaCmts) {
|
||||
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))
|
||||
@@ -163,22 +207,160 @@ namespace Elwig.Documents {
|
||||
.AddCell(NewCell(colspan: 2)
|
||||
.SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER));
|
||||
|
||||
foreach (var areaCom in contractType.AreaComs) {
|
||||
tbl.AddCell(NewTd(new KernedParagraph(10).Add(Normal($"{areaCom.Kg.AtKg.Name} ")).Add(Normal($"({areaCom.Kg.AtKg.KgNr:00000})", 8))))
|
||||
.AddCell(NewTd(areaCom.Rd?.Name))
|
||||
.AddCell(NewTd(Regex.Replace(areaCom.GstNr.Replace(",", ", ").Replace("-", "\u2013"), @"\s+", " "), 10))
|
||||
.AddCell(NewTd($"{areaCom.Area:N0}", right: true))
|
||||
.AddCell(NewTd(areaCom.WineCult?.Name, center: true))
|
||||
.AddCell(NewTd(areaCom.YearTo == null ? (areaCom.YearFrom == null ? "unbefristet" : $"ab {areaCom.YearFrom}") : (areaCom.YearFrom == null ? $"bis {areaCom.YearTo}" : $"{areaCom.YearFrom}–{areaCom.YearTo}"), center: true));
|
||||
foreach (var areaCmt in contractType.AreaCmts) {
|
||||
var c = areaCmt.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(areaCmt.GstNr.Replace(",", ", ").Replace("-", "\u2013"), @"\s+", " "), 10))
|
||||
.AddCell(NewTd($"{areaCmt.Area:N0}", right: true))
|
||||
.AddCell(NewTd(areaCmt.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($"{ActiveAreaCommitments.Sum(a => a.Area):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
|
||||
tbl.AddCell(NewTd($"{activeAreaCmts.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;
|
||||
}
|
||||
|
||||
protected Cell[] NewBusinessSharesCells(MemberHistoryPoint p, bool isDiff = true) {
|
||||
if (App.Client.HasRedWhite) {
|
||||
return [
|
||||
NewTd(isDiff ? (p.SharesRed == 0 ? "" : p.SharesRed < 0 ? $"–{-p.SharesRed:N0}" : $"+{p.SharesRed:N0}") : $"{p.SharesRed:N0}",
|
||||
right: true, bold: !isDiff, borderTop: !isDiff),
|
||||
NewTd(isDiff ? (p.SharesWhite == 0 ? "" : p.SharesWhite < 0 ? $"–{-p.SharesWhite:N0}" : $"+{p.SharesWhite:N0}") : $"{p.SharesWhite:N0}",
|
||||
right: true, bold: !isDiff, borderTop: !isDiff),
|
||||
NewTd(isDiff ? (p.SharesDormant == 0 ? "" : p.SharesDormant < 0 ? $"–{-p.SharesDormant:N0}" : $"+{p.SharesDormant:N0}") : $"{p.SharesDormant:N0}",
|
||||
right: true, bold: !isDiff, borderTop: !isDiff),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
NewTd(isDiff ? (p.Shares == 0 ? "" : p.Shares < 0 ? $"–{-p.Shares:N0}" : $"+{p.Shares:N0}") : $"{p.Shares:N0}",
|
||||
right: true, bold: !isDiff, borderTop: !isDiff),
|
||||
NewTd(isDiff ? (p.SharesDormant == 0 ? "" : p.SharesDormant < 0 ? $"–{-p.SharesDormant:N0}" : $"+{p.SharesDormant:N0}") : $"{p.SharesDormant:N0}",
|
||||
right : true, bold: !isDiff, borderTop: !isDiff),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
protected Table NewMemberBusinessSharesTable(IEnumerable<MemberHistory> history) {
|
||||
var tbl = new Table(ColsMM(18, 93, App.Client.HasRedWhite ? 18 : 12, App.Client.HasRedWhite ? 12 : 18, 12, 12), true)
|
||||
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
|
||||
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
|
||||
.SetBorder(Border.NO_BORDER)
|
||||
.SetFontSize(10);
|
||||
|
||||
var tw = App.Client.HasRedWhite ? 1 : 2;
|
||||
tbl.AddHeaderCell(NewTh("Meldung am", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Text", rowspan: 2, colspan: tw, left: true))
|
||||
.AddHeaderCell(NewTh("Wirksam ab", rowspan: 2))
|
||||
.AddHeaderCell(NewTh("Geschäftsanteile", colspan: App.Client.HasRedWhite ? 3 : 2));
|
||||
if (App.Client.HasRedWhite) {
|
||||
tbl.AddHeaderCell(NewTh("rot"))
|
||||
.AddHeaderCell(NewTh("weiß"))
|
||||
.AddHeaderCell(NewTh("ruhend"));
|
||||
} else {
|
||||
tbl.AddHeaderCell(NewTh("normal"))
|
||||
.AddHeaderCell(NewTh("ruhend"));
|
||||
}
|
||||
|
||||
if (!history.Any() || Member.EntryDate <= history.First().DateNotice) {
|
||||
tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
|
||||
.AddCell(NewTd("Eintritt", colspan: tw, bold: true))
|
||||
.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
|
||||
.AddCells(NewBusinessSharesCells(new()));
|
||||
}
|
||||
|
||||
var recorded = history.Aggregate(new MemberHistoryPoint(), (s, h) => new(
|
||||
s.Shares + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.Normal ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.Normal && h.FromType == BusinessShareType.Normal ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.Normal ? h.Shares : 0),
|
||||
s.SharesRed + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.Red ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.Red && h.FromType == BusinessShareType.Red ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.Red ? h.Shares : 0),
|
||||
s.SharesWhite + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.White ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.White && h.FromType == BusinessShareType.White ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.White ? h.Shares : 0),
|
||||
s.SharesDormant + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.Dormant ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.Dormant && h.FromType == BusinessShareType.Dormant ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.Dormant ? h.Shares : 0)));
|
||||
var cur = new MemberHistoryPoint(Member.Shares - recorded.Shares, Member.SharesRed - recorded.SharesRed, Member.SharesWhite - recorded.SharesWhite, Member.SharesDormant - recorded.SharesDormant);
|
||||
|
||||
if (cur.Shares != 0 || cur.SharesRed != 0 || cur.SharesWhite != 0 || cur.SharesDormant != 0) {
|
||||
tbl.AddCell(NewTd())
|
||||
.AddCell(NewTd("Nicht in Software erfasst", colspan: tw, italic: true))
|
||||
.AddCell(NewTd())
|
||||
.AddCells(NewBusinessSharesCells(cur));
|
||||
if (history.Any()) {
|
||||
tbl.AddCell(NewTd(borderTop: true))
|
||||
.AddCell(NewTd($"Bis Lese {history.First().DateNotice.Year - 1}", borderTop: true, colspan: tw, bold: true))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
.AddCells(NewBusinessSharesCells(cur, isDiff: false));
|
||||
}
|
||||
}
|
||||
|
||||
DateOnly? lastDate = null;
|
||||
foreach (var row in history) {
|
||||
int s = 0, r = 0, w = 0, d = 0;
|
||||
if (row.FromMgNr == Member.MgNr && !(row.FromMgNr == row.ToMgNr && row.FromType == row.ToType)) {
|
||||
switch (row.FromType) {
|
||||
case BusinessShareType.Normal: s -= row.Shares; break;
|
||||
case BusinessShareType.Red: r -= row.Shares; break;
|
||||
case BusinessShareType.White: w -= row.Shares; break;
|
||||
case BusinessShareType.Dormant: d -= row.Shares; break;
|
||||
}
|
||||
}
|
||||
if (row.ToMgNr == Member.MgNr) {
|
||||
switch (row.ToType) {
|
||||
case BusinessShareType.Normal: s += row.Shares; break;
|
||||
case BusinessShareType.Red: r += row.Shares; break;
|
||||
case BusinessShareType.White: w += row.Shares; break;
|
||||
case BusinessShareType.Dormant: d += row.Shares; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastDate < Member.EntryDate && row.DateNotice <= Member.EntryDate) {
|
||||
tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
|
||||
.AddCell(NewTd("Eintritt", colspan: tw, bold: true))
|
||||
.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
|
||||
.AddCells(NewBusinessSharesCells(new()));
|
||||
}
|
||||
if (lastDate != null && row.DateNotice.Year != lastDate?.Year) {
|
||||
tbl.AddCell(NewTd(borderTop: true))
|
||||
.AddCell(NewTd(lastDate?.Year == row.DateNotice.Year - 1 ? $"Lese {lastDate?.Year}" : $"Lese {lastDate?.Year}–{row.DateNotice.Year - 1}", borderTop: true, colspan: tw, bold: true))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
.AddCells(NewBusinessSharesCells(cur, isDiff: false));
|
||||
}
|
||||
if (lastDate < Member.ExitDate && row.DateNotice <= Member.ExitDate) {
|
||||
tbl.AddCell(NewTd($"{Member.ExitDate:dd.MM.yyyy}"))
|
||||
.AddCell(NewTd("Austritt", colspan: tw, bold: true))
|
||||
.AddCell(NewTd($"31.12.{Member.ExitDate.Value.Year + 1}")) // FIXME
|
||||
.AddCells(NewBusinessSharesCells(new()));
|
||||
}
|
||||
cur = new(cur.Shares + s, cur.SharesRed + r, cur.SharesWhite + w, cur.SharesDormant + d);
|
||||
|
||||
tbl.AddCell(NewTd($"{row.DateNotice:dd.MM.yyyy}"))
|
||||
.AddCell(NewTd(row.FormatReasonPhrase(Member.MgNr), colspan: tw))
|
||||
.AddCell(NewTd($"{row.DateEffective:dd.MM.yyyy}"))
|
||||
.AddCells(NewBusinessSharesCells(new(s, r, w, d)));
|
||||
lastDate = row.DateNotice;
|
||||
}
|
||||
|
||||
if (Member.EntryDate != null && history.Any() && Member.EntryDate > lastDate) {
|
||||
tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
|
||||
.AddCell(NewTd("Eintritt", colspan: tw, bold: true))
|
||||
.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
|
||||
.AddCells(NewBusinessSharesCells(new()));
|
||||
}
|
||||
|
||||
if (Member.ExitDate != null && (!history.Any() || Member.ExitDate > lastDate)) {
|
||||
tbl.AddCell(NewTd($"{Member.ExitDate:dd.MM.yyyy}"))
|
||||
.AddCell(NewTd("Austritt", colspan: tw, bold: true))
|
||||
.AddCell(NewTd($"31.12.{Member.ExitDate.Value.Year + 1}")) // FIXME
|
||||
.AddCells(NewBusinessSharesCells(new()));
|
||||
}
|
||||
|
||||
tbl.AddCell(NewTd(borderTop: true))
|
||||
.AddCell(NewTd(lastDate == null ? "Aktuell" : $"Ab Lese {lastDate?.Year}", borderTop: true, colspan: tw, bold: true))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
.AddCells(NewBusinessSharesCells(cur, isDiff: false));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ namespace Elwig.Documents {
|
||||
public string Filter;
|
||||
public List<MemberListRow> Members;
|
||||
|
||||
public string[] AreaComFilters;
|
||||
public bool FilterAreaComs => AreaComFilters.Length > 0;
|
||||
public string[] AreaCmtFilters;
|
||||
public bool FilterAreaCmts => AreaCmtFilters.Length > 0;
|
||||
|
||||
public MemberList(string filter, IEnumerable<MemberListRow> members) :
|
||||
base(Name) {
|
||||
Filter = filter;
|
||||
Members = [.. members];
|
||||
AreaComFilters = [..members
|
||||
AreaCmtFilters = [..members
|
||||
.SelectMany(m => m.AreaCommitmentsFiltered)
|
||||
.Select(c => c.VtrgId)
|
||||
.Distinct()
|
||||
@@ -46,11 +46,11 @@ namespace Elwig.Documents {
|
||||
}
|
||||
|
||||
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)
|
||||
var tbl = new Table(AreaCmtFilters.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;
|
||||
var headerSpan = FilterAreaCmts ? 3 : 2;
|
||||
tbl.AddHeaderCell(NewTh("Nr.", rowspan: headerSpan))
|
||||
.AddHeaderCell(NewTh("Name", rowspan: headerSpan, left: true))
|
||||
.AddHeaderCell(NewTh("Adresse", rowspan: headerSpan, left: true))
|
||||
@@ -59,21 +59,21 @@ namespace Elwig.Documents {
|
||||
.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) {
|
||||
.AddHeaderCell(NewTh("Geb. Fl.", colspan: FilterAreaCmts ? AreaCmtFilters.Length : 1));
|
||||
if (FilterAreaCmts) {
|
||||
foreach (var vtrgId in AreaCmtFilters) {
|
||||
tbl.AddHeaderCell(NewTh(vtrgId));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < (FilterAreaComs ? AreaComFilters.Length : 1); i++) {
|
||||
for (int i = 0; i < (FilterAreaCmts ? AreaCmtFilters.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))
|
||||
tbl.AddCell(NewCell(colspan: 8 + Math.Max(AreaCmtFilters.Length, 1)).SetHeightMM(5).SetKeepWithNext(true));
|
||||
tbl.AddCell(NewCell(new KernedParagraph(m.Branch ?? "", 16).SetFont(BF), colspan: 8 + Math.Max(AreaCmtFilters.Length, 1))
|
||||
.SetPaddingsMM(1, 2, 1, 2)
|
||||
.SetBorder(new SolidBorder(BorderThickness))
|
||||
.SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)));
|
||||
@@ -86,10 +86,10 @@ namespace Elwig.Documents {
|
||||
.AddCell(NewTd($"{m.Plz}", 8))
|
||||
.AddCell(NewTd(m.Locality, 6))
|
||||
.AddCell(NewTd(m.LfbisNr ?? "", 8))
|
||||
.AddCell(NewTd($"{m.BusinessShares:N0}", 8, right: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd($"{m.SharesActive:N0}", 8, right: true).SetPaddingLeft(0).SetPaddingRight(0))
|
||||
.AddCell(NewTd(m.DefaultKg ?? "", 6));
|
||||
if (AreaComFilters.Length > 0) {
|
||||
foreach (var v in AreaComFilters) {
|
||||
if (AreaCmtFilters.Length > 0) {
|
||||
foreach (var v in AreaCmtFilters) {
|
||||
tbl.AddCell(NewTd($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}", 8, right: true));
|
||||
}
|
||||
} else {
|
||||
@@ -101,7 +101,7 @@ namespace Elwig.Documents {
|
||||
.AddCell(NewTd(m.BillingAddress ?? "", 8))
|
||||
.AddCell(NewTd($"{m.BillingPlz}", 8))
|
||||
.AddCell(NewTd(m.BillingLocality ?? "", 6))
|
||||
.AddCell(NewTd(colspan: 3 + Math.Max(AreaComFilters.Length, 1)));
|
||||
.AddCell(NewTd(colspan: 3 + Math.Max(AreaCmtFilters.Length, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,40 +7,60 @@ 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;
|
||||
|
||||
public PaymentVariantSummary(PaymentVar v, PaymentVariantSummaryData data) :
|
||||
private List<Credit> _credits = [];
|
||||
private List<PaymentDeliveryPart> _parts = [];
|
||||
|
||||
public PaymentVariantSummary(PaymentVar v, PaymentVariantSummaryData? data = null) :
|
||||
base($"{Name} {v.Year} - {v.Name}") {
|
||||
Variant = v;
|
||||
BillingData = BillingData.FromJson(v.Data);
|
||||
Data = data;
|
||||
CurrencySymbol = v.Season.Currency.Symbol ?? v.Season.Currency.Code;
|
||||
MemberNum = v.Credits.Count;
|
||||
}
|
||||
|
||||
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;
|
||||
IsPreview = MemberNum == 0;
|
||||
DeliveryNum = v.DeliveryPartPayments.DistinctBy(p => p.DeliveryPart.Delivery).Count();
|
||||
DeliveryPartNum = v.DeliveryPartPayments.Count;
|
||||
ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult();
|
||||
Modifiers = v.Season.Modifiers.ToDictionary(m => m.ModId);
|
||||
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)
|
||||
@@ -48,10 +68,10 @@ namespace Elwig.Documents {
|
||||
doc.Add(new KernedParagraph(Variant.Name, 14)
|
||||
.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
|
||||
.SetMarginsMM(0, 0, 10, 0));
|
||||
doc.Add(NewVariantStatTable().SetMarginBottomMM(10));
|
||||
doc.Add(NewModifierStatTable());
|
||||
doc.Add(NewVariantStatTable(Data).SetMarginBottomMM(10));
|
||||
doc.Add(NewModifierStatTable(Modifiers, ModifierStat));
|
||||
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
|
||||
doc.Add(NewPriceTable());
|
||||
doc.Add(NewPriceTable(Data));
|
||||
}
|
||||
|
||||
protected Cell NewSectionHdr(string text, int colspan = 1, bool borderLeft = false) {
|
||||
@@ -67,37 +87,37 @@ namespace Elwig.Documents {
|
||||
.SetBorderLeft(borderLeft ? new SolidBorder(BorderThickness) : Border.NO_BORDER);
|
||||
}
|
||||
|
||||
protected Table NewVariantStatTable() {
|
||||
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 = Variant.DeliveryPartPayments.Sum(p => p.NetAmount);
|
||||
//var sum2 = Variant.Credits.Sum(p => p.); //Variant.MemberPayments.Sum(p => p.Amount);
|
||||
var deliveryModifiers = Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount);
|
||||
var memberModifiers = Variant.Credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount);
|
||||
var sum2 = Variant.Credits.Sum(p => p.NetAmount);
|
||||
//var sum1 = _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 = -Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
|
||||
var netSum = Variant.Credits.Sum(p => p.NetAmount) - Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
|
||||
var vat = Variant.Credits.Sum(p => p.VatAmount);
|
||||
var grossSum = Variant.Credits.Sum(p => p.GrossAmount);
|
||||
var totalMods = Variant.Credits.Sum(p => p.Modifiers ?? 0m);
|
||||
var considered = -Variant.Credits.Sum(p => p.PrevModifiers ?? 0m);
|
||||
var totalSum = Variant.Credits.Sum(p => p.Amount);
|
||||
var 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();
|
||||
var weiRows = data.Rows.Where(r => r.QualityLevel == "Wein");
|
||||
var minWei = weiRows.Min(r => r.UnCtrd.MinPrice);
|
||||
var maxWei = weiRows.Max(r => r.UnCtrd.MaxPrice);
|
||||
var quwRows = data.Rows.Where(r => r.QualityLevel != "Wein");
|
||||
var minPrice = quwRows.Min(r => r.UnCtrd.MinPrice);
|
||||
var maxPrice = quwRows.Max(r => r.UnCtrd.MaxPrice);
|
||||
var ctrdRows = data.Rows
|
||||
.Where(r => r.Ctrd.MaxPrice != null && r.UnCtrd.MinPrice != null)
|
||||
.Select(r => r.Ctrd.MaxPrice - r.UnCtrd.MinPrice);
|
||||
var minCtrd = ctrdRows.Min();
|
||||
var maxCtrd = ctrdRows.Max();
|
||||
|
||||
tbl.AddCell(NewSectionHdr("Allgemein", colspan: 5))
|
||||
.AddCell(NewSectionHdr("Berücksichtigt", colspan: 3, borderLeft: true))
|
||||
@@ -119,7 +139,7 @@ namespace Elwig.Documents {
|
||||
|
||||
.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(NewSectionTh("(Automatische) Nachzeichnung von GA:", colspan: 2, borderLeft: true))
|
||||
.AddCell(NewTd(BillingData.ConsiderAutoBusinessShares ? "Ja" : "Nein", center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Berechnung:"))
|
||||
@@ -163,21 +183,21 @@ namespace Elwig.Documents {
|
||||
.AddCell(NewTd(CurrencySymbol))
|
||||
.AddCell(NewTd($"{Math.Abs(payed):N2}", right: true))
|
||||
.AddCell(NewSectionTh("Preis (abgewertet):", borderTop: true, borderLeft: true))
|
||||
.AddCell(NewTd((minWei != maxWei ? $"{minWei:N4}\u2013{maxWei:N4}" : $"{minWei:N4}") + $" {CurrencySymbol}/kg", colspan: 2, center: true, borderTop: true))
|
||||
.AddCell(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 != maxPrice ? $"{minPrice:N4}–{maxPrice:N4}" : $"{minPrice:N4}") + $" {CurrencySymbol}/kg", colspan: 2, center: 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 != maxGeb ? $"{minGeb:N4}\u2013{maxGeb:N4} {CurrencySymbol}/kg" : minGeb == 0 ? "-" : $"{minGeb:N4} {CurrencySymbol}/kg", colspan: 2, center: true))
|
||||
.AddCell(NewTd(minCtrd == null && maxCtrd == null ? "-" : minCtrd != maxCtrd ? $"{minCtrd:N4}\u2013{maxCtrd:N4} {CurrencySymbol}/kg" : minCtrd == 0 ? "-" : $"{minCtrd:N4} {CurrencySymbol}/kg", colspan: 2, center: true))
|
||||
|
||||
.AddCell(NewSectionTh("Bruttosumme:", colspan: 2))
|
||||
.AddCell(NewTd(borderTop: true))
|
||||
@@ -191,26 +211,26 @@ namespace Elwig.Documents {
|
||||
.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(NewTd($"{data.Rows.Sum(r => r.UnCtrd.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(NewTd($"{data.Rows.Sum(r => r.Ctrd.Weight + r.LowCtrd.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));
|
||||
.AddCell(NewTd($"{data.Rows.Sum(r => r.UnCtrd.Weight + r.LowCtrd.Weight + r.Ctrd.Weight):N0} kg", colspan: 2, right: true, borderTop: true));
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
protected Table NewModifierStatTable() {
|
||||
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)
|
||||
@@ -228,8 +248,8 @@ namespace Elwig.Documents {
|
||||
.AddCell(NewTh($"[{CurrencySymbol}]"))
|
||||
.AddCell(NewTh($"[{CurrencySymbol}]"));
|
||||
|
||||
foreach (var m in ModifierStat) {
|
||||
var mod = Modifiers[m.ModId];
|
||||
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))
|
||||
@@ -241,7 +261,7 @@ namespace Elwig.Documents {
|
||||
return tbl;
|
||||
}
|
||||
|
||||
protected Table NewPriceTable() {
|
||||
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);
|
||||
@@ -262,30 +282,30 @@ namespace Elwig.Documents {
|
||||
.AddHeaderCell(NewTh($"[{CurrencySymbol}]"));
|
||||
|
||||
string? lastHdr = null;
|
||||
foreach (var row in Data.Rows) {
|
||||
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
|
||||
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($"{rows.Sum(r => r.UnCtrd.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($"{rows.Sum(r => r.LowCtrd.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($"{rows.Sum(r => r.Ctrd.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.UnCtrd.Weight != 0 ? $"{row.UnCtrd.Weight:N0}" : "-", right: true))
|
||||
.AddCell(NewTd(row.UnCtrd.MaxPrice != null ? $"{row.UnCtrd.MaxPrice:N4}" : "-", right: true))
|
||||
.AddCell(NewTd(row.LowCtrd.Weight != 0 ? $"{row.LowCtrd.Weight:N0}" : "-", right: true))
|
||||
.AddCell(NewTd(row.LowCtrd.MaxPrice != null ? $"{row.LowCtrd.MaxPrice:N4}" : "-", right: true))
|
||||
.AddCell(NewTd(row.Ctrd.Weight != 0 ? $"{row.Ctrd.Weight:N0}" : "-", right: true))
|
||||
.AddCell(NewTd(row.Ctrd.MaxPrice != null ? $"{row.Ctrd.MaxPrice:N4}" : "-", right: true))
|
||||
.AddCell(NewTd($"{row.Amount:N2}", right: true));
|
||||
lastHdr = hdr;
|
||||
}
|
||||
|
||||
+16
-17
@@ -9,10 +9,10 @@
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
|
||||
<Version>1.0.4.0</Version>
|
||||
<Version>1.2.0.0</Version>
|
||||
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationManifest>App.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -23,22 +23,21 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="bblanchon.PDFium.Win32" Version="148.0.7734" />
|
||||
<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="bblanchon.PDFium.Win32" Version="156.0.8076" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||
<PackageReference Include="itext" Version="9.8.0" />
|
||||
<PackageReference Include="itext.bouncy-castle-adapter" Version="9.8.0" />
|
||||
<PackageReference Include="LinqKit" Version="1.3.11" />
|
||||
<PackageReference Include="MailKit" Version="4.15.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3856.49" />
|
||||
<PackageReference Include="NJsonSchema" Version="11.5.2" />
|
||||
<PackageReference Include="ScottPlot.WPF" Version="5.1.57" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.5" />
|
||||
<PackageReference Include="System.IO.Ports" Version="10.0.5" />
|
||||
<PackageReference Include="System.Management" Version="10.0.5" />
|
||||
<PackageReference Include="MailKit" Version="4.18.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.12" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.12" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.4258.31" />
|
||||
<PackageReference Include="NJsonSchema" Version="11.6.1" />
|
||||
<PackageReference Include="ScottPlot.WPF" Version="5.1.59" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.12" />
|
||||
<PackageReference Include="System.IO.Ports" Version="10.0.12" />
|
||||
<PackageReference Include="System.Management" Version="10.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+286
-66
@@ -1,20 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Elwig.Models.Entities;
|
||||
using System.Linq;
|
||||
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;
|
||||
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 System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
|
||||
public record struct AreaComBucket(int Area, int Obligation, int Right);
|
||||
public record struct MemberHistoryPoint(int Shares, int SharesRed, int SharesWhite, int SharesDormant);
|
||||
public record struct AreaCmtBucket(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);
|
||||
public record struct MemberStat(string Variety, string Discr, int Weight);
|
||||
@@ -42,13 +44,14 @@ namespace Elwig.Helpers {
|
||||
public DbSet<WineAttr> WineAttributes { get; private set; }
|
||||
public DbSet<WineCult> WineCultivations { get; private set; }
|
||||
public DbSet<Branch> Branches { get; private set; }
|
||||
public DbSet<AreaComType> AreaCommitmentTypes { get; private set; }
|
||||
public DbSet<AreaCmtType> AreaCommitmentTypes { get; private set; }
|
||||
public DbSet<Member> Members { get; private set; }
|
||||
public DbSet<BillingAddr> BillingAddresses { get; private set; }
|
||||
public DbSet<MemberTelNr> MemberTelephoneNrs { get; private set; }
|
||||
public DbSet<MemberEmailAddr> MemberEmailAddrs { get; private set; }
|
||||
public DbSet<MemberHistory> MemberHistory { get; private set; }
|
||||
public DbSet<AreaCom> AreaCommitments { get; private set; }
|
||||
public DbSet<AreaCmtContract> AreaCommitmentContracts { get; private set; }
|
||||
public DbSet<AreaCmt> AreaCommitments { get; private set; }
|
||||
public DbSet<Season> Seasons { get; private set; }
|
||||
public DbSet<DeliverySchedule> DeliverySchedules { get; private set; }
|
||||
public DbSet<DeliveryScheduleWineVar> DeliveryScheduleWineVarieties { get; private set; }
|
||||
@@ -65,9 +68,10 @@ namespace Elwig.Helpers {
|
||||
public DbSet<DeliveryPartBucket> DeliveryPartBuckets { get; private set; }
|
||||
|
||||
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
|
||||
public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
|
||||
public DbSet<AreaCmtUnderDeliveryRowSingle> AreaCmtUnderDeliveryRows { get; private set; }
|
||||
public DbSet<MemberDeliveryPerVarietyRowSingle> MemberDeliveryPerVariantRows { get; private set; }
|
||||
public DbSet<MemberAreaComsRowSingle> MemberAreaComsRows { get; private set; }
|
||||
public DbSet<MemberAreaCmtTotalsRowSingle> MemberAreaCmtTotalsRows { get; private set; }
|
||||
public DbSet<MemberAreaCmtsRow> MemberAreaCmtsRows { get; private set; }
|
||||
public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
|
||||
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
|
||||
public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; }
|
||||
@@ -81,7 +85,72 @@ 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 readonly Dictionary<int, Dictionary<int, Dictionary<string, AreaComBucket>>> _memberAreaCommitmentBuckets = [];
|
||||
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<AreaCmt>> _compiledQueryAreaCommitments =
|
||||
EF.CompileAsyncQuery<AppDbContext, int?, AreaCmt>((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, AreaCmtBucket>>> _memberAreaCommitmentBuckets = [];
|
||||
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = [];
|
||||
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBucketsStrict = [];
|
||||
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = [];
|
||||
@@ -94,35 +163,94 @@ namespace Elwig.Helpers {
|
||||
LogFile = new(file) {
|
||||
AutoFlush = true
|
||||
};
|
||||
} catch (Exception e) {
|
||||
MessageBox.Show($"Unable to open database log file:\n\n{e.Message}", "Database Log", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
InteractionService.ShowException("Database Log", $"Unable to open database log file", exc);
|
||||
}
|
||||
}
|
||||
SavedLastWriteTime = LastWriteTime;
|
||||
SavedChanges += OnSavedChanges;
|
||||
}
|
||||
|
||||
private static void CreateFunctions(SqliteConnection cnx) {
|
||||
cnx.CreateFunction<string, string?, bool?>("REGEXP", (pattern, value) => {
|
||||
return value == null ? null : Regex.IsMatch(value, pattern);
|
||||
}, true);
|
||||
cnx.CreateFunction<string?, string?, string?>("NORMALIZE_ADDRESS", (address, locality) => {
|
||||
return string.IsNullOrWhiteSpace(address) ? null : Normalizer.NormalizeAddress(address, locality);
|
||||
}, true);
|
||||
}
|
||||
|
||||
public static SqliteConnection Connect(string? connectionString = null) {
|
||||
var cnx = new SqliteConnection(connectionString ?? ConnectionString);
|
||||
cnx.CreateFunction<string, string?, bool?>("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true);
|
||||
CreateFunctions(cnx);
|
||||
cnx.Open();
|
||||
return cnx;
|
||||
}
|
||||
|
||||
public static async Task<SqliteConnection> ConnectAsync(string? connectionString = null) {
|
||||
var cnx = new SqliteConnection(connectionString ?? ConnectionString);
|
||||
cnx.CreateFunction<string, string?, bool?>("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true);
|
||||
CreateFunctions(cnx);
|
||||
await cnx.OpenAsync();
|
||||
return cnx;
|
||||
}
|
||||
|
||||
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<AreaCmtContract>().Navigation(c => c.Kg).AutoInclude();
|
||||
modelBuilder.Entity<AreaCmtContract>().Navigation(c => c.Rd).AutoInclude();
|
||||
modelBuilder.Entity<AreaCmt>().Navigation(c => c.Contract).AutoInclude();
|
||||
modelBuilder.Entity<AreaCmt>().Navigation(c => c.WineCult).AutoInclude();
|
||||
modelBuilder.Entity<AreaCmt>().Navigation(c => c.AreaCmtType).AutoInclude();
|
||||
modelBuilder.Entity<AreaCmtType>().Navigation(c => c.WineVar).AutoInclude();
|
||||
modelBuilder.Entity<AreaCmtType>().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<DeliverySchedule>().Navigation(s => s.Attribute).AutoInclude();
|
||||
modelBuilder.Entity<DeliverySchedule>().Navigation(s => s.Cultivation).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();
|
||||
@@ -138,23 +266,23 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
public async Task<bool> MgNrExists(int mgnr) {
|
||||
return await Members.FindAsync(mgnr) != null;
|
||||
return await _compiledQueryMembers.Invoke(this, mgnr, true).AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> FbNrExists(int fbnr) {
|
||||
return await AreaCommitments.FindAsync(fbnr) != null;
|
||||
return await _compiledQueryAreaCommitments.Invoke(this, fbnr).AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> SortIdExists(string sortId) {
|
||||
return await WineVarieties.FindAsync(sortId) != null;
|
||||
return await _compiledQueryWineVarieties.Invoke(this, sortId).AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> AttrIdExists(string attrId) {
|
||||
return await WineAttributes.FindAsync(attrId) != null;
|
||||
return await _compiledQueryWineAttributes.Invoke(this, attrId, true).AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> CultIdExists(string cultId) {
|
||||
return await WineCultivations.FindAsync(cultId) != null;
|
||||
return await _compiledQueryWineCultivations.Invoke(this, cultId).AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<int> NextMgNr() {
|
||||
@@ -166,87 +294,117 @@ namespace Elwig.Helpers {
|
||||
|
||||
public async Task<int> NextFbNr() {
|
||||
int c = 0;
|
||||
(await AreaCommitments.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync())
|
||||
(await AreaCommitmentContracts.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");
|
||||
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;
|
||||
return (await Deliveries.Where(d => d.DateString == dateStr && d.ZwstId == zwstid).Select(d => (int?)d.LNr).MaxAsync() ?? 0) + 1;
|
||||
}
|
||||
|
||||
public async Task<int> NextDId(int year) {
|
||||
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;
|
||||
return (await Deliveries.Where(d => d.Year == year).Select(d => (int?)d.DId).MaxAsync() ?? 0) + 1;
|
||||
}
|
||||
|
||||
public async Task<int> NextDPNr(int year, int did) {
|
||||
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;
|
||||
return (await DeliveryParts.Where(p => p.Year == year && p.DId == did).Select(p => (int?)p.DPNr).MaxAsync() ?? 0) + 1;
|
||||
}
|
||||
|
||||
public async Task<int> NextRdNr(int kgnr) {
|
||||
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;
|
||||
return (await WbRde.Where(r => r.KgNr == kgnr).Select(r => (int?)r.RdNr).MaxAsync() ?? 0) + 1;
|
||||
}
|
||||
|
||||
public async Task<int> NextAvNr(int year) {
|
||||
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;
|
||||
return (await PaymentVariants.Where(v => v.Year == year).Select(v => (int?)v.AvNr).MaxAsync() ?? 0) + 1;
|
||||
}
|
||||
|
||||
public async Task<int> NextDsNr(int year) {
|
||||
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;
|
||||
return (await DeliverySchedules.Where(s => s.Year == year).Select(v => (int?)v.DsNr).MaxAsync() ?? 0) + 1;
|
||||
}
|
||||
|
||||
public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> newModifiers) {
|
||||
foreach (var m in Modifiers.Where(m => m.Year == part.Year)) {
|
||||
public async Task<int> NextHistNr() {
|
||||
return (await MemberHistory.Select(c => (int?)c.HistNr).MaxAsync() ?? 0) + 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()) {
|
||||
var mod = new DeliveryPartModifier {
|
||||
Year = part.Year,
|
||||
DId = part.DId,
|
||||
DPNr = part.DPNr,
|
||||
ModId = m.ModId,
|
||||
};
|
||||
var old = oldModifiers.Where(pa => pa.ModId == m.ModId).FirstOrDefault();
|
||||
if (newModifiers.Any(md => md.ModId == m.ModId)) {
|
||||
if (old == null) {
|
||||
var old = oldModIds.Contains(m.ModId);
|
||||
if (newModIds.Contains(m.ModId)) {
|
||||
if (!old) {
|
||||
Add(mod);
|
||||
} else {
|
||||
Update(mod);
|
||||
}
|
||||
} else {
|
||||
if (old != null) {
|
||||
if (old) {
|
||||
Remove(mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDeliveryScheduleWineVarieties(DeliverySchedule schedule, IEnumerable<(WineVar, int)> oldVarieties, IEnumerable<(WineVar, int)> newVarieties) {
|
||||
foreach (var v in WineVarieties) {
|
||||
public async Task UpdateDeliveryScheduleWineVarieties(DeliverySchedule schedule, IEnumerable<(string, int)> oldVarieties, IEnumerable<(string, int)> newVarieties) {
|
||||
foreach (var v in await FetchWineVarieties().ToArrayAsync()) {
|
||||
var e = new DeliveryScheduleWineVar {
|
||||
Year = schedule.Year,
|
||||
DsNr = schedule.DsNr,
|
||||
SortId = v.SortId,
|
||||
Priority = 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);
|
||||
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);
|
||||
if (n != -1) {
|
||||
e.Priority = n;
|
||||
if (o == -1) {
|
||||
@@ -262,10 +420,26 @@ 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();
|
||||
var buckets = new Dictionary<int, Dictionary<string, AreaComBucket>>();
|
||||
var buckets = new Dictionary<int, Dictionary<string, AreaCmtBucket>>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"SELECT mgnr, bucket, area, min_kg, max_kg FROM v_area_commitment_bucket WHERE year = {year}";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
@@ -352,7 +526,51 @@ namespace Elwig.Helpers {
|
||||
_memberUnderDelivery[year] = buckets;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
|
||||
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<int, SortedDictionary<DateOnly, MemberHistoryPoint>>> GetMemberHistory() {
|
||||
var today = DateOnly.FromDateTime(DateTime.Today).AddDays(1);
|
||||
var history = new Dictionary<int, SortedDictionary<DateOnly, MemberHistoryPoint>>();
|
||||
|
||||
var members = await Members.ToListAsync();
|
||||
foreach (var m in members) {
|
||||
history[m.MgNr] = new() {
|
||||
[today] = new(m.Shares, m.SharesRed, m.SharesWhite, m.SharesDormant),
|
||||
};
|
||||
}
|
||||
|
||||
var hist = await MemberHistory.OrderByDescending(h => h.DateNoticeString).ToListAsync();
|
||||
foreach (var h in hist) {
|
||||
if (h.FromMgNr is int from) {
|
||||
var latest = history[from].Last().Value;
|
||||
switch (h.FromType) {
|
||||
case BusinessShareType.Normal: latest.Shares += h.Shares; break;
|
||||
case BusinessShareType.Red: latest.SharesRed += h.Shares; break;
|
||||
case BusinessShareType.White: latest.SharesWhite += h.Shares; break;
|
||||
case BusinessShareType.Dormant: latest.SharesDormant += h.Shares; break;
|
||||
}
|
||||
history[from][h.DateNotice] = latest;
|
||||
}
|
||||
if (h.ToMgNr is int to) {
|
||||
var latest = history[to].Last().Value;
|
||||
switch (h.ToType) {
|
||||
case BusinessShareType.Normal: latest.Shares -= h.Shares; break;
|
||||
case BusinessShareType.Red: latest.SharesRed -= h.Shares; break;
|
||||
case BusinessShareType.White: latest.SharesWhite -= h.Shares; break;
|
||||
case BusinessShareType.Dormant: latest.SharesDormant -= h.Shares; break;
|
||||
}
|
||||
history[to][h.DateNotice] = latest;
|
||||
}
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, AreaCmtBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
|
||||
if (!_memberAreaCommitmentBuckets.ContainsKey(year))
|
||||
await FetchMemberAreaCommitmentBuckets(year, cnx);
|
||||
return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, []);
|
||||
@@ -391,10 +609,12 @@ 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 = await WineVarieties.FindAsync(id[..2]);
|
||||
var attribute = await WineAttributes.FindAsync(id[2..]);
|
||||
var variety = varieties.GetValueOrDefault(id[..2]);
|
||||
var attribute = attributes.GetValueOrDefault(id[2..]);
|
||||
var name = (variety?.Name ?? "") + (id[2..] == "_" ? " (kein Qual.Wein)" : attribute != null ? $" ({attribute})" : "");
|
||||
buckets[id] = new(
|
||||
name,
|
||||
|
||||
+112
-37
@@ -1,44 +1,59 @@
|
||||
using Elwig.Services;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
public static class AppDbUpdater {
|
||||
|
||||
// Don't forget to update value in Tests/fetch-resources.bat!
|
||||
public static readonly int RequiredSchemaVersion = 37;
|
||||
public static readonly int RequiredSchemaVersion = 42;
|
||||
|
||||
private static int VersionOffset = 0;
|
||||
|
||||
public static async Task<Version> CheckDb() {
|
||||
using var cnx = AppDbContext.Connect();
|
||||
if (!File.Exists(App.Config.DatabaseFile))
|
||||
throw new FileNotFoundException($"Die Datei \"{App.Config.DatabaseFile}\" exisitiert nicht");
|
||||
|
||||
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
||||
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})");
|
||||
long? applId, schemaVers;
|
||||
using (var cnx = await AppDbContext.ConnectAsync()) {
|
||||
|
||||
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 cnx.QuickCheck();
|
||||
|
||||
applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
|
||||
if (applId != 0x454C5747)
|
||||
throw new FileFormatException($"Invalid application_id in database (0x{applId:X08})");
|
||||
|
||||
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);
|
||||
|
||||
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
|
||||
var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
|
||||
await UpdateDbSchema((int)(schemaVers / 100), RequiredSchemaVersion);
|
||||
|
||||
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}");
|
||||
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));
|
||||
|
||||
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(SqliteConnection cnx, int fromVersion, int toVersion) {
|
||||
private static async Task UpdateDbSchema(int fromVersion, int toVersion) {
|
||||
if (fromVersion == toVersion) {
|
||||
return;
|
||||
} else if (fromVersion > toVersion) {
|
||||
@@ -48,43 +63,103 @@ 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)
|
||||
.ToArray();
|
||||
.OrderBy(s => s.Item1).ThenBy(s => s.Item2)];
|
||||
|
||||
List<string> toExecute = [];
|
||||
List<(int ToVersion, string File)> toExecute = [];
|
||||
var vers = fromVersion;
|
||||
while (vers < toVersion) {
|
||||
var (_, to, name) = scripts.Where(s => s.From == vers).Last();
|
||||
toExecute.Add(name);
|
||||
var (_, to, name) = scripts.Last(s => s.From == vers);
|
||||
toExecute.Add((to, name));
|
||||
vers = to;
|
||||
}
|
||||
if (toExecute.Count == 0)
|
||||
return;
|
||||
|
||||
await cnx.ExecuteBatch("""
|
||||
PRAGMA locking_mode = EXCLUSIVE;
|
||||
BEGIN EXCLUSIVE;
|
||||
""");
|
||||
foreach (var script in toExecute) {
|
||||
await cnx.ExecuteEmbeddedScript(asm, script);
|
||||
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.IntegrityCheck();
|
||||
await cnx.ForeignKeyCheck();
|
||||
|
||||
foreach (var (to, script) in toExecute) {
|
||||
await cnx.ExecuteEmbeddedScript(asm, script);
|
||||
if (to == 42) {
|
||||
await UpdateDbSchema_41_To_42(cnx);
|
||||
}
|
||||
}
|
||||
|
||||
await cnx.IntegrityCheck();
|
||||
await cnx.ForeignKeyCheck();
|
||||
|
||||
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);
|
||||
}
|
||||
var violations = await cnx.ForeignKeyCheck();
|
||||
if (violations.Length > 0) {
|
||||
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
|
||||
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
|
||||
}
|
||||
|
||||
private static async Task UpdateDbSchema_41_To_42(SqliteConnection cnx) {
|
||||
List<(int MgNr, string LfbisNr, string Name, string Address, int Plz)> members = [];
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = """
|
||||
SELECT m.mgnr, m.lfbis_nr,
|
||||
COALESCE(a.name, (COALESCE(m.prefix || ' ', '') || COALESCE(m.given_name || ' ', '') || m.name || COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, ''))),
|
||||
COALESCE(a.address, m.address),
|
||||
COALESCE(aplz.plz, mplz.plz)
|
||||
FROM member m
|
||||
JOIN postal_dest mp ON (mp.country, mp.id) = (m.country, m.postal_dest)
|
||||
JOIN AT_plz_dest ma ON (ma.country, ma.id) = (mp.country, mp.id)
|
||||
JOIN AT_plz mplz ON mplz.plz = ma.plz
|
||||
LEFT JOIN member_billing_address a ON a.mgnr = m.mgnr
|
||||
LEFT JOIN postal_dest ap ON (ap.country, ap.id) = (a.country, a.postal_dest)
|
||||
LEFT JOIN AT_plz_dest aa ON (aa.country, aa.id) = (ap.country, mp.id)
|
||||
LEFT JOIN AT_plz aplz ON aplz.plz = aa.plz
|
||||
WHERE organic
|
||||
""";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var header = await reader.GetColumnSchemaAsync();
|
||||
while (await reader.ReadAsync()) {
|
||||
members.Add((reader.GetInt32(0), reader.GetString(1), reader.GetString(2), reader.GetString(3), reader.GetInt32(4)));
|
||||
}
|
||||
}
|
||||
|
||||
await cnx.ExecuteBatch($"""
|
||||
COMMIT;
|
||||
VACUUM;
|
||||
PRAGMA schema_version = {toVersion * 100 + VersionOffset};
|
||||
""");
|
||||
if (members.Count > 0)
|
||||
InteractionService.ShowInformation("Datenbank-Update", "Es wird versucht, die Bio-Kontrollstellen-Informationen der Mitglieder automatisch abzurufen. Dies könnte einen kurzen Moment dauern.");
|
||||
|
||||
try {
|
||||
Mouse.OverrideCursor = Cursors.Wait;
|
||||
foreach (var m in members) {
|
||||
string? oocId = null;
|
||||
string? authCode = null;
|
||||
|
||||
var certs = await OrganicService.FetchTracesCertificates(lfbisNr: m.LfbisNr, address: (m.Name, m.Address, m.Plz.ToString()));
|
||||
|
||||
if (certs.Length > 0) {
|
||||
oocId = certs[0].OperatorId;
|
||||
authCode = certs[0].AuthorityCode;
|
||||
}
|
||||
|
||||
if (oocId != null || authCode != null) {
|
||||
oocId = oocId == null ? "NULL" : $"'{oocId}'";
|
||||
authCode = authCode == null ? "NULL" : $"'{authCode}'";
|
||||
await cnx.ExecuteBatch($"UPDATE member SET ooc_id = {oocId}, org_auth_code = {authCode} WHERE mgnr = {m.MgNr}");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Elwig.Models.Entities;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -14,15 +13,32 @@ namespace Elwig.Helpers.Billing {
|
||||
protected readonly Season Season;
|
||||
protected readonly Dictionary<string, string> Attributes;
|
||||
protected readonly Dictionary<string, (decimal?, decimal?)> Modifiers;
|
||||
protected readonly Dictionary<string, (string, string?, string?, int?, decimal?)> AreaComTypes;
|
||||
protected readonly Dictionary<string, (string, string?, string?, int?, decimal?)> AreaCmtTypes;
|
||||
|
||||
public Billing(int year) {
|
||||
protected Billing(int year, Season season,
|
||||
Dictionary<string, string> attributes,
|
||||
Dictionary<string, (decimal?, decimal?)> modifiers,
|
||||
Dictionary<string, (string, string?, string?, int?, decimal?)> areaCmtTypes
|
||||
) {
|
||||
Year = year;
|
||||
Season = season;
|
||||
Attributes = attributes;
|
||||
Modifiers = modifiers;
|
||||
AreaCmtTypes = areaCmtTypes;
|
||||
}
|
||||
|
||||
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 areaCmtTypes = ctx.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount));
|
||||
return (season, attributes, modifiers, areaCmtTypes);
|
||||
}
|
||||
|
||||
public static async Task<Billing> Create(int year) {
|
||||
using var ctx = new AppDbContext();
|
||||
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));
|
||||
var (season, attributes, modifiers, areaCmtTypes) = await LoadData(ctx, year);
|
||||
return new Billing(year, season, attributes, modifiers, areaCmtTypes);
|
||||
}
|
||||
|
||||
public async Task FinishSeason() {
|
||||
@@ -34,56 +50,40 @@ namespace Elwig.Helpers.Billing {
|
||||
""");
|
||||
}
|
||||
|
||||
public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceBs = 0, int allowanceKgPerBs = 0, double allowanceRel = 0, int addMinBs = 1) {
|
||||
if (addMinBs < 1) addMinBs = 1;
|
||||
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;
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await cnx.ExecuteBatch($"""
|
||||
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
|
||||
DELETE FROM member_history WHERE source = 'elwig' AND reason = '{MemberHistory.REASON_PRESCRIPTION}' AND deduct_year = {Year};
|
||||
INSERT INTO member_history (histnr, from_mgnr, from_type, to_mgnr, to_type, date_notice, date_effective, reason, source, deduct_year, shares, value_per_share, currency, comment)
|
||||
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}', '{date:yyyy-MM-dd}', '{MemberHistory.REASON_PRESCRIPTION}', 'elwig', s.year,
|
||||
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, 'durch automatische Nachzeichnung'
|
||||
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 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;
|
||||
WHERE s.year = {Year} AND adjust_shares >= {addMinShares} AND m.active
|
||||
""");
|
||||
}
|
||||
|
||||
public async Task UnAdjustBusinessShares() {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await cnx.ExecuteBatch($"""
|
||||
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';
|
||||
DELETE FROM member_history WHERE source = 'elwig' AND reason = '{MemberHistory.REASON_PRESCRIPTION}' AND deduct_year = {Year};
|
||||
""");
|
||||
}
|
||||
|
||||
public async Task CalculateBuckets(
|
||||
bool? honorGebundenField = null,
|
||||
bool? honorContractedField = null,
|
||||
bool? allowAttributesIntoLower = null,
|
||||
bool? avoidUnderDeliveries = null,
|
||||
SqliteConnection? cnx = null
|
||||
) {
|
||||
using var ctx = new AppDbContext();
|
||||
var honorGebunden = honorGebundenField ?? Season.Billing_HonorGebunden;
|
||||
var honorContracted = honorContractedField ?? Season.Billing_HonorContracted;
|
||||
var allowAttrsIntoLower = allowAttributesIntoLower ?? Season.Billing_AllowAttrsIntoLower;
|
||||
var avoidUnderDlvrs = avoidUnderDeliveries ?? Season.Billing_AvoidUnderDeliveries;
|
||||
var attrVals = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower));
|
||||
@@ -96,10 +96,10 @@ namespace Elwig.Helpers.Billing {
|
||||
var deliveries = new List<(int, int, int, string, int, double, string, string?, string[], bool?)>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"""
|
||||
SELECT mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden
|
||||
SELECT mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, contracted
|
||||
FROM v_delivery
|
||||
WHERE year = {Year}
|
||||
ORDER BY mgnr, sortid, abgewertet ASC, {(honorGebunden ? "gebunden IS NOT NULL DESC, gebunden DESC," : "")}
|
||||
ORDER BY mgnr, sortid, abgewertet ASC, {(honorContracted ? "contracted IS NOT NULL DESC, contracted DESC," : "")}
|
||||
attribute_prio DESC, COALESCE(attrid, '~'), kmw DESC, lsnr, dpnr
|
||||
""";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
@@ -115,14 +115,14 @@ namespace Elwig.Helpers.Billing {
|
||||
}
|
||||
|
||||
int lastMgNr = 0;
|
||||
Dictionary<string, AreaComBucket>? rightsAndObligations = null;
|
||||
Dictionary<string, AreaCmtBucket>? rightsAndObligations = null;
|
||||
Dictionary<string, int> used = [];
|
||||
foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden) in deliveries) {
|
||||
foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, contracted) in deliveries) {
|
||||
if (lastMgNr != mgnr) {
|
||||
rightsAndObligations = await ctx.GetMemberAreaCommitmentBuckets(Year, mgnr);
|
||||
used = [];
|
||||
}
|
||||
if ((honorGebunden && gebunden == false) ||
|
||||
if ((honorContracted && contracted == false) ||
|
||||
rightsAndObligations == null || rightsAndObligations.Count == 0 ||
|
||||
qualid == "WEI" || qualid == "RSW" || qualid == "LDW")
|
||||
{
|
||||
@@ -140,7 +140,7 @@ namespace Elwig.Helpers.Billing {
|
||||
foreach (var p in Utils.Permutate(attributes, attributes.Intersect(attrForced))) {
|
||||
var c = p.Count();
|
||||
var key = sortid + string.Join("", p);
|
||||
if (rightsAndObligations.TryGetValue(key, out AreaComBucket value)) {
|
||||
if (rightsAndObligations.TryGetValue(key, out AreaCmtBucket value)) {
|
||||
int i = (c == 0) ? 1 : 2;
|
||||
var u = used.GetValueOrDefault(key, 0);
|
||||
var vr = Math.Max(0, Math.Min(value.Right - u, w));
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
|
||||
@@ -13,7 +14,7 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
public enum CalculationMode { Elwig, WgMaster }
|
||||
public enum CurveMode { Oe, Kmw }
|
||||
public record struct Curve(CurveMode Mode, Dictionary<double, decimal> Normal, Dictionary<double, decimal>? Gebunden);
|
||||
public record struct Curve(CurveMode Mode, Dictionary<double, decimal> Normal, Dictionary<double, decimal>? Contracted);
|
||||
|
||||
public static JsonSchema? Schema { get; private set; }
|
||||
|
||||
@@ -92,6 +93,14 @@ 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 {
|
||||
@@ -107,6 +116,10 @@ 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();
|
||||
}
|
||||
@@ -166,10 +179,10 @@ namespace Elwig.Helpers.Billing {
|
||||
} else {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
var geb = obj["geb"];
|
||||
if (geb is JsonObject) {
|
||||
c2 = GetCurveData(geb.AsObject(), cMode);
|
||||
} else if (geb?.AsValue().TryGetValue(out decimal v) == true) {
|
||||
var ctrd = obj["ctrd"] ?? obj["geb"];
|
||||
if (ctrd is JsonObject) {
|
||||
c2 = GetCurveData(ctrd.AsObject(), cMode);
|
||||
} else if (ctrd?.AsValue().TryGetValue(out decimal v) == true) {
|
||||
var splitVal = GetCurveValueAt(c1, quw);
|
||||
c2 = c1.ToDictionary(e => e.Key, e => e.Value + (e.Key >= quw ? v : 0));
|
||||
c2[quw] = splitVal + v;
|
||||
@@ -261,7 +274,7 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
protected static JsonNode GraphEntryToJson(GraphEntry entry) {
|
||||
try {
|
||||
if (entry.GebundenFlatBonus == null) {
|
||||
if (entry.ContractedFlatBonus == null) {
|
||||
return JsonValue.Create((decimal)entry.DataGraph.DataY.Distinct().Single());
|
||||
}
|
||||
} catch { }
|
||||
@@ -273,10 +286,10 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower());
|
||||
|
||||
if (entry.GebundenFlatBonus != null) {
|
||||
curve["geb"] = (decimal)entry.GebundenFlatBonus;
|
||||
} else if (entry.GebundenGraph != null) {
|
||||
curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower());
|
||||
if (entry.ContractedFlatBonus != null) {
|
||||
curve["ctrd"] = (decimal)entry.ContractedFlatBonus;
|
||||
} else if (entry.ContractedGraph != null) {
|
||||
curve["ctrd"] = GraphToJson(entry.ContractedGraph, entry.Mode.ToString().ToLower());
|
||||
}
|
||||
|
||||
return curve;
|
||||
@@ -412,7 +425,7 @@ namespace Elwig.Helpers.Billing {
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonObject FromGraphEntries(
|
||||
public static BillingData FromGraphEntries(
|
||||
IEnumerable<GraphEntry> graphEntries,
|
||||
BillingData? origData = null,
|
||||
IEnumerable<RawVaribute>? vaributes = null,
|
||||
@@ -446,40 +459,29 @@ namespace Elwig.Helpers.Billing {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
BillingData data = origData != null && origData.Mode == CalculationMode.Elwig ? new BillingData((JsonObject)origData.Data.DeepClone()) : new BillingData();
|
||||
if (payment.Count == 0) {
|
||||
data["payment"] = 0;
|
||||
data.Data["payment"] = 0;
|
||||
} else if (payment.Count == 1 && payment.First().Key == "default") {
|
||||
data["payment"] = payment.Single().Value?.DeepClone();
|
||||
data.Data["payment"] = payment.Single().Value?.DeepClone();
|
||||
} else {
|
||||
data["payment"] = payment;
|
||||
data.Data["payment"] = payment;
|
||||
}
|
||||
if (qualityWei.Count == 1 && qualityWei.First().Key == "default") {
|
||||
data["quality"] = new JsonObject() {
|
||||
data.Data["quality"] = new JsonObject() {
|
||||
["WEI"] = qualityWei.Single().Value?.DeepClone()
|
||||
};
|
||||
} else if (qualityWei.Count >= 1) {
|
||||
data["quality"] = new JsonObject() {
|
||||
data.Data["quality"] = new JsonObject() {
|
||||
["WEI"] = qualityWei
|
||||
};
|
||||
} else {
|
||||
data.Data.Remove("quality");
|
||||
}
|
||||
data["curves"] = curves;
|
||||
data.Data["curves"] = curves;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -10,20 +10,38 @@ namespace Elwig.Helpers.Billing {
|
||||
public class BillingVariant : Billing {
|
||||
|
||||
protected readonly int AvNr;
|
||||
protected readonly PaymentVar PaymentVariant;
|
||||
protected readonly PaymentBillingData Data;
|
||||
protected PaymentVar PaymentVariant;
|
||||
protected PaymentBillingData Data;
|
||||
|
||||
public BillingVariant(int year, int avnr) : base(year) {
|
||||
protected BillingVariant(int year, int avnr, Season season,
|
||||
Dictionary<string, string> attributes,
|
||||
Dictionary<string, (decimal?, decimal?)> modifiers,
|
||||
Dictionary<string, (string, string?, string?, int?, decimal?)> areaCmtTypes,
|
||||
PaymentVar paymentVar, PaymentBillingData data) :
|
||||
base(year, season, attributes, modifiers, areaCmtTypes) {
|
||||
AvNr = avnr;
|
||||
using var ctx = new AppDbContext();
|
||||
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));
|
||||
PaymentVariant = paymentVar;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public async Task Calculate(bool strictPrices = true, bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
|
||||
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, areaCmtTypes) = await LoadData(ctx, year);
|
||||
var (paymentVar, data) = await LoadData(ctx, year, avnr);
|
||||
return new BillingVariant(year, avnr, season, attributes, modifiers, areaCmtTypes, paymentVar, data);
|
||||
}
|
||||
|
||||
public async Task Calculate(bool strictPrices = true, bool? honorContracted = 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);
|
||||
await CalculateBuckets(honorContracted, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
|
||||
await DeleteInDb(cnx);
|
||||
await SetCalcTime(cnx);
|
||||
KeyNotFoundException? exception = null;
|
||||
@@ -55,10 +73,10 @@ namespace Elwig.Helpers.Billing {
|
||||
v.avnr,
|
||||
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
|
||||
IIF(lc.amount < 0, 0, ROUND(lp.amount / POW(10, s.precision - 2))) AS prev_net_amount,
|
||||
IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
|
||||
IIF(m.accounting, s.vat_normal, s.vat_flatrate) AS vat,
|
||||
ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
|
||||
ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
|
||||
ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) +
|
||||
ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0)) +
|
||||
IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
|
||||
AS modifiers,
|
||||
IIF(lc.amount < 0, 0, lc.modifiers) AS prev_modifiers
|
||||
@@ -80,7 +98,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} AND p.amount != COALESCE(lp.amount, 0);
|
||||
""");
|
||||
await cnx.ExecuteBatch($"""
|
||||
UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
|
||||
@@ -119,11 +137,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.sum * COALESCE(m.abs, 0)),
|
||||
ROUND(s.weight_total * COALESCE(m.abs, 0)),
|
||||
COALESCE(m.rel, 0)
|
||||
FROM (SELECT {Year} AS year, m.mgnr,
|
||||
ROUND(AVG(COALESCE(a.sum, b.sum)) * {multiplier}) AS baseline,
|
||||
COUNT(*) = {lastYears} AND MIN(COALESCE(a.sum, b.sum)) > 0 AS allowed
|
||||
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
|
||||
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")}
|
||||
@@ -132,7 +150,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 sum >= baseline
|
||||
WHERE weight_total >= baseline
|
||||
ON CONFLICT DO UPDATE
|
||||
SET mod_abs = mod_abs + excluded.mod_abs,
|
||||
mod_rel = mod_rel + excluded.mod_rel
|
||||
@@ -152,7 +170,7 @@ namespace Elwig.Helpers.Billing {
|
||||
|
||||
protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) {
|
||||
var invalid = new HashSet<string>();
|
||||
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>();
|
||||
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCmt)>();
|
||||
using (var cmd = cnx.CreateCommand()) {
|
||||
cmd.CommandText = $"""
|
||||
SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, d.attrid, d.cultid, b.discr, b.value, d.oe, d.kmw, d.qualid, COALESCE(a.area_com, TRUE)
|
||||
@@ -177,13 +195,13 @@ namespace Elwig.Helpers.Billing {
|
||||
foreach (var part in parts) {
|
||||
if (part.Value == 0)
|
||||
continue;
|
||||
var ungeb = part.Discr == "_";
|
||||
var unctrd = part.Discr == "_";
|
||||
var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
|
||||
var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
|
||||
var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
|
||||
var attrId = part.AttrAreaCmt ? payAttrId : part.AttrId;
|
||||
var ctrd = !unctrd && (payAttrId == attrId || !part.AttrAreaCmt);
|
||||
decimal price = 0;
|
||||
try {
|
||||
price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
|
||||
price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, ctrd, part.Oe, part.Kmw);
|
||||
} catch (KeyNotFoundException e) {
|
||||
invalid.Add(e.Message.Split('\'')[1]);
|
||||
}
|
||||
@@ -214,7 +232,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), COALESCE(m.rel, 0)
|
||||
SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0) * d.weight, 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)
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 {
|
||||
@@ -70,14 +71,14 @@ namespace Elwig.Helpers.Billing {
|
||||
return (curves, dict3);
|
||||
}
|
||||
|
||||
private static List<GraphEntry> CreateGraphEntries(
|
||||
private static async Task<List<GraphEntry>> CreateGraphEntries(
|
||||
AppDbContext ctx, int precision,
|
||||
Dictionary<int, Curve> curves,
|
||||
Dictionary<int, List<RawVaribute>> entries
|
||||
) {
|
||||
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);
|
||||
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);
|
||||
return entries
|
||||
.Select(e => new GraphEntry(e.Key, precision, curves[e.Key], e.Value
|
||||
.Select(s => new Varibute(s, vars, attrs, cults))
|
||||
@@ -85,18 +86,18 @@ namespace Elwig.Helpers.Billing {
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<GraphEntry> GetPaymentGraphEntries(AppDbContext ctx, Season season) {
|
||||
public async Task<IEnumerable<GraphEntry>> GetPaymentGraphEntries(AppDbContext ctx, Season season) {
|
||||
var root = GetPaymentEntry();
|
||||
var (curves, entries) = GetGraphEntries(root);
|
||||
return CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
|
||||
return (await CreateGraphEntries(ctx, season.Precision, curves, entries)).Where(e => e.Vaributes.Count > 0);
|
||||
}
|
||||
|
||||
public IEnumerable<GraphEntry> GetQualityGraphEntries(AppDbContext ctx, Season season, int idOffset = 0) {
|
||||
public async Task<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 = CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
|
||||
var list = (await CreateGraphEntries(ctx, season.Precision, curves, entries)).Where(e => e.Vaributes.Count > 0);
|
||||
foreach (var e in list) {
|
||||
e.Id += idOffset;
|
||||
e.Abgewertet = true;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Elwig.Helpers.Billing {
|
||||
public class GraphEntry {
|
||||
|
||||
public const int MinX = 50;
|
||||
public const int MinXGeb = 73;
|
||||
public const int MinXCtrd = 73;
|
||||
public const int MaxX = 120;
|
||||
|
||||
public int Id { get; set; }
|
||||
@@ -15,11 +15,11 @@ namespace Elwig.Helpers.Billing {
|
||||
public bool Abgewertet { get; set; }
|
||||
|
||||
public Graph DataGraph { get; set; }
|
||||
public Graph? GebundenGraph { get; set; }
|
||||
public double? GebundenFlatBonus {
|
||||
public Graph? ContractedGraph { get; set; }
|
||||
public double? ContractedFlatBonus {
|
||||
get {
|
||||
try {
|
||||
var val = GebundenGraph?.DataX.Zip(GebundenGraph.DataY)
|
||||
var val = ContractedGraph?.DataX.Zip(ContractedGraph.DataY)
|
||||
.Select(e => Math.Round(e.Second - DataGraph.GetPriceAtOe(e.First), Precision))
|
||||
.Distinct()
|
||||
.Single();
|
||||
@@ -30,10 +30,10 @@ namespace Elwig.Helpers.Billing {
|
||||
}
|
||||
set {
|
||||
if (value is not double v) return;
|
||||
var values = Enumerable.Range(MinXGeb, MaxX - MinXGeb + 1)
|
||||
var values = Enumerable.Range(MinXCtrd, MaxX - MinXCtrd + 1)
|
||||
.Select(i => Math.Round(DataGraph.GetPriceAtOe(i) + v, Precision))
|
||||
.ToArray();
|
||||
GebundenGraph = new Graph(values, Precision, MinXGeb, MaxX);
|
||||
ContractedGraph = new Graph(values, Precision, MinXCtrd, MaxX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,39 +52,39 @@ namespace Elwig.Helpers.Billing {
|
||||
Vaributes = [];
|
||||
}
|
||||
|
||||
public GraphEntry(int id, int precision, BillingData.CurveMode mode, Dictionary<double, decimal> data, Dictionary<double, decimal>? gebunden) :
|
||||
public GraphEntry(int id, int precision, BillingData.CurveMode mode, Dictionary<double, decimal> data, Dictionary<double, decimal>? contracted) :
|
||||
this(id, precision, mode) {
|
||||
DataGraph = new Graph(data, precision, MinX, MaxX);
|
||||
if (gebunden != null) GebundenGraph = new Graph(gebunden, precision, MinXGeb, MaxX);
|
||||
if (contracted != null) ContractedGraph = new Graph(contracted, precision, MinXCtrd, MaxX);
|
||||
}
|
||||
|
||||
public GraphEntry(int id, int precision, BillingData.Curve curve, List<Varibute> vaributes) :
|
||||
this(id, precision, curve.Mode) {
|
||||
DataGraph = new Graph(curve.Normal, precision, MinX, MaxX);
|
||||
if (curve.Gebunden != null)
|
||||
GebundenGraph = new Graph(curve.Gebunden, precision, MinXGeb, MaxX);
|
||||
if (curve.Contracted != null)
|
||||
ContractedGraph = new Graph(curve.Contracted, precision, MinXCtrd, MaxX);
|
||||
Vaributes = vaributes;
|
||||
}
|
||||
|
||||
private GraphEntry(int id, int precision, BillingData.CurveMode mode, Graph dataGraph, Graph? gebundenGraph, List<Varibute> vaributes) {
|
||||
private GraphEntry(int id, int precision, BillingData.CurveMode mode, Graph dataGraph, Graph? contractedGraph, List<Varibute> vaributes) {
|
||||
Id = id;
|
||||
Precision = precision;
|
||||
Mode = mode;
|
||||
DataGraph = dataGraph;
|
||||
GebundenGraph = gebundenGraph;
|
||||
ContractedGraph = contractedGraph;
|
||||
Vaributes = vaributes;
|
||||
}
|
||||
|
||||
public void AddGebundenGraph() {
|
||||
GebundenGraph ??= new Graph(Precision, MinXGeb, MaxX);
|
||||
public void AddContractedGraph() {
|
||||
ContractedGraph ??= new Graph(Precision, MinXCtrd, MaxX);
|
||||
}
|
||||
|
||||
public void RemoveGebundenGraph() {
|
||||
GebundenGraph = null;
|
||||
public void RemoveContractedGraph() {
|
||||
ContractedGraph = null;
|
||||
}
|
||||
|
||||
public GraphEntry Copy(int id) {
|
||||
return new GraphEntry(id, Precision, Mode, (Graph)DataGraph.Clone(), (Graph?)GebundenGraph?.Clone(), []);
|
||||
return new GraphEntry(id, Precision, Mode, (Graph)DataGraph.Clone(), (Graph?)ContractedGraph?.Clone(), []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace Elwig.Helpers.Billing {
|
||||
return dict;
|
||||
}
|
||||
|
||||
public decimal CalculatePrice(string sortid, string? attrid, string? cultid, string qualid, bool gebunden, double oe, double kmw) {
|
||||
public decimal CalculatePrice(string sortid, string? attrid, string? cultid, string qualid, bool contracted, double oe, double kmw) {
|
||||
var curve = GetQualityCurve(qualid, sortid, attrid, cultid) ?? GetCurve(sortid, attrid, cultid);
|
||||
return GetCurveValueAt((gebunden ? curve.Gebunden : null) ?? curve.Normal, curve.Mode == CurveMode.Oe ? oe : kmw);
|
||||
return GetCurveValueAt((contracted ? curve.Contracted : null) ?? curve.Normal, curve.Mode == CurveMode.Oe ? oe : kmw);
|
||||
}
|
||||
|
||||
private Curve LookupCurve(JsonValue val) {
|
||||
|
||||
@@ -8,16 +8,18 @@ using System.Threading.Tasks;
|
||||
namespace Elwig.Helpers {
|
||||
public class ClientParameters {
|
||||
|
||||
public enum Type { Matzen, Winzerkeller, Weinland, Baden };
|
||||
public enum Type { Matzen, Winzerkeller, Weinland, Baden, Seewinkel };
|
||||
|
||||
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;
|
||||
@@ -47,13 +49,19 @@ namespace Elwig.Helpers {
|
||||
public string? Bic;
|
||||
public string? UstIdNr;
|
||||
public string? LfbisNr;
|
||||
public string? OrganicAuthority;
|
||||
public string? OrganicOperatorId;
|
||||
public string? OrganicAuthorityCode;
|
||||
|
||||
public string? PhoneNr;
|
||||
public string? FaxNr;
|
||||
public string? EmailAddress;
|
||||
public string? Website;
|
||||
|
||||
public bool EnableMemberHistory;
|
||||
public int ModeBusinessShares;
|
||||
public bool HasRedWhite => ModeBusinessShares == 1;
|
||||
|
||||
public int ModeMemberDataSheetShares;
|
||||
public int ModeDeliveryNoteStats;
|
||||
public int ModeWineQualityStatistics;
|
||||
public int OrderingMemberList;
|
||||
@@ -73,7 +81,7 @@ namespace Elwig.Helpers {
|
||||
public int ExportEbicsVersion;
|
||||
public int ExportEbicsAddress;
|
||||
|
||||
public (int? AllowanceKg, double? AllowanceBs, int? AllowanceKgPerBs, double? AllowancePercent, int? MinBs) AutoAdjustBs;
|
||||
public (int? AllowanceKg, double? AllowanceShares, int? AllowanceKgPerShare, double? AllowancePercent, int? MinShares) AutoAdjustShares;
|
||||
|
||||
public ClientParameters(AppDbContext ctx) : this(ctx.ClientParameters.ToDictionary(e => e.Param, e => e.Value)) { }
|
||||
|
||||
@@ -93,6 +101,8 @@ 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"] ?? "");
|
||||
@@ -106,8 +116,22 @@ namespace Elwig.Helpers {
|
||||
UstIdNr = parameters.GetValueOrDefault("CLIENT_USTIDNR");
|
||||
Bic = parameters.GetValueOrDefault("CLIENT_BIC");
|
||||
Iban = parameters.GetValueOrDefault("CLIENT_IBAN");
|
||||
OrganicAuthority = parameters.GetValueOrDefault("CLIENT_ORGANIC_AUTHORITY");
|
||||
OrganicOperatorId = parameters.GetValueOrDefault("CLIENT_ORGANIC_OPERATORID");
|
||||
OrganicAuthorityCode = parameters.GetValueOrDefault("CLIENT_ORGANIC_AUTHORITYCODE");
|
||||
|
||||
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_MEMBERDATASHEET_SHARES", "FULL")?.ToUpper()) {
|
||||
case "NONE": ModeMemberDataSheetShares = 0; break;
|
||||
case "FULL": ModeMemberDataSheetShares = 1; break;
|
||||
}
|
||||
switch (parameters.GetValueOrDefault("MODE_DELIVERYNOTE_STATS", "SHORT")?.ToUpper()) {
|
||||
case "NONE": ModeDeliveryNoteStats = 0; break;
|
||||
case "GA_ONLY": ModeDeliveryNoteStats = 1; break;
|
||||
@@ -172,7 +196,7 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
var autoAdjust = (parameters.GetValueOrDefault("AUTOADJUST_BUSINESSSHARES") ?? "").Split(';');
|
||||
AutoAdjustBs = autoAdjust.Length == 5 ? (
|
||||
AutoAdjustShares = 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,
|
||||
@@ -185,6 +209,16 @@ 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 memberDataSheetShares = "FULL";
|
||||
switch (ModeMemberDataSheetShares) {
|
||||
case 0: memberDataSheetShares = "NONE"; break;
|
||||
case 1: memberDataSheetShares = "FULL"; break;
|
||||
}
|
||||
string deliveryNoteStats = "SHORT";
|
||||
switch (ModeDeliveryNoteStats) {
|
||||
case 0: deliveryNoteStats = "NONE"; break;
|
||||
@@ -231,9 +265,9 @@ namespace Elwig.Helpers {
|
||||
case 1: exportEbicsAddress = "LINES"; break;
|
||||
case 2: exportEbicsAddress = "FULL"; break;
|
||||
}
|
||||
string autoAdjust = $"{AutoAdjustBs.AllowanceKg};{AutoAdjustBs.AllowanceBs?.ToString(CultureInfo.InvariantCulture)};" +
|
||||
$"{AutoAdjustBs.AllowanceKgPerBs};{AutoAdjustBs.AllowancePercent?.ToString(CultureInfo.InvariantCulture)};" +
|
||||
$"{AutoAdjustBs.MinBs}";
|
||||
string autoAdjust = $"{AutoAdjustShares.AllowanceKg};{AutoAdjustShares.AllowanceShares?.ToString(CultureInfo.InvariantCulture)};" +
|
||||
$"{AutoAdjustShares.AllowanceKgPerShare};{AutoAdjustShares.AllowancePercent?.ToString(CultureInfo.InvariantCulture)};" +
|
||||
$"{AutoAdjustShares.MinShares}";
|
||||
return [
|
||||
("CLIENT_NAME_TOKEN", NameToken),
|
||||
("CLIENT_NAME_SHORT", NameShort),
|
||||
@@ -251,7 +285,11 @@ namespace Elwig.Helpers {
|
||||
("CLIENT_USTIDNR", UstIdNr),
|
||||
("CLIENT_BIC", Bic),
|
||||
("CLIENT_IBAN", Iban),
|
||||
("CLIENT_ORGANIC_AUTHORITY", OrganicAuthority),
|
||||
("CLIENT_ORGANIC_OPERATORID", OrganicOperatorId),
|
||||
("CLIENT_ORGANIC_AUTHORITYCODE", OrganicAuthorityCode),
|
||||
("ENABLE_MEMBERHISTORY", EnableMemberHistory ? "YES" : "NO"),
|
||||
("MODE_BUSINESSSHARES", businessShares),
|
||||
("MODE_MEMBERDATASHEET_SHARES", memberDataSheetShares),
|
||||
("MODE_DELIVERYNOTE_STATS", deliveryNoteStats),
|
||||
("MODE_WINEQUALITYSTATISTICS", modeWineQualityStatistics),
|
||||
("ORDERING_MEMBERLIST", orderingMemberList),
|
||||
|
||||
+30
-4
@@ -17,10 +17,11 @@ namespace Elwig.Helpers {
|
||||
public string? Filling;
|
||||
public string? Limit;
|
||||
public bool Required;
|
||||
public bool SyncTime;
|
||||
public string? Log;
|
||||
public string? _Log;
|
||||
|
||||
public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, bool? required, string? log) {
|
||||
public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, bool? required, bool? syncTime, string? log) {
|
||||
Id = id;
|
||||
Type = type;
|
||||
Model = model;
|
||||
@@ -29,6 +30,7 @@ namespace Elwig.Helpers {
|
||||
Filling = filling;
|
||||
Limit = limit;
|
||||
Required = required ?? true;
|
||||
SyncTime = syncTime ?? true;
|
||||
_Log = log;
|
||||
Log = log != null ? Path.Combine(App.DataPath, log) : null;
|
||||
}
|
||||
@@ -37,19 +39,30 @@ namespace Elwig.Helpers {
|
||||
public class Config {
|
||||
|
||||
private static readonly string[] TrueValues = ["1", "true", "yes", "on"];
|
||||
private static readonly string[] FalseValues = ["0", "false", "no", "off"];
|
||||
|
||||
private readonly string FileName;
|
||||
|
||||
public bool Debug;
|
||||
public string DatabaseFile = App.DataPath + "database.sqlite3";
|
||||
public string DatabaseFile = Path.Combine(App.DataPath, "database.sqlite3");
|
||||
public string? DatabaseLog = null;
|
||||
public string? Branch = null;
|
||||
public WeighingMode? WeighingMode;
|
||||
|
||||
public bool BackupAuto = true;
|
||||
public string BackupPath = Path.Combine(App.DataPath, "backups");
|
||||
public int BackupRetainHours = 36;
|
||||
public int BackupRetainDays = 8;
|
||||
public int BackupRetainWeeks = 6;
|
||||
public int BackupRetainMonths = 18;
|
||||
|
||||
public string? UpdateUrl = null;
|
||||
public bool UpdateAuto = false;
|
||||
|
||||
public string? SyncUrl = null;
|
||||
public string SyncUsername = "";
|
||||
public string SyncPassword = "";
|
||||
public bool SyncMain = false;
|
||||
|
||||
public string? SmtpHost = null;
|
||||
public int? SmtpPort = null;
|
||||
@@ -57,9 +70,10 @@ namespace Elwig.Helpers {
|
||||
public string? SmtpUsername = null;
|
||||
public string? SmtpPassword = null;
|
||||
public string? SmtpFrom = null;
|
||||
public (string Host, int Port, string Mode, string Username, string Password, string From)? Smtp =>
|
||||
public double SmtpWait = 0;
|
||||
public (string Host, int Port, string Mode, string Username, string Password, string From, double Wait)? Smtp =>
|
||||
SmtpHost == null || SmtpPort == null || SmtpMode == null || SmtpUsername == null || SmtpPassword == null || SmtpFrom == null ?
|
||||
null : (SmtpHost, (int)SmtpPort, SmtpMode, SmtpUsername, SmtpPassword, SmtpFrom);
|
||||
null : (SmtpHost, (int)SmtpPort, SmtpMode, SmtpUsername, SmtpPassword, SmtpFrom, SmtpWait);
|
||||
|
||||
public IList<ScaleConfig> Scales;
|
||||
private readonly List<ScaleConfig> ScaleList = [];
|
||||
@@ -80,11 +94,21 @@ namespace Elwig.Helpers {
|
||||
Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
|
||||
var weighing = config["general:weighing"];
|
||||
WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null;
|
||||
|
||||
BackupAuto = !FalseValues.Contains(config["backup:auto"]?.ToLower());
|
||||
BackupPath = Path.Combine(Path.GetDirectoryName(DatabaseFile) ?? App.DataPath, config["backup:path"] ?? "backups");
|
||||
BackupRetainHours = int.TryParse(config["backup:retain_hours"], out var hours) ? hours : 36;
|
||||
BackupRetainDays = int.TryParse(config["backup:retain_days"], out var days) ? days : 8;
|
||||
BackupRetainWeeks = int.TryParse(config["backup:retain_weeks"], out var weeks) ? weeks : 6;
|
||||
BackupRetainMonths = int.TryParse(config["backup:retain_months"], out var months) ? months : 18;
|
||||
|
||||
UpdateUrl = config["update:url"];
|
||||
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
|
||||
|
||||
SyncUrl = config["sync:url"];
|
||||
SyncUsername = config["sync:username"] ?? "";
|
||||
SyncPassword = config["sync:password"] ?? "";
|
||||
SyncMain = TrueValues.Contains(config["sync:main"]?.ToLower());
|
||||
|
||||
SmtpHost = config["smtp:host"];
|
||||
SmtpPort = config["smtp:port"]?.All(char.IsAsciiDigit) == true && config["smtp:port"]?.Length > 0 ? int.Parse(config["smtp:port"]!) : null;
|
||||
@@ -92,6 +116,7 @@ namespace Elwig.Helpers {
|
||||
SmtpUsername = config["smtp:username"];
|
||||
SmtpPassword = config["smtp:password"];
|
||||
SmtpFrom = config["smtp:from"];
|
||||
SmtpWait = double.TryParse(config["smtp:wait"], out var res) ? res : 0;
|
||||
|
||||
var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key).Order();
|
||||
ScaleList.Clear();
|
||||
@@ -101,6 +126,7 @@ namespace Elwig.Helpers {
|
||||
s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"],
|
||||
config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"],
|
||||
config[$"scale.{s}:required"] != null ? TrueValues.Contains(config[$"scale.{s}:required"]?.ToLower()) : null,
|
||||
config[$"scale.{s}:synctime"] != null ? !FalseValues.Contains(config[$"scale.{s}:synctime"]?.ToLower()) : null,
|
||||
config[$"scale.{s}:log"]
|
||||
));
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ using System;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
|
||||
using Row = Tuple<(string?, string?, string?, string?, string, int, string, int), (string, int, string, string, string, int, string, double, double)>;
|
||||
using Row = Tuple<(string?, string?, string?, string?, string, int, string, bool, int), (string, int, string, string, string, int, string, double, double)>;
|
||||
|
||||
public class Bki : Csv<Row> {
|
||||
public class Bki : CsvFile<Row> {
|
||||
|
||||
private readonly string _clientData;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Elwig.Helpers.Export {
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = $"""
|
||||
SELECT lfbis_nr, name, other_names, billing_name, address, plz, ort, area,
|
||||
SELECT lfbis_nr, name, other_names, billing_name, address, plz, ort, vollablieferer, area,
|
||||
date, weight, type, sortid, qualid, year, hkid, kmw, oe
|
||||
FROM v_bki_delivery
|
||||
WHERE year = {year}
|
||||
@@ -38,28 +38,24 @@ namespace Elwig.Helpers.Export {
|
||||
List<Row> rows = [];
|
||||
while (await r.ReadAsync()) {
|
||||
rows.Add(new(
|
||||
(r.IsDBNull(0) ? null : r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetInt32(7)),
|
||||
(r.GetString(8), r.GetInt32(9), r.GetString(10), r.GetString(11), r.GetString(12), r.GetInt32(13), r.GetString(14), r.GetDouble(15), r.GetDouble(16))
|
||||
(r.IsDBNull(0) ? null : r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetBoolean(7), r.GetInt32(8)),
|
||||
(r.GetString(9), r.GetInt32(10), r.GetString(11), r.GetString(12), r.GetString(13), r.GetInt32(14), r.GetString(15), r.GetDouble(16), r.GetDouble(17))
|
||||
));
|
||||
}
|
||||
|
||||
await ExportAsync(rows);
|
||||
}
|
||||
|
||||
public void Export(int year) {
|
||||
ExportAsync(year).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override string FormatRow(Row row) {
|
||||
var (member, delivery) = row;
|
||||
var (lfBisNr, familyName, name, billingName, address, plz, ort, area) = member;
|
||||
var (lfBisNr, familyName, name, billingName, address, plz, ort, vollablieferer, area) = member;
|
||||
var (date, weight, type, sortid, qualid, year, hkid, kmw, oe) = delivery;
|
||||
|
||||
var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName);
|
||||
var (a1, a2) = Utils.SplitAddress(address);
|
||||
var memberData = $"{lfBisNr};{n1};{n2};{a1};\t{a2};{plz};{ort}";
|
||||
var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}";
|
||||
var vollData = $"N;;;{area / 10_000.0}";
|
||||
var vollData = $"{(vollablieferer ? "J" : "N")};{(vollablieferer ? area / 10_000.0 : null):N4};{(vollablieferer ? area / 10_000.0 : null):N4};{area / 10_000.0:N4}";
|
||||
|
||||
return $"{_clientData};{memberData};{deliveryData};{vollData}";
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public abstract class Csv<T> : IExporter<T> {
|
||||
public abstract class CsvFile<T> : IExporter<T> {
|
||||
|
||||
public static string FileExtension => "csv";
|
||||
|
||||
@@ -15,11 +15,11 @@ namespace Elwig.Helpers.Export {
|
||||
protected readonly char Separator;
|
||||
protected string? Header;
|
||||
|
||||
public Csv(string filename, char separator = ';') :
|
||||
public CsvFile(string filename, char separator = ';') :
|
||||
this(filename, separator, Utils.UTF8) {
|
||||
}
|
||||
|
||||
public Csv(string filename, char separator, Encoding encoding) {
|
||||
public CsvFile(string filename, char separator, Encoding encoding) {
|
||||
_writer = new StreamWriter(filename, false, encoding);
|
||||
Separator = separator;
|
||||
}
|
||||
@@ -29,11 +29,6 @@ namespace Elwig.Helpers.Export {
|
||||
_writer.Dispose();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() {
|
||||
GC.SuppressFinalize(this);
|
||||
return _writer.DisposeAsync();
|
||||
}
|
||||
|
||||
public async Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||
progress?.Report(0.0);
|
||||
int count = data.Count() + 2, i = 0;
|
||||
@@ -50,10 +45,6 @@ namespace Elwig.Helpers.Export {
|
||||
progress?.Report(100.0);
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public string FormatRow(IEnumerable row) {
|
||||
return string.Join(Separator, row);
|
||||
}
|
||||
@@ -61,7 +52,7 @@ namespace Elwig.Helpers.Export {
|
||||
public abstract string FormatRow(T row);
|
||||
}
|
||||
|
||||
public class CsvSimple : Csv<IEnumerable<object?>> {
|
||||
public class CsvSimple : CsvFile<IEnumerable<object?>> {
|
||||
|
||||
public CsvSimple(string filename, char separator, Encoding encoding) :
|
||||
base(filename, separator, encoding) {
|
||||
@@ -18,73 +18,85 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
|
||||
public static async Task ExportSqlite(string filename, bool zipFile) {
|
||||
if (zipFile) {
|
||||
File.Delete(filename);
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
|
||||
var tmp = filename + ".tmp";
|
||||
try {
|
||||
File.Delete(tmp);
|
||||
if (zipFile) {
|
||||
using var zip = ZipFile.Open(tmp, ZipArchiveMode.Create);
|
||||
|
||||
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
|
||||
await writer.WriteAsync("elwig:1");
|
||||
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
|
||||
await writer.WriteAsync("elwig:1");
|
||||
}
|
||||
|
||||
var (applId, userVers, schemaVers, size) = await GetMeta();
|
||||
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
|
||||
var obj = new JsonObject {
|
||||
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
["database"] = new JsonObject {
|
||||
["application_id"] = applId,
|
||||
["user_version"] = userVers,
|
||||
["schema_version"] = schemaVers,
|
||||
["file_size"] = size,
|
||||
},
|
||||
};
|
||||
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
|
||||
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
|
||||
} else {
|
||||
File.Copy(App.Config.DatabaseFile, tmp);
|
||||
}
|
||||
|
||||
var (applId, userVers, schemaVers, size) = await GetMeta();
|
||||
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
|
||||
var obj = new JsonObject {
|
||||
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
["database"] = new JsonObject {
|
||||
["application_id"] = applId,
|
||||
["user_version"] = userVers,
|
||||
["schema_version"] = schemaVers,
|
||||
["file_size"] = size,
|
||||
},
|
||||
};
|
||||
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
|
||||
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
|
||||
} else {
|
||||
File.Copy(App.Config.DatabaseFile, filename, true);
|
||||
File.Move(tmp, filename, true);
|
||||
} finally {
|
||||
File.Delete(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExportSql(string filename, bool zipFile) {
|
||||
if (zipFile) {
|
||||
File.Delete(filename);
|
||||
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
|
||||
var tmp = filename + ".tmp";
|
||||
try {
|
||||
File.Delete(tmp);
|
||||
if (zipFile) {
|
||||
using var zip = ZipFile.Open(tmp, ZipArchiveMode.Create);
|
||||
|
||||
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
|
||||
await writer.WriteAsync("elwig:1");
|
||||
}
|
||||
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
|
||||
await writer.WriteAsync("elwig:1");
|
||||
}
|
||||
|
||||
var (applId, userVers, schemaVers, size) = await GetMeta();
|
||||
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
|
||||
var obj = new JsonObject {
|
||||
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
["database"] = new JsonObject {
|
||||
["application_id"] = applId,
|
||||
["user_version"] = userVers,
|
||||
["schema_version"] = schemaVers,
|
||||
["file_size"] = size,
|
||||
},
|
||||
};
|
||||
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
var (applId, userVers, schemaVers, size) = await GetMeta();
|
||||
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
|
||||
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
|
||||
var obj = new JsonObject {
|
||||
["timestamp"] = $"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}",
|
||||
["zwstid"] = App.ZwstId,
|
||||
["device"] = Environment.MachineName,
|
||||
["database"] = new JsonObject {
|
||||
["application_id"] = applId,
|
||||
["user_version"] = userVers,
|
||||
["schema_version"] = schemaVers,
|
||||
["file_size"] = size,
|
||||
},
|
||||
};
|
||||
await writer.WriteAsync(obj.ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
|
||||
var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
|
||||
using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) {
|
||||
var sql = zip.CreateEntry("database.sql", CompressionLevel.SmallestSize);
|
||||
using (var writer = new StreamWriter(sql.Open(), Utils.UTF8)) {
|
||||
await ExportSql(writer);
|
||||
}
|
||||
} else {
|
||||
using var stream = File.OpenWrite(tmp);
|
||||
using var writer = new StreamWriter(stream, Utils.UTF8);
|
||||
await ExportSql(writer);
|
||||
}
|
||||
} else {
|
||||
using var stream = File.OpenWrite(filename);
|
||||
using var writer = new StreamWriter(stream, Utils.UTF8);
|
||||
await ExportSql(writer);
|
||||
File.Move(tmp, filename, true);
|
||||
} finally {
|
||||
File.Delete(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +212,7 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ImportSqlite(string filename, bool zipFile = false) {
|
||||
public static async Task ImportSqlite(string filename, bool zipFile = false, bool keepSource = true) {
|
||||
if (zipFile) {
|
||||
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
|
||||
try {
|
||||
@@ -209,7 +221,7 @@ namespace Elwig.Helpers.Export {
|
||||
foreach (var entry in zip.Entries) {
|
||||
if (entry.Name.EndsWith(".sqlite3")) {
|
||||
entry.ExtractToFile(newName);
|
||||
await ImportSqlite(newName);
|
||||
await ImportSqlite(newName, keepSource: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -221,7 +233,11 @@ namespace Elwig.Helpers.Export {
|
||||
|
||||
var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3");
|
||||
File.Move(App.Config.DatabaseFile, oldName, true);
|
||||
File.Move(filename, App.Config.DatabaseFile, false);
|
||||
if (keepSource) {
|
||||
File.Copy(filename, App.Config.DatabaseFile, false);
|
||||
} else {
|
||||
File.Move(filename, App.Config.DatabaseFile, false);
|
||||
}
|
||||
|
||||
using var cnx = await AppDbContext.ConnectAsync();
|
||||
await cnx.ExecuteBatch("VACUUM");
|
||||
@@ -234,7 +250,7 @@ namespace Elwig.Helpers.Export {
|
||||
using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) {
|
||||
await cnx.ExecuteBatch(await reader.ReadToEndAsync());
|
||||
}
|
||||
await ImportSqlite(newName);
|
||||
await ImportSqlite(newName, keepSource: false);
|
||||
} finally {
|
||||
if (File.Exists(newName)) File.Delete(newName);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Security;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public class Ebics(PaymentVar variant, string filename, int version, Ebics.AddressMode mode = Ebics.AddressMode.Full) : IBankingExporter {
|
||||
public class Ebics(PaymentVar variant, string filename, int version, Ebics.AddressMode mode = Ebics.AddressMode.Full) : IExporter<Transaction> {
|
||||
|
||||
public enum AddressMode { Omit = 0, Lines = 1, Full = 2 }
|
||||
|
||||
@@ -28,15 +28,6 @@ namespace Elwig.Helpers.Export {
|
||||
Writer.Dispose();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() {
|
||||
GC.SuppressFinalize(this);
|
||||
return Writer.DisposeAsync();
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
|
||||
ExportAsync(transactions, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
|
||||
if (transactions.Any(tx => tx.Amount < 0)) {
|
||||
throw new ArgumentException("Tranaction amount may not be negative");
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using Elwig.Models.Dtos;
|
||||
using System;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public class Elba : Csv<Transaction>, IBankingExporter {
|
||||
|
||||
public static new string FileExtension => "elba";
|
||||
|
||||
public Elba(string filename) : base(filename) { }
|
||||
|
||||
public override string FormatRow(Transaction row) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+367
-148
@@ -1,4 +1,6 @@
|
||||
using Elwig.Models;
|
||||
using Elwig.Models.Entities;
|
||||
using Elwig.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -8,7 +10,6 @@ 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 {
|
||||
@@ -34,14 +35,16 @@ namespace Elwig.Helpers.Export {
|
||||
public static async Task Import(IEnumerable<string> filenames, ImportMode mode) {
|
||||
try {
|
||||
Dictionary<string, Branch> branches;
|
||||
Dictionary<int, AT_Kg> kgs;
|
||||
Dictionary<int, int> currentDids;
|
||||
Dictionary<string, int> currentLsNrs;
|
||||
Dictionary<int, List<WbRd>> currentWbRde;
|
||||
Dictionary<int, AT_Kg> kgs;
|
||||
Dictionary<int, WbKg> currentWbKgs;
|
||||
List<WbGl> currentWbGls;
|
||||
|
||||
using (var ctx = new AppDbContext()) {
|
||||
branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId);
|
||||
branches = await ctx.FetchBranches().ToDictionaryAsync(b => b.ZwstId);
|
||||
kgs = await ctx.Katastralgemeinden.ToDictionaryAsync(k => k.KgNr);
|
||||
currentDids = await ctx.Deliveries
|
||||
.GroupBy(d => d.Year)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));
|
||||
@@ -50,8 +53,8 @@ namespace Elwig.Helpers.Export {
|
||||
currentWbRde = await ctx.WbRde
|
||||
.GroupBy(r => r.KgNr)
|
||||
.ToDictionaryAsync(g => g.Key, g => g.ToList());
|
||||
currentWbKgs = await ctx.WbKgs.ToDictionaryAsync(k => k.KgNr);
|
||||
currentWbGls = await ctx.WbGls.ToListAsync();
|
||||
kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr);
|
||||
}
|
||||
|
||||
var data = new List<(
|
||||
@@ -59,26 +62,30 @@ namespace Elwig.Helpers.Export {
|
||||
List<BillingAddr> BillingAddresses,
|
||||
List<MemberTelNr> TelephoneNumbers,
|
||||
List<MemberEmailAddr> EmailAddresses,
|
||||
List<AreaCom> AreaCommitments,
|
||||
List<MemberHistory> MemberHistory,
|
||||
List<AreaCmt> AreaCommitments,
|
||||
List<AreaCmtContract> 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, DateTime CreatedAt, DateTime ModifiedAt)>> Timestamps)>();
|
||||
Dictionary<string, List<(int Id1, int Id2, int Id3, DateTime CreatedAt, DateTime ModifiedAt)>> Timestamps)>();
|
||||
|
||||
var metaData = new List<(string FileName, string ZwstId, string Device,
|
||||
int? MemberNum, string? MemberFilters,
|
||||
int? AreaComNum, string? AreaComFilters,
|
||||
int? AreaCmtNum, string? AreaCmtFilters,
|
||||
int? DeliveryNum, string? DeliveryFilters)>();
|
||||
|
||||
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];
|
||||
|
||||
@@ -86,9 +93,11 @@ 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)) {
|
||||
if (await reader.ReadToEndAsync() != "elwig:1")
|
||||
var v = await reader.ReadToEndAsync();
|
||||
if (!v.StartsWith("elwig:") || !acceptableVersions.Contains(v[6..]))
|
||||
throw new FileFormatException($"Ungültige Elwig-Export-Datei ({filename})");
|
||||
}
|
||||
|
||||
@@ -96,14 +105,14 @@ 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_commitments"]?["count"]?.AsValue().GetValue<int>();
|
||||
var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
var areaCmtCount = meta!["area_commitment_contracts"]?["count"]?.AsValue().GetValue<int>();
|
||||
var areaCmtFilters = meta!["area_commitment_contracts"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
|
||||
var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
|
||||
metaData.Add((Path.GetFileName(filename),
|
||||
meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
|
||||
memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
|
||||
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
|
||||
areaCmtCount, areaCmtFilters != null ? string.Join(" / ", areaCmtFilters) : null,
|
||||
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
|
||||
|
||||
var wbKgsJson = zip.GetEntry("wb_kgs.json");
|
||||
@@ -114,6 +123,7 @@ namespace Elwig.Helpers.Export {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (k, g) = obj.ToWbKg(currentWbGls);
|
||||
r.WbKgs.Add(k);
|
||||
currentWbKgs[k.KgNr] = k;
|
||||
if (g != null) {
|
||||
currentWbGls[g.GlNr] = g;
|
||||
r.WbGls.Add(g);
|
||||
@@ -127,29 +137,65 @@ namespace Elwig.Helpers.Export {
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs);
|
||||
var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs, currentWbKgs);
|
||||
r.Members.Add(m);
|
||||
if (b != null) r.BillingAddresses.Add(b);
|
||||
r.TelephoneNumbers.AddRange(telNrs);
|
||||
r.EmailAddresses.AddRange(emailAddrs);
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["member"].Add((m.MgNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
r.Timestamps["member"].Add((m.MgNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
}
|
||||
}
|
||||
|
||||
var areaComsJson = zip.GetEntry("area_commitments.json");
|
||||
if (areaComsJson != null) {
|
||||
using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
|
||||
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 (areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde);
|
||||
r.AreaCommitments.Add(areaCom);
|
||||
var h = obj.ToMemberHistory();
|
||||
r.MemberHistory.Add(h);
|
||||
}
|
||||
}
|
||||
|
||||
// legacy area commitments
|
||||
var areaCmtsJson = zip.GetEntry("area_commitments.json");
|
||||
if (areaCmtsJson != null) {
|
||||
using var reader = new StreamReader(areaCmtsJson.Open(), Utils.UTF8);
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (contract, areaCmt, wbrd, timestamps) = obj.ToAreaCmt(kgs, currentWbKgs, currentWbRde);
|
||||
r.Contracts.Add(contract);
|
||||
r.AreaCommitments.Add(areaCmt);
|
||||
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((areaCmt.FbNr, areaCmt.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, areaCmts, wbrd, timestamps) = obj.ToAreaCmtContract(kgs, currentWbKgs, currentWbRde);
|
||||
r.Contracts.Add(contract);
|
||||
r.AreaCommitments.AddRange(areaCmts.Select(v => v.Item1));
|
||||
if (wbrd != null) {
|
||||
r.Riede.Add(wbrd);
|
||||
}
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["area_commitment"].Add((areaCom.FbNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
r.Timestamps["area_commitment_contract"].Add((contract.FbNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
foreach (var (areaCmt, ts) in areaCmts) {
|
||||
if (!ts.HasValue) continue;
|
||||
r.Timestamps["area_commitment"].Add((areaCmt.FbNr, areaCmt.RevNr, 0, ts.Value.CreatedAt, ts.Value.ModifiedAt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,13 +205,17 @@ namespace Elwig.Helpers.Export {
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync()) != null) {
|
||||
var obj = JsonNode.Parse(line)!.AsObject();
|
||||
var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde);
|
||||
var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbKgs, currentWbRde);
|
||||
r.Deliveries.Add(d);
|
||||
r.DeliveryParts.AddRange(parts);
|
||||
r.DeliveryParts.AddRange(parts.Select(p => p.Item1));
|
||||
r.Modifiers.AddRange(mods);
|
||||
r.Riede.AddRange(rde);
|
||||
if (timestamps.HasValue)
|
||||
r.Timestamps["delivery"].Add((d.Year, d.DId, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception exc) when (
|
||||
@@ -174,30 +224,27 @@ namespace Elwig.Helpers.Export {
|
||||
exc is FileNotFoundException ||
|
||||
exc is IOException) {
|
||||
data.RemoveAt(data.Count - 1);
|
||||
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message;
|
||||
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
|
||||
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowException("Fehler beim Importieren", $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen", exc);
|
||||
await AddImportedFiles(Path.GetFileName(filename));
|
||||
} catch (Exception exc) {
|
||||
data.RemoveAt(data.Count - 1);
|
||||
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message;
|
||||
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
|
||||
var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
|
||||
if (r == MessageBoxResult.Yes) {
|
||||
if (InteractionService.AskException("Fehler beim Importieren", $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?", exc)) {
|
||||
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 Imported, 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 importedAreaCmts = 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)>();
|
||||
|
||||
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
|
||||
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, history, areaCommitments, contracts, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
|
||||
var branch = branches[meta.ZwstId];
|
||||
var device = meta.Device;
|
||||
|
||||
using var ctx = new AppDbContext();
|
||||
var tx = await ctx.Database.BeginTransactionAsync();
|
||||
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
|
||||
|
||||
var kgnrs = wbKgs.Select(k => k.KgNr).ToList();
|
||||
var duplicateKgNrs = await ctx.WbKgs
|
||||
@@ -205,6 +252,12 @@ 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))
|
||||
@@ -220,11 +273,20 @@ namespace Elwig.Helpers.Export {
|
||||
if (duplicateMgNrs.Count > 0)
|
||||
importDuplicateMembers = ImportQuestion(branch.Name, device, "Mitglieder", true, duplicateMgNrs.Count);
|
||||
|
||||
var fbnrs = areaCommitments.Select(c => c.FbNr).ToList();
|
||||
var duplicateFbNrs = await ctx.AreaCommitments
|
||||
var fbnrs = contracts.Select(c => c.FbNr).ToList();
|
||||
var duplicateFbNrs = await ctx.AreaCommitmentContracts
|
||||
.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
|
||||
@@ -258,40 +320,52 @@ namespace Elwig.Helpers.Export {
|
||||
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
|
||||
}
|
||||
|
||||
if (importDuplicateMembers || importNewMembers || importDuplicateDeliveries || importNewDeliveries) {
|
||||
if (importDuplicateMembers || importNewMembers || importDuplicateContracts || importNewContracts || importDuplicateDeliveries || importNewDeliveries) {
|
||||
ctx.AddRange(wbGls);
|
||||
ctx.UpdateRange(wbKgs.Where(k => duplicateKgNrs.Contains(k.KgNr)));
|
||||
ctx.AddRange(wbKgs.Where(k => !duplicateKgNrs.Contains(k.KgNr)));
|
||||
}
|
||||
|
||||
if (importDuplicateMembers) {
|
||||
ctx.RemoveRange(ctx.BillingAddresses.Where(a => duplicateMgNrs.Contains(a.MgNr)));
|
||||
ctx.RemoveRange(ctx.MemberTelephoneNrs.Where(n => duplicateMgNrs.Contains(n.MgNr)));
|
||||
ctx.RemoveRange(ctx.MemberEmailAddrs.Where(a => duplicateMgNrs.Contains(a.MgNr)));
|
||||
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.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 (areaCommitments.Count > 0) {
|
||||
|
||||
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) {
|
||||
ctx.AddRange(riede);
|
||||
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));
|
||||
var n = importNewContracts ? contracts.Count - duplicateFbNrs.Count : 0;
|
||||
var o = importDuplicateContracts ? duplicateFbNrs.Count : 0;
|
||||
importedAreaCmts.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, contracts.Count - n - o, meta.AreaCmtFilters));
|
||||
}
|
||||
|
||||
if (allowedDuplicateLsNrs.Count > 0) {
|
||||
@@ -300,9 +374,10 @@ 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.Where(p => allowedDuplicateLsNrs.Contains(p.Delivery.LsNr)));
|
||||
ctx.RemoveRange(ctx.DeliveryParts.IgnoreAutoIncludes().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))));
|
||||
@@ -314,9 +389,10 @@ 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.Where(p => l.Contains(p.Delivery.LsNr)));
|
||||
ctx.RemoveRange(ctx.DeliveryParts.IgnoreAutoIncludes().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))));
|
||||
@@ -333,17 +409,21 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
|
||||
await tx.CommitAsync();
|
||||
|
||||
var primaryKeys = new Dictionary<string, string>() {
|
||||
["member"] = "mgnr, 0",
|
||||
["area_commitment"] = "fbnr, 0",
|
||||
["delivery"] = "year, did",
|
||||
["member"] = "mgnr, 0, 0",
|
||||
["area_commitment_contract"] = "fbnr, 0, 0",
|
||||
["area_commitment"] = "fbnr, revnr, 0",
|
||||
["delivery"] = "year, did, 0",
|
||||
["delivery_part"] = "year, did, dpnr",
|
||||
};
|
||||
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});"));
|
||||
$"WHERE ({primaryKeys[e.Key]}) = ({m.Id1}, {m.Id2}, {m.Id3});"));
|
||||
using var cnx = AppDbContext.Connect();
|
||||
await cnx.ExecuteBatch($"""
|
||||
BEGIN;
|
||||
@@ -357,7 +437,7 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
App.HintContextChange();
|
||||
|
||||
MessageBox.Show(
|
||||
InteractionService.ShowInformation("Importieren erfolgreich",
|
||||
$"Das importieren der Daten war erfolgreich!\n" +
|
||||
$"Folgendes wurde importiert:\n" +
|
||||
string.Join("\n", [
|
||||
@@ -367,10 +447,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.Imported)}",
|
||||
..importedAreaComs.Select(d =>
|
||||
$" {d.FileName} ({d.Imported})\n" +
|
||||
$" ({d.Imported} importiert, {d.NotImported} nicht importiert)\n" +
|
||||
$"Flächenbindungen: {importedAreaCmts.Sum(d => d.New + d.Overwritten)}",
|
||||
..importedAreaCmts.Select(d =>
|
||||
$" {d.FileName} ({d.New + d.Overwritten})\n" +
|
||||
$" ({d.New} importiert, {d.Overwritten} überschreiben, {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)}",
|
||||
@@ -379,38 +459,33 @@ 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) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
|
||||
private static bool ImportQuestion(string branch, string device, string subject, bool duplicate, int number) {
|
||||
return MessageBox.Show(
|
||||
return InteractionService.AskQuestion($"{subject} importieren",
|
||||
$"Sollen {number} {(duplicate ? "" : "neue ")}{subject} durch die Zweigstelle\n" +
|
||||
$"{branch} (Gerät {device}) {(duplicate ? "überschrieben" : "importiert")} werden?",
|
||||
$"{subject} importieren",
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes
|
||||
) == MessageBoxResult.Yes;
|
||||
$"{branch} (Gerät {device}) {(duplicate ? "überschrieben" : "importiert")} werden?", true);
|
||||
}
|
||||
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<MemberHistory> history, 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<AreaCom> areaComs, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<MemberHistory> history, IEnumerable<AreaCmtContract> areaCmts, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
|
||||
return new ElwigExport {
|
||||
Members = (members, filters),
|
||||
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
|
||||
History = (history, ["von exportierten Mitgliedern"]),
|
||||
AreaCmts = (areaCmts, ["von exportierten Mitgliedern"]),
|
||||
WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]),
|
||||
}.Export(filename);
|
||||
}
|
||||
@@ -425,7 +500,8 @@ 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<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
|
||||
public (IEnumerable<MemberHistory> History, IEnumerable<string> Filters)? History { get; set; }
|
||||
public (IEnumerable<AreaCmtContract> AreaCmts, IEnumerable<string> Filters)? AreaCmts { get; set; }
|
||||
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
|
||||
|
||||
public async Task Export(string filename) {
|
||||
@@ -434,7 +510,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:1");
|
||||
await writer.WriteAsync("elwig:2");
|
||||
}
|
||||
|
||||
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
|
||||
@@ -444,21 +520,26 @@ 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 (AreaComs != null)
|
||||
obj["area_commitments"] = new JsonObject {
|
||||
["count"] = AreaComs.Value.AreaComs.Count(),
|
||||
["filters"] = new JsonArray(AreaComs.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 (AreaCmts != null)
|
||||
obj["area_commitment_contracts"] = new JsonObject {
|
||||
["count"] = AreaCmts.Value.AreaCmts.Count(),
|
||||
["revisions"] = AreaCmts.Value.AreaCmts.Sum(c => c.Revisions.Count),
|
||||
["filters"] = new JsonArray(AreaCmts.Value.Filters.Select(f => (JsonNode)f).ToArray()),
|
||||
};
|
||||
if (Deliveries != null)
|
||||
obj["deliveries"] = new JsonObject {
|
||||
@@ -484,10 +565,17 @@ namespace Elwig.Helpers.Export {
|
||||
await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
}
|
||||
if (AreaComs != null) {
|
||||
var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
|
||||
if (History != null) {
|
||||
var json = zip.CreateEntry("member_history.json", CompressionLevel.SmallestSize);
|
||||
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
|
||||
foreach (var c in AreaComs.Value.AreaComs) {
|
||||
foreach (var h in History.Value.History) {
|
||||
await writer.WriteLineAsync(h.ToJson().ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
}
|
||||
if (AreaCmts != null) {
|
||||
var json = zip.CreateEntry("area_commitment_contracts.json", CompressionLevel.SmallestSize);
|
||||
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
|
||||
foreach (var c in AreaCmts.Value.AreaCmts) {
|
||||
await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts));
|
||||
}
|
||||
}
|
||||
@@ -542,16 +630,21 @@ 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,
|
||||
["business_shares"] = m.BusinessShares,
|
||||
["shares"] = m.Shares,
|
||||
["shares_red"] = m.SharesRed,
|
||||
["shares_white"] = m.SharesWhite,
|
||||
["shares_dormant"] = m.SharesDormant,
|
||||
["accounting_nr"] = m.AccountingNr,
|
||||
["zwstid"] = m.ZwstId,
|
||||
["lfbis_nr"] = m.LfbisNr,
|
||||
["ustid_nr"] = m.UstIdNr,
|
||||
["ooc_id"] = m.OrganicOperatorId,
|
||||
["org_auth_code"] = m.OrganicAuthorityCode,
|
||||
["juridical_pers"] = m.IsJuridicalPerson,
|
||||
["volllieferant"] = m.IsVollLieferant,
|
||||
["buchführend"] = m.IsBuchführend,
|
||||
["full_delivery"] = m.IsFullDelivery,
|
||||
["accounting"] = m.IsAccounting,
|
||||
["organic"] = m.IsOrganic,
|
||||
["funktionär"] = m.IsFunktionär,
|
||||
["officer"] = m.IsOfficer,
|
||||
["active"] = m.IsActive,
|
||||
["deceased"] = m.IsDeceased,
|
||||
["iban"] = m.Iban,
|
||||
@@ -591,11 +684,11 @@ namespace Elwig.Helpers.Export {
|
||||
};
|
||||
}
|
||||
|
||||
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs) {
|
||||
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs) {
|
||||
var mgnr = json["mgnr"]!.AsValue().GetValue<int>();
|
||||
var kgnr = json["default_kgnr"]?.AsValue().GetValue<int>();
|
||||
if (kgnr != null && !kgs.Values.Any(k => k.WbKg?.KgNr == kgnr)) {
|
||||
throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten → Herkunftshierarchie)");
|
||||
if (kgnr != null && !currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
|
||||
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
|
||||
}
|
||||
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
|
||||
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
|
||||
@@ -611,16 +704,21 @@ namespace Elwig.Helpers.Export {
|
||||
Birthday = json["birthday"]?.AsValue().GetValue<string>(),
|
||||
EntryDateString = json["entry_date"]?.AsValue().GetValue<string>(),
|
||||
ExitDateString = json["exit_date"]?.AsValue().GetValue<string>(),
|
||||
BusinessShares = json["business_shares"]?.AsValue().GetValue<int>() ?? 0,
|
||||
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,
|
||||
AccountingNr = json["accounting_nr"]?.AsValue().GetValue<string>(),
|
||||
ZwstId = json["zwstid"]?.AsValue().GetValue<string>(),
|
||||
LfbisNr = json["lfbis_nr"]?.AsValue().GetValue<string>(),
|
||||
UstIdNr = json["ustid_nr"]?.AsValue().GetValue<string>(),
|
||||
OrganicOperatorId = json["ooc_id"]?.AsValue().GetValue<string>(),
|
||||
OrganicAuthorityCode = json["org_auth_code"]?.AsValue().GetValue<string>(),
|
||||
IsJuridicalPerson = json["juridical_pers"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsVollLieferant = json["volllieferant"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsBuchführend = json["buchführend"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsFullDelivery = json["full_delivery"]?.AsValue().GetValue<bool>() ?? json["volllieferant"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsAccounting = json["accounting"]?.AsValue().GetValue<bool>() ?? json["buchführend"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsOrganic = json["organic"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsFunktionär = json["funktionär"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsOfficer = json["officer"]?.AsValue().GetValue<bool>() ?? json["funktionär"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsActive = json["active"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsDeceased = json["deceased"]?.AsValue().GetValue<bool>() ?? false,
|
||||
Iban = json["iban"]?.AsValue().GetValue<string>(),
|
||||
@@ -639,44 +737,99 @@ 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>(),
|
||||
}).ToList(), json["email_addresses"]!.AsArray().Select(a => a!.AsObject()).Select((a, i) => new MemberEmailAddr {
|
||||
})], [.. 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 AreaCom c) {
|
||||
public static JsonObject ToJson(this MemberHistory h) {
|
||||
return new JsonObject {
|
||||
["histnr"] = h.HistNr,
|
||||
["from_mgnr"] = h.FromMgNr,
|
||||
["from_type"] = h.FromType?.ToString().ToLower(),
|
||||
["to_mgnr"] = h.ToMgNr,
|
||||
["to_type"] = h.ToType?.ToString().ToLower(),
|
||||
["date_notice"] = h.DateNoticeString,
|
||||
["date_effective"] = h.DateEffectiveString,
|
||||
["reason"] = h.Reason,
|
||||
["source"] = h.Source,
|
||||
["shares"] = h.Shares,
|
||||
["signed_company"] = h.CompanyHasSigned,
|
||||
["signed_member"] = h.MemberHasSigned,
|
||||
["value_per_share"] = h.ValuePerShare,
|
||||
["currency"] = h.CurrencyCode,
|
||||
["deduct_year"] = h.DeductYear,
|
||||
["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 = Enum.TryParse<BusinessShareType>(json["from_type"]?.AsValue().GetValue<string>(), true, out var from) ? from : null,
|
||||
ToMgNr = json["to_mgnr"]?.AsValue().GetValue<int>(),
|
||||
ToType = Enum.TryParse<BusinessShareType>(json["to_type"]?.AsValue().GetValue<string>(), true, out var to) ? to : null,
|
||||
DateNoticeString = json["date_notice"]!.AsValue().GetValue<string>(),
|
||||
DateEffectiveString = json["date_effective"]!.AsValue().GetValue<string>(),
|
||||
Reason = json["reason"]!.AsValue().GetValue<string>(),
|
||||
Source = json["source"]!.AsValue().GetValue<string>(),
|
||||
Shares = json["shares"]!.AsValue().GetValue<int>(),
|
||||
CompanyHasSigned = json["signed_company"]?.AsValue().GetValue<bool>() ?? false,
|
||||
MemberHasSigned = json["signed_member"]?.AsValue().GetValue<bool>() ?? false,
|
||||
ValuePerShare = json["value_per_share"]?.AsValue().GetValue<decimal>(),
|
||||
CurrencyCode = json["currency"]?.AsValue().GetValue<string>(),
|
||||
DeductYear = json["deduct_year"]?.AsValue().GetValue<int>(),
|
||||
Comment = json["comment"]?.AsValue().GetValue<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
public static JsonObject ToJson(this AreaCmtContract 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,
|
||||
["year_from"] = c.YearFrom,
|
||||
["year_to"] = c.YearTo,
|
||||
["revisions"] = new JsonArray(c.Revisions.OrderBy(r => r.RevNr).Select(r => r.ToJson()).ToArray()),
|
||||
["comment"] = c.Comment,
|
||||
["created_at"] = $"{c.CreatedAt:yyyy-MM-ddTHH:mm:ssK}",
|
||||
["modified_at"] = $"{c.ModifiedAt:yyyy-MM-ddTHH:mm:ssK}",
|
||||
};
|
||||
}
|
||||
|
||||
public static (AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) {
|
||||
private static JsonObject ToJson(this AreaCmt 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 (AreaCmtContract, List<(AreaCmt, (DateTime CreatedAt, DateTime ModifiedAt)?)>, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCmtContract(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs, 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>();
|
||||
if (!currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
|
||||
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
|
||||
}
|
||||
WbRd? rd = null;
|
||||
bool newRd = false;
|
||||
if (ried != null) {
|
||||
@@ -695,18 +848,75 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
|
||||
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
|
||||
return (new AreaCom {
|
||||
return (new AreaCmtContract {
|
||||
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, (AreaCmt, (DateTime, DateTime)?)>(r => {
|
||||
var createdAt = json["created_at"]?.AsValue().GetValue<string>();
|
||||
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
|
||||
return (new AreaCmt {
|
||||
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 (AreaCmtContract, AreaCmt, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCmt(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs, Dictionary<int, List<WbRd>> riede) {
|
||||
var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
|
||||
var ried = json["ried"]?.AsValue().GetValue<string>();
|
||||
if (!currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
|
||||
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
|
||||
}
|
||||
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 AreaCmtContract {
|
||||
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 AreaCmt {
|
||||
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 :
|
||||
@@ -723,43 +933,45 @@ 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 => {
|
||||
var obj = new JsonObject {
|
||||
["dpnr"] = p.DPNr,
|
||||
["sortid"] = p.SortId,
|
||||
["attrid"] = p.AttrId,
|
||||
["cultid"] = p.CultId,
|
||||
["weight"] = p.Weight,
|
||||
["kmw"] = p.Kmw,
|
||||
["qualid"] = p.QualId,
|
||||
["hkid"] = p.HkId,
|
||||
["kgnr"] = p.KgNr,
|
||||
["ried"] = p.Rd?.Name,
|
||||
["net_weight"] = p.IsNetWeight,
|
||||
["manual_weighing"] = p.IsManualWeighing,
|
||||
["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}",
|
||||
};
|
||||
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
|
||||
if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
|
||||
if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden;
|
||||
if (p.Unloading != null) obj["unloading"] = p.Unloading;
|
||||
if (p.Temperature != null) obj["temperature"] = p.Temperature;
|
||||
if (p.Acid != null) obj["acid"] = p.Acid;
|
||||
if (p.ScaleId != null) obj["scale_id"] = p.ScaleId;
|
||||
if (p.WeighingData != null) obj["weighing_data"] = JsonNode.Parse(p.WeighingData);
|
||||
if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason;
|
||||
return obj;
|
||||
}).ToArray()),
|
||||
["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}",
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
private static JsonObject ToJson(this DeliveryPart p) {
|
||||
var obj = new JsonObject {
|
||||
["dpnr"] = p.DPNr,
|
||||
["sortid"] = p.SortId,
|
||||
["attrid"] = p.AttrId,
|
||||
["cultid"] = p.CultId,
|
||||
["weight"] = p.Weight,
|
||||
["kmw"] = p.Kmw,
|
||||
["qualid"] = p.QualId,
|
||||
["hkid"] = p.HkId,
|
||||
["kgnr"] = p.KgNr,
|
||||
["ried"] = p.Rd?.Name,
|
||||
["net_weight"] = p.IsNetWeight,
|
||||
["manual_weighing"] = p.IsManualWeighing,
|
||||
["modids"] = new JsonArray(p.PartModifiers.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}",
|
||||
};
|
||||
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
|
||||
if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
|
||||
if (p.IsContracted != null) obj["contracted"] = p.IsContracted;
|
||||
if (p.Unloading != null) obj["unloading"] = p.Unloading;
|
||||
if (p.Temperature != null) obj["temperature"] = p.Temperature;
|
||||
if (p.Acid != null) obj["acid"] = p.Acid;
|
||||
if (p.ScaleTerminalId != null) obj["scale_terminal"] = p.ScaleTerminalId;
|
||||
if (p.WeighingData != null) obj["weighing_data"] = JsonNode.Parse(p.WeighingData);
|
||||
if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason;
|
||||
return obj;
|
||||
}
|
||||
|
||||
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, WbKg> currentWbKgs, 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);
|
||||
@@ -782,9 +994,12 @@ namespace Elwig.Helpers.Export {
|
||||
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
|
||||
Comment = json["comment"]?.AsValue().GetValue<string>(),
|
||||
ImportedAt = DateTime.Now,
|
||||
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => {
|
||||
}, [.. json["parts"]!.AsArray().Select(p => p!.AsObject()).Select<JsonObject, (DeliveryPart, (DateTime, DateTime)?)>(p => {
|
||||
var kgnr = p["kgnr"]?.AsValue().GetValue<int>();
|
||||
var ried = p["ried"]?.AsValue().GetValue<string>();
|
||||
if (kgnr != null && !currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
|
||||
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
|
||||
}
|
||||
WbRd? rd = null;
|
||||
if (ried != null && kgnr != null) {
|
||||
var rde = riede.GetValueOrDefault(kgnr.Value, []);
|
||||
@@ -800,7 +1015,9 @@ namespace Elwig.Helpers.Export {
|
||||
wbRde.Add(rd);
|
||||
}
|
||||
}
|
||||
return new DeliveryPart {
|
||||
var createdAt = p["created_at"]?.AsValue().GetValue<string>();
|
||||
var modifiedAt = p["modified_at"]?.AsValue().GetValue<string>();
|
||||
return (new DeliveryPart {
|
||||
Year = year,
|
||||
DId = did,
|
||||
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
|
||||
@@ -818,20 +1035,22 @@ namespace Elwig.Helpers.Export {
|
||||
Comment = p["comment"]?.AsValue().GetValue<string>(),
|
||||
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
|
||||
IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
|
||||
IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(),
|
||||
IsContracted = p["contracted"]?.AsValue().GetValue<bool>() ?? p["gebunden"]?.AsValue().GetValue<bool>(),
|
||||
Unloading = p["unloading"]?.AsValue().GetValue<string>() ?? ((p["lesewagen"]?.AsValue().GetValue<bool>() ?? false) ? DeliveryPart.Pumped : null),
|
||||
Temperature = p["temperature"]?.AsValue().GetValue<double>(),
|
||||
Acid = p["acid"]?.AsValue().GetValue<double>(),
|
||||
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
|
||||
ScaleTerminalId = p["scale_terminal"]?.AsValue().GetValue<string>() ?? p["scale_id"]?.AsValue().GetValue<string>(),
|
||||
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts),
|
||||
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
|
||||
};
|
||||
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
|
||||
}, 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 {
|
||||
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),
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using Elwig.Models.Dtos;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
/// <summary>
|
||||
/// Interface for exporting banking data
|
||||
/// </summary>
|
||||
public interface IBankingExporter : IExporter<Transaction> { }
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Export {
|
||||
public interface IExporter<T> : IDisposable, IAsyncDisposable {
|
||||
public interface IExporter<T> : IDisposable {
|
||||
/// <summary>
|
||||
/// The default file extension of the exported files to be used (whithout a preceding ".")
|
||||
/// </summary>
|
||||
@@ -14,7 +14,9 @@ namespace Elwig.Helpers.Export {
|
||||
/// </summary>
|
||||
/// <param name="data">The data to be exported</param>
|
||||
/// <param name="progress">The progress object to report to</param>
|
||||
void Export(IEnumerable<T> data, IProgress<double>? progress = null);
|
||||
void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
|
||||
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronosly export the given data to the given file.
|
||||
|
||||
@@ -109,8 +109,13 @@ namespace Elwig.Helpers.Export {
|
||||
<style:paragraph-properties fo:text-align="center"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="sectionheader" style:family="table-cell" style:parent-style-name="default">
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/>
|
||||
<style:paragraph-properties fo:text-align="left"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold" fo:font-size="14pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="th" style:family="table-cell" style:parent-style-name="default">
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle"/>
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle" fo:wrap-option="wrap"/>
|
||||
<style:paragraph-properties fo:text-align="center"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
@@ -211,35 +216,59 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
}
|
||||
|
||||
await Content.WriteAsync(
|
||||
$" <table:table-row>\r\n" +
|
||||
FormatCell(table.FullName, colSpan: totalSpan, style: "header") +
|
||||
$" </table:table-row>\r\n" +
|
||||
$" <table:table-row>\r\n" +
|
||||
FormatCell(table.Subtitle, colSpan: totalSpan, style: "subheader") +
|
||||
$" </table:table-row>\r\n" +
|
||||
$" <table:table-row>\r\n" +
|
||||
$" <table:table-cell table:number-columns-repeated=\"{totalSpan}\"/>\r\n" +
|
||||
$" </table:table-row>\r\n" +
|
||||
$" <table:table-row>\r\n");
|
||||
foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) {
|
||||
var hasUnits = units.Length > 0;
|
||||
await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th"));
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n <table:table-row>\r\n");
|
||||
foreach (var (span, units) in table.ColumnSpans.Zip(table.ColumnUnits)) {
|
||||
if (units.Length == 0) {
|
||||
await Content.WriteAsync($" <table:covered-table-cell table:number-columns-repeated=\"{span}\"/>\r\n");
|
||||
continue;
|
||||
}
|
||||
foreach (var u in units) {
|
||||
await Content.WriteAsync(FormatCell(u == null ? null : $"[{u}]", style: "th"));
|
||||
}
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n");
|
||||
await Content.WriteAsync($"""
|
||||
<table:table-row>
|
||||
{FormatCell(table.FullName, colSpan: totalSpan, style: "header")}
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
{FormatCell(table.Subtitle, colSpan: totalSpan, style: "subheader")}
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
|
||||
</table:table-row>
|
||||
""");
|
||||
|
||||
foreach (var row in table.GetData()) {
|
||||
await FormatRow(row, table.ColumnUnits, mergeSubRowCells);
|
||||
var firstSection = true;
|
||||
foreach (var sec in table.GetData()) {
|
||||
if (!firstSection) {
|
||||
await Content.WriteAsync($"""
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
|
||||
</table:table-row>
|
||||
""");
|
||||
}
|
||||
if (sec.Name != null) {
|
||||
await Content.WriteAsync($"""
|
||||
<table:table-row>
|
||||
{FormatCell(sec.Name, colSpan: totalSpan, style: "sectionheader")}
|
||||
</table:table-row>
|
||||
""");
|
||||
}
|
||||
|
||||
await Content.WriteAsync(" <table:table-row>\r\n");
|
||||
foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) {
|
||||
var hasUnits = units.Length > 0;
|
||||
await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th"));
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n <table:table-row>\r\n");
|
||||
foreach (var (span, units) in table.ColumnSpans.Zip(table.ColumnUnits)) {
|
||||
if (units.Length == 0) {
|
||||
await Content.WriteAsync($" <table:covered-table-cell table:number-columns-repeated=\"{span}\"/>\r\n");
|
||||
continue;
|
||||
}
|
||||
foreach (var u in units) {
|
||||
await Content.WriteAsync(FormatCell(u == null ? null : $"[{u}]", style: "th"));
|
||||
}
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n");
|
||||
|
||||
foreach (var row in sec.Rows) {
|
||||
await FormatRow(row, table.ColumnUnits, mergeSubRowCells);
|
||||
}
|
||||
firstSection = false;
|
||||
}
|
||||
|
||||
await Content.WriteAsync(" </table:table>\r\n");
|
||||
@@ -314,7 +343,8 @@ namespace Elwig.Helpers.Export {
|
||||
}
|
||||
c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString(CultureInfo.InvariantCulture)}\"{add}><text:p>{data}</text:p></{ct}>";
|
||||
} else {
|
||||
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{SecurityElement.Escape(data.ToString())}</text:p></{ct}>";
|
||||
var lines = string.Join("", data.ToString()?.Split('\n').Select(l => $"<text:p>{SecurityElement.Escape(l)}</text:p>") ?? []);
|
||||
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}>{lines}</{ct}>";
|
||||
}
|
||||
|
||||
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-columns-repeated=\"{colSpan - 1}\"/>\r\n" : "");
|
||||
@@ -13,7 +13,9 @@ namespace Elwig.Helpers.Export {
|
||||
|
||||
private readonly StreamWriter _writer;
|
||||
|
||||
public VCard(string filename) : this(filename, Utils.UTF8) { }
|
||||
public VCard(string filename) :
|
||||
this(filename, Utils.UTF8) {
|
||||
}
|
||||
|
||||
public VCard(string filename, Encoding encoding) {
|
||||
_writer = new StreamWriter(filename, false, encoding);
|
||||
@@ -24,11 +26,6 @@ namespace Elwig.Helpers.Export {
|
||||
_writer.Dispose();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() {
|
||||
GC.SuppressFinalize(this);
|
||||
return _writer.DisposeAsync();
|
||||
}
|
||||
|
||||
public async Task ExportAsync(IEnumerable<Member> data, IProgress<double>? progress = null) {
|
||||
progress?.Report(0.0);
|
||||
int count = data.Count() + 1, i = 0;
|
||||
@@ -58,10 +55,6 @@ namespace Elwig.Helpers.Export {
|
||||
progress?.Report(100.0);
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<Member> data, IProgress<double>? progress = null) {
|
||||
ExportAsync(data, progress).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static string? Escape(string? text) {
|
||||
return text?.Replace("\\", "\\\\").Replace(",", "\\,").Replace(";", "\\;").Replace("\n", "\\n").Replace("\r", "");
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Hashing;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
public static partial class Extensions {
|
||||
@@ -131,11 +136,39 @@ namespace Elwig.Helpers {
|
||||
return await cmd.ExecuteScalarAsync();
|
||||
}
|
||||
|
||||
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(this SqliteConnection cnx) {
|
||||
public static async Task QuickCheck(this SqliteConnection cnx) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA quick_check";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var list = new List<string>();
|
||||
while (await reader.ReadAsync()) {
|
||||
list.Add(reader.GetString(0));
|
||||
}
|
||||
|
||||
if (list.Count != 1 || list[0] != "ok") {
|
||||
throw new FileFormatException($"Integrity problems ({list.Count}):\n" + string.Join("\n", list.Take(50)));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task IntegrityCheck(this SqliteConnection cnx) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA integrity_check";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var list = new List<string>();
|
||||
while (await reader.ReadAsync()) {
|
||||
list.Add(reader.GetString(0));
|
||||
}
|
||||
|
||||
if (list.Count != 1 || list[0] != "ok") {
|
||||
throw new FileFormatException($"Integrity problems ({list.Count}):\n" + string.Join("\n", list.Take(50)));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ForeignKeyCheck(this SqliteConnection cnx) {
|
||||
using var cmd = cnx.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA foreign_key_check";
|
||||
using var reader = await cmd.ExecuteReaderAsync();
|
||||
var list = new List<(string, long, string, long)>();
|
||||
var list = new List<(string Table, long RowId, string Parent, long FkId)>();
|
||||
while (await reader.ReadAsync()) {
|
||||
var table = reader.GetString(0);
|
||||
var rowid = reader.GetInt64(1);
|
||||
@@ -143,7 +176,42 @@ namespace Elwig.Helpers {
|
||||
var fkid = reader.GetInt64(3);
|
||||
list.Add((table, rowid, parent, fkid));
|
||||
}
|
||||
return [.. list];
|
||||
|
||||
if (list.Count > 0) {
|
||||
throw new InvalidDataException($"Foreign key violations ({list.Count}):\n" + string.Join("\n", list.Take(50)
|
||||
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Join<T>(this IEnumerable<T> src, Func<T> separatorFactory) {
|
||||
var srcArr = src.ToArray();
|
||||
for (int i = 0; i < srcArr.Length; i++) {
|
||||
yield return srcArr[i];
|
||||
if (i < srcArr.Length - 1) {
|
||||
yield return separatorFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddToolTipCell(this Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false, Brush? color = null) {
|
||||
var tb = new TextBlock() {
|
||||
Text = text,
|
||||
TextAlignment = alignRight ? TextAlignment.Right : alignCenter ? TextAlignment.Center : TextAlignment.Left,
|
||||
Margin = new(0, 12 * row, 0, 0),
|
||||
FontWeight = bold ? FontWeights.Bold : FontWeights.Normal,
|
||||
};
|
||||
if (color != null) tb.Foreground = color;
|
||||
tb.SetValue(Grid.ColumnProperty, col);
|
||||
tb.SetValue(Grid.ColumnSpanProperty, colSpan);
|
||||
grid.Children.Add(tb);
|
||||
}
|
||||
|
||||
public static (int Year, int Week) GetYearAndWeek(this DateTime date) {
|
||||
return (ISOWeek.GetYear(date), ISOWeek.GetWeekOfYear(date));
|
||||
}
|
||||
|
||||
public static (int Year, int Tertial) GetYearAndTertial(this DateTime date) {
|
||||
return (date.Month <= 7 ? date.Year - 1 : date.Year, date.Month == 12 || date.Month <= 3 ? 2 : date.Month <= 7 ? 3 : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
public static partial class Normalizer {
|
||||
|
||||
[GeneratedRegex(@"(?=(?:(?:bundes)?straße|gasse|weg|platz|allee|ring|zeile?|steig|hof|markt|kai|ufer|brücke|tor|stiege|promenade|gang|siedlung)\b)|[\s-]+", RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex GeneratedStreetNameWordsRegex();
|
||||
private static readonly Regex StreetNameWordsRegex = GeneratedStreetNameWordsRegex();
|
||||
|
||||
private static readonly HashSet<string> StreetTypes = [
|
||||
"Straße", "Gasse", "Weg", "Platz", "Allee", "Ring", "Zeile", "Zeil", "Steig", "Hof",
|
||||
"Markt", "Kai", "Ufer", "Brücke", "Tor", "Stiege", "Promenade", "Gang", "Siedlung"];
|
||||
|
||||
private static readonly HashSet<string> StreetArticles = ["der", "die", "das", "des", "dem", "den"];
|
||||
|
||||
private static readonly HashSet<string> StreetPrepositions = [
|
||||
"an", "am", "auf", "aus", "bei", "beim", "hinter", "hinterm", "in", "im", "ins", "nach",
|
||||
"neben", "über", "unter", "von", "vom", "van", "vor", "zu", "zum", "zur", "zwischen",
|
||||
"durch", "mit"];
|
||||
|
||||
private static readonly HashSet<string> StreetNumbers = [ "Zwei", "Drei", "Vier", "Fünf", "Sechs", "Sieben" ];
|
||||
|
||||
private static readonly HashSet<string> StreetAdjectives = [
|
||||
"alter", "alte", "alten", "altem", "altes",
|
||||
"äußere", "äußere", "äußeren", "äußerem", "äußeres",
|
||||
// "breiter", "breite", "breiten", "breitem", "breites",
|
||||
"dritter", "dritte", "dritten", "drittem", "drittes",
|
||||
"erster", "erste", "ersten", "erstem", "erstes",
|
||||
"großer", "große", "großen", "großem", "großes",
|
||||
"hoher", "hohe", "hohen", "hohem", "hohes",
|
||||
"innerer", "innere", "inneren", "innerem", "inneres",
|
||||
"kleiner", "kleine", "kleinen", "kleinem", "kleines",
|
||||
"kurzer", "kurze", "kurzen", "kurzem", "kurzes",
|
||||
"langer", "lange", "langen", "langem", "langes",
|
||||
"linker", "linke", "linken", "linkem", "linkes",
|
||||
"mittlerer", "mittlere", "mittleren", "mittlerem", "mittleres",
|
||||
"neuer", "neue", "neuen", "neuem", "neues",
|
||||
"oberer", "obere", "oberen", "oberem", "oberes",
|
||||
"rechter", "rechte", "rechten", "rechtem", "rechtes",
|
||||
"schmaler", "schmale", "schmalen", "schmalem", "schmales",
|
||||
"schöner", "schöne", "schönen", "schönem", "schönes",
|
||||
"unterer", "untere", "unteren", "unterem", "unteres",
|
||||
"hinterer", "hintere", "hinteren", "hinterem", "hinteres",
|
||||
"vorderer", "vordere", "vorderen", "vorderem", "vorderes",
|
||||
"runder", "runde", "runden", "rundem", "rundes",
|
||||
"kalter", "kalte", "kalten", "kaltem", "kaltes"];
|
||||
|
||||
private static readonly HashSet<string> StreetLastNames = [
|
||||
"Habsburger", "Lothringer", "Leitzenberger", "Luyderer", "Preiner", "Radinger", "Seefelder", "Staribacher", "Gschmeidler",
|
||||
"Hannover", "Münster", "Salzgitter", "Speyer", "Trier"];
|
||||
|
||||
private static readonly HashSet<string> StreetPersonTitles = [
|
||||
"St.", "Familie",
|
||||
"Dr.", "DDr.", "Hofrat", "Baurat", "Kommerzialrat", "Dir.", "Ing.", "Prof.", "Mag.", "DI", "OSR", "Präsident",
|
||||
"Landeshauptmann", "LH", "Bürgermeister", "Bgm.", "Minister",
|
||||
"Kaiser", "Kaiserin", "König", "Königin", "Erzherzog", "Erzherzogin", "Herzog", "Fürst", "Graf", "Gräfin", "Markgraf", "Kronprinz", "Prinz", "Ritter", "Freiherr", "Lord",
|
||||
"Abt", "Bischof", "Propst", "Dechant", "Kardinal", "Monsignore", "Pfarrer", "Pater", "Prälat",
|
||||
"Oberst"];
|
||||
|
||||
private static readonly HashSet<string> StreetGivenNames = [
|
||||
"Adalbert", "Adam", "Adele", "Adelheid", "Adolf", "Adolph", "Adrian", "Albert", "Albrecht", "Alexander", "Alfons", "Alfred", "Alois", "Aloisia",
|
||||
"Andreas", "Andrea", "Andre", "Anna", "Anton", "Arnold", "Arthur", "August", "Augustin", "Augustine", "Agnes", "Angelika", "Ada", "Adelheid", "Anni", "Anita",
|
||||
"Barbara", "Beate", "Beatrix", "Benedikt", "Bernhard", "Berta", "Bertha", "Berthold", "Brüder", "Bruno", "Brigitte",
|
||||
"Camillo", "Carl", "Caspar", "Catharina", "Christian", "Christine", "Christof", "Christoph", "Clara", "Clemens", "Conrad", "Claus", "Camillo", "Claudia",
|
||||
"Daniel", "Dietmar", "Dietrich",
|
||||
"Edgar", "Edi", "Edith", "Edmund", "Egon", "Elsa", "Eduard", "Emmerich", "Erhard", "Erich", "Ernst", "Erwin", "Eugen", "Ewald", "Eberhard",
|
||||
"Elisabeth", "Emanuel", "Emerich", "Emil", "Emma", "Engelbert", "Elias",
|
||||
"Franz", "Ferdinand", "Friedrich", "Felix", "Florian", "Fritz", "Franziska", "Fred",
|
||||
"Georg", "Gregor", "Gustav", "Gabriel", "Gebrüder", "Gabriele", "Gerhard", "Gerhart", "Gertrude", "Gottfried", "Gottlieb", "Grete",
|
||||
"Günther", "Günter",
|
||||
"Hans", "Hanns", "Heinrich", "Heinz", "Helmut", "Helmuth", "Hermann", "Herta", "Herbert", "Hubert", "Harald", "Horst", "Helene", "Helga",
|
||||
"Hieronymus", "Hilde", "Hugo",
|
||||
"Isabelle", "Ignaz", "Ilse", "Ingeborg", "Isidor",
|
||||
"Josef", "Josefine", "Joseph", "Johann", "Johanna", "Johan", "Johannes", "Hannes", "Harry", "Jakob", "Jacob", "Jan", "Julian", "Julius", "Julia", "Jürgen",
|
||||
"Karl", "Kasimir", "Konrad", "Klemens", "Klara", "Katharina", "Kurt", "Kaspar", "Klaus", "Koloman", "Konstantin", "Killian",
|
||||
"Leopold", "Leo", "Lorenz", "Lothar", "Louise", "Ludmilla", "Ludwig", "Lukas", "Ladislaus", "Lambert", "Laurenz", "Liese",
|
||||
"Lise", "Lilly", "Lina", "Lothar", "Lotte", "Luise", "Leonhard", "Lotte", "Martha",
|
||||
"Maria", "Marie", "Michael", "Martin", "Max", "Maximilian", "Manuel", "Manuela", "Moritz", "Marianne", "Manfred", "Marc",
|
||||
"Mathias", "Matthias", "Matthäus",
|
||||
"Margarete", "Margarethe",
|
||||
"Nico", "Niko", "Niklas", "Niclas", "Nikolaus", "Norbert",
|
||||
"Otto", "Ottokar", "Oskar", "Olga", "Oswald", "Othmar",
|
||||
"Paul", "Pauline", "Patrick", "Pepi", "Peter", "Philipp", "Philip", "Petrus",
|
||||
"Robert", "Rudolf", "Richard", "Ruprecht", "Raffael", "Rolf", "Roman", "Rosa", "Rosalia", "Rupert", "Raimund",
|
||||
"Sigmund", "Siegfried", "Simon", "Stanislaus", "Stefan", "Stephan", "Sepp", "Samuel", "Sebastian", "Susi",
|
||||
"Theodor", "Thomas", "Theresia", "Thaddäus", "Theo", "Theophil", "Tina", "Titus", "Toni", "Trude",
|
||||
"Ullrich", "Ulrich", "Udo",
|
||||
"Viktor", "Vinzenz", "Victor",
|
||||
"Wolfgang", "Willibald", "Wilhelm", "Willhelm", "Wilhelmine", "Walter", "Walther", "Wenzel", "Werner", "Willi",
|
||||
"Zacharias"];
|
||||
|
||||
private static readonly HashSet<string> LocalityNames = [
|
||||
"Groß-Enzersdorf",
|
||||
"Groß Burgstall", "Klein Eberharts", "Klein-Engersdorf", "Groß-Enzersdorf", "Klein-Erlauf",
|
||||
"Groß Gerungs", "Klein Gundholz", "Klein-Harras", "Klein-Heinrichschlag", "Groß-Höbarten",
|
||||
"Klein-Jetzelsdorf", "Klein-Mariazell", "Groß Meinharts", "Klein-Meiseldorf", "Groß-Neusiedl", "Klein-Neusiedl", "Klein-Pöchlarn",
|
||||
"Groß-Radischen", "Klein-Radischen", "Groß Raßberg", "Klein Reinprechts", "Klein-Reinprechtsdorf", "Groß-Reipersdorf", "Klein-Ruprechts",
|
||||
"Groß-Schollach", "Klein-Schollach", "Groß-Schweinbarth", "Groß-Siegharts", "Klein-Siegharts", "Groß Sierning",
|
||||
"Klein-Ulrichschlag", "Klein Weinberg", "Klein Wetzles", "Klein-Wien",
|
||||
];
|
||||
|
||||
private static bool IsGivenName(this string word) {
|
||||
return StreetGivenNames.Contains(word);
|
||||
}
|
||||
|
||||
private static bool IsFamilyName(this string word) {
|
||||
if (StreetLastNames.Contains(word))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsPersonalName(this string word) {
|
||||
return word.IsGivenName() || word.IsFamilyName();
|
||||
}
|
||||
|
||||
private static bool IsGeographicalDerivativeName(this string word) {
|
||||
if (!word.EndsWith("er") || word.IsPersonalName() || word == "Weißer")
|
||||
return false;
|
||||
return word.StartsWith("St. ") || word.StartsWith("Ober ") || word.StartsWith("Unter ") || word.StartsWith("Maria") ||
|
||||
word.EndsWith("sdorfer") || word.EndsWith("ndorfer") || word.EndsWith("ldorfer") ||
|
||||
word.EndsWith("felder") ||
|
||||
word.EndsWith("sthaler") || word.EndsWith("nthaler") || word.EndsWith("lthaler") ||
|
||||
word.EndsWith("dauer") || word.EndsWith("lauer") || word.EndsWith("mauer") || word.EndsWith("nauer") || word.EndsWith("ßauer") ||
|
||||
word.EndsWith("lbacher") || word.EndsWith("nbacher") || word.EndsWith("ßbacher") ||
|
||||
word.EndsWith("ringer") || word.EndsWith("singer") ||
|
||||
word.EndsWith("sbrunner") || word.EndsWith("nbrunner") || word.EndsWith("lbrunner") || word.EndsWith("abrunner") || word.EndsWith("ingbrunner") ||
|
||||
word.EndsWith("sburger") || word.EndsWith("nburger") || word.EndsWith("mburger") || word.EndsWith("lburger") || word.EndsWith("neuburger") ||
|
||||
word.EndsWith("berger") ||
|
||||
word.EndsWith("winkler") ||
|
||||
word.EndsWith("lsteiner") || word.EndsWith("nsteiner") ||
|
||||
word.EndsWith("städter") ||
|
||||
word.EndsWith("stättner") || word.EndsWith("stettner") ||
|
||||
word.EndsWith("brücker") ||
|
||||
word.EndsWith("further") || word.EndsWith("furter") ||
|
||||
word.EndsWith("aaer") ||
|
||||
word.EndsWith("seer") || word.EndsWith("leer") ||
|
||||
word.EndsWith("tzner") ||
|
||||
word.EndsWith("zeller") ||
|
||||
word.EndsWith("rther") ||
|
||||
word.EndsWith("sdner") ||
|
||||
word.EndsWith("gerner") || word.EndsWith("berner") ||
|
||||
word.EndsWith("frieder") ||
|
||||
word.EndsWith("litzer") || word.EndsWith("mitzer") || word.EndsWith("eutzer") ||
|
||||
word.EndsWith("loner") ||
|
||||
word.EndsWith("skirchner") || word.EndsWith("nkirchner") || word.EndsWith("ingkirchner") ||
|
||||
word.EndsWith("ziger") ||
|
||||
word.EndsWith("ließer") ||
|
||||
word.EndsWith("gerer") ||
|
||||
word.EndsWith("kreuzer") ||
|
||||
word == "Wiener" || word == "Brünner" || word == "Znaimer" || word == "Triester" || word == "Prager" || word == "Berliner" || word == "Basler" ||
|
||||
word == "Retzer" || word == "Tullner" || word == "Badner" ||
|
||||
word == "Gruber" || word == "Sulzer" || word == "Harraser" || word == "Dörfleser" || word == "Neusiedler" ||
|
||||
word == "Dornauer" || word == "Siebenhauser" || word == "Alser" ||
|
||||
word == "Bockenheimer";
|
||||
}
|
||||
|
||||
public static string NormalizeAddress(string address, string? locality = null) {
|
||||
var idx = address.IndexOf(',');
|
||||
if (idx >= 0) address = address[..idx];
|
||||
var (name, nr) = Utils.SplitAddress(address);
|
||||
return (NormalizeStreetName(name, locality) + " " + nr).Trim();
|
||||
}
|
||||
|
||||
public static string NormalizeStreetName(string name, string? locality = null) {
|
||||
name = name.Trim().ToLowerInvariant();
|
||||
if (name == "") {
|
||||
return "";
|
||||
} else if (name == "nr.") {
|
||||
if (locality == null)
|
||||
return "Nr.";
|
||||
name = locality.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
name = name.Replace("hautp", "haupt")
|
||||
.Replace("leop.", "leopold ").Replace("ferd.", "ferdinand ").Replace("gottfr.", "gottfried ")
|
||||
.Replace("heinr.", "heinrich").Replace("joh.", "johann ").Replace("jos.", "josef ")
|
||||
.Replace("msgr.", "monsignore ").Replace("pf.", "pfarrer ")
|
||||
.Replace("präs.", "präsident ");
|
||||
name = name.Replace("str.", "straße").Replace("strasse", "straße")
|
||||
.Replace("bruecke", "brücke").Replace("pl.", "platz")
|
||||
.Replace("gr.", "groß").Replace("kl.", "klein")
|
||||
.Replace("sankt-", "st.").Replace("sankt ", "st.")
|
||||
.Replace("wr.", "wiener ")
|
||||
.Replace(".", ". ");
|
||||
List<string> words = [.. StreetNameWordsRegex.Split(name)
|
||||
.Where(w => !string.IsNullOrWhiteSpace(w))
|
||||
.Select(w => StreetArticles.Contains(w) || StreetPrepositions.Contains(w) ? w : string.Concat(w[0..1].ToUpper(), w.AsSpan(1)))
|
||||
.Select(w => w == "Ddr." ? "DDr." : w == "Lh" || w == "Osr" ? w.ToUpper() : w)
|
||||
];
|
||||
if (words[0] == "Di") words[0] = "DI";
|
||||
|
||||
var temp = "|" + string.Join('|', words) + "|";
|
||||
temp = temp.Replace("|Dipl.|Ing.|", "|DI|").Replace("|Dipl|Ing.|", "|DI|")
|
||||
.Replace("|Kaiser|Ebersdorf|", "|Kaiser-Ebersdorf|")
|
||||
.Replace("|Kalk|Deutz|", "|Kalk-Deutz|").Replace("|Kalk|Deutzer|", "|Kalk-Deutzer|")
|
||||
.Replace("|Hohe|Wand|", "|Hohe Wand|").Replace("|Hohen|Wand|", "|Hohen Wand|").Replace("|Hohewand|", "|Hohe Wand|")
|
||||
.Replace("|Alten|Markt|", "|Altenmarkt|")
|
||||
.Replace("|Kirch|Hof|", "|Kirchhof|").Replace("|Bisc|Hof|", "|Bischof|")
|
||||
.Replace("|Wiener|Neustadt|", "|Wiener Neustadt|").Replace("|Wiener|Neustädter|", "|Wiener Neustädter|")
|
||||
.Replace("|St.|Pölten|", "|St. Pölten|").Replace("|St.|Pöltner|", "|St. Pöltner|")
|
||||
.Replace("|Gross|", "|Groß|")
|
||||
.Replace("|Nr|", "|Nr.|").Replace("|Und|", "|und|");
|
||||
words = [.. temp.Trim('|').Split('|')];
|
||||
|
||||
if (words.Count > 1 && (words[0] == "Groß" || words[0] == "Klein") && !StreetTypes.Contains(words[1])) {
|
||||
var w = words[0] + words[1].ToLowerInvariant();
|
||||
words.RemoveRange(0, 2);
|
||||
words.Insert(0, w);
|
||||
}
|
||||
if (words[0].StartsWith("Groß") || words[0].StartsWith("Klein")) {
|
||||
var repl = LocalityNames.FirstOrDefault(n => n.Replace(" ", "").Replace("-", "").Equals(words[0], StringComparison.InvariantCultureIgnoreCase));
|
||||
if (repl != null) words[0] = repl;
|
||||
}
|
||||
|
||||
if (words.Count > 2 && words[^2] == "in") {
|
||||
// remove suffix "in ..."
|
||||
words.RemoveRange(words.Count - 2, 2);
|
||||
}
|
||||
|
||||
char sep = name.Contains('-') ? '-' : ' ';
|
||||
if (words.Count <= 1) {
|
||||
// single word
|
||||
} else if (StreetTypes.Contains(words[0]) && !StreetTypes.Contains(words[^1])) {
|
||||
// "Straße der ..." / "Straße des ..."
|
||||
sep = ' ';
|
||||
} else if (StreetTypes.Contains(words[^1])) {
|
||||
// "... Straße" / "...straße"
|
||||
var type = words[^1];
|
||||
var isCompound = true;
|
||||
var isAdjective = StreetAdjectives.Contains(words[^2].ToLowerInvariant());
|
||||
var isGeographical = false;
|
||||
var isPerson = false;
|
||||
|
||||
var geo = words.FindLastIndex(IsGeographicalDerivativeName);
|
||||
if (geo >= 0 && geo < words.Count - 2 && words[^2] != "Haupt" && words[^2] != "Bundes" && words[^2] != "Keller") {
|
||||
isPerson = true;
|
||||
isGeographical = true;
|
||||
} else if (words.Any(w => w.Contains(' ') && !w.IsGeographicalDerivativeName())) {
|
||||
isPerson = true;
|
||||
isGeographical = true;
|
||||
} else if (StreetNumbers.Contains(words[0])) {
|
||||
isPerson = true;
|
||||
isGeographical = true;
|
||||
} else if (!StreetPrepositions.Contains(words[0]) && !StreetArticles.Contains(words[0]) && words.FindIndex(StreetPrepositions.Contains) > 0) {
|
||||
isPerson = true;
|
||||
isGeographical = true;
|
||||
}
|
||||
|
||||
if (words.Count(w => w.Contains('.')) >= 2 || words.Contains("van") || words.Contains("von") || words.Contains("St.") ||
|
||||
StreetPersonTitles.Contains(words[0]) || words.Any(w => w.IsGivenName()))
|
||||
{
|
||||
isPerson = true;
|
||||
}
|
||||
|
||||
if (!isPerson && isAdjective || StreetArticles.Contains(words[^2]) || StreetPrepositions.Contains(words[^2])) {
|
||||
isCompound = false;
|
||||
}
|
||||
|
||||
if (!isPerson && words[^2].EndsWith("er") && !StreetLastNames.Contains(words[^2])) {
|
||||
isGeographical = IsGeographicalDerivativeName(words[^2]);
|
||||
} else if (words.Count >= 3 && words.Contains("Bad")) {
|
||||
isPerson = true;
|
||||
isGeographical = true;
|
||||
}
|
||||
|
||||
sep = isPerson ? '-' : ' ';
|
||||
|
||||
if (isPerson && words.Count == 2 && !isGeographical) {
|
||||
isPerson = false;
|
||||
isCompound = true;
|
||||
}
|
||||
if (isGeographical || isPerson) {
|
||||
isCompound = false;
|
||||
}
|
||||
if (isCompound) {
|
||||
var w = words[^2] + type.ToLowerInvariant();
|
||||
words.RemoveRange(words.Count - 2, 2);
|
||||
words.Add(w);
|
||||
}
|
||||
|
||||
if (isPerson) {
|
||||
words = [.. words.Select(w => w.Replace(' ', '-'))];
|
||||
}
|
||||
} else {
|
||||
// unknown
|
||||
}
|
||||
|
||||
if (sep == '-' && StreetPrepositions.Contains(words[0]) && words[0] != "von" && words[0] != "van") {
|
||||
sep = ' ';
|
||||
int i;
|
||||
for (i = 0; StreetPrepositions.Contains(words[i]) && i < words.Count; i++);
|
||||
var word = string.Join('-', words.Skip(i));
|
||||
words = [.. words.Take(i).Append(word)];
|
||||
}
|
||||
|
||||
var norm = string.Join(sep, words);
|
||||
return string.Concat(norm[0..1].ToUpperInvariant(), norm.AsSpan(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
using Elwig.Services;
|
||||
using Elwig.Windows;
|
||||
using System;
|
||||
using System.Drawing.Printing;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public static class Pdf {
|
||||
|
||||
public static Task Init(Action? evtHandler = null) {
|
||||
public static void Init(Action? evtHandler = null) {
|
||||
PdfiumNative.FPDF_InitLibrary();
|
||||
evtHandler?.Invoke();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task Cleanup() {
|
||||
public static void Cleanup() {
|
||||
PdfiumNative.FPDF_DestroyLibrary();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static void Show(TempFile file, string title) {
|
||||
@@ -32,24 +28,13 @@ namespace Elwig.Helpers.Printing {
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task Print(string path, int copies = 1, bool doublePaged = false) {
|
||||
await Print(path, new() {
|
||||
Copies = (short)copies,
|
||||
Collate = true,
|
||||
Duplex = doublePaged ? Duplex.Vertical : Duplex.Simplex,
|
||||
});
|
||||
}
|
||||
|
||||
public static Task Print(string path, PrinterSettings settings) {
|
||||
public static void Print(string path, int copies = 1, bool doublePaged = false) {
|
||||
try {
|
||||
using var printDoc = new PdfPrintDocument(path) {
|
||||
PrinterSettings = settings,
|
||||
};
|
||||
printDoc.Print();
|
||||
} catch (Exception e) {
|
||||
MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
var printer = new PdfPrinter();
|
||||
printer.Print(path, copies, doublePaged);
|
||||
} catch (Exception exc) {
|
||||
InteractionService.ShowException("Fehler beim Drucken", "Beim Drucken ist ein Fehler aufgetreten", exc);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing.Printing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public class PdfPrintDocument : PrintDocument {
|
||||
|
||||
private readonly IntPtr _handle;
|
||||
private readonly int _pageCount;
|
||||
private readonly double _dpi;
|
||||
|
||||
private int _currentPage;
|
||||
|
||||
public PdfPrintDocument(string path, string? password = null, double dpi = 300.0) :
|
||||
base() {
|
||||
_handle = PdfiumNative.FPDF_LoadDocument(path, password);
|
||||
_pageCount = PdfiumNative.FPDF_GetPageCount(_handle);
|
||||
_dpi = dpi;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
PdfiumNative.FPDF_CloseDocument(_handle);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void OnBeginPrint(PrintEventArgs evt) {
|
||||
_currentPage = (PrinterSettings.FromPage != 0) ? (PrinterSettings.FromPage - 1) : 0;
|
||||
base.OnBeginPrint(evt);
|
||||
}
|
||||
|
||||
protected override void OnPrintPage(PrintPageEventArgs evt) {
|
||||
if (_currentPage < _pageCount) {
|
||||
IntPtr page = PdfiumNative.FPDF_LoadPage(_handle, _currentPage);
|
||||
double width = PdfiumNative.FPDF_GetPageWidth(page);
|
||||
double height = PdfiumNative.FPDF_GetPageHeight(page);
|
||||
int pixelWidth = (int)(width / 72.0 * _dpi);
|
||||
int pixelHeight = (int)(height / 72.0 * _dpi);
|
||||
|
||||
IntPtr bitmap = PdfiumNative.FPDFBitmap_Create(pixelWidth, pixelHeight, 1);
|
||||
PdfiumNative.FPDF_RenderPageBitmap(bitmap, page, 0, 0, pixelWidth, pixelHeight, 0, 0);
|
||||
|
||||
IntPtr buffer = PdfiumNative.FPDFBitmap_GetBuffer(bitmap);
|
||||
int stride = PdfiumNative.FPDFBitmap_GetStride(bitmap);
|
||||
using (var bmp = new Bitmap(pixelWidth, pixelHeight, stride, PixelFormat.Format32bppArgb, buffer)) {
|
||||
evt.Graphics?.DrawImage(bmp, evt.PageBounds);
|
||||
}
|
||||
_currentPage++;
|
||||
|
||||
PdfiumNative.FPDFBitmap_Destroy(bitmap);
|
||||
PdfiumNative.FPDF_ClosePage(page);
|
||||
}
|
||||
evt.HasMorePages = _currentPage < ((PrinterSettings.ToPage == 0) ? _pageCount : Math.Min(PrinterSettings.ToPage, _pageCount));
|
||||
base.OnPrintPage(evt);
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class PdfiumNative {
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_InitLibrary();
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_DestroyLibrary();
|
||||
|
||||
[LibraryImport("pdfium.dll", StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial IntPtr FPDF_LoadDocument(string filePath, string? password);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_CloseDocument(IntPtr document);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial int FPDF_GetPageCount(IntPtr document);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial IntPtr FPDF_LoadPage(IntPtr document, int pageIndex);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_ClosePage(IntPtr page);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial double FPDF_GetPageWidth(IntPtr page);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial double FPDF_GetPageHeight(IntPtr page);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial IntPtr FPDFBitmap_Create(int width, int height, int alpha);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDFBitmap_Destroy(IntPtr bitmap);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial IntPtr FPDFBitmap_GetBuffer(IntPtr bitmap);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial int FPDFBitmap_GetStride(IntPtr bitmap);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_RenderPageBitmap(IntPtr bitmap, IntPtr page, int start_x, int start_y, int size_x, int size_y, int rotate, int flags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Printing;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public class PdfPrinter {
|
||||
|
||||
public string PrinterName { get; init; }
|
||||
|
||||
public PdfPrinter(string? printerName = null) {
|
||||
PrinterName = printerName ?? GetDefaultPrinterName() ?? throw new InvalidOperationException("No default Windows printer is configured.");
|
||||
}
|
||||
|
||||
public void Print(string pdfPath, int copies = 1, bool doublePaged = false) {
|
||||
if (!File.Exists(pdfPath)) throw new FileNotFoundException("PDF file not found.", pdfPath);
|
||||
|
||||
IntPtr document = PdfiumNative.FPDF_LoadDocument(pdfPath, null);
|
||||
if (document == IntPtr.Zero) throw new InvalidOperationException("PDFium could not open the PDF.");
|
||||
|
||||
try {
|
||||
int pageCount = PdfiumNative.FPDF_GetPageCount(document);
|
||||
if (pageCount <= 0)
|
||||
return;
|
||||
|
||||
using var printer = new Win32Printer.Printer(PrinterName);
|
||||
printer.TrySetDuplex(doublePaged ? Duplexing.TwoSidedLongEdge : Duplexing.OneSided);
|
||||
printer.StartDocument(Path.GetFileName(pdfPath));
|
||||
bool documentStarted = true;
|
||||
try {
|
||||
for (int i = 0; i < copies; i++) {
|
||||
for (int j = 0; j < pageCount; j++) {
|
||||
PrintPage(document, printer, j);
|
||||
}
|
||||
}
|
||||
printer.EndDocument();
|
||||
documentStarted = false;
|
||||
} finally {
|
||||
if (documentStarted) printer.AbortDocument();
|
||||
}
|
||||
} finally {
|
||||
PdfiumNative.FPDF_CloseDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintPage(IntPtr document, Win32Printer.Printer printer, int pageIndex) {
|
||||
if (!PdfiumNative.FPDF_GetPageSizeByIndex(document, pageIndex, out double pageWidthPt, out double pageHeightPt)) {
|
||||
throw new InvalidOperationException($"Unable to obtain PDF page {pageIndex} size.");
|
||||
}
|
||||
|
||||
IntPtr page = PdfiumNative.FPDF_LoadPage(document, pageIndex);
|
||||
if (page == IntPtr.Zero)
|
||||
throw new InvalidOperationException($"Unable to load PDF page {pageIndex}.");
|
||||
|
||||
try {
|
||||
int renderWidth = (int)Math.Round(pageWidthPt / 72.0 * printer.DpiX);
|
||||
int renderHeight = (int)Math.Round(pageHeightPt / 72.0 * printer.DpiY);
|
||||
int offsetX = (printer.PrintableWidth - renderWidth) / 2;
|
||||
int offsetY = (printer.PrintableHeight - renderHeight) / 2;
|
||||
|
||||
printer.StartPage();
|
||||
try {
|
||||
PdfiumNative.FPDF_RenderPage(printer.Hdc, page, offsetX, offsetY, renderWidth, renderHeight, 0, PdfiumNative.FPDF_PRINTING | PdfiumNative.FPDF_ANNOT);
|
||||
} finally {
|
||||
printer.EndPage();
|
||||
}
|
||||
} finally {
|
||||
PdfiumNative.FPDF_ClosePage(page);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetDefaultPrinterName() {
|
||||
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\Windows");
|
||||
string? printer = key?.GetValue("Device") as string;
|
||||
if (string.IsNullOrWhiteSpace(printer))
|
||||
return null;
|
||||
|
||||
// Device has the form:
|
||||
// Printer Name,winspool,PORT
|
||||
int comma = printer.IndexOf(',');
|
||||
return comma >= 0 ? printer[..comma] : printer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
internal partial class PdfiumNative {
|
||||
|
||||
public const int FPDF_ANNOT = 0x01;
|
||||
public const int FPDF_PRINTING = 0x800;
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_InitLibrary();
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_DestroyLibrary();
|
||||
|
||||
[LibraryImport("pdfium.dll", StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial IntPtr FPDF_LoadDocument(string filePath, string? password);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_CloseDocument(IntPtr document);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial int FPDF_GetPageCount(IntPtr document);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial IntPtr FPDF_LoadPage(IntPtr document, int pageIndex);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_ClosePage(IntPtr page);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool FPDF_GetPageSizeByIndex(IntPtr document, int pageIndex, out double width, out double height);
|
||||
|
||||
[LibraryImport("pdfium.dll")]
|
||||
public static partial void FPDF_RenderPage(IntPtr hdc, IntPtr page, int startX, int startY, int sizeX, int sizeY, int rotate, int flags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Printing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Elwig.Helpers.Printing {
|
||||
public partial class Win32Printer {
|
||||
|
||||
private const int DM_OUT_BUFFER = 2;
|
||||
|
||||
private const int HORZRES = 8;
|
||||
private const int VERTRES = 10;
|
||||
private const int LOGPIXELSX = 88;
|
||||
private const int LOGPIXELSY = 90;
|
||||
private const int PHYSICALWIDTH = 110;
|
||||
private const int PHYSICALHEIGHT = 111;
|
||||
private const int PHYSICALOFFSETX = 112;
|
||||
private const int PHYSICALOFFSETY = 113;
|
||||
|
||||
private const int DM_DUPLEX = 0x1000;
|
||||
private const short DMDUP_SIMPLEX = 1;
|
||||
private const short DMDUP_VERTICAL = 2;
|
||||
private const short DMDUP_HORIZONTAL = 3;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct DOCINFO {
|
||||
public int cbSize;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string? lpszDocName;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string? lpszOutput;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string? lpszDatatype;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct DEVMODE {
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string dmDeviceName;
|
||||
|
||||
public short dmSpecVersion;
|
||||
public short dmDriverVersion;
|
||||
public short dmSize;
|
||||
public short dmDriverExtra;
|
||||
public int dmFields;
|
||||
|
||||
public short dmOrientation;
|
||||
public short dmPaperSize;
|
||||
public short dmPaperLength;
|
||||
public short dmPaperWidth;
|
||||
public short dmScale;
|
||||
public short dmCopies;
|
||||
public short dmDefaultSource;
|
||||
public short dmPrintQuality;
|
||||
public short dmColor;
|
||||
public short dmDuplex;
|
||||
public short dmYResolution;
|
||||
public short dmTTOption;
|
||||
public short dmCollate;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string dmFormName;
|
||||
|
||||
public short dmLogPixels;
|
||||
public int dmBitsPerPel;
|
||||
public int dmPelsWidth;
|
||||
public int dmPelsHeight;
|
||||
public int dmDisplayFlags;
|
||||
public int dmDisplayFrequency;
|
||||
public int dmICMMethod;
|
||||
public int dmICMIntent;
|
||||
public int dmMediaType;
|
||||
public int dmDitherType;
|
||||
public int dmReserved1;
|
||||
public int dmReserved2;
|
||||
public int dmPanningWidth;
|
||||
public int dmPanningHeight;
|
||||
}
|
||||
|
||||
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);
|
||||
|
||||
[DllImport("winspool.drv", SetLastError = true)]
|
||||
private static extern bool ClosePrinter(IntPtr hPrinter);
|
||||
|
||||
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int DocumentProperties(IntPtr hwnd, IntPtr hPrinter, string pDeviceName, IntPtr pDevModeOutput, IntPtr pDevModeInput, int fMode);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr CreateDC(string? pDriver, string? pDevice, string? pOutput, IntPtr pInitData);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
private static extern bool DeleteDC(IntPtr hdc);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
private static extern int GetDeviceCaps(IntPtr hdc, int index);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int StartDoc(IntPtr hdc, ref DOCINFO lpdi);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
private static extern int EndDoc(IntPtr hdc);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
private static extern int AbortDoc(IntPtr hdc);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
private static extern int StartPage(IntPtr hdc);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
private static extern int EndPage(IntPtr hdc);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr GlobalFree(IntPtr hMem);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GlobalLock(IntPtr hMem);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool GlobalUnlock(IntPtr hMem);
|
||||
|
||||
public class Printer : IDisposable {
|
||||
private readonly string _printerName;
|
||||
private readonly IntPtr _printerHandle;
|
||||
private readonly IntPtr _devMode;
|
||||
private readonly IntPtr _hdc;
|
||||
|
||||
public IntPtr Hdc => _hdc;
|
||||
|
||||
public int PrintableWidth => GetDeviceCaps(_hdc, HORZRES);
|
||||
public int PrintableHeight => GetDeviceCaps(_hdc, VERTRES);
|
||||
public int DpiX => GetDeviceCaps(_hdc, LOGPIXELSX);
|
||||
public int DpiY => GetDeviceCaps(_hdc, LOGPIXELSY);
|
||||
public int PhysicalWidth => GetDeviceCaps(_hdc, PHYSICALWIDTH);
|
||||
public int PhysicalHeight => GetDeviceCaps(_hdc, PHYSICALHEIGHT);
|
||||
public int PhysicalOffsetX => GetDeviceCaps(_hdc, PHYSICALOFFSETX);
|
||||
public int PhysicalOffsetY => GetDeviceCaps(_hdc, PHYSICALOFFSETY);
|
||||
|
||||
public Printer(string printerName) {
|
||||
_printerName = printerName;
|
||||
|
||||
if (!OpenPrinter(_printerName, out _printerHandle, IntPtr.Zero)) {
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to open printer '{printerName}'.");
|
||||
}
|
||||
|
||||
try {
|
||||
_devMode = CreateDevMode();
|
||||
_hdc = CreateDC(null, _printerName, null, _devMode);
|
||||
if (_hdc == IntPtr.Zero)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to create printer DC for '{_printerName}'.");
|
||||
} catch {
|
||||
if (_devMode != IntPtr.Zero) Marshal.FreeHGlobal(_devMode);
|
||||
ClosePrinter(_printerHandle);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetDuplex(Duplexing duplex) {
|
||||
var devMode = Marshal.PtrToStructure<DEVMODE>(_devMode);
|
||||
switch (duplex) {
|
||||
case Duplexing.OneSided: devMode.dmDuplex = DMDUP_SIMPLEX; break;
|
||||
case Duplexing.TwoSidedShortEdge: devMode.dmDuplex = DMDUP_HORIZONTAL; break;
|
||||
case Duplexing.TwoSidedLongEdge: devMode.dmDuplex = DMDUP_VERTICAL; break;
|
||||
}
|
||||
if ((devMode.dmFields & DM_DUPLEX) != 0) {
|
||||
Marshal.StructureToPtr(devMode, _devMode, false);
|
||||
return true;
|
||||
} else {
|
||||
devMode.dmFields |= DM_DUPLEX;
|
||||
Marshal.StructureToPtr(devMode, _devMode, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr CreateDevMode() {
|
||||
int size = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, IntPtr.Zero, IntPtr.Zero, 0);
|
||||
if (size <= 0)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "DocumentProperties failed.");
|
||||
|
||||
IntPtr devMode = Marshal.AllocHGlobal(size);
|
||||
int result = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, devMode, IntPtr.Zero, DM_OUT_BUFFER);
|
||||
if (result < 0) {
|
||||
Marshal.FreeHGlobal(devMode);
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to obtain printer DEVMODE.");
|
||||
}
|
||||
|
||||
return devMode;
|
||||
}
|
||||
|
||||
public void StartDocument(string documentName) {
|
||||
var docInfo = new DOCINFO {
|
||||
cbSize = Marshal.SizeOf<DOCINFO>(),
|
||||
lpszDocName = documentName,
|
||||
lpszDatatype = null,
|
||||
lpszOutput = null
|
||||
};
|
||||
|
||||
if (StartDoc(_hdc, ref docInfo) <= 0)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "StartDoc failed.");
|
||||
}
|
||||
|
||||
public void StartPage() {
|
||||
if (Win32Printer.StartPage(_hdc) <= 0)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "StartPage failed.");
|
||||
}
|
||||
|
||||
public void EndPage() {
|
||||
if (Win32Printer.EndPage(_hdc) <= 0)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "EndPage failed.");
|
||||
}
|
||||
|
||||
public void EndDocument() {
|
||||
if (EndDoc(_hdc) <= 0)
|
||||
throw new Win32Exception( Marshal.GetLastWin32Error(), "EndDoc failed.");
|
||||
}
|
||||
|
||||
public void AbortDocument() {
|
||||
AbortDoc(_hdc);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (_hdc != IntPtr.Zero)
|
||||
DeleteDC(_hdc);
|
||||
|
||||
if (_devMode != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(_devMode);
|
||||
|
||||
if (_printerHandle != IntPtr.Zero)
|
||||
ClosePrinter(_printerHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
-58
@@ -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,6 +31,7 @@ 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 {
|
||||
@@ -44,7 +45,6 @@ namespace Elwig.Helpers {
|
||||
public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
|
||||
public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 6 ? 1 : 0);
|
||||
public static int FollowingSeason => DateTime.Now.Year + (DateTime.Now.Month >= 11 ? 1 : 0);
|
||||
public static DateTime Today => (DateTime.Now.Hour >= 3) ? DateTime.Today : DateTime.Today.AddDays(-1);
|
||||
|
||||
[GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)]
|
||||
private static partial Regex GeneratedSerialRegex();
|
||||
@@ -209,16 +209,29 @@ namespace Elwig.Helpers {
|
||||
return Regex.Replace(iban.Trim(), ".{4}", "$0 ").Trim();
|
||||
}
|
||||
|
||||
public static void RunBackground(string title, Func<Task> a) {
|
||||
public static void RunBackground(string title, Func<Task> function) {
|
||||
Task.Run(async () => {
|
||||
try {
|
||||
await a();
|
||||
} catch (Exception e) {
|
||||
MessageBox.Show(e.ToString(), title, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
await function();
|
||||
} catch (Exception exc) {
|
||||
InteractionService.ShowException(title, exc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
@@ -290,6 +303,11 @@ namespace Elwig.Helpers {
|
||||
return d.ShowDialog() == true ? (d.Weight, d.Reason) : null;
|
||||
}
|
||||
|
||||
public static int? ShowTareCorrectionDialog(int? actualTare, int? targetTare = null) {
|
||||
var d = new TareCorrectionDialog(actualTare, targetTare);
|
||||
return d.ShowDialog() == true ? d.TargetTare - d.ActualTare : null;
|
||||
}
|
||||
|
||||
public static (string?, int[])? ShowDeliverySplittingDialog(Delivery delivery) {
|
||||
var d = new DeliverySplittingDialog(delivery);
|
||||
return d.ShowDialog() == true ? (d.MgNr?.ToString() ?? d.LsNr, d.Weights ?? []) : null;
|
||||
@@ -305,7 +323,7 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
public class Footer {
|
||||
private readonly List<List<object>> Items = [[]];
|
||||
private readonly List<List<List<object>>> Items = [[[]]];
|
||||
private readonly string LineBreak;
|
||||
private readonly string Separator;
|
||||
|
||||
@@ -316,7 +334,7 @@ namespace Elwig.Helpers {
|
||||
|
||||
public Footer Item(object? text) {
|
||||
if (text == null) return this;
|
||||
Items[^1].Add(text);
|
||||
Items[^1][^1].Add(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -324,13 +342,38 @@ namespace Elwig.Helpers {
|
||||
return text == null ? this : Item($"{name}: {text}");
|
||||
}
|
||||
|
||||
public Footer NextGroup() {
|
||||
Items[^1].Add([]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Footer NextLine() {
|
||||
Items.Add([]);
|
||||
Items.Add([[]]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Footer RebalanceLines() {
|
||||
for (int i = 0; i < Items.Count - 1; i++) {
|
||||
var groups = Items[i];
|
||||
var nextGroups = Items[i + 1];
|
||||
if (groups.Count <= 1) continue;
|
||||
var g = groups[^1];
|
||||
var gl = g.Sum(i => (i is Text txt ? txt.GetText().Length : i.ToString()?.Length ?? 0) + Separator.Length);
|
||||
var l1a = groups.SelectMany(g => g).Sum(i => (i is Text txt ? txt.GetText().Length : i.ToString()?.Length ?? 0) + Separator.Length) - Separator.Length;
|
||||
var l2a = nextGroups.SelectMany(g => g).Sum(i => (i is Text txt ? txt.GetText().Length : i.ToString()?.Length ?? 0) + Separator.Length) - Separator.Length;
|
||||
var l1b = l1a - gl;
|
||||
var l2b = l2a + gl;
|
||||
if (Math.Abs(l1b - l2b) <= Math.Abs(l1a - l2a)) {
|
||||
Items[i].Remove(g);
|
||||
Items[i + 1].Insert(0, g);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return string.Join(LineBreak, Items.Select(l => string.Join(Separator, l.ToString())));
|
||||
return string.Join(LineBreak, Items.Select(l => string.Join(Separator, l.SelectMany(g => g).ToString())));
|
||||
}
|
||||
|
||||
public IList<ILeafElement> ToLeafElements() {
|
||||
@@ -339,10 +382,12 @@ namespace Elwig.Helpers {
|
||||
foreach (var line in Items) {
|
||||
if (!first1) l.Add(new Text(LineBreak));
|
||||
var first2 = true;
|
||||
foreach (var item in line) {
|
||||
if (!first2) l.Add(new Text(Separator));
|
||||
l.Add(item as ILeafElement ?? new Text(item.ToString() ?? ""));
|
||||
first2 = false;
|
||||
foreach (var group in line) {
|
||||
foreach (var item in group) {
|
||||
if (!first2) l.Add(new Text(Separator));
|
||||
l.Add(item as ILeafElement ?? new Text(item.ToString() ?? ""));
|
||||
first2 = false;
|
||||
}
|
||||
}
|
||||
first1 = false;
|
||||
}
|
||||
@@ -413,8 +458,8 @@ namespace Elwig.Helpers {
|
||||
return output.OrderByDescending(l => l.Count());
|
||||
}
|
||||
|
||||
public static List<RawVaribute> GetVaributes(AppDbContext ctx, int year, bool onlyDelivered = true) {
|
||||
var varieties = ctx.WineVarieties.Select(v => new RawVaribute(v.SortId, "", null)).ToList();
|
||||
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();
|
||||
var delivered = ctx.DeliveryParts
|
||||
.Where(d => d.Year == year)
|
||||
.Select(d => new RawVaribute(d.SortId, d.AttrId ?? "", d.CultId ?? ""))
|
||||
@@ -423,13 +468,11 @@ namespace Elwig.Helpers {
|
||||
return [.. (onlyDelivered ? delivered : delivered.Union(varieties)).Order()];
|
||||
}
|
||||
|
||||
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();
|
||||
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))];
|
||||
}
|
||||
|
||||
[LibraryImport("wininet.dll")]
|
||||
@@ -445,7 +488,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));
|
||||
@@ -504,7 +547,7 @@ namespace Elwig.Helpers {
|
||||
public static async Task<SmtpClient?> GetSmtpClient() {
|
||||
if (App.Config.Smtp == null)
|
||||
return null;
|
||||
var (host, port, mode, username, password, _) = App.Config.Smtp.Value;
|
||||
var (host, port, mode, username, password, _, _) = App.Config.Smtp.Value;
|
||||
var client = new SmtpClient();
|
||||
await client.ConnectAsync(host, port, mode == "starttls" ? SecureSocketOptions.StartTls : SecureSocketOptions.None);
|
||||
await client.AuthenticateAsync(username, password);
|
||||
@@ -524,6 +567,7 @@ namespace Elwig.Helpers {
|
||||
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
|
||||
msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address)));
|
||||
msg.Subject = subject;
|
||||
msg.Date = DateTime.Now;
|
||||
var body = new Multipart("mixed") {
|
||||
new TextPart("plain") { Text = text }
|
||||
};
|
||||
@@ -534,12 +578,12 @@ namespace Elwig.Helpers {
|
||||
msg.Body = body;
|
||||
await client!.SendAsync(msg);
|
||||
await AddSentMails([(
|
||||
"email", member.MgNr, member.AdministrativeName,
|
||||
member.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
|
||||
subject, docs.Select(d => d.Title).ToArray()
|
||||
)]);
|
||||
"email", member.MgNr, member.AdministrativeName,
|
||||
member.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).ToArray(),
|
||||
subject, docs.Select(d => d.Title).ToArray()
|
||||
)]);
|
||||
} catch (Exception exc) {
|
||||
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowException(exc);
|
||||
return false;
|
||||
} finally {
|
||||
if (client != null)
|
||||
@@ -553,41 +597,40 @@ 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) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowError("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!");
|
||||
return;
|
||||
}
|
||||
await doc.Generate();
|
||||
using (var ctx = new AppDbContext()) {
|
||||
await doc.Generate(ctx);
|
||||
}
|
||||
await doc.Print();
|
||||
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
InteractionService.ShowError("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!");
|
||||
return;
|
||||
}
|
||||
await doc.Generate();
|
||||
using (var ctx = new AppDbContext()) {
|
||||
await doc.Generate(ctx);
|
||||
}
|
||||
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
|
||||
if (success)
|
||||
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);
|
||||
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.");
|
||||
} else if (mode == ExportMode.SavePdf) {
|
||||
if (doc.IsPreview) {
|
||||
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!",
|
||||
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
InteractionService.ShowWarning("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!");
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
await doc.Generate();
|
||||
using (var ctx = new AppDbContext()) {
|
||||
await doc.Generate(ctx);
|
||||
}
|
||||
doc.Show();
|
||||
}
|
||||
}
|
||||
@@ -619,16 +662,16 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
}
|
||||
|
||||
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments() => ActiveAreaCommitments(CurrentYear);
|
||||
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments(int year) =>
|
||||
public static Expression<Func<AreaCmt, bool>> ActiveAreaCommitments() => ActiveAreaCommitments(CurrentYear);
|
||||
public static Expression<Func<AreaCmt, bool>> ActiveAreaCommitments(int year) =>
|
||||
c => (c.YearFrom == null || c.YearFrom <= year) && (c.YearTo == null || c.YearTo >= year);
|
||||
|
||||
public static IQueryable<AreaCom> ActiveAreaCommitments(IQueryable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear);
|
||||
public static IQueryable<AreaCom> ActiveAreaCommitments(IQueryable<AreaCom> query, int year) =>
|
||||
public static IQueryable<AreaCmt> ActiveAreaCommitments(IQueryable<AreaCmt> query) => ActiveAreaCommitments(query, CurrentYear);
|
||||
public static IQueryable<AreaCmt> ActiveAreaCommitments(IQueryable<AreaCmt> query, int year) =>
|
||||
query.Where(ActiveAreaCommitments(year));
|
||||
|
||||
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear);
|
||||
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query, int year) =>
|
||||
public static IEnumerable<AreaCmt> ActiveAreaCommitments(IEnumerable<AreaCmt> query) => ActiveAreaCommitments(query, CurrentYear);
|
||||
public static IEnumerable<AreaCmt> ActiveAreaCommitments(IEnumerable<AreaCmt> query, int year) =>
|
||||
query.Where(c => ActiveAreaCommitments(year).Invoke(c));
|
||||
|
||||
public static async Task<(DateTime DateTime, string Type, int MgNr, string Name, string[] Addresses, string Subject, string[] Attachments)[]> GetSentMails(DateOnly? fromDate = null, DateOnly? toDate = null) {
|
||||
@@ -659,7 +702,7 @@ namespace Elwig.Helpers {
|
||||
p[2],
|
||||
int.Parse(p[3]),
|
||||
p[4],
|
||||
p[5].Split(',').Select(a => a.Replace(" | ", "\n")).ToArray(),
|
||||
[.. p[5].Split(',').Select(a => a.Replace(" | ", "\n"))],
|
||||
p[6],
|
||||
p[7].Split(',')
|
||||
));
|
||||
@@ -701,5 +744,10 @@ namespace Elwig.Helpers {
|
||||
var filename = Path.Combine(App.MailsPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{NormalizeFileName(subject)}.txt");
|
||||
await File.WriteAllTextAsync(filename, $"# {subject}\r\n# Vorgesehene Empfänger: {recipients}\r\n\r\n" + body, Utils.UTF8);
|
||||
}
|
||||
|
||||
public static string GetTitle(string window, IEnumerable<string> filterNames, IEnumerable<string> filter) {
|
||||
var f = string.Join(" / ", filterNames.Union(filter.Select(f => $"\"{f}\"")));
|
||||
return window + (f.Length == 0 ? "" : ": " + f) + " - Elwig";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+75
-22
@@ -136,8 +136,7 @@ namespace Elwig.Helpers {
|
||||
return new(false, "PLZ zu kurz");
|
||||
}
|
||||
using var ctx = new AppDbContext();
|
||||
int plz = int.Parse(input.Text);
|
||||
if (ctx.Postleitzahlen.Find(plz) == null) {
|
||||
if (!int.TryParse(input.Text, out var plz) || ctx.Postleitzahlen.Find(plz) == null) {
|
||||
return new(false, "Ungültige PLZ");
|
||||
}
|
||||
return new(true, null);
|
||||
@@ -423,8 +422,7 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
using var ctx = new AppDbContext();
|
||||
int nr = int.Parse(input.Text);
|
||||
if (!ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
|
||||
if (!int.TryParse(input.Text, out var nr) || !ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
|
||||
return new(false, "Ungültige Mitgliedsnummer");
|
||||
}
|
||||
|
||||
@@ -440,8 +438,7 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
using var ctx = new AppDbContext();
|
||||
int nr = int.Parse(input.Text);
|
||||
if (nr != m?.MgNr && ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
|
||||
if (!int.TryParse(input.Text, out var nr) || nr != m?.MgNr && ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
|
||||
return new(false, "Mitgliedsnummer wird bereits verwendet");
|
||||
}
|
||||
|
||||
@@ -476,7 +473,7 @@ namespace Elwig.Helpers {
|
||||
return res;
|
||||
} else if (!required && input.Text.Length == 0) {
|
||||
return new(true, null);
|
||||
} else if (!ctx.MgNrExists(int.Parse(input.Text)).GetAwaiter().GetResult()) {
|
||||
} else if (!int.TryParse(input.Text, out var nr) ||!ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
|
||||
return new(false, "Ein Mitglied mit dieser Mitgliedsnummer existiert nicht");
|
||||
}
|
||||
|
||||
@@ -537,7 +534,7 @@ namespace Elwig.Helpers {
|
||||
return (parts[0]?.Length == 4) ? new(true, null) : new(false, "Datum ist ungültig");
|
||||
} else if (p == 1) {
|
||||
// only month and year provided
|
||||
int m = parts[0] != null && parts[0] != "" ? int.Parse(parts[0] ?? "0") : 0;
|
||||
int m = parts[0] != null && parts[0] != "" && int.TryParse(parts[0], out var v) ? v : 0;
|
||||
if (parts[1]?.Length != 4 || parts[0]?.Length != 2 || m < 1 || m > 12)
|
||||
return new(false, "Datum ist ungültig");
|
||||
return new(true, null);
|
||||
@@ -580,7 +577,7 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
}
|
||||
|
||||
public static ValidationResult CheckFbNr(TextBox input, bool required, AreaCom? c) {
|
||||
public static ValidationResult CheckFbNr(TextBox input, bool required, AreaCmtContract? c) {
|
||||
var res = CheckInteger(input, required);
|
||||
if (!res.IsValid) {
|
||||
return res;
|
||||
@@ -589,8 +586,7 @@ namespace Elwig.Helpers {
|
||||
}
|
||||
|
||||
using var ctx = new AppDbContext();
|
||||
int nr = int.Parse(input.Text);
|
||||
if (nr != c?.FbNr && ctx.FbNrExists(nr).GetAwaiter().GetResult()) {
|
||||
if (!int.TryParse(input.Text, out var nr) || (nr != c?.FbNr && ctx.FbNrExists(nr).GetAwaiter().GetResult())) {
|
||||
return new(false, "Flächenbindungsnummer wird bereits verwendet");
|
||||
}
|
||||
|
||||
@@ -605,8 +601,7 @@ namespace Elwig.Helpers {
|
||||
return new(true, null);
|
||||
}
|
||||
|
||||
var oe = double.Parse(input.Text);
|
||||
if (oe < 10 || oe >= 300) {
|
||||
if (!double.TryParse(input.Text, out var oe) || oe < 10 || oe >= 300) {
|
||||
return new(false, "Ungültiger Oechsle-Wert");
|
||||
}
|
||||
|
||||
@@ -621,14 +616,44 @@ namespace Elwig.Helpers {
|
||||
return new(true, null);
|
||||
}
|
||||
|
||||
var kmw = double.Parse(input.Text);
|
||||
if (kmw < 5 || kmw >= 50) {
|
||||
if (!double.TryParse(input.Text, out var kmw) || kmw < 5 || kmw >= 50) {
|
||||
return new(false, "Ungültiger KMW-Wert");
|
||||
}
|
||||
|
||||
return new(true, null);
|
||||
}
|
||||
|
||||
public static ValidationResult CheckOrganicOperatorId(TextBox input, bool required) {
|
||||
string text = "";
|
||||
int pos = input.CaretIndex;
|
||||
for (int i = 0, v = 0; i < input.Text.Length; i++) {
|
||||
char ch = input.Text[i];
|
||||
if (char.IsAsciiDigit(ch)) {
|
||||
if (v == 3 && text[^1] != '-')
|
||||
text += "-";
|
||||
v++;
|
||||
text += ch;
|
||||
}
|
||||
|
||||
if (i == input.CaretIndex - 1) {
|
||||
pos = text.Length;
|
||||
} else if (v >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
input.Text = text;
|
||||
input.CaretIndex = pos;
|
||||
|
||||
if (text.Length == 0) {
|
||||
return required ? new(false, "OOC-ID ist nicht optional") : new(true, null);
|
||||
} else if (!text.StartsWith("040-") || text.Length != 11) {
|
||||
return new(false, "OOC-ID ist ungültig");
|
||||
}
|
||||
|
||||
return new(true, null);
|
||||
}
|
||||
|
||||
public static ValidationResult CheckOrganicAuthorityCode(TextBox input, bool required) {
|
||||
string text = "";
|
||||
int pos = input.CaretIndex;
|
||||
@@ -637,15 +662,18 @@ namespace Elwig.Helpers {
|
||||
if (v < 2 && char.IsAsciiLetter(ch)) {
|
||||
v++;
|
||||
text += ch;
|
||||
} else if ((v == 2 || v == 6) && ch == '-') {
|
||||
v++;
|
||||
} else if ((v == 2 || v == 5) && ch == '-' && text[^1] != '-') {
|
||||
text += ch;
|
||||
} else if (v >= 2 && char.IsLetterOrDigit(ch)) {
|
||||
} else if (v >= 2 && v <= 4 && char.IsLetter(ch)) {
|
||||
if (v == 2 && text[^1] != '-')
|
||||
text += "-";
|
||||
if (text.StartsWith("AT")) {
|
||||
if (v == 3 && ch == 'B' || v == 4 && ch == 'I' || v == 5 && ch == 'O') {
|
||||
if (v == 2 && ch == 'B' || v == 3 && ch == 'I' || v == 4 && ch == 'O') {
|
||||
v++;
|
||||
text += ch;
|
||||
} else if (v > 6 && char.IsAsciiDigit(ch)) {
|
||||
}
|
||||
} else if (text.StartsWith("DE")) {
|
||||
if (v == 2 && ch == 'Ö' || v == 3 && ch == 'K' || v == 4 && ch == 'O') {
|
||||
v++;
|
||||
text += ch;
|
||||
}
|
||||
@@ -653,11 +681,16 @@ namespace Elwig.Helpers {
|
||||
v++;
|
||||
text += ch;
|
||||
}
|
||||
} else if (v >= 5 && v <= 7 && char.IsAsciiDigit(ch)) {
|
||||
if (v == 5 && text[^1] != '-')
|
||||
text += "-";
|
||||
v++;
|
||||
text += ch;
|
||||
}
|
||||
|
||||
if (i == input.CaretIndex - 1) {
|
||||
pos = text.Length;
|
||||
} else if (text.StartsWith("AT") && v >= 10) {
|
||||
} else if ((text.StartsWith("AT") || text.StartsWith("DE")) && v >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -668,7 +701,7 @@ namespace Elwig.Helpers {
|
||||
if (text.Length == 0)
|
||||
return required ? new(false, "Bio-Kontrollstellen-Code ist nicht optional") : new(true, null);
|
||||
|
||||
if (text.StartsWith("AT")) {
|
||||
if (text.StartsWith("AT") || text.StartsWith("DE")) {
|
||||
if (text.Length != 10) {
|
||||
return new(false, "Bio-Kontrollstellen-Code ist ungültig");
|
||||
}
|
||||
@@ -678,5 +711,25 @@ namespace Elwig.Helpers {
|
||||
|
||||
return new(true, null);
|
||||
}
|
||||
|
||||
public static ValidationResult CheckAddress(TextBox input, bool required, string? locality = null) {
|
||||
var text = input.Text;
|
||||
var (name, nr) = Utils.SplitAddress(text);
|
||||
if (!string.IsNullOrWhiteSpace(nr)) {
|
||||
var street = Normalizer.NormalizeStreetName(name.Replace(",", ""), locality);
|
||||
var idx = input.CaretIndex;
|
||||
input.Text = $"{street} {nr}".Trim();
|
||||
if (idx >= (text.Length - nr.Length)) {
|
||||
input.CaretIndex = street.Length + (idx - (text.Length - nr.Length)) + 1;
|
||||
} else if (idx > 0) {
|
||||
input.CaretIndex = idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.Length == 0)
|
||||
return required ? new(false, "Adresse ist nicht optional") : new(true, null);
|
||||
|
||||
return new(true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
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 {
|
||||
|
||||
public string Manufacturer => "Avery";
|
||||
public int InternalScaleNr => 1;
|
||||
public string Model { get; private set; }
|
||||
public string ScaleId { get; private set; }
|
||||
public string ScaleTerminalId { get; private set; }
|
||||
public bool IsReady { get; private set; }
|
||||
public bool HasFillingClearance { get; private set; }
|
||||
|
||||
@@ -19,13 +18,11 @@ namespace Elwig.Helpers.Weighing {
|
||||
|
||||
private bool IsRunning = true;
|
||||
private readonly Thread BackgroundThread;
|
||||
private readonly string Connection;
|
||||
|
||||
public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) :
|
||||
base(cnx, null, null, null, log, true, !required) {
|
||||
ScaleId = id;
|
||||
ScaleTerminalId = id;
|
||||
Model = model;
|
||||
Connection = cnx;
|
||||
IsReady = true;
|
||||
HasFillingClearance = false;
|
||||
Stream.WriteTimeout = -1;
|
||||
@@ -60,9 +57,8 @@ namespace Elwig.Helpers.Weighing {
|
||||
} catch (TimeoutException) {
|
||||
await Task.Delay(500);
|
||||
await Reconnect();
|
||||
} catch (Exception ex) {
|
||||
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
} catch (Exception exc) {
|
||||
InteractionService.ShowException("Waagenfehler", "Beim Wiegen ist ein Fehler Aufgetreten", exc, showExcType: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -5,18 +5,17 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Weighing {
|
||||
public class GassnerScale : Scale, ICommandScale {
|
||||
public class GassnerEnqScale : Scale, ICommandScale {
|
||||
|
||||
public string Manufacturer => "Gassner";
|
||||
public int InternalScaleNr => 1;
|
||||
public string Model { get; private set; }
|
||||
public string ScaleId { get; private set; }
|
||||
public string ScaleTerminalId { get; private set; }
|
||||
public bool IsReady { get; private set; }
|
||||
public bool HasFillingClearance { get; private set; }
|
||||
|
||||
public GassnerScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
|
||||
public GassnerEnqScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
|
||||
base(cnx, empty, filling, limit, log) {
|
||||
ScaleId = id;
|
||||
ScaleTerminalId = id;
|
||||
Model = model;
|
||||
IsReady = true;
|
||||
HasFillingClearance = false;
|
||||
@@ -24,10 +23,9 @@ namespace Elwig.Helpers.Weighing {
|
||||
Stream.ReadTimeout = 11000;
|
||||
}
|
||||
|
||||
protected Task SendCommand(char command) => SendCommand(Convert.ToByte(command));
|
||||
protected Task SendCommand(string command) => SendCommand(Encoding.ASCII.GetBytes(command));
|
||||
|
||||
protected async Task SendCommand(byte command) {
|
||||
byte[] cmd = [command];
|
||||
protected async Task SendCommand(byte[] cmd) {
|
||||
await Stream.WriteAsync(cmd);
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, Encoding.ASCII.GetString(cmd));
|
||||
}
|
||||
@@ -53,15 +51,15 @@ namespace Elwig.Helpers.Weighing {
|
||||
return line[1..^1];
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) {
|
||||
protected async Task<WeighingResult> Weigh(int? internalScaleNr, bool incIdentNr, bool retry = true) {
|
||||
string record;
|
||||
try {
|
||||
await SendCommand(incIdentNr ? '\x05' : '?');
|
||||
await SendCommand(incIdentNr ? "\x05" : "?");
|
||||
record = await ReceiveResponse();
|
||||
} catch (IOException) {
|
||||
if (!retry || Tcp == null) throw;
|
||||
ReconnectTcp();
|
||||
return await Weigh(incIdentNr, false);
|
||||
return await Weigh(internalScaleNr, incIdentNr, false);
|
||||
}
|
||||
if (record.Length != 45)
|
||||
throw new FormatException("Invalid response from scale: Received record has invalid size");
|
||||
@@ -80,6 +78,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
GrossWeight = int.Parse(brutto),
|
||||
TareWeight = int.Parse(tara),
|
||||
NetWeight = int.Parse(netto),
|
||||
ScaleId = scaleNr,
|
||||
WeighingId = identNr,
|
||||
FullWeighingId = identNr,
|
||||
Date = DateOnly.TryParseExact(date, "yyyyMMdd", out var d) ? d : null,
|
||||
@@ -87,12 +86,12 @@ namespace Elwig.Helpers.Weighing {
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<WeighingResult> Weigh() {
|
||||
return await Weigh(true);
|
||||
public async Task<WeighingResult> Weigh(int? scaleNr = null) {
|
||||
return await Weigh(scaleNr, true);
|
||||
}
|
||||
|
||||
public async Task<WeighingResult> GetCurrentWeight() {
|
||||
return await Weigh(false);
|
||||
public async Task<WeighingResult> GetCurrentWeight(int? scaleNr = null) {
|
||||
return await Weigh(scaleNr, false);
|
||||
}
|
||||
|
||||
public async Task Empty() {
|
||||
@@ -126,8 +125,9 @@ namespace Elwig.Helpers.Weighing {
|
||||
await SetFillingClearance(false);
|
||||
}
|
||||
|
||||
public Task SetDateAndTime(DateTime dateTime) {
|
||||
throw new NotImplementedException("Für Waagen vom Typ 'Gassner' ist diese Funktion noch nicht implementiert");
|
||||
public async Task SetDateAndTime(DateTime dateTime) {
|
||||
await SendCommand($"C{dateTime:yyyyMMddHHmmss}\r");
|
||||
await ReceiveResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@ namespace Elwig.Helpers.Weighing {
|
||||
/// Get the current weight on the scale without performing a weighing process
|
||||
/// </summary>
|
||||
/// <returns>Result of the weighing process (probably without a weighing id)</returns>
|
||||
Task<WeighingResult> GetCurrentWeight();
|
||||
Task<WeighingResult> GetCurrentWeight(int? scaleNr = null);
|
||||
|
||||
/// <summary>
|
||||
/// Perform a weighing process
|
||||
/// </summary>
|
||||
/// <returns>Result of the weighing process (including a weighing id)</returns>
|
||||
Task<WeighingResult> Weigh();
|
||||
Task<WeighingResult> Weigh(int? scaleNr = null);
|
||||
|
||||
/// <summary>
|
||||
/// Empty the scale container or grant clearance to do so
|
||||
|
||||
@@ -18,12 +18,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
/// <summary>
|
||||
/// Unique identificator of the scale
|
||||
/// </summary>
|
||||
string ScaleId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal identifying number of the scale in its system
|
||||
/// </summary>
|
||||
int InternalScaleNr { get; }
|
||||
string ScaleTerminalId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the scale is currently processing a request or not
|
||||
|
||||
@@ -5,8 +5,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
|
||||
public string Manufacturer => "NONE";
|
||||
public string Model => "NONE";
|
||||
public string ScaleId => id;
|
||||
public int InternalScaleNr => 0;
|
||||
public string ScaleTerminalId => id;
|
||||
public bool IsReady => false;
|
||||
public bool HasFillingClearance => false;
|
||||
public int? WeightLimit => null;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 {
|
||||
@@ -28,10 +28,10 @@ namespace Elwig.Helpers.Weighing {
|
||||
int? limit = config.Limit != null ? int.Parse(config.Limit) : null;
|
||||
if (config.Type == "SysTec-IT") {
|
||||
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else if (config.Type == "Avery-Async") {
|
||||
} else if (config.Type == "Gassner-Enq" || config.Type == "Gassner") {
|
||||
return new GassnerEnqScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else if (config.Type == "Avery-Event" || config.Type == "Avery-Async") {
|
||||
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Log, config.Required);
|
||||
} else if (config.Type == "Gassner") {
|
||||
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else {
|
||||
throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
|
||||
}
|
||||
@@ -42,11 +42,10 @@ namespace Elwig.Helpers.Weighing {
|
||||
if (cnx.StartsWith("serial:")) {
|
||||
try {
|
||||
Serial = Utils.OpenSerialConnection(cnx);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception exc) {
|
||||
if (!softFail) throw;
|
||||
if (!failSilent)
|
||||
MessageBox.Show($"Verbindung zu Waage konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
InteractionService.ShowException("Waagenfehler", "Verbindung zu Waage konnte nicht hergestellt werden", exc, isError: false);
|
||||
}
|
||||
Stream = Serial?.BaseStream ?? Stream.Null;
|
||||
} else if (cnx.StartsWith("tcp:")) {
|
||||
|
||||
@@ -8,15 +8,14 @@ namespace Elwig.Helpers.Weighing {
|
||||
public class SysTecITScale : Scale, ICommandScale {
|
||||
|
||||
public string Manufacturer => "SysTec";
|
||||
public int InternalScaleNr => 1;
|
||||
public string Model { get; private set; }
|
||||
public string ScaleId { get; private set; }
|
||||
public string ScaleTerminalId { get; private set; }
|
||||
public bool IsReady { get; private set; }
|
||||
public bool HasFillingClearance { get; private set; }
|
||||
|
||||
public SysTecITScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
|
||||
base(cnx, empty, filling, limit, log) {
|
||||
ScaleId = id;
|
||||
ScaleTerminalId = id;
|
||||
Model = model;
|
||||
IsReady = true;
|
||||
HasFillingClearance = false;
|
||||
@@ -62,7 +61,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
throw new WeighingException($"Fehler {error}: {msg}");
|
||||
} else if (error[0] == '3') {
|
||||
switch (error[1]) {
|
||||
case '1': msg = "Übertragunsfehler"; break;
|
||||
case '1': msg = "Übertragungsfehler"; break;
|
||||
case '2': msg = "Ungültiger Befehl"; break;
|
||||
case '3': msg = "Ungültiger Parameter"; break;
|
||||
}
|
||||
@@ -74,15 +73,15 @@ namespace Elwig.Helpers.Weighing {
|
||||
return line[1..^3];
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) {
|
||||
protected async Task<WeighingResult> Weigh(int internalScalNr, bool incIdentNr, bool retry = true) {
|
||||
string record;
|
||||
try {
|
||||
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}");
|
||||
await SendCommand(incIdentNr ? $"RN{internalScalNr}" : $"RM{internalScalNr}");
|
||||
record = await ReceiveResponse();
|
||||
} catch (IOException) {
|
||||
if (!retry || Tcp == null) throw;
|
||||
ReconnectTcp();
|
||||
return await Weigh(incIdentNr, false);
|
||||
return await Weigh(internalScalNr, incIdentNr, false);
|
||||
}
|
||||
if (record.Length != 62)
|
||||
throw new FormatException("Invalid response from scale: Received record has invalid size");
|
||||
@@ -114,6 +113,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
GrossWeight = int.Parse(brutto),
|
||||
TareWeight = int.Parse(tara),
|
||||
NetWeight = int.Parse(netto),
|
||||
ScaleId = scaleNr,
|
||||
WeighingId = identNr,
|
||||
FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null,
|
||||
Date = parsedDate,
|
||||
@@ -121,12 +121,12 @@ namespace Elwig.Helpers.Weighing {
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<WeighingResult> Weigh() {
|
||||
return await Weigh(true);
|
||||
public async Task<WeighingResult> Weigh(int? scaleNr = 1) {
|
||||
return await Weigh(scaleNr ?? 1, true);
|
||||
}
|
||||
|
||||
public async Task<WeighingResult> GetCurrentWeight() {
|
||||
return await Weigh(false);
|
||||
public async Task<WeighingResult> GetCurrentWeight(int? scaleNr = 1) {
|
||||
return await Weigh(scaleNr ?? 1, false);
|
||||
}
|
||||
|
||||
public async Task Empty() {
|
||||
|
||||
@@ -21,6 +21,11 @@ namespace Elwig.Helpers.Weighing {
|
||||
/// </summary>
|
||||
public int? NetWeight;
|
||||
|
||||
/// <summary>
|
||||
/// Reported id of the scale
|
||||
/// </summary>
|
||||
public string? ScaleId;
|
||||
|
||||
/// <summary>
|
||||
/// Weighing id (or IdentNr) provided by the scale
|
||||
/// </summary>
|
||||
@@ -44,11 +49,12 @@ namespace Elwig.Helpers.Weighing {
|
||||
/// <returns><[GrossWeight-TaraWeight=]NetWeight/WeighingId/Date/Time></returns>
|
||||
public override readonly string ToString() {
|
||||
var w = NetWeight != null ? (GrossWeight != null && TareWeight != null ? $"{GrossWeight}-{TareWeight}=" : "") + $"{NetWeight}kg" : "";
|
||||
return $"<{w}/{WeighingId}/{Date:yyyy-MM-dd}/{Time:HH:mm}>";
|
||||
return $"<{w}/{WeighingId}/{Date:yyyy-MM-dd}/{Time:HH:mm:ss}>";
|
||||
}
|
||||
|
||||
public readonly JsonObject ToJson() {
|
||||
var obj = new JsonObject();
|
||||
if (ScaleId != null) obj["scale"] = ScaleId;
|
||||
if (FullWeighingId != null) obj["id"] = FullWeighingId;
|
||||
if (WeighingId != null) obj["nr"] = int.Parse(WeighingId);
|
||||
if (Date != null) obj["date"] = $"{Date:yyyy-MM-dd}";
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Elwig.Models {
|
||||
public enum BusinessShareType {
|
||||
Normal = 1, Red = 2, White = 3, Dormant = 9,
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Models.Dtos {
|
||||
public class AreaComUnderDeliveryData : DataTable<AreaComUnderDeliveryRow> {
|
||||
public class AreaCmtUnderDeliveryData : DataTable<AreaCmtUnderDeliveryRow> {
|
||||
|
||||
private static readonly (string, string, string?, int)[] FieldNames = [
|
||||
("MgNr", "MgNr.", null, 12),
|
||||
@@ -21,19 +21,19 @@ namespace Elwig.Models.Dtos {
|
||||
("UnderDeliveries", "Unterliefert", "kg|%", 34),
|
||||
];
|
||||
|
||||
public AreaComUnderDeliveryData(IEnumerable<AreaComUnderDeliveryRow> rows, int year) :
|
||||
public AreaCmtUnderDeliveryData(IEnumerable<AreaCmtUnderDeliveryRow> rows, int year) :
|
||||
base($"Unterlieferungen FB", $"Unterlieferungen laut Flächenbindungen {year}", rows, FieldNames) {
|
||||
}
|
||||
|
||||
public static async Task<AreaComUnderDeliveryData> ForSeason(DbSet<AreaComUnderDeliveryRowSingle> table, int year) {
|
||||
return new AreaComUnderDeliveryData(
|
||||
public static async Task<AreaCmtUnderDeliveryData> ForSeason(DbSet<AreaCmtUnderDeliveryRowSingle> table, int year) {
|
||||
return new AreaCmtUnderDeliveryData(
|
||||
(await FromDbSet(table, year)).GroupBy(
|
||||
r => r.MgNr,
|
||||
(k, g) => new AreaComUnderDeliveryRow(g)
|
||||
(k, g) => new AreaCmtUnderDeliveryRow(g)
|
||||
), year);
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<AreaComUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaComUnderDeliveryRowSingle> table, int year) {
|
||||
private static async Task<IEnumerable<AreaCmtUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaCmtUnderDeliveryRowSingle> table, int year) {
|
||||
return await table.FromSql($"""
|
||||
SELECT m.mgnr, m.name AS name_1,
|
||||
COALESCE(m.prefix || ' ', '') || m.given_name ||
|
||||
@@ -51,7 +51,7 @@ namespace Elwig.Models.Dtos {
|
||||
}
|
||||
}
|
||||
|
||||
public class AreaComUnderDeliveryRow {
|
||||
public class AreaCmtUnderDeliveryRow {
|
||||
public int MgNr;
|
||||
public string Name1;
|
||||
public string? Name2;
|
||||
@@ -66,7 +66,7 @@ namespace Elwig.Models.Dtos {
|
||||
.Select(v => v.First < v.Second ? ((int?, double?))(v.First - v.Second, v.First * 100.0 / v.Second - 100.0) : (null, null))
|
||||
.ToArray();
|
||||
|
||||
public AreaComUnderDeliveryRow(IEnumerable<AreaComUnderDeliveryRowSingle> rows) {
|
||||
public AreaCmtUnderDeliveryRow(IEnumerable<AreaCmtUnderDeliveryRowSingle> rows) {
|
||||
var f = rows.First();
|
||||
MgNr = f.MgNr;
|
||||
Name1 = f.Name1;
|
||||
@@ -82,7 +82,7 @@ namespace Elwig.Models.Dtos {
|
||||
}
|
||||
|
||||
[Keyless]
|
||||
public class AreaComUnderDeliveryRowSingle {
|
||||
public class AreaCmtUnderDeliveryRowSingle {
|
||||
[Column("mgnr")]
|
||||
public int MgNr { get; set; }
|
||||
[Column("name_1")]
|
||||
@@ -17,6 +17,7 @@ namespace Elwig.Models.Dtos {
|
||||
("Address", "Adresse", null, 60),
|
||||
("Plz", "PLZ", null, 10),
|
||||
("Locality", "Ort", null, 60),
|
||||
("AccountingNr", "BH-Konto", null, 20),
|
||||
("Iban", "IBAN", null, 45),
|
||||
("TgNr", "TG-Nr.", null, 20),
|
||||
("Sum", "Zwischens.", "€", 20),
|
||||
@@ -29,7 +30,7 @@ namespace Elwig.Models.Dtos {
|
||||
("Gross", "Brutto", "€", 20),
|
||||
("Penalties", "Pönalen FB", "€", 20),
|
||||
("Penalty", "Unterl. GA", "€", 20),
|
||||
("AutoBs", "GA Nachz.", "€", 20),
|
||||
("AutoShares", "GA Nachz.", "€", 20),
|
||||
("Custom", "Weitere", "€", 20),
|
||||
("Others", "Sonstige", "€", 20),
|
||||
("Considered", "Berückstgt.", "€", 20),
|
||||
@@ -53,12 +54,12 @@ namespace Elwig.Models.Dtos {
|
||||
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, m.iban, c.tgnr, s.year, s.precision,
|
||||
p.plz, o.name AS ort, m.address, m.accounting_nr, 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 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,
|
||||
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,
|
||||
x.amount AS custom_mod
|
||||
FROM credit c
|
||||
LEFT JOIN member m ON m.mgnr = c.mgnr
|
||||
@@ -83,6 +84,7 @@ namespace Elwig.Models.Dtos {
|
||||
public string Address;
|
||||
public int Plz;
|
||||
public string Locality;
|
||||
public string? AccountingNr;
|
||||
public string? Iban;
|
||||
public string TgNr;
|
||||
public decimal Sum;
|
||||
@@ -94,7 +96,7 @@ namespace Elwig.Models.Dtos {
|
||||
public decimal Gross;
|
||||
public decimal? Penalties;
|
||||
public decimal? Penalty;
|
||||
public decimal? AutoBs;
|
||||
public decimal? AutoShares;
|
||||
public decimal? Custom;
|
||||
public decimal? Others;
|
||||
public decimal? Considered;
|
||||
@@ -108,6 +110,7 @@ namespace Elwig.Models.Dtos {
|
||||
Address = row.Address;
|
||||
Plz = row.Plz;
|
||||
Locality = row.Locality;
|
||||
AccountingNr = row.AccountingNr;
|
||||
Iban = row.Iban != null ? Utils.FormatIban(row.Iban) : null;
|
||||
TgNr = $"{row.Year}/{row.TgNr}";
|
||||
Total = Utils.DecFromDb(row.NetAmount, prec1);
|
||||
@@ -122,14 +125,14 @@ namespace Elwig.Models.Dtos {
|
||||
}
|
||||
decimal mod = (row.Modifiers == null) ? 0 : Utils.DecFromDb((long)row.Modifiers, prec1);
|
||||
if (data.ConsiderContractPenalties)
|
||||
Penalties = (row.FbPenalty == null) ? null : Utils.DecFromDb((long)row.FbPenalty, prec1);
|
||||
Penalties = (row.AcPenalty == null) ? null : Utils.DecFromDb((long)row.AcPenalty, prec1);
|
||||
if (data.ConsiderTotalPenalty)
|
||||
Penalty = (row.BsPenalty == null) ? null : Utils.DecFromDb((long)row.BsPenalty, prec1);
|
||||
Penalty = (row.SharesPenalty == null) ? null : Utils.DecFromDb((long)row.SharesPenalty, prec1);
|
||||
if (data.ConsiderAutoBusinessShares)
|
||||
AutoBs = (row.AutoBs == null) ? null : Utils.DecFromDb((long)row.AutoBs, prec1);
|
||||
AutoShares = (row.AutoShares == null) ? null : Utils.DecFromDb((long)row.AutoShares, prec1);
|
||||
if (data.ConsiderCustomModifiers)
|
||||
Custom = (row.CustomMod == null) ? null : Utils.DecFromDb((long)row.CustomMod, prec1);
|
||||
mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoBs ?? 0) + (Custom ?? 0);
|
||||
mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoShares ?? 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);
|
||||
@@ -153,6 +156,8 @@ namespace Elwig.Models.Dtos {
|
||||
public required string LocalityFull { get; set; }
|
||||
[NotMapped]
|
||||
public string Locality => LocalityFull.Split(",")[0];
|
||||
[Column("accounting_nr")]
|
||||
public string? AccountingNr { get; set; }
|
||||
[Column("iban")]
|
||||
public string? Iban { get; set; }
|
||||
[Column("year")]
|
||||
@@ -179,12 +184,12 @@ namespace Elwig.Models.Dtos {
|
||||
public long? PrevModifiers { get; set; }
|
||||
[Column("amount")]
|
||||
public long Amount { 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("shares_penalty")]
|
||||
public long? SharesPenalty { get; set; }
|
||||
[Column("ac_penalty")]
|
||||
public long? AcPenalty { get; set; }
|
||||
[Column("auto_shares")]
|
||||
public long? AutoShares { get; set; }
|
||||
[Column("custom_mod")]
|
||||
public long? CustomMod { get; set; }
|
||||
}
|
||||
|
||||
@@ -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.FindAsync(year, avnr);
|
||||
var variant = await paymentVariants.Include(v => v.Season.Modifiers).Where(v => v.Year == year && v.AvNr == avnr).SingleAsync();
|
||||
BillingData? varData = null;
|
||||
try { varData = variant != null ? BillingData.FromJson(variant.Data) : null; } catch { }
|
||||
try { varData = variant.Data != 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,13 +42,10 @@ namespace Elwig.Models.Dtos {
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<CreditNoteDeliveryRowSingle>> FromDbSet(DbSet<CreditNoteDeliveryRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) {
|
||||
var y = year?.ToString() ?? "NULL";
|
||||
var v = avnr?.ToString() ?? "NULL";
|
||||
var m = mgnr?.ToString() ?? "NULL";
|
||||
return await table.FromSqlRaw($"""
|
||||
return await table.FromSql($"""
|
||||
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, 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, 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
|
||||
FROM v_delivery d
|
||||
JOIN wine_variety s ON s.sortid = d.sortid
|
||||
LEFT JOIN wine_attribute a ON a.attrid = d.attrid
|
||||
@@ -59,7 +56,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 = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL)
|
||||
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)
|
||||
ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
|
||||
""").ToListAsync();
|
||||
}
|
||||
@@ -75,6 +72,7 @@ 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;
|
||||
@@ -97,24 +95,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).ToArray();
|
||||
Modifiers = [.. modifiers.Select(m => m.Name)];
|
||||
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))
|
||||
.ToArray();
|
||||
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))];
|
||||
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;
|
||||
@@ -161,6 +159,8 @@ 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")]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -9,13 +10,14 @@ namespace Elwig.Models.Dtos {
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
public string? Subtitle { get; set; }
|
||||
public IEnumerable<T> Rows { get; private set; }
|
||||
public IEnumerable<(string? Name, IEnumerable<T> Rows)> Sections { get; private set; }
|
||||
public IEnumerable<T> Rows => Sections.SelectMany(s => s.Rows);
|
||||
public int RowNum => Rows.Count();
|
||||
public int ColNum => ColumnNames.Count();
|
||||
|
||||
public IEnumerable<(string, Type?)> ColumnDefs => _map.Select(m => (m.Item1, m.Item2?.PropertyType ?? m.Item3?.FieldType));
|
||||
public IEnumerable<string> ColumnNames => ColumnDefs.Select(m => m.Item1);
|
||||
public IEnumerable<Type?> ColumnTypes => ColumnDefs.Select(m => m.Item2);
|
||||
public IEnumerable<(string Name, Type? Type)> ColumnDefs => _map.Select(m => (m.Name, m.PInfo?.PropertyType ?? m.FInfo?.FieldType));
|
||||
public IEnumerable<string> ColumnNames => ColumnDefs.Select(m => m.Name);
|
||||
public IEnumerable<Type?> ColumnTypes => ColumnDefs.Select(m => m.Type);
|
||||
public IEnumerable<Type?> ColumnFlatTypes { get; private set; }
|
||||
public IEnumerable<int> ColumnSpans { get; private set; }
|
||||
public IEnumerable<int?> ColumnWidths { get; private set; }
|
||||
@@ -23,70 +25,87 @@ namespace Elwig.Models.Dtos {
|
||||
|
||||
private readonly PropertyInfo[] _properties;
|
||||
private readonly FieldInfo[] _fields;
|
||||
private readonly (string, PropertyInfo?, FieldInfo?)[] _map;
|
||||
private readonly (string Name, PropertyInfo? PInfo, FieldInfo? FInfo)[] _map;
|
||||
|
||||
public DataTable(string name, string fullName, string subtitle, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) :
|
||||
this(name, fullName, rows, colNames) {
|
||||
public DataTable(string name, string fullName, string subtitle, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
|
||||
this(name, fullName, sections, colNames) {
|
||||
Subtitle = subtitle;
|
||||
}
|
||||
|
||||
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) {
|
||||
public DataTable(string name, string fullName, string subtitle, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
|
||||
this(name, fullName, subtitle, [(null, rows)], colNames) {
|
||||
}
|
||||
|
||||
public DataTable(string name, string fullName, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) {
|
||||
_fields = typeof(T).GetFields();
|
||||
_properties = typeof(T).GetProperties();
|
||||
colNames ??= _properties.Select(p => p.Name).Union(_fields.Select(f => f.Name)).Select(i => (i, i, (string?)null, (int?)null)).ToList();
|
||||
_map = colNames.Select(n => (n.Item2, _properties.FirstOrDefault(p => p?.Name == n.Item1, null), _fields.FirstOrDefault(f => f?.Name == n.Item1, null))).ToArray();
|
||||
colNames ??= [.. _properties.Select(p => p.Name).Union(_fields.Select(f => f.Name)).Select(i => (i, i, (string?)null, (int?)null))];
|
||||
_map = [.. colNames.Select(n => (n.Name, _properties.FirstOrDefault(p => p?.Name == n.Property, null), _fields.FirstOrDefault(f => f?.Name == n.Property, null)))];
|
||||
Name = name;
|
||||
FullName = fullName;
|
||||
Rows = rows;
|
||||
ColumnFlatTypes = ColumnTypes.SelectMany(type => {
|
||||
Sections = sections;
|
||||
ColumnFlatTypes = [.. ColumnTypes.SelectMany(type => {
|
||||
var elType = type?.GetElementType();
|
||||
return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Select(f => f.FieldType) :
|
||||
type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Select(f => f.FieldType) :
|
||||
return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Select(f => (Type?)f.FieldType) :
|
||||
type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Select(f => (Type?)f.FieldType) :
|
||||
[type];
|
||||
}).ToList();
|
||||
ColumnSpans = ColumnTypes.Select(type => {
|
||||
})];
|
||||
ColumnSpans = [.. ColumnTypes.Select(type => {
|
||||
var elType = type?.GetElementType();
|
||||
return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Length :
|
||||
type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Length : 1;
|
||||
}).ToList();
|
||||
ColumnWidths = colNames.Select(c => c.Item4).ToList();
|
||||
ColumnUnits = colNames.Select(c => c.Item3?.Split("|").Select(p => p.Length == 0 ? null : p).ToArray() ?? []).ToList();
|
||||
type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Length :
|
||||
1;
|
||||
})];
|
||||
ColumnWidths = [.. colNames.Select(c => c.Width)];
|
||||
ColumnUnits = [.. colNames.Select(c => c.Unit?.Split("|").Select(p => p.Length == 0 ? null : p).ToArray() ?? [])];
|
||||
}
|
||||
|
||||
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?)>? colNames = null) :
|
||||
this(name, fullName, rows, colNames?.Select(c => (c.Item1, c.Item2, c.Item3, (int?)null))) {
|
||||
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
|
||||
this(name, fullName, [(null, rows)], colNames) {
|
||||
}
|
||||
|
||||
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?)>? colNames = null) :
|
||||
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit)>? colNames = null) :
|
||||
this(name, fullName, rows, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)null))) {
|
||||
}
|
||||
|
||||
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int Width)>? colNames = null) :
|
||||
this(name, fullName, rows, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)c.Width))) {
|
||||
}
|
||||
|
||||
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
|
||||
this(name, name, rows, colNames) {
|
||||
}
|
||||
|
||||
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) :
|
||||
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit)>? colNames = null) :
|
||||
this(name, name, rows, colNames) {
|
||||
}
|
||||
|
||||
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?, int)>? colNames = null) :
|
||||
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int Width)>? colNames = null) :
|
||||
this(name, name, rows, colNames) {
|
||||
}
|
||||
|
||||
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?, int)>? colNames = null) :
|
||||
this(name, fullName, rows, colNames?.Select(c => (c.Item1, c.Item2, c.Item3, (int?)c.Item4))) {
|
||||
public DataTable(string name, string fullName, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit)>? colNames = null) :
|
||||
this(name, fullName, sections, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)null))) {
|
||||
}
|
||||
|
||||
protected IEnumerable<(string, object?)> GetNamedRowData(T row) {
|
||||
return _map.Select(i => (i.Item1, i.Item2?.GetValue(row) ?? i.Item3?.GetValue(row)));
|
||||
public DataTable(string name, string fullName, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit, int Width)>? colNames = null) :
|
||||
this(name, fullName, sections, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)c.Width))) {
|
||||
}
|
||||
|
||||
protected IEnumerable<object?> GetRowData(T row) {
|
||||
return GetNamedRowData(row).Select(i => i.Item2);
|
||||
protected IEnumerable<(string Name, object? Value)> GetNamedRowValues(T row) {
|
||||
return _map.Select(i => (i.Name, i.PInfo?.GetValue(row) ?? i.FInfo?.GetValue(row)));
|
||||
}
|
||||
|
||||
public IEnumerable<IEnumerable<object?>> GetData() {
|
||||
return Rows.Select(GetRowData);
|
||||
protected IEnumerable<object?> GetRowValues(T row) {
|
||||
return GetNamedRowValues(row).Select(i => i.Value);
|
||||
}
|
||||
|
||||
public IEnumerable<IEnumerable<(string, object?)>> GetNamedData() {
|
||||
return Rows.Select(GetNamedRowData);
|
||||
public IEnumerable<(string? Name, IEnumerable<IEnumerable<object?>> Rows)> GetData() {
|
||||
return Sections.Select(s => (s.Name, s.Rows.Select(GetRowValues)));
|
||||
}
|
||||
|
||||
public IEnumerable<(string? Name, IEnumerable<IEnumerable<(string Name, object? Value)>> Rows)> GetNamedData() {
|
||||
return Sections.Select(s => (s.Name, s.Rows.Select(GetNamedRowValues)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,7 @@ namespace Elwig.Models.Dtos {
|
||||
}
|
||||
|
||||
public static async Task<DeliveryAncmtListData> FromQuery(IQueryable<DeliveryAncmt> query, List<string> 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);
|
||||
return new((await query.ToListAsync()).Select(d => new DeliveryAncmtListRow(d)), filterNames);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,12 +52,8 @@ 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.*
|
||||
@@ -65,7 +61,7 @@ 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
|
||||
""").ToListAsync();
|
||||
""").IgnoreAutoIncludes().ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,12 +40,7 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Models.Dtos {
|
||||
class MemberAreaComsData : DataTable<MemberAreaComsRow> {
|
||||
class MemberAreaCmtTotalsData : DataTable<MemberAreaCmtTotalsRow> {
|
||||
|
||||
private static readonly (string, string, string?, int)[] FieldNames = [
|
||||
("MgNr", "MgNr.", null, 12),
|
||||
@@ -21,19 +21,19 @@ namespace Elwig.Models.Dtos {
|
||||
("DeliveryRights", "Lieferrecht", "kg", 22),
|
||||
];
|
||||
|
||||
public MemberAreaComsData(IEnumerable<MemberAreaComsRow> rows, int year) :
|
||||
base($"Flächenbindungen", $"Flächenbindungen pro Mitglied {year}", rows, FieldNames) {
|
||||
public MemberAreaCmtTotalsData(IEnumerable<MemberAreaCmtTotalsRow> rows, int year) :
|
||||
base($"Gebundene Fläche", $"Gebundene Fläche pro Mitglied {year}", rows, FieldNames) {
|
||||
}
|
||||
|
||||
public static async Task<MemberAreaComsData> ForSeason(DbSet<MemberAreaComsRowSingle> table, int year) {
|
||||
return new MemberAreaComsData(
|
||||
public static async Task<MemberAreaCmtTotalsData> ForSeason(DbSet<MemberAreaCmtTotalsRowSingle> table, int year) {
|
||||
return new MemberAreaCmtTotalsData(
|
||||
(await FromDbSet(table, year)).GroupBy(
|
||||
r => r.MgNr,
|
||||
(k, g) => new MemberAreaComsRow(g)
|
||||
(k, g) => new MemberAreaCmtTotalsRow(g)
|
||||
), year);
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<MemberAreaComsRowSingle>> FromDbSet(DbSet<MemberAreaComsRowSingle> table, int year) {
|
||||
private static async Task<IEnumerable<MemberAreaCmtTotalsRowSingle>> FromDbSet(DbSet<MemberAreaCmtTotalsRowSingle> table, int year) {
|
||||
return await table.FromSql($"""
|
||||
SELECT m.mgnr, m.name AS name_1,
|
||||
COALESCE(m.prefix || ' ', '') || m.given_name ||
|
||||
@@ -50,7 +50,7 @@ namespace Elwig.Models.Dtos {
|
||||
}
|
||||
}
|
||||
|
||||
public class MemberAreaComsRow {
|
||||
public class MemberAreaCmtTotalsRow {
|
||||
public int MgNr;
|
||||
public string Name1;
|
||||
public string? Name2;
|
||||
@@ -63,7 +63,7 @@ namespace Elwig.Models.Dtos {
|
||||
public int[] DeliveryObligations;
|
||||
public int[] DeliveryRights;
|
||||
|
||||
public MemberAreaComsRow(IEnumerable<MemberAreaComsRowSingle> rows) {
|
||||
public MemberAreaCmtTotalsRow(IEnumerable<MemberAreaCmtTotalsRowSingle> rows) {
|
||||
var f = rows.First();
|
||||
MgNr = f.MgNr;
|
||||
Name1 = f.Name1;
|
||||
@@ -71,16 +71,16 @@ namespace Elwig.Models.Dtos {
|
||||
Address = f.Address;
|
||||
Plz = f.Plz;
|
||||
Locality = f.Locality.Split(",")[0];
|
||||
SortIds = rows.Select(r => r.VtrgId[..2]).ToArray();
|
||||
AttrIds = rows.Select(r => r.VtrgId[2..]).ToArray();
|
||||
Areas = rows.Select(r => r.Area).ToArray();
|
||||
DeliveryObligations = rows.Select(r => r.MinKg).ToArray();
|
||||
DeliveryRights = rows.Select(r => r.MaxKg).ToArray();
|
||||
SortIds = [.. rows.Select(r => r.VtrgId[..2])];
|
||||
AttrIds = [.. rows.Select(r => r.VtrgId[2..])];
|
||||
Areas = [.. rows.Select(r => r.Area)];
|
||||
DeliveryObligations = [.. rows.Select(r => r.MinKg)];
|
||||
DeliveryRights = [.. rows.Select(r => r.MaxKg)];
|
||||
}
|
||||
}
|
||||
|
||||
[Keyless]
|
||||
public class MemberAreaComsRowSingle {
|
||||
public class MemberAreaCmtTotalsRowSingle {
|
||||
[Column("mgnr")]
|
||||
public int MgNr { get; set; }
|
||||
[Column("name_1")]
|
||||
@@ -0,0 +1,87 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Models.Dtos {
|
||||
class MemberAreaCmtsData : DataTable<MemberAreaCmtsRow> {
|
||||
|
||||
private static readonly (string, string, string?, int)[] FieldNames = [
|
||||
("MgNr", "MgNr.", null, 12),
|
||||
("Name1", "Name", null, 40),
|
||||
("Name2", "Vorname", null, 40),
|
||||
("Address", "Adresse", null, 60),
|
||||
("Plz", "PLZ", null, 10),
|
||||
("Locality", "Ort", null, 60),
|
||||
("SortId", "Sorte", null, 12),
|
||||
("AttrId", "Attribut", null, 16),
|
||||
("CultId", "Bewirt.", null, 16),
|
||||
("GstNr", "Parzelle(n)", null, 25),
|
||||
("Area", "Fläche", "m²", 22),
|
||||
("MinKg", "Lieferpflicht", "kg", 22),
|
||||
("MaxKg", "Lieferrecht", "kg", 22),
|
||||
];
|
||||
|
||||
public MemberAreaCmtsData(IEnumerable<MemberAreaCmtsRow> rows, int year) :
|
||||
base($"Flächenbindungen", $"Flächenbindungen pro Mitglied {year}", rows, FieldNames) {
|
||||
}
|
||||
|
||||
public static async Task<MemberAreaCmtsData> ForSeason(DbSet<MemberAreaCmtsRow> table, int year) {
|
||||
return new MemberAreaCmtsData(await FromDbSet(table, year), year);
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<MemberAreaCmtsRow>> FromDbSet(DbSet<MemberAreaCmtsRow> table, int year) {
|
||||
return await table.FromSql($"""
|
||||
SELECT m.mgnr, m.name AS name_1,
|
||||
COALESCE(m.prefix || ' ', '') || m.given_name ||
|
||||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
|
||||
p.plz, o.name AS ort, m.address,
|
||||
t.sortid, t.attrid, c.cultid,
|
||||
c.gstnr, c.area,
|
||||
CAST(ROUND(area * COALESCE(t.min_kg_per_ha, 0) / 10000.0, 0) AS INTEGER) AS min_kg,
|
||||
CAST(ROUND(area * MIN(COALESCE(a.max_kg_per_ha, s.max_kg_per_ha), s.max_kg_per_ha) / 10000.0, 0) AS INTEGER) AS max_kg
|
||||
FROM v_virtual_season s, area_commitment c
|
||||
JOIN area_commitment_type t ON t.vtrgid = c.vtrgid
|
||||
LEFT JOIN wine_attribute a ON a.attrid = t.attrid
|
||||
LEFT JOIN member m ON m.mgnr = c.mgnr
|
||||
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
|
||||
LEFT JOIN AT_ort o ON o.okz = p.okz
|
||||
WHERE s.year = {year} AND
|
||||
(year_from IS NULL OR year_from <= s.year) AND
|
||||
(year_to IS NULL OR year_to >= s.year)
|
||||
ORDER BY c.mgnr, t.sortid, t.attrid, c.cultid, c.gstnr;
|
||||
""").ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Keyless]
|
||||
public class MemberAreaCmtsRow {
|
||||
[Column("mgnr")]
|
||||
public int MgNr { get; set; }
|
||||
[Column("name_1")]
|
||||
public required string Name1 { get; set; }
|
||||
[Column("name_2")]
|
||||
public string? Name2 { get; set; }
|
||||
[Column("address")]
|
||||
public required string Address { get; set; }
|
||||
[Column("plz")]
|
||||
public int Plz { get; set; }
|
||||
[Column("ort")]
|
||||
public required string Locality { get; set; }
|
||||
[Column("sortid")]
|
||||
public required string SortId { get; set; }
|
||||
[Column("attrid")]
|
||||
public string? AttrId { get; set; }
|
||||
[Column("cultid")]
|
||||
public string? CultId { get; set; }
|
||||
[Column("gstnr")]
|
||||
public required string GstNr { get; set; }
|
||||
[Column("area")]
|
||||
public int Area { get; set; }
|
||||
[Column("min_kg")]
|
||||
public int MinKg { get; set; }
|
||||
[Column("max_kg")]
|
||||
public int MaxKg { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Elwig.Models.Dtos {
|
||||
public class MemberDeliveryData : DataTable<MemberDeliveryRow> {
|
||||
|
||||
private static readonly (string, string, string?, int?)[] FieldNames = [
|
||||
("MgNr", "MgNr.", null, 12),
|
||||
("MgNr", "MgNr.", null, 12),
|
||||
("Name1", "Name", null, 40),
|
||||
("Name2", "Vorname", null, 40),
|
||||
("Address", "Adresse", null, 60),
|
||||
|
||||
@@ -19,13 +19,13 @@ namespace Elwig.Models.Dtos {
|
||||
("Locality", "Ort", null, 60),
|
||||
("DefaultKg", "Stammgemeinde", null, 60),
|
||||
("Branch", "Zweigstelle", null, 40),
|
||||
("BusinessShares", "GA", null, 10),
|
||||
("SharesActive", "GA", null, 10),
|
||||
("BillingName", "Rechnungsname", null, 60),
|
||||
("BillingAddress", "Rechnungsadresse", null, 60),
|
||||
("BillingPlz", "PLZ", null, 10),
|
||||
("BillingLocality", "Ort", null, 60),
|
||||
("LfbisNr", "Betr.-Nr.", null, 20),
|
||||
("IsBuchführend", "buchf.", null, 15),
|
||||
("IsAccounting", "buchf.", null, 15),
|
||||
("IsOrganic", "Bio", null, 15),
|
||||
("IsActive", "aktiv", null, 15),
|
||||
("EntryDate", "Eintritt", null, 20),
|
||||
@@ -46,19 +46,15 @@ namespace Elwig.Models.Dtos {
|
||||
base(MemberList.Name, MemberList.Name, string.Join(" / ", filterNames), rows, FieldNames) {
|
||||
}
|
||||
|
||||
public static async Task<MemberListData> FromQuery(IQueryable<Member> query, List<string> filterNames, IEnumerable<string> filterAreaCom) {
|
||||
var areaComs = await query.ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments));
|
||||
public static async Task<MemberListData> FromQuery(IQueryable<Member> query, List<string> filterNames, IEnumerable<string> filterAreaCmt) {
|
||||
var areaCmts = await query.Include(m => m.AreaCommitments).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)))),
|
||||
areaCmts[m.MgNr].Sum(c => c.Area),
|
||||
areaCmts[m.MgNr].Where(c => filterAreaCmt.Contains(c.VtrgId)).GroupBy(c => c.VtrgId).ToDictionary(g => g.Key, g => g.Sum(c => c.Area)))),
|
||||
filterNames);
|
||||
}
|
||||
}
|
||||
@@ -70,7 +66,10 @@ namespace Elwig.Models.Dtos {
|
||||
public string? Name2;
|
||||
public string? DefaultKg;
|
||||
public string? Branch;
|
||||
public int BusinessShares;
|
||||
public int Shares;
|
||||
public int SharesRed;
|
||||
public int SharesWhite;
|
||||
public int SharesActive;
|
||||
public string Address;
|
||||
public int Plz;
|
||||
public string Locality;
|
||||
@@ -84,7 +83,7 @@ namespace Elwig.Models.Dtos {
|
||||
public string? Bic;
|
||||
public int? AreaCommitment;
|
||||
public (string VtrgId, int Area)[] AreaCommitmentsFiltered;
|
||||
public bool IsBuchführend;
|
||||
public bool IsAccounting;
|
||||
public bool IsOrganic;
|
||||
public bool IsActive;
|
||||
public DateOnly? EntryDate;
|
||||
@@ -95,14 +94,17 @@ namespace Elwig.Models.Dtos {
|
||||
public string? AdditionalContact;
|
||||
public string? Comment;
|
||||
|
||||
public MemberListRow(Member m, int? areaCom = null, Dictionary<string, int>? filtered = null) {
|
||||
public MemberListRow(Member m, int? areaCmt = null, Dictionary<string, int>? filtered = null) {
|
||||
MgNr = m.MgNr;
|
||||
Name1 = m.Name;
|
||||
AdminName1 = m.AdministrativeName1;
|
||||
Name2 = m.AdministrativeName2;
|
||||
DefaultKg = m.DefaultKg?.Name;
|
||||
Branch = m.Branch?.Name;
|
||||
BusinessShares = m.BusinessShares;
|
||||
Shares = m.Shares;
|
||||
SharesRed = m.SharesRed;
|
||||
SharesWhite = m.SharesWhite;
|
||||
SharesActive = Shares + SharesRed + SharesWhite;
|
||||
Address = m.Address;
|
||||
Plz = m.PostalDest.AtPlz!.Plz;
|
||||
Locality = m.PostalDest.AtPlz!.Ort.Name;
|
||||
@@ -116,7 +118,7 @@ namespace Elwig.Models.Dtos {
|
||||
UstIdNr = m.UstIdNr;
|
||||
Iban = m.Iban != null ? Utils.FormatIban(m.Iban) : null;
|
||||
Bic = m.Bic;
|
||||
IsBuchführend = m.IsBuchführend;
|
||||
IsAccounting = m.IsAccounting;
|
||||
IsOrganic = m.IsOrganic;
|
||||
IsActive = m.IsActive;
|
||||
EntryDate = m.EntryDate;
|
||||
@@ -131,7 +133,7 @@ namespace Elwig.Models.Dtos {
|
||||
.Distinct()
|
||||
.Concat(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).Except([EmailAddress])));
|
||||
Comment = m.Comment;
|
||||
AreaCommitment = areaCom == 0 ? null : areaCom;
|
||||
AreaCommitment = areaCmt == 0 ? null : areaCmt;
|
||||
AreaCommitmentsFiltered = filtered != null ? filtered.OrderBy(f => f.Key).Select(f => (f.Key, f.Value)).ToArray() : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using Elwig.Helpers;
|
||||
using Elwig.Models.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elwig.Models.Dtos {
|
||||
public class MemberMovementData : DataTable<MemberMovementDataRow> {
|
||||
|
||||
private static readonly (string, string, string?, int)[] FieldNames = [
|
||||
("Type", "\u2026 durch", null, 30),
|
||||
("MgNr", "MgNr.", null, 12),
|
||||
("Name1", "Name", null, 40),
|
||||
("Name2", "Vorname", null, 25),
|
||||
("Address", "Adresse", null, 50),
|
||||
("Plz", "PLZ", null, 10),
|
||||
("Locality", "Ort", null, 50),
|
||||
("Date", "Datum", null, 20),
|
||||
("SharesOld", "GA\nalt", null, 10),
|
||||
("SharesDiff", "GA\nDiff.", null, 10),
|
||||
("SharesNew", "GA\nneu", null, 10),
|
||||
("Costs", "Kosten", "€", 15),
|
||||
];
|
||||
|
||||
private MemberMovementData(string name, IEnumerable<(string? Name, IEnumerable<MemberMovementDataRow> Rows)> sections) :
|
||||
base("Mitgl.-Bewegungen", name, sections, FieldNames) {
|
||||
}
|
||||
|
||||
public static MemberMovementData FromMemberHistory(string name, IEnumerable<MemberHistory> rows, Dictionary<int, SortedDictionary<DateOnly, MemberHistoryPoint>> history) {
|
||||
(string? Name, List<MemberMovementDataRow> Rows)[] sections = [
|
||||
("Beitritt", []),
|
||||
("Nachzeichnung (GA)", []),
|
||||
("Überschreibung", []),
|
||||
("Kündigung (GA)", []),
|
||||
("Austritt", []),
|
||||
];
|
||||
foreach (var h in rows.OrderBy(r => r.DateNotice).ThenBy(r => r.ToMgNr).ThenBy(r => r.FromMgNr)) {
|
||||
|
||||
int cat;
|
||||
if (h.ToMgNr == h.FromMgNr) {
|
||||
// Umwandlung
|
||||
if (h.ToType == BusinessShareType.Dormant) {
|
||||
// Kündigung (GA)
|
||||
cat = 3;
|
||||
} else if (h.FromType == h.ToType) {
|
||||
// Überschreibung
|
||||
cat = 2;
|
||||
} else {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
} else if (h.DateNotice <= h.ToMember?.EntryDate && h.DateNotice >= h.FromMember?.ExitDate) {
|
||||
// Überschreibung
|
||||
cat = 2;
|
||||
} else if (h.DateNotice <= h.ToMember?.EntryDate) {
|
||||
// Beitritt
|
||||
cat = 0;
|
||||
} else if (h.DateNotice >= h.FromMember?.ExitDate) {
|
||||
// Austritt
|
||||
cat = 4;
|
||||
} else if (h.ToMember == null) {
|
||||
// Auszahlung
|
||||
continue;
|
||||
} else {
|
||||
// Nachzeichnung (GA)
|
||||
cat = 1;
|
||||
}
|
||||
|
||||
Member a;
|
||||
Member? b = null;
|
||||
if (h.FromMember != null && h.ToMember != null) {
|
||||
if (cat <= 1) {
|
||||
a = h.ToMember;
|
||||
b = h.FromMember;
|
||||
} else {
|
||||
a = h.FromMember;
|
||||
b = h.ToMember;
|
||||
}
|
||||
} else if (h.FromMember != null) {
|
||||
a = h.FromMember;
|
||||
} else if (h.ToMember != null) {
|
||||
a = h.ToMember;
|
||||
} else {
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var oldA = history[a.MgNr].GetValueOrDefault(h.DateNotice);
|
||||
var d = new MemberMovementDataRow {
|
||||
Type = [h.ReasonPretty],
|
||||
|
||||
MgNr = [a.MgNr],
|
||||
Name1 = [a.Name],
|
||||
Name2 = [a.AdministrativeName2],
|
||||
Address = [a.Address],
|
||||
Plz = [a.PostalDest.AtPlz!.Plz],
|
||||
Locality = [a.PostalDest.AtPlz.Ort.Name],
|
||||
|
||||
Date = h.DateNotice,
|
||||
SharesOld = [oldA.Shares + oldA.SharesRed + oldA.SharesWhite],
|
||||
SharesDiff = [cat != 3 && a == h.ToMember ? h.Shares : -h.Shares],
|
||||
Costs = [h.FromMember == null ? h.TotalValue : null],
|
||||
};
|
||||
d.SharesNew = [d.SharesOld[^1] + (h.FromMgNr == h.ToMgNr ? 0 : d.SharesDiff[^1])];
|
||||
|
||||
if (b != null) {
|
||||
var oldB = history[b.MgNr].GetValueOrDefault(h.DateNotice);
|
||||
d.Type = [..d.Type, "\u2026 " + (cat <= 1 ? "von" : "an")];
|
||||
|
||||
d.MgNr = [.. d.MgNr, b.MgNr];
|
||||
d.Name1 = [.. d.Name1, b.Name];
|
||||
d.Name2 = [.. d.Name2, b.AdministrativeName2];
|
||||
d.Address = [.. d.Address, b.Address];
|
||||
d.Plz = [.. d.Plz, b.PostalDest.AtPlz!.Plz];
|
||||
d.Locality = [.. d.Locality, b.PostalDest.AtPlz.Ort.Name];
|
||||
|
||||
d.SharesOld = [.. d.SharesOld, oldB.Shares + oldB.SharesRed + oldB.SharesWhite];
|
||||
d.SharesDiff = [.. d.SharesDiff, b == h.ToMember ? h.Shares : -h.Shares];
|
||||
d.SharesNew = [.. d.SharesNew, d.SharesOld[^1] + d.SharesDiff[^1]];
|
||||
}
|
||||
|
||||
sections[cat].Rows.Add(d);
|
||||
}
|
||||
return new MemberMovementData($"Mitglieder-Bewegungen {name}", sections.Select(s => (s.Name, (IEnumerable<MemberMovementDataRow>)s.Rows)));
|
||||
}
|
||||
}
|
||||
|
||||
public class MemberMovementDataRow {
|
||||
public string[] Type = [];
|
||||
|
||||
public int[] MgNr = [];
|
||||
public string[] Name1 = [];
|
||||
public string?[] Name2 = [];
|
||||
public string[] Address = [];
|
||||
public int[] Plz = [];
|
||||
public string[] Locality = [];
|
||||
|
||||
public DateOnly Date;
|
||||
public int?[] SharesOld = [];
|
||||
public int[] SharesDiff = [];
|
||||
public int?[] SharesNew = [];
|
||||
public decimal?[] Costs = [];
|
||||
}
|
||||
}
|
||||
@@ -13,35 +13,74 @@ namespace Elwig.Models.Dtos {
|
||||
("Address", "Adresse", null, 60),
|
||||
("Plz", "PLZ", null, 10),
|
||||
("Locality", "Ort", null, 60),
|
||||
("BusinessShares", "GA", null, 10),
|
||||
("Shares", "GA", null, 10),
|
||||
("DeliveryObligation", "Lieferpflicht", "kg", 22),
|
||||
("DeliveryRight", "Lieferrecht", "kg", 22),
|
||||
("Weight", "Geliefert", "kg", 22),
|
||||
("WeightTotal", "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 static async Task<OverUnderDeliveryData> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) {
|
||||
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) {
|
||||
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, 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
|
||||
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
|
||||
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_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
|
||||
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
|
||||
""").ToListAsync();
|
||||
return new OverUnderDeliveryData(rows, year);
|
||||
return (new OverUnderDeliveryData(rows, year), new OverUnderDeliveryData(rows, year, "R"), new OverUnderDeliveryData(rows, year, "W"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,17 +100,41 @@ namespace Elwig.Models.Dtos {
|
||||
public required string LocalityFull { get; set; }
|
||||
[NotMapped]
|
||||
public string Locality => LocalityFull.Split(",")[0];
|
||||
[Column("business_shares")]
|
||||
public int BusinessShares { get; set; }
|
||||
[Column("shares")]
|
||||
public int Shares { get; set; }
|
||||
[Column("shares_red")]
|
||||
public int SharesRed { get; set; }
|
||||
[Column("shares_white")]
|
||||
public int SharesWhite { 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 =>
|
||||
Weight < DeliveryObligation ? (Weight - DeliveryObligation, Weight * 100.0 / DeliveryObligation - 100.0) :
|
||||
Weight > DeliveryRight ? (Weight - DeliveryRight, Weight * 100.0 / DeliveryRight - 100.0) : (null, null);
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ namespace Elwig.Models.Dtos {
|
||||
("Cultivation", "Bewirt.", null, 20),
|
||||
("QualityLevel", "Qualitätsstufe", null, 30),
|
||||
("Oe", "Gradation", "°Oe", 20),
|
||||
("Ungeb", "ungebunden", "kg|€/kg|€/kg", 60),
|
||||
("LowGeb", "attributlos gebunden", "kg|€/kg|€/kg", 60),
|
||||
("Geb", "gebunden", "kg|€/kg|€/kg", 60),
|
||||
("UnCtrd", "ungebunden", "kg|€/kg|€/kg", 60),
|
||||
("LowCtrd", "attributlos gebunden", "kg|€/kg|€/kg", 60),
|
||||
("Ctrd", "gebunden", "kg|€/kg|€/kg", 60),
|
||||
("Amount", "Gesamt", "€", 25),
|
||||
];
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace Elwig.Models.Dtos {
|
||||
string? Cultivation,
|
||||
string QualityLevel,
|
||||
double Oe,
|
||||
(int Weight, decimal? MinPrice, decimal? MaxPrice) Ungeb,
|
||||
(int Weight, decimal? MinPrice, decimal? MaxPrice) LowGeb,
|
||||
(int Weight, decimal? MinPrice, decimal? MaxPrice) Geb,
|
||||
(int Weight, decimal? MinPrice, decimal? MaxPrice) UnCtrd,
|
||||
(int Weight, decimal? MinPrice, decimal? MaxPrice) LowCtrd,
|
||||
(int Weight, decimal? MinPrice, decimal? MaxPrice) Ctrd,
|
||||
decimal Amount
|
||||
);
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace Elwig.Models.Dtos {
|
||||
public static async Task<PaymentVariantSummaryData> ForPaymentVariant(PaymentVar v, DbSet<PaymentVariantSummaryRow> table) {
|
||||
return new(v, (await FromDbSet(table, v.Year, v.AvNr))
|
||||
.Select(r => new PaymentRow(r.Type, r.Variety, r.Attribute, r.Cultivation, r.QualityLevel, r.Oe,
|
||||
(r.WeightUngeb, r.MinPriceUngeb != null ? Utils.DecFromDb(r.MinPriceUngeb.Value, v.Season.Precision) : null, r.MaxPriceUngeb != null ? Utils.DecFromDb(r.MaxPriceUngeb.Value, v.Season.Precision) : null),
|
||||
(r.WeightLowGeb, r.MinPriceLowGeb != null ? Utils.DecFromDb(r.MinPriceLowGeb.Value, v.Season.Precision) : null, r.MaxPriceLowGeb != null ? Utils.DecFromDb(r.MaxPriceLowGeb.Value, v.Season.Precision) : null),
|
||||
(r.WeightGeb, r.MinPriceGeb != null ? Utils.DecFromDb(r.MinPriceGeb.Value, v.Season.Precision) : null, r.MaxPriceGeb != null ? Utils.DecFromDb(r.MaxPriceGeb.Value, v.Season.Precision) : null),
|
||||
(r.WeightUnCtrd, r.MinPriceUnCtrd != null ? Utils.DecFromDb(r.MinPriceUnCtrd.Value, v.Season.Precision) : null, r.MaxPriceUnCtrd != null ? Utils.DecFromDb(r.MaxPriceUnCtrd.Value, v.Season.Precision) : null),
|
||||
(r.WeightLowCtrd, r.MinPriceLowCtrd != null ? Utils.DecFromDb(r.MinPriceLowCtrd.Value, v.Season.Precision) : null, r.MaxPriceLowCtrd != null ? Utils.DecFromDb(r.MaxPriceLowCtrd.Value, v.Season.Precision) : null),
|
||||
(r.WeightCtrd, r.MinPriceCtrd != null ? Utils.DecFromDb(r.MinPriceCtrd.Value, v.Season.Precision) : null, r.MaxPriceCtrd != null ? Utils.DecFromDb(r.MaxPriceCtrd.Value, v.Season.Precision) : null),
|
||||
Utils.DecFromDb(r.Amount, v.Season.Precision)))
|
||||
.ToArray());
|
||||
}
|
||||
@@ -59,15 +59,15 @@ namespace Elwig.Models.Dtos {
|
||||
c.name AS cultivation,
|
||||
q.name AS quality_level,
|
||||
ROUND(kmw * (4.54 + 0.022 * kmw)) AS oe,
|
||||
SUM(IIF(w.discr = '_', w.value, 0)) AS weight_ungeb,
|
||||
MIN(IIF(w.discr = '_', b.price, NULL)) AS min_price_ungeb,
|
||||
MAX(IIF(w.discr = '_', b.price, NULL)) AS max_price_ungeb,
|
||||
SUM(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), w.value, 0)) AS weight_lowgeb,
|
||||
MIN(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS min_price_lowgeb,
|
||||
MAX(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS max_price_lowgeb,
|
||||
SUM(IIF(w.discr = COALESCE(p.attrid, ''), w.value, 0)) AS weight_geb,
|
||||
MIN(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS min_price_geb,
|
||||
MAX(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS max_price_geb,
|
||||
SUM(IIF(w.discr = '_', w.value, 0)) AS weight_unctrd,
|
||||
MIN(IIF(w.discr = '_', b.price, NULL)) AS min_price_unctrd,
|
||||
MAX(IIF(w.discr = '_', b.price, NULL)) AS max_price_unctrd,
|
||||
SUM(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), w.value, 0)) AS weight_lowctrd,
|
||||
MIN(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS min_price_lowctrd,
|
||||
MAX(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS max_price_lowctrd,
|
||||
SUM(IIF(w.discr = COALESCE(p.attrid, ''), w.value, 0)) AS weight_ctrd,
|
||||
MIN(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS min_price_ctrd,
|
||||
MAX(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS max_price_ctrd,
|
||||
SUM(b.amount) AS amount
|
||||
FROM payment_delivery_part_bucket b
|
||||
LEFT JOIN delivery_part_bucket w ON (w.year, w.did, w.dpnr, w.bktnr) = (b.year, b.did, b.dpnr, b.bktnr)
|
||||
@@ -97,24 +97,24 @@ namespace Elwig.Models.Dtos {
|
||||
public required string QualityLevel { get; set; }
|
||||
[Column("oe")]
|
||||
public double Oe { get; set; }
|
||||
[Column("weight_ungeb")]
|
||||
public int WeightUngeb { get; set; }
|
||||
[Column("min_price_ungeb")]
|
||||
public long? MinPriceUngeb { get; set; }
|
||||
[Column("max_price_ungeb")]
|
||||
public long? MaxPriceUngeb { get; set; }
|
||||
[Column("weight_lowgeb")]
|
||||
public int WeightLowGeb { get; set; }
|
||||
[Column("min_price_lowgeb")]
|
||||
public long? MinPriceLowGeb { get; set; }
|
||||
[Column("max_price_lowgeb")]
|
||||
public long? MaxPriceLowGeb { get; set; }
|
||||
[Column("weight_geb")]
|
||||
public int WeightGeb { get; set; }
|
||||
[Column("min_price_geb")]
|
||||
public long? MinPriceGeb { get; set; }
|
||||
[Column("max_price_geb")]
|
||||
public long? MaxPriceGeb { get; set; }
|
||||
[Column("weight_unctrd")]
|
||||
public int WeightUnCtrd { get; set; }
|
||||
[Column("min_price_unctrd")]
|
||||
public long? MinPriceUnCtrd { get; set; }
|
||||
[Column("max_price_unctrd")]
|
||||
public long? MaxPriceUnCtrd { get; set; }
|
||||
[Column("weight_lowctrd")]
|
||||
public int WeightLowCtrd { get; set; }
|
||||
[Column("min_price_lowctrd")]
|
||||
public long? MinPriceLowCtrd { get; set; }
|
||||
[Column("max_price_lowctrd")]
|
||||
public long? MaxPriceLowCtrd { get; set; }
|
||||
[Column("weight_ctrd")]
|
||||
public int WeightCtrd { get; set; }
|
||||
[Column("min_price_ctrd")]
|
||||
public long? MinPriceCtrd { get; set; }
|
||||
[Column("max_price_ctrd")]
|
||||
public long? MaxPriceCtrd { get; set; }
|
||||
[Column("amount")]
|
||||
public long Amount { get; set; }
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Elwig.Models.Dtos {
|
||||
("AttrId", "Attr.", null, 10),
|
||||
("CultId", "Bewirt.", null, 15),
|
||||
("QualId", "Qual.", null, 15),
|
||||
("Geb", "gebunden", null, 20),
|
||||
("Contracted", "gebunden", null, 20),
|
||||
("Weight", "Menge", "kg", 20),
|
||||
];
|
||||
|
||||
@@ -28,12 +28,12 @@ namespace Elwig.Models.Dtos {
|
||||
private static async Task<IEnumerable<WeightBreakdownRow>> FromDbSet(DbSet<WeightBreakdownRow> table, int year, string? zwstid) {
|
||||
zwstid = zwstid == null ? "NULL" : $"'{zwstid}'";
|
||||
return await table.FromSqlRaw($"""
|
||||
SELECT type, sortid, attrid, cultid, v.qualid, geb, SUM(weight) AS weight
|
||||
SELECT type, sortid, attrid, cultid, v.qualid, contracted, SUM(weight) AS weight
|
||||
FROM v_stat_total v
|
||||
LEFT JOIN wine_quality_level q ON q.qualid = v.qualid
|
||||
WHERE year = {year} AND ({zwstid} IS NULL OR zwstid = {zwstid})
|
||||
GROUP BY type, sortid, attrid, cultid, v.qualid, geb
|
||||
ORDER BY type DESC, sortid, attrid, cultid, q.min_kmw, geb
|
||||
GROUP BY type, sortid, attrid, cultid, v.qualid, contracted
|
||||
ORDER BY type DESC, sortid, attrid, cultid, q.min_kmw, contracted
|
||||
""").ToListAsync();
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,8 @@ namespace Elwig.Models.Dtos {
|
||||
public string? CultId { get; set; }
|
||||
[Column("qualid")]
|
||||
public required string QualId { get; set; }
|
||||
[Column("geb")]
|
||||
public required string Geb { get; set; }
|
||||
[Column("contracted")]
|
||||
public required string Contracted { get; set; }
|
||||
[Column("weight")]
|
||||
public int Weight { get; set; }
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace Elwig.Models.Entities {
|
||||
[Column("dest")]
|
||||
public string Dest { get; private set; } = null!;
|
||||
|
||||
[NotMapped]
|
||||
public string FullDest => $"{Ort.Name} ({Dest})";
|
||||
|
||||
[ForeignKey("Plz")]
|
||||
public virtual AT_Plz AtPlz { get; private set; } = null!;
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
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")]
|
||||
public class AreaCom {
|
||||
[Table("area_commitment"), PrimaryKey("FbNr", "RevNr")]
|
||||
public class AreaCmt {
|
||||
[Column("fbnr")]
|
||||
public int FbNr { get; set; }
|
||||
|
||||
[Column("revnr")]
|
||||
public int RevNr { get; set; }
|
||||
|
||||
[Column("mgnr")]
|
||||
public int MgNr { get; set; }
|
||||
|
||||
@@ -22,24 +23,15 @@ 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]
|
||||
@@ -72,25 +64,16 @@ namespace Elwig.Models.Entities {
|
||||
set => ITime = value == null ? null : ((DateTimeOffset)value.Value.ToUniversalTime()).ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
[ForeignKey("FbNr")]
|
||||
public virtual AreaCmtContract Contract { get; private set; } = null!;
|
||||
|
||||
[ForeignKey("MgNr")]
|
||||
public virtual Member Member { get; private set; } = null!;
|
||||
|
||||
[ForeignKey("VtrgId")]
|
||||
public virtual AreaComType AreaComType { get; private set; } = null!;
|
||||
public virtual AreaCmtType AreaCmtType { get; private set; } = null!;
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
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 AreaCmtContract {
|
||||
[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(AreaCmt.Contract))]
|
||||
public virtual ICollection<AreaCmt> Revisions { get; private set; } = null!;
|
||||
|
||||
[NotMapped]
|
||||
public AreaCmt? 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,7 +5,7 @@ using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute;
|
||||
|
||||
namespace Elwig.Models.Entities {
|
||||
[Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId", "Discriminator", IsUnique = true)]
|
||||
public class AreaComType {
|
||||
public class AreaCmtType {
|
||||
[Column("vtrgid")]
|
||||
public required string VtrgId { get; set; }
|
||||
|
||||
@@ -106,21 +106,21 @@ namespace Elwig.Models.Entities {
|
||||
[InverseProperty(nameof(DeliveryPart.Delivery))]
|
||||
public virtual ICollection<DeliveryPart> Parts { get; private set; } = null!;
|
||||
[NotMapped]
|
||||
public IEnumerable<DeliveryPart> FilteredParts => PartFilter == null ? Parts : Parts.Where(p => PartFilter(p));
|
||||
public IEnumerable<DeliveryPart> FilteredParts => PartFilter == null ? Parts : Parts.Where(p => PartFilter(p));
|
||||
|
||||
[NotMapped]
|
||||
public Predicate<DeliveryPart>? PartFilter { get; set; }
|
||||
|
||||
public int Weight => Parts.Select(p => p.Weight).Sum();
|
||||
public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
|
||||
public int Weight => Parts.Sum(p => p.Weight);
|
||||
public int FilteredWeight => FilteredParts.Sum(p => p.Weight);
|
||||
|
||||
public IEnumerable<RawVaribute> Vaributes => Parts
|
||||
.GroupBy(p => (p.SortId, p.AttrId, p.CultId))
|
||||
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
|
||||
.OrderByDescending(g => g.Sum(p => p.Weight))
|
||||
.Select(g => new RawVaribute(g.Key.SortId, g.Key.AttrId, g.Key.CultId));
|
||||
public IEnumerable<RawVaribute> FilteredVaributes => FilteredParts
|
||||
.GroupBy(p => (p.SortId, p.AttrId, p.CultId))
|
||||
.OrderByDescending(g => g.Select(p => p.Weight).Sum())
|
||||
.OrderByDescending(g => g.Sum(p => p.Weight))
|
||||
.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(Parts.Select(p => p.Comment).Distinct());
|
||||
list.AddRange(FilteredParts.Select(p => p.Comment).Distinct());
|
||||
return Utils.GetSearchScore(list, keywords);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +91,8 @@ namespace Elwig.Models.Entities {
|
||||
[Column("hand_picked")]
|
||||
public bool? IsHandPicked { get; set; }
|
||||
|
||||
[Column("gebunden")]
|
||||
public bool? IsGebunden { get; set; }
|
||||
[Column("contracted")]
|
||||
public bool? IsContracted { get; set; }
|
||||
|
||||
[Column("unloading")]
|
||||
public string? Unloading { get; set; }
|
||||
@@ -118,26 +118,28 @@ namespace Elwig.Models.Entities {
|
||||
[Column("acid")]
|
||||
public double? Acid { get; set; }
|
||||
|
||||
[Column("scale_id")]
|
||||
public string? ScaleId { get; set; }
|
||||
[Column("scale_terminal")]
|
||||
public string? ScaleTerminalId { get; set; }
|
||||
|
||||
[Column("weighing_data")]
|
||||
public string? WeighingData { get; set; }
|
||||
[NotMapped]
|
||||
public (string? Id, int? Gross, int? Tare, int? Net, DateOnly? Date, TimeOnly? Time) WeighingInfo {
|
||||
public (string? ScaleId, string? Id, int? Gross, int? Tare, int? TareCorrection, int? Net, DateOnly? Date, TimeOnly? Time) WeighingInfo {
|
||||
get {
|
||||
try {
|
||||
var obj = JsonNode.Parse(WeighingData!)!.AsObject();
|
||||
return (
|
||||
obj["scale"]?.AsValue().GetValue<string>(),
|
||||
obj["id"]?.AsValue().GetValue<string>(),
|
||||
obj["gross_weight"]?.AsValue().GetValue<int>(),
|
||||
obj["tare_weight"]?.AsValue().GetValue<int>(),
|
||||
obj["tare_correction"]?.AsValue().GetValue<int>(),
|
||||
obj["net_weight"]?.AsValue().GetValue<int>(),
|
||||
DateOnly.TryParseExact(obj["date"]?.AsValue().GetValue<string>(), "yyyy-MM-dd", out var d) ? d : null,
|
||||
TimeOnly.TryParseExact(obj["time"]?.AsValue().GetValue<string>(), ["HH:mm:ss", "HH:mm"], out var t) ? t : null
|
||||
);
|
||||
} catch {
|
||||
return (null, null, null, null, null, null);
|
||||
return (null, null, null, null, null, null, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace Elwig.Models.Entities {
|
||||
|
||||
[Column("cultid")]
|
||||
public string? CultId { get; set; }
|
||||
[NotMapped]
|
||||
public bool IsOrganic => Cultivation?.IsOrganic ?? false;
|
||||
|
||||
[Column("max_weight")]
|
||||
public int? MaxWeight { get; set; }
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace Elwig.Models.Entities {
|
||||
[NotMapped]
|
||||
public string ShortName => (!string.IsNullOrWhiteSpace(GivenName) ? $"{GivenName} " : "") + Name;
|
||||
[NotMapped]
|
||||
public string FullAdministrativeName => $"{AdministrativeName} ({MgNr})";
|
||||
[NotMapped]
|
||||
public string AdministrativeName => AdministrativeName1 + (!string.IsNullOrWhiteSpace(AdministrativeName2) ? $" {AdministrativeName2}" : "");
|
||||
[NotMapped]
|
||||
public string AdministrativeName1 => IsJuridicalPerson ? Name : Name.Replace('ß', 'ẞ').ToUpper();
|
||||
@@ -67,8 +69,20 @@ namespace Elwig.Models.Entities {
|
||||
set => ExitDateString = value?.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
[Column("business_shares")]
|
||||
public int BusinessShares { get; set; }
|
||||
[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 SharesActive => Shares + SharesRed + SharesWhite;
|
||||
|
||||
[Column("accounting_nr")]
|
||||
public string? AccountingNr { get; set; }
|
||||
@@ -82,20 +96,26 @@ namespace Elwig.Models.Entities {
|
||||
[Column("ustid_nr")]
|
||||
public string? UstIdNr { get; set; }
|
||||
|
||||
[Column("ooc_id")]
|
||||
public string? OrganicOperatorId { get; set; }
|
||||
|
||||
[Column("org_auth_code")]
|
||||
public string? OrganicAuthorityCode { get; set; }
|
||||
|
||||
[Column("juridical_pers")]
|
||||
public bool IsJuridicalPerson { get; set; }
|
||||
|
||||
[Column("volllieferant")]
|
||||
public bool IsVollLieferant { get; set; }
|
||||
[Column("full_delivery")]
|
||||
public bool IsFullDelivery { get; set; }
|
||||
|
||||
[Column("buchführend")]
|
||||
public bool IsBuchführend { get; set; }
|
||||
[Column("accounting")]
|
||||
public bool IsAccounting { get; set; }
|
||||
|
||||
[Column("organic")]
|
||||
public bool IsOrganic { get; set; }
|
||||
|
||||
[Column("funktionär")]
|
||||
public bool IsFunktionär { get; set; }
|
||||
[Column("officer")]
|
||||
public bool IsOfficer { get; set; }
|
||||
|
||||
[Column("active")]
|
||||
public bool IsActive { get; set; }
|
||||
@@ -179,19 +199,24 @@ namespace Elwig.Models.Entities {
|
||||
[ForeignKey("ZwstId")]
|
||||
public virtual Branch? Branch { get; private set; }
|
||||
|
||||
[InverseProperty(nameof(AreaCom.Member))]
|
||||
public virtual ICollection<AreaCom> AreaCommitments { get; private set; } = null!;
|
||||
[InverseProperty(nameof(AreaCmt.Member))]
|
||||
public virtual ICollection<AreaCmt> AreaCommitments { get; private set; } = null!;
|
||||
|
||||
public IQueryable<AreaCom> ActiveAreaCommitments(AppDbContext ctx, int? year = null) {
|
||||
public IQueryable<AreaCmt> ActiveAreaCommitments(AppDbContext ctx, int? year = null) {
|
||||
return ctx.AreaCommitments.Where(c => c.MgNr == MgNr).Where(Utils.ActiveAreaCommitments(year ?? Utils.CurrentYear));
|
||||
}
|
||||
|
||||
[InverseProperty(nameof(BillingAddr.Member))]
|
||||
public virtual BillingAddr? BillingAddress { get; private set; }
|
||||
|
||||
[InverseProperty(nameof(Delivery.Member))]
|
||||
[InverseProperty(nameof(DeliveryAncmt.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!;
|
||||
|
||||
|
||||
@@ -1,31 +1,130 @@
|
||||
using Elwig.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Elwig.Models.Entities {
|
||||
[Table("member_history"), PrimaryKey("MgNr", "DateString", "Type")]
|
||||
[Table("member_history"), PrimaryKey("HistNr")]
|
||||
public class MemberHistory {
|
||||
[Column("mgnr")]
|
||||
public int MgNr { get; set; }
|
||||
|
||||
[Column("date")]
|
||||
public required string DateString { get; set; }
|
||||
public const string REASON_PURCHASE = "purchase"; // Kauf
|
||||
public const string REASON_CONVERT = "convert"; // Änderung
|
||||
public const string REASON_TRANSFER = "transfer"; // Übertragung
|
||||
public const string REASON_PRESCRIPTION = "prescription"; // Vorschreibung
|
||||
public const string REASON_EXCLUSION = "exclusion"; // Ausschluss
|
||||
public const string REASON_DECEASED = "deceased"; // Tod
|
||||
public const string REASON_RETIREMENT = "retirement"; // Kündigung/stilllegen
|
||||
public const string REASON_PAYOUT = "payout"; // Auszahlung
|
||||
|
||||
[Column("histnr")]
|
||||
public int HistNr { get; set; }
|
||||
|
||||
[Column("from_mgnr")]
|
||||
public int? FromMgNr { get; set; }
|
||||
[Column("from_type")]
|
||||
public int? FromTypeValue { get; set; }
|
||||
[NotMapped]
|
||||
public DateOnly Date {
|
||||
get => DateOnly.ParseExact(DateString, "yyyy-MM-dd");
|
||||
public BusinessShareType? FromType {
|
||||
get => (BusinessShareType?)FromTypeValue;
|
||||
set => FromTypeValue = (int?)value;
|
||||
}
|
||||
|
||||
[Column("to_mgnr")]
|
||||
public int? ToMgNr { get; set; }
|
||||
[Column("to_type")]
|
||||
public int? ToTypeValue { get; set; }
|
||||
[NotMapped]
|
||||
public BusinessShareType? ToType {
|
||||
get => (BusinessShareType?)ToTypeValue;
|
||||
set => ToTypeValue = (int?)value;
|
||||
}
|
||||
|
||||
[Column("date_notice")]
|
||||
public required string DateNoticeString { get; set; }
|
||||
[NotMapped]
|
||||
public DateOnly DateNotice {
|
||||
get => DateOnly.ParseExact(DateNoticeString, "yyyy-MM-dd");
|
||||
set => value.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
[Column("type")]
|
||||
public required string Type { get; set; }
|
||||
[Column("date_effective")]
|
||||
public required string DateEffectiveString { get; set; }
|
||||
[NotMapped]
|
||||
public DateOnly DateEffective {
|
||||
get => DateOnly.ParseExact(DateEffectiveString, "yyyy-MM-dd");
|
||||
set => value.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
[Column("business_shares")]
|
||||
public int BusinessShares { get; set; }
|
||||
[Column("reason")]
|
||||
public required string Reason { get; set; }
|
||||
[NotMapped]
|
||||
public string ReasonPretty => Reason switch {
|
||||
REASON_PURCHASE => "Kauf",
|
||||
REASON_CONVERT => "Änderung",
|
||||
REASON_TRANSFER => "Übertragung",
|
||||
REASON_PRESCRIPTION => "Vorschreibung",
|
||||
REASON_EXCLUSION => "Ausschluss",
|
||||
REASON_DECEASED => "Tod",
|
||||
REASON_RETIREMENT => "Kündigung",
|
||||
REASON_PAYOUT => "Auszahlung",
|
||||
_ => Reason,
|
||||
};
|
||||
|
||||
[Column("source")]
|
||||
public required string Source { get; set; }
|
||||
|
||||
[Column("shares")]
|
||||
public int Shares { get; set; }
|
||||
|
||||
[Column("signed_company")]
|
||||
public bool CompanyHasSigned { get; set; }
|
||||
|
||||
[Column("signed_member")]
|
||||
public bool MemberHasSigned { 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("deduct_year")]
|
||||
public int? DeductYear { get; set; }
|
||||
|
||||
[Column("comment")]
|
||||
public string? Comment { get; set; }
|
||||
|
||||
[ForeignKey("MgNr")]
|
||||
public virtual Member Member { get; private set; } = null!;
|
||||
public string FormatReasonPhrase(int mgnr) {
|
||||
if (Reason == REASON_TRANSFER) {
|
||||
if (ToMgNr == mgnr) {
|
||||
return $"{ReasonPretty} von {FromMember?.FullAdministrativeName ?? "Vorgänger"}" + (Comment != null ? $", {Comment}" : "");
|
||||
} else if (FromMgNr == mgnr) {
|
||||
return $"{ReasonPretty} an {ToMember?.FullAdministrativeName ?? "Nachfolger"}" + (Comment != null ? $", {Comment}" : "");
|
||||
}
|
||||
}
|
||||
return ReasonPretty + (Comment != null ? $", {Comment}" : "");
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public int? PoVMgNr { get; set; }
|
||||
[NotMapped]
|
||||
public string PoVReasonPhrase => FormatReasonPhrase(PoVMgNr ?? 0);
|
||||
|
||||
[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!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,20 @@ namespace Elwig.Models.Entities {
|
||||
[Column("vat_flatrate")]
|
||||
public double VatFlatrate { get; set; }
|
||||
|
||||
[Column("min_kg_per_bs")]
|
||||
public int MinKgPerBusinessShare { get; set; }
|
||||
[Column("min_kg_per_share")]
|
||||
public int? MinKgPerShare { get; set; }
|
||||
[Column("max_kg_per_share")]
|
||||
public int? MaxKgPerShare { get; set; }
|
||||
|
||||
[Column("max_kg_per_bs")]
|
||||
public int MaxKgPerBusinessShare { 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("penalty_per_kg")]
|
||||
public long? PenaltyPerKgValue { get; set; }
|
||||
@@ -55,23 +64,23 @@ namespace Elwig.Models.Entities {
|
||||
set => PenaltyNoneValue = value != null ? DecToDb(value.Value) : null;
|
||||
}
|
||||
|
||||
[Column("penalty_per_bs_amount")]
|
||||
public long? PenaltyPerBsAmountValue { get; set; }
|
||||
[Column("penalty_per_share_amount")]
|
||||
public long? PenaltyPerShareAmountValue { get; set; }
|
||||
[NotMapped]
|
||||
public decimal? PenaltyPerBsAmount {
|
||||
get => PenaltyPerBsAmountValue != null ? DecFromDb(PenaltyPerBsAmountValue.Value) : null;
|
||||
set => PenaltyPerBsAmountValue = value != null ? DecToDb(value.Value) : null;
|
||||
public decimal? PenaltyPerShareAmount {
|
||||
get => PenaltyPerShareAmountValue != null ? DecFromDb(PenaltyPerShareAmountValue.Value) : null;
|
||||
set => PenaltyPerShareAmountValue = value != null ? DecToDb(value.Value) : null;
|
||||
}
|
||||
|
||||
[Column("penalty_per_bs_none")]
|
||||
public long? PenaltyPerBsNoneValue { get; set; }
|
||||
[Column("penalty_per_share_none")]
|
||||
public long? PenaltyPerShareNoneValue { get; set; }
|
||||
[NotMapped]
|
||||
public decimal? PenaltyPerBsNone {
|
||||
get => PenaltyPerBsNoneValue != null ? DecFromDb(PenaltyPerBsNoneValue.Value) : null;
|
||||
set => PenaltyPerBsNoneValue = value != null ? DecToDb(value.Value) : null;
|
||||
public decimal? PenaltyPerShareNone {
|
||||
get => PenaltyPerShareNoneValue != null ? DecFromDb(PenaltyPerShareNoneValue.Value) : null;
|
||||
set => PenaltyPerShareNoneValue = value != null ? DecToDb(value.Value) : null;
|
||||
}
|
||||
|
||||
[Column("bs_value")]
|
||||
[Column("share_value")]
|
||||
public long? BusinessShareValueValue { get; set; }
|
||||
[NotMapped]
|
||||
public decimal? BusinessShareValue {
|
||||
@@ -98,7 +107,7 @@ namespace Elwig.Models.Entities {
|
||||
[Column("calc_mode")]
|
||||
public int CalcMode { get; set; }
|
||||
[NotMapped]
|
||||
public bool Billing_HonorGebunden {
|
||||
public bool Billing_HonorContracted {
|
||||
get => (CalcMode & 0x1) != 0;
|
||||
set => CalcMode = value ? CalcMode | 0x1 : CalcMode & ~0x1;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Elwig.Models.Entities {
|
||||
[Column("name")]
|
||||
public required string Name { get; set; }
|
||||
|
||||
[Column("organic")]
|
||||
public bool IsOrganic { get; set; }
|
||||
|
||||
[Column("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
|
||||
@@ -27,13 +27,15 @@ namespace Elwig.Models.Entities {
|
||||
public virtual ICollection<WbGem> Gems { get; private set; } = null!;
|
||||
|
||||
[InverseProperty(nameof(Parent))]
|
||||
public virtual ICollection<WineOrigin> Children { get; private set; } = null!;
|
||||
public virtual ICollection<WineOrigin> RealChildren { get; private set; } = null!;
|
||||
[NotMapped]
|
||||
public List<WineOrigin> Children { get; private set; } = [];
|
||||
|
||||
public int Level => (Parent?.Level + 1) ?? 0;
|
||||
|
||||
public string HkIdLevel => $"{new string(' ', Level * 2)}{HkId}";
|
||||
|
||||
public int TotalChildNum => 1 + Children.Select(c => c.TotalChildNum).Sum();
|
||||
public int TotalChildNum => 1 + Children.Sum(c => c.TotalChildNum);
|
||||
|
||||
private int SortKey1 => (Parent?.SortKey1 ?? 0) | (TotalChildNum << ((3 - Level) * 8));
|
||||
public int SortKey => SortKey1 | ((Level < 3) ? (-1 >>> (Level * 8 + 8)) : 0);
|
||||
|
||||
@@ -92,6 +92,12 @@
|
||||
{"$ref": "#/definitions/curve_data"}
|
||||
]
|
||||
},
|
||||
"ctrd": {
|
||||
"anyOf": [
|
||||
{"type": "number"},
|
||||
{"$ref": "#/definitions/curve_data"}
|
||||
]
|
||||
},
|
||||
"geb": {
|
||||
"anyOf": [
|
||||
{"type": "number"},
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
-- 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';
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 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);
|
||||
@@ -0,0 +1,326 @@
|
||||
-- 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 CHECK (shares_red >= 0);
|
||||
ALTER TABLE member ADD COLUMN shares_white INTEGER NOT NULL DEFAULT 0 CHECK (shares_white >= 0);
|
||||
ALTER TABLE member ADD COLUMN shares_dormant INTEGER NOT NULL DEFAULT 0 CHECK (shares_dormant >= 0);
|
||||
|
||||
UPDATE sqlite_schema SET sql = REPLACE(sql, 'shares INTEGER NOT NULL DEFAULT 0', 'shares INTEGER NOT NULL DEFAULT 0 CHECK (shares >= 0)')
|
||||
WHERE type = 'table' AND name = 'member';
|
||||
|
||||
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_notice TEXT NOT NULL CHECK (date_notice REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$') DEFAULT CURRENT_DATE,
|
||||
date_effective TEXT NOT NULL CHECK (date_effective 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 CHECK (shares > 0),
|
||||
signed INTEGER NOT NULL CHECK (signed IN (TRUE, FALSE)) DEFAULT FALSE,
|
||||
value_per_share INTEGER DEFAULT NULL,
|
||||
currency TEXT DEFAULT NULL,
|
||||
deduct_year INTEGER 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_notice, date_effective, reason, source, shares, value_per_share, currency, deduct_year, comment)
|
||||
SELECT ROW_NUMBER() OVER(ORDER BY h.date, h.mgnr), NULL, NULL, h.mgnr, 1, h.date, h.date,
|
||||
IIF(h.type = 'auto', 'prescription', h.type), 'elwig', h.business_shares, s.bs_value / POW(10, s.precision - 2), s.currency,
|
||||
IIF(h.type = 'auto', s.year, NULL), 'automatische Nachzeichnung'
|
||||
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 OF from_mgnr, from_type, to_mgnr, to_type, shares 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_notice, 1, 4) + 0) > s.year AND h1.from_mgnr = m.mgnr
|
||||
LEFT JOIN member_history h2 ON (SUBSTR(h2.date_notice, 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_notice, 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 = 'prescription' AND h.source = 'elwig'
|
||||
GROUP BY year, h.to_mgnr
|
||||
ORDER BY year, h.to_mgnr;
|
||||
|
||||
PRAGMA writable_schema = OFF;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user