Compare commits

..

69 Commits

Author SHA1 Message Date
f97dfc3c72 Bump version to 0.10.1 2024-08-14 12:02:36 +02:00
d3839c288a MemberList: Allow filtering area commitments 2024-08-14 11:57:19 +02:00
2764a0ca21 Dtos: Fix error when given_name is null 2024-08-14 11:57:04 +02:00
48970de652 Billing: Fix error when no deliveries exist in season 2024-08-14 11:56:25 +02:00
367c3ac357 Bump version to 0.10.0 2024-08-13 15:23:58 +02:00
4d89a17e80 Billing: Allow users to add custom member modifiers before VAT 2024-08-13 14:43:12 +02:00
f52c11b91e PaymentVariantSummary: Subtract member modifiers from sum 2024-08-12 15:44:04 +02:00
f48c6a02cb [] Member: Add IsJuridicalPerson 2024-08-12 15:18:34 +02:00
025ff08d84 [] Documents: Add DeliveryAncmtList 2024-08-10 15:45:37 +02:00
b091bd0ec3 [] Windows: Add DeliveryAncmtWindow 2024-08-09 22:11:47 +02:00
804a17911c [] Windows: Add DeliveryScheduleAdminWindow 2024-08-09 17:45:14 +02:00
170cfda37e Windows: Minor cleanups 2024-08-09 17:44:48 +02:00
2d737e2780 [] Models: Add DeliveryAncmt 2024-08-09 17:44:21 +02:00
2333077aa5 Billing: Include predecessors in Treuebonus for WGM 2024-08-08 19:09:38 +02:00
9127cd3f03 MemberAdminWindow: Fix area commitment transfer for new members 2024-08-08 17:35:58 +02:00
036a0dc978 App: Use Version class 2024-08-02 20:48:53 +02:00
7749f6ab45 Bump version to 0.9.3 2024-08-02 12:22:52 +02:00
cf05a0c658 DeliveryJournalData: Add delivery and member branch to excel export 2024-08-02 11:42:27 +02:00
5567d9f25a Bump version to 0.9.2 2024-08-01 16:06:13 +02:00
4403754ada MemberAdminWindow: Fix error when saving telephone numbers 2024-08-01 15:57:49 +02:00
8db6007264 MainWindow: Fix closing behaviour when other windows are open 2024-08-01 13:49:04 +02:00
944270744a Bump version to 0.9.1 2024-08-01 09:12:33 +02:00
10ee1d6548 [] MemberService: Export area commitments with members 2024-07-30 17:00:29 +02:00
db4de5b5fe [] Windows: Add option to export selected member or delivery only 2024-07-30 14:24:53 +02:00
f69d2809f3 MailWindow: Allow users to send emails without documents 2024-07-30 12:57:07 +02:00
1c2e0baa68 [] Services: Use .elwig.zip as export extension everywhere 2024-07-30 12:57:04 +02:00
39f93da0ba SysTecITScale: Add error code 20 for negative weight 2024-07-30 00:07:12 +02:00
91717f8efb Services: Add also messages for TaskCanceledException 2024-07-29 23:35:03 +02:00
7786fb421a DeliveryAdminWindow: Fix scale button ordering 2024-07-29 23:12:00 +02:00
12d2aeecaf Bump version to 0.9.0 2024-07-28 22:13:17 +02:00
1bc0d67d26 [] Services: Add 'Check internet connection' message 2024-07-28 09:35:01 +02:00
38315cd928 [] Services: Update 'upload successful' message 2024-07-28 00:27:21 +02:00
53d3affefe DeliveryConfirmation: Use checkboxes for 'gerebelt' 2024-07-27 21:55:20 +02:00
4c3f0c40fa DeliveryAdminWindow: Handle members without default KG correctly 2024-07-27 20:46:13 +02:00
662862090e [] ElwigData: Fix import error when no deliveries are present 2024-07-27 19:44:22 +02:00
a5164e286f [] Services: Use .elwig.zip as export extension 2024-07-27 19:44:22 +02:00
ae1e985656 [] MainWindow: Add functionality to import data from files 2024-07-27 19:44:22 +02:00
36c1bd35a7 [] MemberAdminWindow: Add Export item in menu 2024-07-27 19:44:22 +02:00
4aa3362029 DeliveryConfirmation: Use 'gerebelt' instead of 'bto./nto.' 2024-07-27 19:44:15 +02:00
cc97004b30 [] ElwigData: Fix importing of duplicate data 2024-07-27 16:57:23 +02:00
935b31f6e3 DeliveryAdminWindow: Force user to input gerebelt gewogen input 2024-07-26 19:47:47 +02:00
f09753ccc2 Remove byte order marks 2024-07-26 19:44:41 +02:00
53c7cb2ec0 Workflows: Check for byte order mark 2024-07-26 19:44:37 +02:00
80c3ec1b9c Windows: Use Indeterminate event in ThreeState CheckBoxes 2024-07-26 16:15:51 +02:00
b49c9c65b1 [] Elwig: Add user-friendly sync method 2024-07-26 14:58:15 +02:00
b6afb94246 MemberListData: Include contact information like phone numbers and email addresses 2024-07-24 19:28:34 +02:00
8e9f2f4e90 WeighingTests: Add Haugsdorf and Sitzendorf 2024-07-24 16:42:38 +02:00
d741ba92dc Printing/Pdf: Set CreateNoWindow to true 2024-07-23 20:22:59 +02:00
84f772a32f MailWindow: Fix crash when no season is present 2024-07-23 20:22:59 +02:00
fd0ed97305 Weighing: Do not ignore gross and tare weight and show it on DeliveryNote 2024-07-23 20:22:59 +02:00
1141331608 MemberAdminWindow: Add filters for E-Mail, Tel.-Nr., and contact options 2024-07-23 20:22:59 +02:00
f235d5b380 DeliveryAdminWindow: Fix ModifierInput_SelectionChanged event 2024-07-23 20:22:59 +02:00
30116f7848 DeliveryAdminWindow: Improve status bar widths 2024-07-23 20:22:59 +02:00
abe7699a5b [] MemberAdminWindow: Rearrange status bar items 2024-07-23 20:22:59 +02:00
bb77a4e79a [] AreaComAdminWindow: Overhaul status bar and search filters 2024-07-23 20:22:59 +02:00
46ea0f29ff [] MemberAdminWindow: Add tooltip to area commitment status bar 2024-07-23 20:22:59 +02:00
4229fbbef6 [] AreaComService: Add GenerateToolTip() 2024-07-23 20:22:59 +02:00
8dde1cb3f4 [] ViewModels: Unify FilterMember property 2024-07-23 20:22:59 +02:00
c314321039 Utils: Change CurrentLastSeason to switch in july 2024-07-23 20:22:59 +02:00
cf1e975d8e [] MemberAdminWindow: Add tooltip for delivieries 2024-07-23 20:22:59 +02:00
60359935a4 [] DeliveryAdminWindow: Move generation of tooltips to DeliveryService 2024-07-23 20:22:59 +02:00
49e988f71a [] AreaComAdminWindow: Implement MVVM 2024-07-23 20:22:59 +02:00
2a8de18772 [] DeliveryAdminWindow: Implement MVVM 2024-07-23 20:22:59 +02:00
af80e827b7 [] MemberAdminWindow: Implement MVVM 2024-07-23 20:22:59 +02:00
9a2fa3ee3d Bump version to 0.8.9 2024-07-23 20:22:52 +02:00
ba9e1d7201 Export/Ods: Escape strings in XML 2024-07-23 20:13:04 +02:00
49f03c0a3c CheckComboBox: Fix SelectedItems property 2024-07-23 11:08:42 +02:00
37adf92e80 Bump version to 0.8.8 2024-07-22 09:48:53 +02:00
4c75dbe4aa GassnerScale: Hot fix record parsing 2024-07-22 09:48:18 +02:00
187 changed files with 7472 additions and 2707 deletions
.gitea/workflows
CHANGELOG.md
Elwig
App.xaml.cs
Controls
Dialogs
Documents
Elwig.csproj
Helpers
Models
Properties
PublishProfiles
Resources
Services
Themes
ViewModels
Windows
app.manifest
Installer
Setup
Tests

@ -2,7 +2,7 @@ name: Test
on:
push:
branches: ["**"]
paths: ["Elwig/**", "Tests/**", "Installer/Files/*.exe"]
paths: ["Elwig/**", "Tests/**", "Installer/Files/*.exe", ".gitea/workflows/test.yaml"]
jobs:
test:
name: Run tests
@ -10,6 +10,19 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check for Byte order marks
shell: powershell
run: |
$pattern = [char]::ConvertFromUtf32(0xFEFF)
$files = git grep -IEl "^$pattern"
if ( $lastexitcode -ne 1 ) {
echo "Files with BOM found:"
echo $files
exit 1
} else {
echo "No files with BOM found"
exit 0
}
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
- name: Setup NuGet

@ -3,6 +3,160 @@ Changelog
=========
[v0.10.1][v0.10.1] (2024-08-14) {#v0.10.1}
------------------------------------------
### Neue Funktionen {#v0.10.1-features}
* In der Mitglieder-Liste (PDF und Excel) werden ggf. gefilterte Flächenbindungen angegeben. (d3839c288a)
### Behobene Fehler {#v0.10.1-bugfixes}
* Fehler beim Berechnen...
* falls in Saison keine Lieferungen vorhanden sind. (48970de652)
* falls der Vorname eines Mitgliedes nicht gesetzt ist. (2764a0ca21)
[v0.10.1]: https://git.necronda.net/winzer/elwig/releases/tag/v0.10.1
[v0.10.0][v0.10.0] (2024-08-13) {#v0.10.0}
------------------------------------------
> [!NOTE]
> Mitglieder können ab dieser Version als juristische Person markiert werden.
> Es ist empfohlen, dies bei entsprechenden Mitglieder zu überprüfen und einzusetzen (z.B. Bezirksbauernkammer, Lagerhaus).
### Neue Funktionen {#v0.10.0-features}
* Traubenanmeldungen und Leseplanung sind ab jetzt verfügbar. ([#14][i14])
* Mitglieder können nun auch als juristische Person markiert werden. ([#54][i54])
* Im Auszahlungsvarianten-Fenster kann unter _Anpassen_ (`PaymentAdjustmentWindow`) nun auch ein Absoluter oder Relativer Zu-/Abschlag pro Mitglied festgelegt werden, der **vor der MwSt.** angewendet wird. (4d89a17e80)
### Behobene Fehler {#v0.10.0-bugfixes}
* Beim Erstellen eines neuen Mitgliedes war es nicht möglich die Flächenbindungen des Vorgängers zu übernehmen. (9127cd3f03)
### Sonstiges {#v0.10.0-misc}
* Für den Treuebonus der WG Matzen werden nun auch Lieferungen der Vorgänger miteinbezogen. (2333077aa5)
* In den Variantendaten einer Auszahlungsvariante (`PaymentVariantSummary`) werden Zu-/Abschläge pro Mitglied explizit angegeben, somit ist die Zwischensumme korrekt. (f52c11b91e)
[v0.10.0]: https://git.necronda.net/winzer/elwig/releases/tag/v0.10.0
[i14]: https://git.necronda.net/winzer/elwig/issues/14
[i54]: https://git.necronda.net/winzer/elwig/issues/54
[v0.9.3][v0.9.3] (2024-08-02) {#v0.9.3}
---------------------------------------
### Sonstiges {#v0.9.3-misc}
* Das Lieferjournal als Excel-Liste beinhaltet nun auch Liefer-Zweigstelle und Stamm-Zweigstelle des Liefernaten. (cf05a0c658)
[v0.9.3]: https://git.necronda.net/winzer/elwig/releases/tag/v0.9.3
[v0.9.2][v0.9.2] (2024-08-01) {#v0.9.2}
---------------------------------------
### Behobene Fehler {#v0.9.2-bugfixes}
* Verhalten beim Schließen des Haupt-Fensters (`MainWindow`) nicht immer richtig. (8db6007264)
* Im Mitglieder-Fenster (`MemberAdminWindow`) führt beim Erstellen eines Mitglied das Eingeben einer Tel.-Nr. zu einem Fehler. (4403754ada)
[v0.9.2]: https://git.necronda.net/winzer/elwig/releases/tag/v0.9.2
[v0.9.1][v0.9.1] (2024-08-01) {#v0.9.1}
---------------------------------------
### Neue Funktionen {#v0.9.1-features}
* Im Rundschreiben-Fenster (`MailWindow`) können E-Mails ohne Anhänge verschickt werden. (f69d2809f3)
* Im Mitglieder-Fenster (`MemberAdminWindow`) und Lieferungen-Fenster (`DeliveryAdminWindow`) kann das/die momentan ausgewählte Mitglied/Lieferung alleine exportiert werden. (db4de5b5fe)
* Exporte für Mitglieder enthalten nun auch deren Flächenbindungen. (10ee1d6548)
### Behobene Fehler {#v0.9.1-bugfixes}
* Sortierung der Wiegen-Knöpfe im Übernahme-Fenster (`DeliveryAdminWindow`). (7786fb421a)
### Sonstiges {#v0.9.1-misc}
* Deutsche Fehlermeldung beim Hochladen/Herunterladen im Haupt-Fenster (`MainWindow`). (91717f8efb)
* Waagen-Fehler _Brutto negativ_ implementiert. (39f93da0ba)
[v0.9.1]: https://git.necronda.net/winzer/elwig/releases/tag/v0.9.1
[v0.9.0][v0.9.0] (2024-07-28) {#v0.9.0}
---------------------------------------
### Neue Funktionen {#v0.9.0-features}
* Flächenbindungen werden als Tabelle in der Status-Leiste im Mitglieder-Fenster (`MemberAdminWindow`) angezeigt. ([#26][i26])
* Filter für E-Mail-Adressen, Tel.-Nr. und Kontaktarten im Mitglieder-Fenster (`MemberAdminWindow`). (1141331608)
* Auf Lieferscheinen (`DeliveryNote`) werden nun _Brutto, Tara, Netto_ werte abgedruckt. (fd0ed97305, 4aa3362029, 53d3affefe)
* Beim Exportieren der Mitgliederliste werden auch Tel.-Nr. und E-Mail-Adressen exportiert. (b6afb94246)
* Benutzerfreundliches Synchronisieren zwischen Standorten. ([#3][i3])
* Übernehmer _müssen_ explizit entscheiden, ob _gerebelt gewogen_ oder nicht. (935b31f6e3)
### Behobene Fehler {#v0.9.0-bugfixes}
* Zu-/Abschläge im Lieferungen-Fenster (`DeliveryAdminWindow`). (f235d5b380)
* Falls keine Saison existierte führte das zu einem Absturz im Rundschreiben-Fenster (`MailWindow`). (84f772a32f)
* Beim Drucken _sollte_ kein Kommandozeilen-Fenster mehr sichtbar sein. (d741ba92dc)
* Wenn bei einem Mitglied keine Stamm-KG hinterlegt war, kam es zu inkonsistentem Verhalten im Übernahme-Fenster (`DeliveryAdminWinodw`). (4c3f0c40fa)
### Sonstiges {#v0.9.0-misc}
* Model-View-ViewModel (MVVM) implementiert! ([#10][i10])
* Die nächste Saison "beginnt" bereits im Juli (statt erst im August) (`CurrentLastSeason`). (c314321039)
* Automatisierte Tests für Waagen (Sitzendorf, Haugsdorf). (8e9f2f4e90)
* Überprüfung auf Byte-Order-Marks in allen Quellcode-Dateien. (53c7cb2ec0, f09753ccc2)
[v0.9.0]: https://git.necronda.net/winzer/elwig/releases/tag/v0.9.0
[i3]: https://git.necronda.net/winzer/elwig/issues/3
[i10]: https://git.necronda.net/winzer/elwig/issues/10
[i26]: https://git.necronda.net/winzer/elwig/issues/26
[v0.8.9][v0.8.9] (2024-07-23) {#v0.8.9}
---------------------------------------
### Behobene Fehler {#v0.8.9-bugfixes}
* Absturz im Rundschreiben-Fenster (`MailWindow`). Fehler bei `CheckComboBox`. (49f03c0a3c)
* Excel-Exporte können nun auch Sonderzeichen (wie `&`) enthalten. (ba9e1d7201)
[v0.8.9]: https://git.necronda.net/winzer/elwig/releases/tag/v0.8.9
[v0.8.8][v0.8.8] (2024-07-22) {#v0.8.8}
---------------------------------------
### Behobene Fehler {#v0.8.8-bugfixes}
* Schnittstelle für Gassner-Waagen nicht funktionsfähig. (4c75dbe4aa)
[v0.8.8]: https://git.necronda.net/winzer/elwig/releases/tag/v0.8.8
[v0.8.7][v0.8.7] (2024-07-22) {#v0.8.7}
---------------------------------------

@ -29,22 +29,12 @@ namespace Elwig {
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
public static readonly string DataPath = @"C:\ProgramData\Elwig\";
public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini");
public static readonly string ExePath = @"C:\Program Files\Elwig\";
public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig");
public static Config Config { get; private set; } = new(Path.Combine(DataPath, "config.ini"));
public static int VersionMajor { get; private set; }
public static int VersionMinor { get; private set; }
public static int VersionPatch { get; private set; }
public static string Version {
get => $"{VersionMajor}.{VersionMinor}.{VersionPatch}";
private set {
var p = value.Split(".").Select(p => int.Parse(p.Trim())).ToArray();
VersionMajor = p.ElementAtOrDefault(0);
VersionMinor = p.ElementAtOrDefault(1);
VersionPatch = p.ElementAtOrDefault(2);
}
}
public static Config Config { get; private set; } = new(ConfigPath);
public static Version Version { get; private set; } = new();
public static int BranchNum { get; private set; }
public static string ZwstId { get; private set; }
@ -109,7 +99,7 @@ namespace Elwig {
}
protected override async void OnStartup(StartupEventArgs evt) {
Version = typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0";
Version = new Version(typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split('+')[0] ?? "0.0.0");
try {
await AppDbUpdater.CheckDb();
@ -230,7 +220,7 @@ namespace Elwig {
public static async Task CheckForUpdates(bool showAlert = false) {
if (Config.UpdateUrl == null) return;
var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
if (latest != null && new Version(latest.Value.Version) > new Version(Version)) {
if (latest != null && new Version(latest.Value.Version) > Version) {
await MainDispatcher.BeginInvoke(() => {
var d = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size);
if (d.ShowDialog() == true) {
@ -264,15 +254,15 @@ namespace Elwig {
}
public static DeliveryAdminWindow FocusReceipt() {
return FocusWindow<DeliveryAdminWindow>(() => new(true), w => w.IsReceipt);
return FocusWindow<DeliveryAdminWindow>(() => new(true), w => w.ViewModel.IsReceipt);
}
public static DeliveryAdminWindow FocusMemberDeliveries(int mgnr) {
return FocusWindow<DeliveryAdminWindow>(() => new(mgnr), w => w.MgNr == 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.MgNr == mgnr);
return FocusWindow<AreaComAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember.MgNr == mgnr);
}
public static BaseDataWindow FocusBaseData() {
@ -302,6 +292,14 @@ namespace Elwig {
return w;
}
public static DeliveryAncmtAdminWindow FocusDeliveryAncmt() {
return FocusWindow<DeliveryAncmtAdminWindow>(() => new());
}
public static DeliveryScheduleAdminWindow FocusDeliverySchedule() {
return FocusWindow<DeliveryScheduleAdminWindow>(() => new());
}
public static PaymentVariantsWindow FocusPaymentVariants(int year) {
return FocusWindow<PaymentVariantsWindow>(() => new(year), w => w.Year == year);
}

@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@ -8,7 +8,7 @@ using System.Windows.Controls;
namespace Elwig.Controls {
public class CheckComboBox : ListBox {
public new static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(CheckComboBox), new FrameworkPropertyMetadata(new ObservableCollection<object>(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChangedCallback));
public new static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(CheckComboBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChangedCallback));
public new IList SelectedItems {
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
@ -20,6 +20,12 @@ namespace Elwig.Controls {
set => SetValue(DelimiterProperty, value);
}
public static readonly DependencyProperty ListDisplayMemberPathProperty = DependencyProperty.Register(nameof(ListDisplayMemberPath), typeof(string), typeof(CheckComboBox), new FrameworkPropertyMetadata(null));
public string ListDisplayMemberPath {
get => (string)GetValue(ListDisplayMemberPathProperty);
set => SetValue(ListDisplayMemberPathProperty, value);
}
public static readonly DependencyProperty AllItemsSelectedContentProperty = DependencyProperty.Register(nameof(AllItemsSelectedContent), typeof(string), typeof(CheckComboBox), new FrameworkPropertyMetadata("All"));
public string AllItemsSelectedContent {
get => (string)GetValue(AllItemsSelectedContentProperty);
@ -72,6 +78,7 @@ namespace Elwig.Controls {
public CheckComboBox() {
SelectionMode = SelectionMode.Multiple;
SelectedItems = new ObservableCollection<object>();
}
public override void OnApplyTemplate() {
@ -115,7 +122,7 @@ namespace Elwig.Controls {
private void OnSelectionChanged(object sender, SelectionChangedEventArgs evt) {
SelectItemsReverse();
var dmp = DisplayMemberPath != null && DisplayMemberPath != "" ? DisplayMemberPath : null;
var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null;
if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
_textBox.Text = AllItemsSelectedContent;
AllItemsSelected = true;

@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
using System.Windows.Controls;
namespace Elwig.Controls {

@ -1,4 +1,4 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:Elwig.Controls">
<Style TargetType="ctrl:UnitTextBox" BasedOn="{StaticResource {x:Type TextBox}}">

@ -1,4 +1,4 @@
using System;
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;

@ -1,4 +1,4 @@
using System;
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;

@ -1,4 +1,4 @@
using System.Windows.Controls;
using System.Windows.Controls;
using System.Windows;
namespace Elwig.Controls {

@ -1,4 +1,4 @@
using System.Windows.Controls;
using System.Windows.Controls;
using System.Windows;
namespace Elwig.Controls {

@ -1,4 +1,4 @@
using System.Windows.Controls;
using System.Windows.Controls;
using System.Windows;
namespace Elwig.Controls {

@ -1,4 +1,4 @@
<Window x:Class="Elwig.Dialogs.AreaComDialog"
<Window x:Class="Elwig.Dialogs.AreaComDialog"
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"

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using System.Windows;
using System.Windows.Controls;

@ -1,4 +1,4 @@
<Window x:Class="Elwig.Dialogs.DeleteMemberDialog"
<Window x:Class="Elwig.Dialogs.DeleteMemberDialog"
AutomationProperties.AutomationId="DeleteMemberDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

@ -1,4 +1,4 @@
<Window x:Class="Elwig.Dialogs.NewSeasonDialog"
<Window x:Class="Elwig.Dialogs.NewSeasonDialog"
AutomationProperties.AutomationId="NewSeasonDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Windows;

@ -1,4 +1,4 @@
<Window x:Class="Elwig.Dialogs.UpdateDialog"
<Window x:Class="Elwig.Dialogs.UpdateDialog"
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"

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using System;
using System.Diagnostics;
using System.IO;

@ -32,7 +32,7 @@ namespace Elwig.Documents {
get {
IAddress addr = (Member.BillingAddress != null && UseBillingAddress) ? Member.BillingAddress : Member;
var plz = addr.PostalDest.AtPlz;
return (addr is BillingAddr ? $"{addr.Name}\n" : "") + $"{Member.AdministrativeName}\n{addr.Address}\n{plz?.Plz} {plz?.Ort.Name.Split(",")[0]}\n{addr.PostalDest.Country.Name}";
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)));
}
}

@ -33,15 +33,21 @@ namespace Elwig.Documents {
bool considerCustomModifiers,
Dictionary<string, UnderDelivery>? underDeliveries = null
) :
base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.Name)} {p.Variant.Name}", p.Member) {
base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.FullName)} {p.Variant.Name}", p.Member) {
UseBillingAddress = true;
ShowDateAndLocation = true;
Data = data;
Payment = p;
Credit = p.Credit;
var season = p.Variant.Season;
if (considerCustomModifiers) {
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
}
var mod = App.Client.IsMatzen ? ctx.Modifiers.Where(m => m.Year == season.Year && m.Name.StartsWith("Treue")).FirstOrDefault() : null;
if (mod != null) {
if (CustomPayment?.ModComment != null) {
MemberModifier = CustomPayment.ModComment;
} else if (mod != null) {
MemberModifier = $"{mod.Name} ({mod.ValueStr})";
} else {
MemberModifier = "Sonstige Zu-/Abschläge";
@ -88,8 +94,5 @@ namespace Elwig.Documents {
.Where(u => u.Item3 != 0)
.ToList();
}
if (considerCustomModifiers) {
CustomPayment = ctx.CustomPayments.Find(p.Year, p.MgNr);
}
}
}}

@ -157,9 +157,9 @@
@Raw(FormatRow($"Autom. Nachz. von GA ({Model.MemberAutoBusinessShares})", Model.MemberAutoBusinessSharesAmount, add: true));
penalty += Model.MemberAutoBusinessSharesAmount;
}
@if (Model.CustomPayment != null) {
@Raw(FormatRow(Model.CustomPayment.Comment ?? (Model.CustomPayment.Amount < 0 ? "Weitere Abzüge" : "Weitere Zuschläge"), Model.CustomPayment.Amount, add: true));
penalty += Model.CustomPayment.Amount;
@if (Model.CustomPayment?.Amount != null) {
@Raw(FormatRow(Model.CustomPayment.Comment ?? ((Model.CustomPayment.Amount.Value) < 0 ? "Weitere Abzüge" : "Weitere Zuschläge"), Model.CustomPayment.Amount.Value, add: true));
penalty += Model.CustomPayment.Amount.Value;
}
@if (Model.Credit == null) {

@ -0,0 +1,21 @@
using Elwig.Models.Dtos;
using System.Collections.Generic;
namespace Elwig.Documents {
public class DeliveryAncmtList : Document {
public new static string Name => "Anmeldeliste";
public string Filter;
public IEnumerable<DeliveryAncmtListRow> Announcements;
public DeliveryAncmtList(string filter, IEnumerable<DeliveryAncmtListRow> announcements) : base($"{Name} {filter}") {
Filter = filter;
Announcements = announcements;
}
public DeliveryAncmtList(string filter, DeliveryAncmtListData data) :
this(filter, data.Rows) {
}
}
}

@ -0,0 +1,46 @@
@using RazorLight
@inherits TemplatePage<Elwig.Documents.DeliveryAncmtList>
@model Elwig.Documents.DeliveryAncmtList
@{ Layout = "Document"; }
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryAncmtList.css" />
<main>
<h1>Anmeldeliste</h1>
<h2>@Model.Filter</h2>
<table class="announcement-list">
<colgroup>
<col style="width: 18mm;"/>
<col style="width: 12mm;"/>
<col style="width: 81mm;"/>
<col style="width: 40mm;"/>
<col style="width: 14mm;"/>
</colgroup>
<thead>
<tr>
<th rowspan="2">Datum</th>
<th rowspan="2">MgNr.</th>
<th rowspan="2" style="text-align: left;">Mitglied</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th>Gewicht</th>
</tr>
<tr>
<th class="unit">[kg]</th>
</tr>
</thead>
<tbody>
@foreach (var a in Model.Announcements) {
<tr>
<td>@($"{a.Date:dd.MM.yyyy}")</td>
<td class="number">@a.MgNr</td>
<td>@a.AdministrativeName</td>
<td>@a.Variety</td>
<td class="number">@($"{a.Weight:N0}")</td>
</tr>
}
<tr class="sum bold">
<td colspan="2">Gesamt:</td>
<td colspan="2">Anmeldungen: @($"{Model.Announcements.Count():N0}")</td>
<td class="number">@($"{Model.Announcements.Sum(a => a.Weight):N0}")</td>
</tr>
</tbody>
</table>
</main>

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

@ -33,9 +33,9 @@
<th>Gewicht</th>
<th rowspan="3" style="padding: 0;">
<svg width="10" height="40" xmlns="http://www.w3.org/2000/svg">
<text x="-40" y="5" transform="rotate(270)" font-size="8pt" font-style="italic" font-family="Times New Roman"
<text x="-40" y="4" transform="rotate(270)" font-size="8pt" font-style="italic" font-family="Times New Roman"
style="text-anchor: start; alignment-baseline: middle;">
bto./nto.
gerebelt
</text>
</svg>
</th>
@ -81,7 +81,7 @@
}
@if (i == p.Buckets.Length - 1) {
<td class="number">@($"{p.Weight:N0}")</td>
<td class="small">@(p.IsNetWeight ? "n" : "b")</td>
<td style="font-size: 7pt;">@(p.IsNetWeight ? "\u2611" : "\u2610")</td>
} else {
<td></td>
<td></td>

@ -60,8 +60,18 @@
}
}
<tr><td></td><td colspan="5">
@Raw(part.IsManualWeighing ? "<i>Handwiegung</i>" : $"<i>Waage:</i> {part.ScaleId ?? "?"}, <i>ID:</i> {part.WeighingId ?? "?"}")
(@(part.IsNetWeight ? "netto/gerebelt gewogen" : "brutto/nicht gerebelt gewogen"))@Raw(part.WeighingReason != null ? $", <i>Begründung:</i>" : "") @part.WeighingReason
@if (part.IsManualWeighing) {
<i>Handwiegung @(part.IsNetWeight ? " (gerebelt gewogen)" : " (nicht gerebelt gewogen)")</i>@Raw(part.WeighingReason != null ? ", <i>Begründung:</i> " : "") @part.WeighingReason
} else {
var info = part.WeighingInfo;
<i>Waage:</i> @(part.ScaleId ?? "?")@(", ") <i>ID:</i> @(info.Id ?? "?")
@(info.Date != null || info.Time != null ? " " : "")@(info.Time != null ? $"{info.Time:HH:mm}" : "")@(info.Date != null ? $", {info.Date:dd.MM.yyyy}" : "")
@if (info.Gross != null && info.Tare != null && info.Net != null) {
<br/><i>Brutto:</i> @($"{info.Gross:N0} kg")@(" ") <i>Tara:</i> @($"{info.Tare:N0} kg")@(" ") <i>Netto:</i> @($"{info.Net:N0} kg")@(" ")@Raw(part.IsNetWeight ? "<i>gerebelt gewogen</i>" : "<i>nicht gerebelt gewogen</i>")
} else {
@Raw($" <i>({(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen")})</i>")
}
}
</td></tr>
@if (part.Comment != null) {
<tr><td></td><td colspan="5"><i>Anmerkung:</i> @part.Comment</td></tr>

@ -90,6 +90,8 @@ namespace Elwig.Documents {
name = "WineQualityStatistics";
} else if (this is PaymentVariantSummary) {
name = "PaymentVariantSummary";
} else if (this is DeliveryAncmtList) {
name = "DeliveryAncmtList";
} else {
throw new InvalidOperationException("Invalid document object");
}
@ -156,7 +158,7 @@ namespace Elwig.Documents {
public void Show() {
if (_pdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet");
Pdf.Show(_pdfFile.NewReference(), Title + (this is BusinessDocument b ? $" - {b.Member.Name}" : ""));
Pdf.Show(_pdfFile.NewReference(), Title + (this is BusinessDocument b ? $" - {b.Member.FullName}" : ""));
}
public MimePart AsEmailAttachment(string filename) {

@ -2,7 +2,7 @@ using Elwig.Models.Entities;
namespace Elwig.Documents {
public class Letterhead : BusinessDocument {
public Letterhead(Member m) : base($"Briefkopf {m.Name}", m, true) {
public Letterhead(Member m) : base($"Briefkopf {m.FullName}", m, true) {
Aside = "";
}
}

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;

@ -1,4 +1,4 @@
@using RazorLight
@using RazorLight
@using Elwig.Helpers
@inherits TemplatePage<Elwig.Documents.MemberDataSheet>
@model Elwig.Documents.MemberDataSheet
@ -18,21 +18,31 @@
<tbody>
<tr class="sectionheading"><th colspan="6">Persönliche Daten</th></tr>
<tr>
<th class="small">Titel (vorangestellt)</th>
<th class="small">Vorname</th>
<th colspan="3" class="small">Nachname</th>
<th class="small">Titel (nachgestellt)</th>
@if (Model.Member.IsJuridicalPerson) {
<th colspan="3" class="small">Name</th>
<th colspan="3" class="small">Zu Handen</th>
} else {
<th class="small">Titel (vorangestellt)</th>
<th class="small">Vorname</th>
<th colspan="3" class="small">Nachname</th>
<th class="small">Titel (nachgestellt)</th>
}
</tr>
<tr>
<td class="large">@Model.Member.Prefix</td>
<td class="large">@Model.Member.GivenName @Model.Member.MiddleName</td>
<td class="large" colspan="3">@Model.Member.FamilyName</td>
<td class="large">@Model.Member.Suffix</td>
@if (Model.Member.IsJuridicalPerson) {
<td colspan="3" class="large">@Model.Member.Name</td>
<td colspan="3" class="large">@Model.Member.ForTheAttentionOf</td>
} else {
<td class="large">@Model.Member.Prefix</td>
<td class="large">@Model.Member.GivenName @Model.Member.MiddleName</td>
<td class="large" colspan="3">@Model.Member.Name</td>
<td class="large">@Model.Member.Suffix</td>
}
</tr>
<tr>
<th>Mitglieds-Nr.:</th>
<td>@Model.Member.MgNr</td>
<th colspan="2">Geburtsjahr/-tag:</th>
<th colspan="2">@(Model.Member.IsJuridicalPerson ? "Gründungsjahr/-tag" : "Geburtsjahr/-tag"):</th>
<td colspan="2">@(string.Join('.', Model.Member.Birthday?.Split('-')?.Reverse() ?? Array.Empty<string>()))</td>
</tr>
<tr>
@ -50,7 +60,7 @@
<tr class="sectionheading"><th colspan="6">Rechnungsadresse (optional)</th></tr>
<tr>
<th>Name:</th>
<td colspan="5">@Model.Member.BillingAddress?.Name</td>
<td colspan="5">@Model.Member.BillingAddress?.FullName</td>
</tr>
<tr>
<th>Adresse:</th>

@ -1,4 +1,4 @@

h2 {
margin-bottom: 0.5em !important;
}

@ -1,5 +1,6 @@
using Elwig.Models.Dtos;
using Elwig.Models.Dtos;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Documents {
public class MemberList : Document {
@ -9,9 +10,17 @@ namespace Elwig.Documents {
public string Filter;
public IEnumerable<MemberListRow> Members;
public string[] AreaComFilters;
public bool FilterAreaComs => AreaComFilters.Length > 0;
public MemberList(string filter, IEnumerable<MemberListRow> members) : base(Name) {
Filter = filter;
Members = members;
AreaComFilters = [..members
.SelectMany(m => m.AreaCommitmentsFiltered)
.Select(c => c.VtrgId)
.Distinct()
.Order()];
}
public MemberList(string filter, MemberListData data) :

@ -8,30 +8,61 @@
<h2>@Model.Filter</h2>
<table class="members">
<colgroup>
<col style="width: 8mm;"/>
<col style="width: 42mm;"/>
<col style="width: 40mm;"/>
<col style="width: 8mm;"/>
<col style="width: 20mm;"/>
@if (Model.AreaComFilters.Length > 1) {
<col style="width: 38mm;"/>
} else {
<col style="width: 42mm;"/>
}
@if (Model.AreaComFilters.Length > 1) {
<col style="width: 36mm;"/>
} else {
<col style="width: 40mm;"/>
}
<col style="width: 8mm;"/>
@if (Model.AreaComFilters.Length > 1) {
<col style="width: 18mm;"/>
} else {
<col style="width: 20mm;"/>
}
<col style="width: 12mm;"/>
<col style="width: 5mm;" />
<col style="width: 18mm;"/>
<col style="width: 5mm;"/>
@if (Model.AreaComFilters.Length > 1) {
<col style="width: 16mm;"/>
} else {
<col style="width: 18mm;"/>
}
<col style="width: 12mm;"/>
@if (Model.AreaComFilters.Length > 1) {
<col style="width: 12mm;"/>
}
</colgroup>
<thead>
<tr>
<th rowspan="2">Nr.</th>
<th rowspan="2" style="text-align: left;">Name</th>
<th rowspan="2" style="text-align: left;">Adresse</th>
<th rowspan="2">PLZ</th>
<th rowspan="2" style="text-align: left;">Ort</th>
<th rowspan="2">Betr.-Nr.</th>
<th rowspan="2">GA</th>
<th rowspan="2" style="text-align: left;">Stamm-KG</th>
<th>Geb. Fl.</th>
@{
var headerSpan = Model.FilterAreaComs ? 3 : 2;
}
<th rowspan="@headerSpan">Nr.</th>
<th rowspan="@headerSpan" style="text-align: left;">Name</th>
<th rowspan="@headerSpan" style="text-align: left;">Adresse</th>
<th rowspan="@headerSpan">PLZ</th>
<th rowspan="@headerSpan" style="text-align: left;">Ort</th>
<th rowspan="@headerSpan">Betr.-Nr.</th>
<th rowspan="@headerSpan">GA</th>
<th rowspan="@headerSpan" style="text-align: left;">Stamm-KG</th>
<th colspan="@(Model.FilterAreaComs ? Model.AreaComFilters.Length : 1)">Geb. Fl.</th>
</tr>
@if (Model.FilterAreaComs) {
<tr>
@foreach (var vtrgId in Model.AreaComFilters) {
<th>@vtrgId</th>
}
</tr>
}
<tr>
<th class="unit">[m²]</th>
@for (int i = 0; i < Math.Max(Model.AreaComFilters.Length, 1); i++) {
<th class="unit">[m²]</th>
}
</tr>
</thead>
<tbody class="small">
@ -40,22 +71,28 @@
}
@foreach (var m in Model.Members) {
if (lastBranch != null && m.Branch != lastBranch) {
<tr class="spacing"><td colspan="9"></td></tr>
<tr class="spacing"><td colspan="@(8 + Math.Max(Model.AreaComFilters.Length, 1))"></td></tr>
<tr class="header">
<th colspan="9">@m.Branch</th>
<th colspan="@(8 + Math.Max(Model.AreaComFilters.Length, 1))">@m.Branch</th>
</tr>
lastBranch = m.Branch;
}
<tr>
<td class="number" rowspan="@(m.BillingName != null ? 2 : 1)">@m.MgNr</td>
<td>@m.Name1.Replace('ß', 'ẞ').ToUpper() @m.Name2</td>
<td>@m.AdminName1 @m.Name2</td>
<td>@m.Address</td>
<td>@m.Plz</td>
<td class="tiny">@m.Locality</td>
<td>@m.LfbisNr</td>
<td class="number">@m.BusinessShares</td>
<td class="tiny">@m.DefaultKg</td>
<td class="number">@($"{m.AreaCommitment:N0}")</td>
@if (Model.AreaComFilters.Length > 0) {
foreach (var v in Model.AreaComFilters) {
<td class="number">@($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}")</td>
}
} else {
<td class="number">@($"{m.AreaCommitment:N0}")</td>
}
</tr>
if (m.BillingName != null) {
<tr>

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;

@ -20,9 +20,10 @@
@{
//var sum1 = Model.Variant.DeliveryPartPayments.Sum(p => p.NetAmount);
//var sum2 = Model.Variant.Credits.Sum(p => p.); //Model.Variant.MemberPayments.Sum(p => p.Amount);
var modifiers = Model.Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount);
var deliveryModifiers = Model.Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount);
var memberModifiers = Model.Variant.Credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount);
var sum2 = Model.Variant.Credits.Sum(p => p.NetAmount);
var sum1 = sum2 - modifiers;
var sum1 = sum2 - deliveryModifiers - memberModifiers;
var payed = -Model.Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
var netSum = Model.Variant.Credits.Sum(p => p.NetAmount) - Model.Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m);
var vat = Model.Variant.Credits.Sum(p => p.VatAmount);
@ -49,7 +50,7 @@
<td class="center">@(Model.BillingData.ConsiderContractPenalties ? "Ja" : "Nein")</td>
</tr>
<tr>
<th style="overflow: visible;">Nto./bto.-Zuschl:</th>
<th style="overflow: visible;">Rebel-Zuschl.:</th>
<td colspan="3" class="center">
@($"{Utils.GetSign(Model.BillingData.NetWeightModifier)}{Math.Abs(Model.BillingData.NetWeightModifier) * 100:N2}") % /
@($"{Utils.GetSign(Model.BillingData.GrossWeightModifier)}{Math.Abs(Model.BillingData.GrossWeightModifier) * 100:N2}") %
@ -84,30 +85,30 @@
<td colspan="2" class="number">@($"{Model.MemberNum:N0}")</td>
</tr>
<tr>
<th colspan="2">Zu-/Abschläge (Lieferungen):</th>
<td class="number">@Utils.GetSign(modifiers)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(modifiers):N2}")</td>
<th colspan="2">Zu-/Abschläge (Mitglieder):</th>
<td class="number">@Utils.GetSign(memberModifiers)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(memberModifiers):N2}")</td>
<th class="lborder">Lieferungen:</th>
<td colspan="2" class="number">@($"{Model.DeliveryNum:N0}")</td>
</tr>
<tr>
<th colspan="2">Zu-/Abschläge (Lieferungen):</th>
<td class="number">@Utils.GetSign(deliveryModifiers)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(deliveryModifiers):N2}")</td>
<th class="lborder">Teillieferungen:</th>
<td colspan="2" class="number">@($"{Model.DeliveryPartNum:N0}")</td>
</tr>
<tr>
<th colspan="2">Gesamtsumme:</th>
<td class="number tborder"></td>
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{sum2:N2}")</td>
<th class="lborder">Teillieferungen:</th>
<td colspan="2" class="number">@($"{Model.DeliveryPartNum:N0}")</td>
<th class="lborder"></th>
<td colspan="2"></td>
</tr>
<tr>
<th colspan="2">Bisher ausgezahlt:</th>
<td class="number">@Utils.GetSign(payed)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(payed):N2}")</td>
<th class="lborder"></th>
<td colspan="2"></td>
</tr>
<tr>
<th colspan="2">Nettosumme:</th>
<td class="number tborder"></td>
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{netSum:N2}")</td>
@{
var weiRows = Model.Data.Rows.Where(r => r.QualityLevel == "Wein");
var minWei = weiRows.Min(r => r.Ungeb.Price);
@ -117,9 +118,9 @@
<td colspan="2" class="center tborder">@(minWei != maxWei ? $"{minWei:N4}{maxWei:N4}" : $"{minWei:N4}") @Model.CurrencySymbol/kg</td>
</tr>
<tr>
<th colspan="2">Mehrwertsteuer:</th>
<td class="number">@Utils.GetSign(vat)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(vat):N2}")</td>
<th colspan="2">Nettosumme:</th>
<td class="number tborder"></td>
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{netSum:N2}")</td>
@{
var quwRows = Model.Data.Rows.Where(r => r.QualityLevel != "Wein");
var minPrice = quwRows.Min(r => r.Ungeb.Price);
@ -129,9 +130,9 @@
<td colspan="2" class="center">@(minPrice != maxPrice ? $"{minPrice:N4}{maxPrice:N4}" : $"{minPrice:N4}") @Model.CurrencySymbol/kg</td>
</tr>
<tr>
<th colspan="2">Bruttosumme:</th>
<td class="number tborder"></td>
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{grossSum:N2}")</td>
<th colspan="2">Mehrwertsteuer:</th>
<td class="number">@Utils.GetSign(vat)</td>
<td class="number"><span class="fleft">@Model.CurrencySymbol</span>@($"{Math.Abs(vat):N2}")</td>
@{
var gebRows = Model.Data.Rows
.Where(r => r.Geb.Price != null && r.Ungeb.Price != null)
@ -144,6 +145,13 @@
@(minGeb != maxGeb ? $"{minGeb:N4}{maxGeb:N4} {Model.CurrencySymbol}/kg" : minGeb == 0 ? "-" : $"{minGeb:N4} {Model.CurrencySymbol}/kg")
</td>
</tr>
<tr>
<th colspan="2">Bruttosumme:</th>
<td class="number tborder"></td>
<td class="number tborder"><span class="fleft">@Model.CurrencySymbol</span>@($"{grossSum:N2}")</td>
<th class="lborder"></th>
<td colspan="2"></td>
</tr>
<tr>
<th colspan="2">Abzüge (Strafen/Pönalen, GA, ...):</th>
<td class="number">@Utils.GetSign(totalMods)</td>

@ -1,4 +1,4 @@
using Elwig.Models.Dtos;
using Elwig.Models.Dtos;
using System.Collections.Generic;
namespace Elwig.Documents {

@ -7,7 +7,7 @@
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.8.7</Version>
<Version>0.10.1</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
@ -25,6 +25,7 @@
</Target>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="LinqKit" Version="1.3.0" />
<PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.32" />

@ -1,4 +1,4 @@
using System;
using System;
using System.Windows.Input;
namespace Elwig.Helpers {

@ -51,6 +51,9 @@ namespace Elwig.Helpers {
public DbSet<MemberHistory> MemberHistory { get; private set; }
public DbSet<AreaCom> 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; }
public DbSet<DeliveryAncmt> DeliveryAnnouncements { get; private set; }
public DbSet<Modifier> Modifiers { get; private set; }
public DbSet<Delivery> Deliveries { get; private set; }
public DbSet<DeliveryPart> DeliveryParts { get; private set; }
@ -236,6 +239,13 @@ namespace Elwig.Helpers {
return c + 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;
}
public async Task<WineQualLevel> GetWineQualityLevel(double kmw) {
return await WineQualityLevels
.Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw))
@ -266,6 +276,31 @@ namespace Elwig.Helpers {
}
}
public void UpdateDeliveryScheduleWineVarieties(DeliverySchedule schedule, IEnumerable<(WineVar, int)> oldVarieties, IEnumerable<(WineVar, int)> newVarieties) {
foreach (var v in WineVarieties) {
var e = new DeliveryScheduleWineVar {
Year = schedule.Year,
DsNr = schedule.DsNr,
SortId = v.SortId,
Priority = 1,
};
var o = oldVarieties.Where(x => x.Item1.SortId == e.SortId).Select(x => x.Item2).FirstOrDefault(-1);
var n = newVarieties.Where(x => x.Item1.SortId == e.SortId).Select(x => x.Item2).FirstOrDefault(-1);
if (n != -1) {
e.Priority = n;
if (o == -1) {
Add(e);
} else {
Update(e);
}
} else {
if (o != -1) {
Remove(e);
}
}
}
}
private async Task FetchMemberAreaCommitmentBuckets(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();

@ -9,11 +9,11 @@ namespace Elwig.Helpers {
public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 24;
public static readonly int RequiredSchemaVersion = 28;
private static int VersionOffset = 0;
public static async Task<string> CheckDb() {
public static async Task<Version> CheckDb() {
using var cnx = AppDbContext.Connect();
var applId = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
@ -28,18 +28,14 @@ namespace Elwig.Helpers {
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);
var userVers = (long?)await AppDbContext.ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
var major = userVers >> 24;
var minor = (userVers >> 16) & 0xFF;
var patch = userVers & 0xFFFF;
var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)(userVers & 0xFFFF));
if (App.VersionMajor > major ||
(App.VersionMajor == major && App.VersionMinor > minor) ||
(App.VersionMajor == major && App.VersionMinor == minor && App.VersionPatch > patch)) {
long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch;
if (App.Version > v) {
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | App.Version.Build;
await AppDbContext.ExecuteBatch(cnx, $"PRAGMA user_version = {vers}");
}
return $"{major}.{minor}.{patch}";
return v;
}
private static async Task UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) {

@ -157,13 +157,15 @@ namespace Elwig.Helpers.Billing {
lastMgNr = mgnr;
}
await AppDbContext.ExecuteBatch(cnx, $"""
UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year};
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
ON CONFLICT DO UPDATE
SET discr = excluded.discr, value = value + excluded.value;
""");
await AppDbContext.ExecuteBatch(cnx, $"UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year}");
if (inserts.Count > 0) {
await AppDbContext.ExecuteBatch(cnx, $"""
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
ON CONFLICT DO UPDATE
SET discr = excluded.discr, value = value + excluded.value;
""");
}
if (!avoidUnderDlvrs) {
if (ownCnx) await cnx.DisposeAsync();

@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using NJsonSchema;
using System;
using System.Collections.Generic;

@ -29,6 +29,8 @@ namespace Elwig.Helpers.Billing {
await CalculatePrices(cnx);
if (Data.ConsiderDelieryModifiers) {
await CalculateDeliveryModifiers(cnx);
}
if (Data.ConsiderCustomModifiers) {
await CalculateMemberModifiers(cnx);
}
await tx.CommitAsync();
@ -104,18 +106,21 @@ namespace Elwig.Helpers.Billing {
if (App.Client.IsMatzen) {
var lastYears = 3;
var multiplier = 0.50;
var includePredecessor = true;
var modName = "Treue%";
await AppDbContext.ExecuteBatch(cnx, $"""
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)),
COALESCE(m.rel, 0)
FROM (SELECT {Year} AS year, mgnr,
ROUND(AVG(sum) * {multiplier}) AS baseline,
COUNT(*) = {lastYears} AND MIN(sum) > 0 AS allowed
FROM v_stat_member
WHERE year > {Year} - {lastYears}
GROUP BY mgnr
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
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")}
WHERE a.year > {Year} - {lastYears}
GROUP BY m.mgnr
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}'
@ -125,6 +130,16 @@ namespace Elwig.Helpers.Billing {
mod_rel = mod_rel + excluded.mod_rel
""");
}
await AppDbContext.ExecuteBatch(cnx, $"""
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
SELECT x.year, {AvNr}, x.mgnr, 0, COALESCE(x.mod_abs * POW(10, s.precision - 2), 0), COALESCE(x.mod_rel, 0)
FROM payment_custom x
JOIN season s ON s.year = x.year
WHERE x.year = {Year}
ON CONFLICT DO UPDATE
SET mod_abs = mod_abs + excluded.mod_abs,
mod_rel = mod_rel + excluded.mod_rel
""");
}
protected async Task CalculatePrices(SqliteConnection cnx) {

@ -1,4 +1,4 @@
using Elwig.Models.Entities;
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.Linq;

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;

@ -1,4 +1,4 @@
using Elwig.Models.Entities;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;

@ -87,7 +87,7 @@ namespace Elwig.Helpers {
SmtpPassword = config["smtp:password"];
SmtpFrom = config["smtp:from"];
var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key);
var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key).Order();
ScaleList.Clear();
Scales = ScaleList;
foreach (var s in scales) {

@ -11,11 +11,7 @@ using Brushes = System.Windows.Media.Brushes;
namespace Elwig.Helpers {
public class ControlUtils {
public enum RenewSourceDefault {
None,
IfOnly,
First
}
public enum RenewSourceDefault { None, IfOnly, First }
private static void SetControlBorderBrush(Control input, Brush brush) {
if (input is ComboBox cb) {
@ -171,15 +167,19 @@ namespace Elwig.Helpers {
return item;
}
public static object? GetItemFromSource(IEnumerable source, object? item) {
return GetItemFromSource(source, Utils.GetEntityIdentifier(item));
public static T? GetItemFromSource<T>(IEnumerable source, T? item) {
return (T?)GetItemFromSource(source, Utils.GetEntityIdentifier(item));
}
public static object? GetItemFromSourceWithPk(IEnumerable source, params object?[] primaryKey) {
return GetItemFromSource(source, (int?)Utils.GetEntityIdetifierForPk(primaryKey));
}
public static void SelectItemWithHash(Selector input, int? hash) {
if (hash == null) {
input.SelectedItem = null;
} else {
input.SelectedItem = GetItemFromSource(input.ItemsSource, (int)hash);
input.SelectedItem = GetItemFromSource(input.ItemsSource, hash);
}
if (input is ListBox lb && lb.SelectedItem is object lbItem) {
lb.ScrollIntoView(lbItem);

@ -29,7 +29,7 @@ namespace Elwig.Helpers.Export {
using var cnx = await AppDbContext.ConnectAsync();
using var cmd = cnx.CreateCommand();
cmd.CommandText = $"""
SELECT lfbis_nr, family_name, name, billing_name, address, plz, ort, area,
SELECT lfbis_nr, name, other_names, billing_name, address, plz, ort, area,
date, weight, type, sortid, qualid, year, hkid, kmw, oe
FROM v_bki_delivery
WHERE year = {year}

@ -83,7 +83,7 @@ namespace Elwig.Helpers.Export {
<PmtId><EndToEndId>{id}</EndToEndId></PmtId>
<Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmount(tx.Amount)}</InstdAmt></Amt>
<Cdtr>
<Nm>{SecurityElement.Escape(a.Name[..Math.Min(140, a.Name.Length)])}</Nm>
<Nm>{SecurityElement.Escape(a.FullName[..Math.Min(140, a.FullName.Length)])}</Nm>
""");
if (ShowAddresses != AddressMode.Omit) {
var full = ShowAddresses == AddressMode.Full;

@ -1,4 +1,4 @@
using System.IO.Compression;
using System.IO.Compression;
using System.IO;
using System.Threading.Tasks;
using Elwig.Models.Entities;
@ -8,12 +8,17 @@ using System.Text.Json.Nodes;
using System.Linq;
using System.Windows;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
namespace Elwig.Helpers.Export {
public static class ElwigData {
public enum ImportMode { Auto, Interactively, FromBranches }
public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
private static readonly JsonSerializerOptions JsonOpts = new() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
public static async Task<string[]> GetImportedFiles() {
try {
return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
@ -22,40 +27,73 @@ namespace Elwig.Helpers.Export {
}
}
public static async Task AddImportedFiles(IEnumerable<string> filenames) {
public static async Task AddImportedFiles(params string[] filenames) {
await File.AppendAllLinesAsync(ImportedTxt, filenames, Utils.UTF8);
}
public static Task Import(string filename, bool interactive) => Import([filename], interactive);
public static Task Import(string filename, ImportMode mode) => Import([filename], mode);
public static async Task Import(IEnumerable<string> filenames, bool interactive) {
public static async Task Import(IEnumerable<string> filenames, ImportMode mode) {
try {
using var ctx = new AppDbContext();
var currentDids = await ctx.Deliveries
.GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));
Dictionary<string, Branch> branches;
Dictionary<int, int> currentDids;
Dictionary<string, int> currentLsNrs;
Dictionary<int, List<WbRd>> currentWbRde;
var deliveries = new List<Delivery>();
var deliveryParts = new List<DeliveryPart>();
var modifiers = new List<DeliveryPartModifier>();
using (var ctx = new AppDbContext()) {
branches = await ctx.Branches.ToDictionaryAsync(b => b.ZwstId);
currentDids = await ctx.Deliveries
.GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));
currentLsNrs = await ctx.Deliveries
.ToDictionaryAsync(d => d.LsNr, d => d.DId);
currentWbRde = await ctx.WbRde
.GroupBy(r => r.KgNr)
.ToDictionaryAsync(g => g.Key, g => g.ToList());
}
var metaData = new List<(string Name, int DeliveryNum, string Filters)>();
var data = new List<(
List<Member> Members,
List<BillingAddr> BillingAddresses,
List<MemberTelNr> TelephoneNumbers,
List<MemberEmailAddr> EmailAddresses,
List<AreaCom> AreaCommitments,
List<WbRd> Riede,
List<Delivery> Deliveries,
List<DeliveryPart> DeliveryParts,
List<DeliveryPartModifier> Modifiers)>();
var metaData = new List<(string FileName, string ZwstId, string Device,
int? MemberNum, string? MemberFilters,
int? AreaComNum, string? AreaComFilters,
int? DeliveryNum, string? DeliveryFilters)>();
foreach (var filename in filenames) {
// TODO read encrypted files
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
var version = zip.GetEntry("version");
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
if (await reader.ReadToEndAsync() != "elwig:1")
throw new FileFormatException("Ungültige Export-Datei");
throw new FileFormatException($"Ungültige Export-Datei ({filename})");
}
var metaJson = zip.GetEntry("meta.json");
var meta = await JsonNode.ParseAsync(metaJson!.Open());
var deliveryCount = meta!["deliveries"]?["count"]!.AsValue().GetValue<int>();
var deliveryFilters = meta!["deliveries"]?["filters"]!.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
if (deliveryCount != null && deliveryFilters != null)
metaData.Add((Path.GetFileName(filename), (int)deliveryCount, string.Join(" / ", deliveryFilters)));
var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var areaComCount = meta!["area_commitments"]?["count"]?.AsValue().GetValue<int>();
var areaComFilters = meta!["area_commitments"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
metaData.Add((Path.GetFileName(filename),
meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null,
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
data.Add(new([], [], [], [], [], [], [], new([], [])));
var r = data[^1];
var membersJson = zip.GetEntry("members.json");
if (membersJson != null) {
@ -63,7 +101,11 @@ namespace Elwig.Helpers.Export {
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
// TODO import members.json
var (m, b, telNrs, emailAddrs) = obj.ToMember();
r.Members.Add(m);
if (b != null) data[^1].BillingAddresses.Add(b);
r.TelephoneNumbers.AddRange(telNrs);
r.EmailAddresses.AddRange(emailAddrs);
}
}
@ -73,7 +115,9 @@ namespace Elwig.Helpers.Export {
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
// TODO import area_commitments.json
var (areaCom, wbrd) = obj.ToAreaCom(currentWbRde);
r.AreaCommitments.Add(areaCom);
if (wbrd != null) r.Riede.Add(wbrd);
}
}
@ -83,85 +127,441 @@ namespace Elwig.Helpers.Export {
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (d, parts, mods) = JsonToDelivery(obj, currentDids);
deliveries.Add(d);
deliveryParts.AddRange(parts);
modifiers.AddRange(mods);
var (d, parts, mods) = obj.ToDelivery(currentLsNrs, currentDids);
r.Deliveries.Add(d);
r.DeliveryParts.AddRange(parts);
r.Modifiers.AddRange(mods);
}
}
}
var lsnrs = deliveries.Select(d => d.LsNr).ToList();
var duplicateLsNrs = await ctx.Deliveries
.Where(d => lsnrs.Contains(d.LsNr))
.Select(d => d.LsNr)
.ToListAsync();
var duplicateDIds = deliveries
.Where(d => duplicateLsNrs.Contains(d.LsNr))
.Select(d => (d.Year, d.DId))
.ToList();
bool overwriteDelivieries = false;
if (duplicateLsNrs.Count > 0) {
var res = MessageBox.Show($"Sollen {duplicateLsNrs.Count} Lieferungen überschreiben werden?", "Lieferungen überschreiben",
MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
overwriteDelivieries = res == MessageBoxResult.Yes;
var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int Imported, int NotImported, string Filters)>();
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string Filters)>();
foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, areaCommitments, riede, deliveries, deliveryParts, modifiers), meta) in data.Zip(metaData)) {
var branch = branches[meta.ZwstId];
var device = meta.Device;
using var ctx = new AppDbContext();
var mgnrs = members.Select(m => m.MgNr).ToList();
var duplicateMgNrs = await ctx.Members
.Where(m => mgnrs.Contains(m.MgNr))
.Select(m => m.MgNr)
.ToListAsync();
bool importNewMembers = false, importDuplicateMembers = false;
if (mode == ImportMode.Interactively) {
if (mgnrs.Count - duplicateMgNrs.Count > 0)
importNewMembers = ImportQuestion(branch.Name, device, "Mitglieder", false, mgnrs.Count - duplicateMgNrs.Count);
} else {
importNewMembers = true;
}
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
.Where(c => fbnrs.Contains(c.FbNr))
.Select(c => c.FbNr)
.ToListAsync();
var lsnrs = deliveries.Select(d => d.LsNr).ToList();
var duplicateLsNrs = await ctx.Deliveries
.Where(d => lsnrs.Contains(d.LsNr))
.Select(d => d.LsNr)
.ToListAsync();
var duplicateDIds = deliveries
.Where(d => duplicateLsNrs.Contains(d.LsNr))
.Select(d => (d.Year, d.DId))
.ToList();
var allowedDuplicateLsNrs = new List<string>();
bool importNewDeliveries = false, importDuplicateDeliveries = false;
if (mode == ImportMode.Interactively) {
if (lsnrs.Count - duplicateLsNrs.Count > 0)
importNewDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", false, lsnrs.Count - duplicateLsNrs.Count);
if (duplicateLsNrs.Count > 0)
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
} else if (mode == ImportMode.FromBranches) {
importNewDeliveries = true;
if (duplicateLsNrs.Count > 0) {
allowedDuplicateLsNrs = await ctx.Deliveries
.Where(d => lsnrs.Contains(d.LsNr) && d.ZwstId == branch.ZwstId)
.Select(d => d.LsNr)
.ToListAsync();
if (duplicateLsNrs.Count - allowedDuplicateLsNrs.Count > 0)
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count - allowedDuplicateLsNrs.Count);
}
} else {
importNewDeliveries = true;
if (duplicateLsNrs.Count > 0)
importDuplicateDeliveries = ImportQuestion(branch.Name, device, "Lieferungen", true, duplicateLsNrs.Count);
}
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.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) {
var imported = areaCommitments.Where(c => (importNewMembers && !duplicateMgNrs.Contains(c.MgNr)) || (importDuplicateMembers && duplicateMgNrs.Contains(c.MgNr))).ToList();
importedAreaComs.Add((meta.FileName, meta.ZwstId, meta.Device, imported.Count, areaCommitments.Count - imported.Count, meta.AreaComFilters));
}
if (allowedDuplicateLsNrs.Count > 0) {
var dids = deliveries
.Where(d => allowedDuplicateLsNrs.Contains(d.LsNr))
.Select(d => (d.Year, d.DId))
.ToList();
ctx.RemoveRange(ctx.DeliveryParts
.Where(p => allowedDuplicateLsNrs.Contains(p.Delivery.LsNr))
.SelectMany(p => p.PartModifiers));
ctx.RemoveRange(ctx.DeliveryParts.Where(p => allowedDuplicateLsNrs.Contains(p.Delivery.LsNr)));
ctx.UpdateRange(deliveries.Where(d => dids.Contains((d.Year, d.DId))));
ctx.AddRange(deliveryParts.Where(p => dids.Contains((p.Year, p.DId))));
ctx.AddRange(modifiers.Where(m => dids.Contains((m.Year, m.DId))));
}
if (importDuplicateDeliveries) {
var l = duplicateLsNrs.Except(allowedDuplicateLsNrs).ToList();
var dids = deliveries
.Where(d => l.Contains(d.LsNr))
.Select(d => (d.Year, d.DId))
.ToList();
ctx.RemoveRange(ctx.DeliveryParts
.Where(p => l.Contains(p.Delivery.LsNr))
.SelectMany(p => p.PartModifiers));
ctx.RemoveRange(ctx.DeliveryParts.Where(p => l.Contains(p.Delivery.LsNr)));
ctx.UpdateRange(deliveries.Where(d => dids.Contains((d.Year, d.DId))));
ctx.AddRange(deliveryParts.Where(p => dids.Contains((p.Year, p.DId))));
ctx.AddRange(modifiers.Where(m => dids.Contains((m.Year, m.DId))));
}
if (importNewDeliveries) {
ctx.AddRange(deliveries.Where(d => !duplicateDIds.Contains((d.Year, d.DId))));
ctx.AddRange(deliveryParts.Where(p => !duplicateDIds.Contains((p.Year, p.DId))));
ctx.AddRange(modifiers.Where(m => !duplicateDIds.Contains((m.Year, m.DId))));
}
if (deliveries.Count > 0) {
var n = importNewDeliveries ? deliveries.Count - duplicateDIds.Count : 0;
var o = allowedDuplicateLsNrs.Count + (importDuplicateDeliveries ? duplicateDIds.Count - allowedDuplicateLsNrs.Count : 0);
importedDeliveries.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, deliveries.Count - n - o, meta.DeliveryFilters));
}
await ctx.SaveChangesAsync();
await AddImportedFiles(Path.GetFileName(meta.FileName));
}
if (overwriteDelivieries) {
ctx.RemoveRange(ctx.Deliveries.Where(d => duplicateLsNrs.Contains(d.LsNr)));
ctx.AddRange(deliveries);
ctx.AddRange(deliveryParts);
ctx.AddRange(modifiers);
} else {
ctx.AddRange(deliveries.Where(d => !duplicateDIds.Contains((d.Year, d.DId))));
ctx.AddRange(deliveryParts.Where(p => !duplicateDIds.Contains((p.Year, p.DId))));
ctx.AddRange(modifiers.Where(m => !duplicateDIds.Contains((m.Year, m.DId))));
}
await ctx.SaveChangesAsync();
await AddImportedFiles(filenames.Select(f => Path.GetFileName(f)));
await App.HintContextChange();
MessageBox.Show(
$"Das importieren der Daten war erfolgreich!\n" +
$"Folgendes wurde importiert:\n" +
$" Lieferungen: {deliveries.Count}\n" +
string.Join("\n", metaData.Select(d => $" {d.Name} ({d.DeliveryNum})\n {d.Filters}")) +
"\n", "Importieren erfolgreich",
string.Join("\n", [
$"Mitglieder: {importedMembers.Sum(d => d.New + d.Overwritten)}",
..importedMembers.Select(d =>
$" {d.FileName} ({d.New + d.Overwritten})\n" +
$" ({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" +
$" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" +
$" Filter: {d.Filters}"),
$"Lieferungen: {importedDeliveries.Sum(d => d.New + d.Overwritten)}",
..importedDeliveries.Select(d =>
$" {d.FileName} ({d.New + d.Overwritten})\n" +
$" ({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) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
public static async Task ExportDeliveries(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
File.Delete(filename);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
private static bool ImportQuestion(string branch, string device, string subject, bool duplicate, int number) {
return MessageBox.Show(
$"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;
}
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
await writer.WriteAsync("elwig:1");
}
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<string> filters) {
return new ElwigExport {
Members = (members, filters)
}.Export(filename);
}
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
await writer.WriteAsync(
$"{{\"timestamp\": \"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}\", " +
$"\"zwstid\": \"{App.ZwstId}\", \"device\": \"{Environment.MachineName}\", " +
$"\"deliveries\": {{" +
$"\"count\": {deliveries.Count()}, " +
$"\"parts\": {deliveries.Sum(d => d.Parts.Count)}, " +
$"\"filters\": [{string.Join(", ", filters.Select(f => '"' + f + '"'))}]" +
$"}}}}");
}
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaCom> areaComs, IEnumerable<string> filters) {
return new ElwigExport {
Members = (members, filters),
AreaComs = (areaComs, ["von exportierten Mitgliedern"]),
}.Export(filename);
}
var json = zip.CreateEntry("deliveries.json");
using (var writer = new StreamWriter(json.Open(), Utils.UTF8)) {
foreach (var d in deliveries) {
await writer.WriteLineAsync(DeliveryToJson(d).ToJsonString());
public static Task Export(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
return new ElwigExport {
Deliveries = (deliveries, filters)
}.Export(filename);
}
public class ElwigExport {
public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
public (IEnumerable<AreaCom> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; }
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
public async Task Export(string filename) {
File.Delete(filename);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
await writer.WriteAsync("elwig:1");
}
var 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,
};
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 (Deliveries != null)
obj["deliveries"] = new JsonObject {
["count"] = Deliveries.Value.Deliveries.Count(),
["parts"] = Deliveries.Value.Deliveries.Sum(d => d.Parts.Count),
["filters"] = new JsonArray(Deliveries.Value.Filters.Select(f => (JsonNode)f).ToArray()),
};
await writer.WriteAsync(obj.ToJsonString(JsonOpts));
}
// TODO encrypt files
if (Members != null) {
var json = zip.CreateEntry("members.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var m in Members.Value.Members) {
await writer.WriteLineAsync(m.ToJson().ToJsonString(JsonOpts));
}
}
if (AreaComs != null) {
var json = zip.CreateEntry("area_commitments.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var c in AreaComs.Value.AreaComs) {
await writer.WriteLineAsync(c.ToJson().ToJsonString(JsonOpts));
}
}
if (Deliveries != null) {
var json = zip.CreateEntry("deliveries.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var d in Deliveries.Value.Deliveries) {
await writer.WriteLineAsync(d.ToJson().ToJsonString(JsonOpts));
}
}
}
}
public static JsonObject DeliveryToJson(Delivery d) {
public static JsonObject ToJson(this Member m) {
return new JsonObject {
["mgnr"] = m.MgNr,
["predecessor_mgnr"] = m.PredecessorMgNr,
["name"] = m.Name,
["prefix"] = m.Prefix,
["given_name"] = m.GivenName,
["middle_names"] = m.MiddleName,
["suffix"] = m.Suffix,
["attn"] = m.ForTheAttentionOf,
["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,
["accounting_nr"] = m.AccountingNr,
["zwstid"] = m.ZwstId,
["lfbis_nr"] = m.LfbisNr,
["ustid_nr"] = m.UstIdNr,
["juridical_pers"] = m.IsJuridicalPerson,
["volllieferant"] = m.IsVollLieferant,
["buchführend"] = m.IsBuchführend,
["organic"] = m.IsOrganic,
["funktionär"] = m.IsFunktionär,
["active"] = m.IsActive,
["deceased"] = m.IsDeceased,
["iban"] = m.Iban,
["bic"] = m.Bic,
["default_kgnr"] = m.DefaultKgNr,
["contact_postal"] = m.ContactViaPost,
["contact_email"] = m.ContactViaEmail,
["address"] = new JsonObject {
["address"] = m.Address,
["postal_dest"] = m.PostalDestId,
["country"] = m.CountryNum,
},
["billing_address"] = m.BillingAddress != null ? new JsonObject {
["name"] = m.BillingAddress.FullName,
["address"] = m.BillingAddress.Address,
["postal_dest"] = m.BillingAddress.PostalDestId,
["country"] = m.BillingAddress.CountryNum,
} : null,
["telephone_numbers"] = new JsonArray(m.TelephoneNumbers.OrderBy(n => n.Nr).Select(n => {
var obj = new JsonObject {
["number"] = n.Number,
["type"] = n.Type,
};
if (n.Comment != null) obj["comment"] = n.Comment;
return obj;
}).ToArray()),
["email_addresses"] = new JsonArray(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => {
var obj = new JsonObject {
["address"] = a.Address,
};
if (a.Comment != null) obj["comment"] = a.Comment;
return obj;
}).ToArray()),
["comment"] = m.Comment,
};
}
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>) ToMember(this JsonNode json) {
var mgnr = json["mgnr"]!.AsValue().GetValue<int>();
return (new Member {
MgNr = mgnr,
PredecessorMgNr = json["predecessor_mgnr"]?.AsValue().GetValue<int>(),
Name = json["name"]!.AsValue().GetValue<string>(),
Prefix = json["prefix"]?.AsValue().GetValue<string>(),
GivenName = json["given_name"]?.AsValue().GetValue<string>(),
MiddleName = json["middle_names"]?.AsValue().GetValue<string>(),
Suffix = json["suffix"]?.AsValue().GetValue<string>(),
ForTheAttentionOf = json["attn"]?.AsValue().GetValue<string>(),
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,
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>(),
IsJuridicalPerson = json["juridical_pers"]?.AsValue().GetValue<bool>() ?? false,
IsVollLieferant = json["volllieferant"]?.AsValue().GetValue<bool>() ?? false,
IsBuchführend = json["buchführend"]?.AsValue().GetValue<bool>() ?? false,
IsOrganic = json["organic"]?.AsValue().GetValue<bool>() ?? false,
IsFunktionär = 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>(),
Bic = json["bic"]?.AsValue().GetValue<string>(),
CountryNum = json["address"]!["country"]!.AsValue().GetValue<int>(),
PostalDestId = json["address"]!["postal_dest"]!.AsValue().GetValue<string>(),
Address = json["address"]!["address"]!.AsValue().GetValue<string>(),
DefaultKgNr = json["default_kgnr"]?.AsValue().GetValue<int>(),
ContactViaPost = json["contact_postal"]?.AsValue().GetValue<bool>() ?? false,
ContactViaEmail = json["contact_email"]?.AsValue().GetValue<bool>() ?? false,
Comment = json["comment"]?.AsValue().GetValue<string>(),
}, json["billing_address"] is JsonObject a ? new BillingAddr {
MgNr = mgnr,
FullName = a["name"]!.AsValue().GetValue<string>(),
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 {
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 {
MgNr = mgnr,
Nr = i + 1,
Address = a["address"]!.AsValue().GetValue<string>(),
Comment = a["comment"]?.AsValue().GetValue<string>(),
}).ToList());
}
public static JsonObject ToJson(this AreaCom c) {
return new JsonObject {
["fbnr"] = c.FbNr,
["mgnr"] = c.MgNr,
["vtrgid"] = c.VtrgId,
["cultid"] = c.CultId,
["area"] = c.Area,
["kgnr"] = c.KgNr,
["gstnr"] = c.GstNr,
["ried"] = c.Rd?.Name,
["year_from"] = c.YearFrom,
["year_to"] = c.YearTo,
["comment"] = c.Comment,
};
}
public static (AreaCom, WbRd?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) {
var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
var ried = json["ried"]?.AsValue().GetValue<string>();
WbRd? rd = null;
bool newRd = false;
if (ried != null) {
var rde = riede[kgnr] ?? throw new ArgumentException($"KG {kgnr:00000} is no WbKg");
rd = rde.FirstOrDefault(r => r.Name == ried);
if (rd == null) {
newRd = true;
rd = new WbRd {
KgNr = kgnr,
RdNr = (rde.Count == 0 ? 0 : rde.Max(r => r.RdNr)) + 1,
Name = ried,
};
rde.Add(rd);
}
}
return (new AreaCom {
FbNr = json["fbnr"]!.AsValue().GetValue<int>(),
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,
YearFrom = json["year_from"]!.AsValue().GetValue<int>(),
YearTo = json["year_to"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
}, newRd ? rd : null);
}
public static JsonObject ToJson(this Delivery d) {
return new JsonObject {
["lsnr"] = d.LsNr,
["year"] = d.Year,
@ -194,7 +594,7 @@ namespace Elwig.Helpers.Export {
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.WeighingId != null) obj["weighing_id"] = p.WeighingId;
if (p.WeighingData != null) obj["weighing_data"] = JsonNode.Parse(p.WeighingData);
if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason;
return obj;
}).ToArray()),
@ -202,9 +602,15 @@ namespace Elwig.Helpers.Export {
};
}
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>) JsonToDelivery(JsonNode json, Dictionary<int, int> currentDids) {
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids) {
var year = json["year"]!.AsValue().GetValue<int>();
var did = ++currentDids[year];
var lsnr = json["lsnr"]!.AsValue().GetValue<string>();
var did = currentLsNrs.GetValueOrDefault(lsnr, -1);
if (did == -1) {
if (!currentDids.ContainsKey(year)) currentDids[year] = 0;
did = ++currentDids[year];
}
currentLsNrs[lsnr] = did;
return (new Delivery {
Year = year,
DId = did,
@ -212,7 +618,7 @@ namespace Elwig.Helpers.Export {
TimeString = json["time"]?.AsValue().GetValue<string>(),
ZwstId = json["zwstid"]!.AsValue().GetValue<string>(),
LNr = json["lnr"]!.AsValue().GetValue<int>(),
LsNr = json["lsnr"]!.AsValue().GetValue<string>(),
LsNr = lsnr,
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
@ -238,7 +644,7 @@ namespace Elwig.Helpers.Export {
Temperature = p["temperature"]?.AsValue().GetValue<double>(),
Acid = p["acid"]?.AsValue().GetValue<double>(),
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
WeighingId = p["weighing_id"]?.AsValue().GetValue<string>(),
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(JsonOpts),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
Year = year,

@ -1,4 +1,4 @@
using Elwig.Models.Dtos;
using Elwig.Models.Dtos;
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
@ -6,6 +6,7 @@ using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
namespace Elwig.Helpers.Export {
@ -312,7 +313,7 @@ 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>{data}</text:p></{ct}>";
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{SecurityElement.Escape(data.ToString())}</text:p></{ct}>";
}
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-rows-repeated=\"{colSpan - 1}\"/>\r\n" : "");

@ -1,4 +1,4 @@
namespace Elwig.Helpers {
namespace Elwig.Helpers {
public enum ExportMode {
Show, SaveList, SavePdf, Print, Email, Export, Upload
}

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;

@ -94,7 +94,11 @@ namespace Elwig.Helpers.Printing {
public static async Task Print(string path, int copies = 1) {
try {
var p = new Process() { StartInfo = new() { FileName = PdfToPrinter } };
var p = new Process() { StartInfo = new() {
FileName = PdfToPrinter,
CreateNoWindow = true,
UseShellExecute = false,
} };
p.StartInfo.ArgumentList.Add(path);
p.StartInfo.ArgumentList.Add("/s");
p.StartInfo.ArgumentList.Add($"copies={copies}");

@ -36,7 +36,7 @@ namespace Elwig.Helpers {
public static int CurrentYear => DateTime.Now.Year;
public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 7 ? 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);
@ -197,7 +197,7 @@ namespace Elwig.Helpers {
}
public static void MailTo(string emailAddress) {
MailTo(new string[] { emailAddress });
MailTo([emailAddress]);
}
public static void MailTo(IEnumerable<string> emailAddresses) {
@ -537,6 +537,11 @@ namespace Elwig.Helpers {
}
}
public static int GetEntityIdetifierForPk(params object?[] primaryKey) {
var pk = primaryKey.Select(k => k?.GetHashCode() ?? 0).ToArray();
return ((IStructuralEquatable)pk).GetHashCode(EqualityComparer<int>.Default);
}
public static int? GetEntityIdentifier(object? obj) {
if (obj == null) {
return null;
@ -552,15 +557,15 @@ namespace Elwig.Helpers {
}
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments() => ActiveAreaCommitments(CurrentYear);
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments(int yearTo) =>
c => (c.YearFrom <= yearTo) && (c.YearTo == null || c.YearTo >= yearTo);
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments(int year) =>
c => (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 yearTo) =>
query.Where(ActiveAreaCommitments(yearTo));
public static IQueryable<AreaCom> ActiveAreaCommitments(IQueryable<AreaCom> 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 yearTo) =>
query.Where(c => ActiveAreaCommitments(yearTo).Invoke(c));
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query, int year) =>
query.Where(c => ActiveAreaCommitments(year).Invoke(c));
}
}

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Text;
using System.Threading;
@ -79,7 +79,7 @@ namespace Elwig.Helpers.Weighing {
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
var parsedDate = DateOnly.Parse(date);
return new() {
Weight = int.Parse(netto),
NetWeight = int.Parse(netto),
WeighingId = identNr,
FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null,
Date = parsedDate,

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.IO.Ports;
using System.Text;
@ -60,23 +60,23 @@ namespace Elwig.Helpers.Weighing {
throw new IOException("Invalid response from scale: Received record has invalid size");
var line = record[2..];
var status = line[ 0.. 2];
var brutto = line[ 2.. 9].Trim();
var tara = line[ 9..16].Trim();
var netto = line[16..23].Trim();
var scaleNr = line[23..25].Trim();
var identNr = line[25..31].Trim();
var date = line[31..39];
var time = line[39..45];
var brutto = line[ 0.. 7].Trim();
var tara = line[ 7..14].Trim();
var netto = line[14..21].Trim();
var scaleNr = line[21..23].Trim();
var identNr = line[23..29].Trim();
var date = line[29..37];
var time = line[37..43];
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
var parsedDate = DateOnly.ParseExact(date, "yyyyMMdd");
return new() {
Weight = int.Parse(netto),
GrossWeight = int.Parse(brutto),
TareWeight = int.Parse(tara),
NetWeight = int.Parse(netto),
WeighingId = identNr,
FullWeighingId = identNr,
Date = parsedDate,
Time = TimeOnly.ParseExact(time, "HHmmss"),
Date = DateOnly.TryParseExact(date, "yyyyMMdd", out var d) ? d : null,
Time = TimeOnly.TryParseExact(time, "HHmmss", out var t) ? t : null,
};
}

@ -1,8 +1,8 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace Elwig.Helpers.Weighing {
/// <summary>
/// Interface for controlling a a scale which responds to commands sent to it
/// Interface for controlling a weighing scale which responds to commands sent to it
/// </summary>
public interface ICommandScale : IScale {
/// <summary>

@ -1,6 +1,6 @@
namespace Elwig.Helpers.Weighing {
namespace Elwig.Helpers.Weighing {
/// <summary>
/// Interface for controlling a a scale which automatically sends weighing updates
/// Interface for controlling a weighing scale which automatically sends weighing updates
/// </summary>
public interface IEventScale : IScale {

@ -2,7 +2,7 @@ using System;
namespace Elwig.Helpers.Weighing {
/// <summary>
/// Interface for controlling a industrial scale (industrial terminal, "IT")
/// Interface for controlling a industrial weighing scale (industrial terminal, "IT")
/// </summary>
public interface IScale : IDisposable {
/// <summary>

@ -1,4 +1,4 @@
using System.IO.Ports;
using System.IO.Ports;
using System.IO;
using System.Net.Sockets;
using System;

@ -38,12 +38,12 @@ namespace Elwig.Helpers.Weighing {
}
var error = line[1..3];
string msg = $"Unbekannter Fehler (Fehler code {error})";
if (error[0] == '0') {
if (error[1] != '0') {
throw new IOException($"Invalid response from scale (error code {error})");
}
} else if (error[0] == '1') {
string msg = $"Unbekannter Fehler (Fehler code {error})";
switch (error[1]) {
case '1': msg = "Allgemeiner Waagenfehler"; break;
case '2': msg = "Waage in Überlast"; break;
@ -53,8 +53,12 @@ namespace Elwig.Helpers.Weighing {
case '7': msg = "Druckmuster enthält ungültiges Kommando"; break;
}
throw new IOException($"Waagenfehler {error}: {msg}");
} else if (error[0] == '2') {
switch (error[1]) {
case '0': msg = "Brutto negativ"; break;
}
throw new IOException($"Fehler {error}: {msg}");
} else if (error[0] == '3') {
string msg = $"Unbekannter Fehler (Fehler code {error})";
switch (error[1]) {
case '1': msg = "Übertragunsfehler"; break;
case '2': msg = "Ungültiger Befehl"; break;
@ -98,7 +102,9 @@ namespace Elwig.Helpers.Weighing {
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
var parsedDate = DateOnly.Parse(date);
return new() {
Weight = int.Parse(netto),
GrossWeight = int.Parse(brutto),
TareWeight = int.Parse(tara),
NetWeight = int.Parse(netto),
WeighingId = identNr,
FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null,
Date = parsedDate,

@ -1,12 +1,7 @@
using System;
using System;
namespace Elwig.Helpers.Weighing {
public class WeighingEventArgs : EventArgs {
public WeighingResult Result { get; set; }
public WeighingEventArgs(WeighingResult result) {
Result = result;
}
public class WeighingEventArgs(WeighingResult result) : EventArgs {
public readonly WeighingResult Result = result;
}
}

@ -1,14 +1,25 @@
using System;
using System.Text.Json.Nodes;
namespace Elwig.Helpers.Weighing {
/// <summary>
/// Result of a weighing process on an industrial scale
/// Result of a weighing process on an industrial weighing scale
/// </summary>
public struct WeighingResult {
/// <summary>
/// Measured gross weight in kg
/// </summary>
public int? GrossWeight;
/// <summary>
/// Measured tare weight in kg
/// </summary>
public int? TareWeight;
/// <summary>
/// Measured net weight in kg
/// </summary>
public int? Weight;
public int? NetWeight;
/// <summary>
/// Weighing id (or IdentNr) provided by the scale
@ -30,12 +41,22 @@ namespace Elwig.Helpers.Weighing {
/// </summary>
public TimeOnly? Time;
/// <returns>&lt;Weight/WeighingId/Date/Time&gt;</returns>
/// <returns>&lt;[GrossWeight-TaraWeight=]NetWeight/WeighingId/Date/Time&gt;</returns>
public override readonly string ToString() {
var w = Weight != null ? $"{Weight}kg" : "";
var w = NetWeight != null ? (GrossWeight != null && TareWeight != null ? $"{GrossWeight}-{TareWeight}=" : "") + $"{NetWeight}kg" : "";
return $"<{w}/{WeighingId}/{Date:yyyy-MM-dd}/{Time:HH:mm}>";
}
public readonly JsonObject ToJson() {
var obj = new JsonObject();
if (FullWeighingId != null) obj["id"] = FullWeighingId;
if (WeighingId != null) obj["nr"] = int.Parse(WeighingId);
if (Date != null) obj["date"] = $"{Date:yyyy-MM-dd}";
if (Time != null) obj["time"] = $"{Time:HH:mm:ss}";
if (GrossWeight != null) obj["gross_weight"] = GrossWeight;
if (TareWeight != null) obj["tare_weight"] = TareWeight;
if (NetWeight != null) obj["net_weight"] = NetWeight;
return obj;
}
}
}

@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
@ -35,7 +35,7 @@ namespace Elwig.Models.Dtos {
private static async Task<IEnumerable<AreaComUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaComUnderDeliveryRowSingle> table, int year) {
return await table.FromSqlRaw($"""
SELECT m.mgnr, m.family_name AS name_1,
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,
@ -54,7 +54,7 @@ namespace Elwig.Models.Dtos {
public class AreaComUnderDeliveryRow {
public int MgNr;
public string Name1;
public string Name2;
public string? Name2;
public string Address;
public int Plz;
public string Locality;
@ -88,7 +88,7 @@ namespace Elwig.Models.Dtos {
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public required string Name2 { get; set; }
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
@ -50,7 +50,7 @@ namespace Elwig.Models.Dtos {
private static async Task<IEnumerable<CreditNoteRowSingle>> FromDbSet(DbSet<CreditNoteRowSingle> table, int year, int avnr) {
return await table.FromSqlRaw($"""
SELECT m.mgnr, m.family_name AS name_1,
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,
@ -79,7 +79,7 @@ namespace Elwig.Models.Dtos {
public class CreditNoteRow {
public int MgNr;
public string Name1;
public string Name2;
public string? Name2;
public string Address;
public int Plz;
public string Locality;
@ -144,7 +144,7 @@ namespace Elwig.Models.Dtos {
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public required string Name2 { get; set; }
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]

@ -1,4 +1,4 @@
using Elwig.Helpers.Billing;
using Elwig.Helpers.Billing;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

@ -0,0 +1,62 @@
using Elwig.Documents;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class DeliveryAncmtListData : DataTable<DeliveryAncmtListRow> {
private static readonly (string, string, string?, int?)[] FieldNames = [
("Date", "Datum", null, 20),
("Branch", "Zweigstelle", null, 30),
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("SortId", "Sorte", null, 10),
("Weight", "Gewicht", "kg", 20),
];
public DeliveryAncmtListData(IEnumerable<DeliveryAncmtListRow> rows, List<string> filterNames) :
base(DeliveryAncmtList.Name, DeliveryAncmtList.Name, string.Join(" / ", filterNames), rows, FieldNames) {
}
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);
}
}
public class DeliveryAncmtListRow {
public DateOnly Date;
public string Branch;
public int MgNr;
public string Name1;
public string? Name2;
public string AdministrativeName;
public string SortId;
public string Variety;
public int Weight;
public DeliveryAncmtListRow(DeliveryAncmt a) {
var s = a.Schedule;
var m = a.Member;
Date = s.Date;
Branch = s.Branch.Name;
MgNr = m.MgNr;
Name1 = m.AdministrativeName1;
Name2 = m.AdministrativeName2;
AdministrativeName = m.AdministrativeName;
SortId = a.SortId;
Variety = a.Variety.Name;
Weight = a.Weight;
}
}
}

@ -1,4 +1,4 @@
using Elwig.Models.Entities;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;

@ -1,4 +1,4 @@
using Elwig.Documents;
using Elwig.Documents;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
@ -14,16 +14,18 @@ namespace Elwig.Models.Dtos {
("Pos", "Pos.", null, 10),
("Date", "Datum", null, 20),
("Time", "Zeit", null, 20),
("DeliveryBranch", "Zweigstelle", null, 30),
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("MemberBranch", "Stamm-Zwst.", null, 30),
("SortId", "Sorte", null, 10),
("AttrId", "Attr.", null, 15),
("CultId", "Bewirt.", null, 15),
("QualId", "Qualität", null, 15),
("Gradation", "Gradation", "°Oe|°KMW", 40),
("Weight", "Gewicht", "kg", 20),
("NetGross", "bto./nto.", null, 20),
("IsNetWeight", "Gerebelt", null, 20),
("HkId", "Herkunft", null, 20),
("Modifiers", "Zu-/Abschläge", null, 40),
("Comment", "Anmerkung", null, 60),
@ -35,7 +37,7 @@ namespace Elwig.Models.Dtos {
public static async Task<DeliveryJournalData> FromQuery(IQueryable<DeliveryPart> query, List<string> filterNames) {
return new((await query
.Include(p => p.Delivery.Member)
.Include(p => p.Delivery.Member.Branch)
.Include(p => p.Delivery.Branch)
.Include(p => p.PartModifiers).ThenInclude(m => m.Modifier)
.Include(p => p.Variety)
@ -53,10 +55,12 @@ namespace Elwig.Models.Dtos {
public int Pos;
public DateOnly Date;
public TimeOnly? Time;
public string DeliveryBranch;
public int MgNr;
public string Name1;
public string Name2;
public string? Name2;
public string AdministrativeName;
public string? MemberBranch;
public string SortId;
public string Variety;
public string? AttrId;
@ -71,7 +75,6 @@ namespace Elwig.Models.Dtos {
public double Oe => Gradation.Oe;
public int Weight { get; set; }
public bool IsNetWeight;
public string NetGross => IsNetWeight ? "n" : "b";
public string? Modifiers;
public string? Comment;
@ -83,10 +86,12 @@ namespace Elwig.Models.Dtos {
Pos = p.DPNr;
Date = d.Date;
Time = d.Time;
DeliveryBranch = d.Branch.Name;
MgNr = m.MgNr;
Name1 = m.FamilyName;
Name1 = m.Name;
Name2 = m.AdministrativeName2;
AdministrativeName = m.AdministrativeName;
MemberBranch = m.Branch?.Name;
SortId = p.SortId;
Variety = p.Variety.Name;

@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
@ -37,7 +37,7 @@ namespace Elwig.Models.Dtos {
private static async Task<IEnumerable<MemberDeliveryPerVariantRowSingle>> FromDbSet(DbSet<MemberDeliveryPerVariantRowSingle> table, int year) {
return await table.FromSqlRaw($"""
SELECT m.mgnr, m.family_name AS name_1,
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,
@ -72,7 +72,7 @@ namespace Elwig.Models.Dtos {
public class MemberDeliveryPerVariantRow {
public int MgNr;
public string Name1;
public string Name2;
public string? Name2;
public string Address;
public int Plz;
public string Locality;
@ -104,7 +104,7 @@ namespace Elwig.Models.Dtos {
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public required string Name2 { get; set; }
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]

@ -1,4 +1,4 @@
using Elwig.Documents;
using Elwig.Documents;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -31,30 +31,42 @@ namespace Elwig.Models.Dtos {
("EntryDate", "Eintritt", null, 20),
("ExitDate", "Austritt", null, 20),
("AreaCommitment", "geb. Fläche", "m²", 20),
("AreaCommitmentsFiltered", "geb. Fläche", "Vtrg.|m²", 30),
("UstIdNr", "UID", null, 25),
("Iban", "IBAN", null, 45),
("Bic", "BIC", null, 30),
("Comment", "Anmerkung", null, 60),
("TelNrLandline", "Festnetz", null, 35),
("TelNrMobile", "Mobil", null, 35),
("EmailAddress", "E-Mail", null, 70),
("AdditionalContact", "Weitere", null, 70),
("Comment", "Anmerkung", null, 80),
];
public MemberListData(IEnumerable<MemberListRow> rows, List<string> filterNames) :
base(MemberList.Name, MemberList.Name, string.Join(" / ", filterNames), rows, FieldNames) {
}
public static async Task<MemberListData> FromQuery(IQueryable<Member> query, List<string> filterNames) {
var areaCom = await query.ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments).Sum(c => c.Area));
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));
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)
.ToListAsync()).Select(m => new MemberListRow(m, areaCom[m.MgNr])), filterNames);
.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)))),
filterNames);
}
}
public class MemberListRow {
public int MgNr;
public string? Name1;
public string? AdminName1;
public string? Name2;
public string? DefaultKg;
public string? Branch;
@ -71,16 +83,22 @@ namespace Elwig.Models.Dtos {
public string? Iban;
public string? Bic;
public int? AreaCommitment;
public (string VtrgId, int Area)[] AreaCommitmentsFiltered;
public bool IsBuchführend;
public bool IsOrganic;
public bool IsActive;
public DateOnly? EntryDate;
public DateOnly? ExitDate;
public string? TelNrLandline;
public string? TelNrMobile;
public string? EmailAddress;
public string? AdditionalContact;
public string? Comment;
public MemberListRow(Member m, int? areaCom = null) {
public MemberListRow(Member m, int? areaCom = null, Dictionary<string, int>? filtered = null) {
MgNr = m.MgNr;
Name1 = m.FamilyName;
Name1 = m.Name;
AdminName1 = m.AdministrativeName1;
Name2 = m.AdministrativeName2;
DefaultKg = m.DefaultKg?.Name;
Branch = m.Branch?.Name;
@ -89,7 +107,7 @@ namespace Elwig.Models.Dtos {
Plz = m.PostalDest.AtPlz!.Plz;
Locality = m.PostalDest.AtPlz!.Ort.Name;
if (m.BillingAddress is BillingAddr a) {
BillingName = a.Name;
BillingName = a.FullName;
BillingAddress = a.Address;
BillingPlz = a.PostalDest.AtPlz!.Plz;
BillingLocality = a.PostalDest.AtPlz!.Ort.Name;
@ -103,8 +121,18 @@ namespace Elwig.Models.Dtos {
IsActive = m.IsActive;
EntryDate = m.EntryDate;
ExitDate = m.ExitDate;
TelNrLandline = m.TelephoneNumbers.OrderBy(n => n.Nr).FirstOrDefault(n => n.Type == "landline")?.Number;
TelNrMobile = m.TelephoneNumbers.OrderBy(n => n.Nr).FirstOrDefault(n => n.Type == "mobile")?.Number;
EmailAddress = m.EmailAddresses.OrderBy(a => a.Nr).FirstOrDefault()?.Address;
AdditionalContact = string.Join(", ", m.TelephoneNumbers
.OrderBy(n => n.Nr)
.Select(n => n.Number)
.Except([TelNrLandline, TelNrMobile])
.Distinct()
.Concat(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).Except([EmailAddress])));
Comment = m.Comment;
AreaCommitment = areaCom == 0 ? null : areaCom;
AreaCommitmentsFiltered = filtered != null ? filtered.OrderBy(f => f.Key).Select(f => (f.Key, f.Value)).ToArray() : [];
}
}
}

@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Threading.Tasks;
@ -26,7 +26,7 @@ namespace Elwig.Models.Dtos {
public static async Task<OverUnderDeliveryData> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) {
var rows = await table.FromSqlRaw($"""
SELECT m.mgnr, m.family_name AS name_1,
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,
@ -53,7 +53,7 @@ namespace Elwig.Models.Dtos {
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public required string Name2 { get; set; }
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]

@ -1,4 +1,4 @@
using Elwig.Documents;
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;

@ -1,4 +1,4 @@
using Elwig.Models.Entities;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

@ -1,4 +1,4 @@
using Elwig.Models.Entities;
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;

@ -1,5 +1,6 @@
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
@ -39,6 +40,16 @@ namespace Elwig.Models.Entities {
[Column("comment")]
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("MgNr")]
public virtual Member Member { get; private set; } = null!;

@ -8,7 +8,7 @@ namespace Elwig.Models.Entities {
public int MgNr { get; set; }
[Column("name")]
public required string Name { get; set; }
public required string FullName { get; set; }
[Column("country")]
public int CountryNum { get; set; }

@ -83,15 +83,15 @@ namespace Elwig.Models.Entities {
get => Utils.DecFromDb(AmountValue, 2);
}
[Column("ctime")]
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime")]
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year, AvNr, MgNr")]
public virtual PaymentMember Payment { get; private set; } = null!;

@ -17,7 +17,6 @@ namespace Elwig.Models.Entities {
[Column("date")]
public required string DateString { get; set; }
[NotMapped]
public DateOnly Date {
get => DateOnly.ParseExact(DateString, "yyyy-MM-dd");
@ -26,7 +25,6 @@ namespace Elwig.Models.Entities {
[Column("time")]
public string? TimeString { get; set; }
[NotMapped]
public TimeOnly? Time {
get => (TimeString == null) ? null : TimeOnly.ParseExact(TimeString, "HH:mm:ss");
@ -63,6 +61,16 @@ namespace Elwig.Models.Entities {
[Column("comment")]
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year")]
public virtual Season Season { get; private set; } = null!;
@ -110,7 +118,7 @@ namespace Elwig.Models.Entities {
public int SearchScore(IEnumerable<string> keywords) {
var list = new string?[] {
LsNr, Time?.ToString("HH:mm"),
Member.FamilyName, Member.MiddleName, Member.GivenName, Member.BillingAddress?.Name,
Member.Name, Member.MiddleName, Member.GivenName, Member.BillingAddress?.FullName,
Comment
}.ToList();
list.AddRange(Parts.Select(p => p.Comment).Distinct());

@ -0,0 +1,56 @@
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace Elwig.Models.Entities {
[Table("delivery_announcement"), PrimaryKey("Year", "DsNr", "MgNr", "SortId")]
public class DeliveryAncmt {
[Column("year")]
public int Year { get; set; }
[Column("dsnr")]
public int DsNr { get; set; }
[Column("mgnr")]
public int MgNr { get; set; }
[Column("sortid")]
public required string SortId { get; set; }
[Column("weight")]
public int Weight { get; set; }
[Column("type")]
public required string Type { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year, DsNr")]
public virtual DeliverySchedule Schedule { get; private set; } = null!;
[ForeignKey("MgNr")]
public virtual Member Member { get; private set; } = null!;
[ForeignKey("SortId")]
public virtual WineVar Variety { get; private set; } = null!;
public int SearchScore(IEnumerable<string> keywords) {
return Utils.GetSearchScore([
Schedule.Description,
Member.Name, Member.MiddleName, Member.GivenName,
Member.BillingAddress?.FullName,
], keywords);
}
}
}

@ -1,8 +1,10 @@
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json.Nodes;
namespace Elwig.Models.Entities {
[Table("delivery_part"), PrimaryKey("Year", "DId", "DPNr")]
@ -99,8 +101,26 @@ namespace Elwig.Models.Entities {
[Column("scale_id")]
public string? ScaleId { get; set; }
[Column("weighing_id")]
public string? WeighingId { 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 {
get {
try {
var obj = JsonNode.Parse(WeighingData!)!.AsObject();
return (
obj["id"]?.AsValue().GetValue<string>(),
obj["gross_weight"]?.AsValue().GetValue<int>(),
obj["tare_weight"]?.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);
}
}
}
[Column("weighing_reason")]
public string? WeighingReason { get; set; }
@ -108,6 +128,16 @@ namespace Elwig.Models.Entities {
[Column("comment")]
public string? Comment { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[InverseProperty(nameof(DeliveryPartModifier.Part))]
public virtual ICollection<DeliveryPartModifier> PartModifiers { get; private set; } = null!;

@ -0,0 +1,71 @@
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("delivery_schedule"), PrimaryKey("Year", "DsNr")]
public class DeliverySchedule {
[Column("year")]
public int Year { get; set; }
[Column("dsnr")]
public int DsNr { get; set; }
[Column("date")]
public required string DateString { get; set; }
[NotMapped]
public DateOnly Date {
get => DateOnly.ParseExact(DateString, "yyyy-MM-dd");
set => DateString = value.ToString("yyyy-MM-dd");
}
[Column("zwstid")]
public required string ZwstId { get; set; }
[Column("description")]
public required string Description { get; set; }
[Column("max_weight")]
public int? MaxWeight { get; set; }
[NotMapped]
public int AnnouncedWeight => Announcements.Sum(a => a.Weight);
[NotMapped]
public double? Percent => (double)AnnouncedWeight / MaxWeight * 100;
[Column("ancmt_from")]
public long? AncmtFromUnix { get; set; }
[NotMapped]
public DateTime? AncmtFrom {
get => AncmtFromUnix != null ? DateTimeOffset.FromUnixTimeSeconds(AncmtFromUnix.Value).LocalDateTime : null;
set => AncmtFromUnix = value != null ? new DateTimeOffset(value.Value).ToUnixTimeSeconds() : null;
}
[Column("ancmt_to")]
public long? AncmtToUnix { get; set; }
[NotMapped]
public DateTime? AncmtTo {
get => AncmtToUnix != null ? DateTimeOffset.FromUnixTimeSeconds(AncmtToUnix.Value).LocalDateTime : null;
set => AncmtToUnix = value != null ? new DateTimeOffset(value.Value).ToUnixTimeSeconds() : null;
}
[ForeignKey("Year")]
public virtual Season Season { get; private set; } = null!;
[ForeignKey("ZwstId")]
public virtual Branch Branch { get; private set; } = null!;
[InverseProperty(nameof(DeliveryScheduleWineVar.Schedule))]
public virtual ICollection<DeliveryScheduleWineVar> Varieties { get; private set; } = null!;
[InverseProperty(nameof(DeliveryAncmt.Schedule))]
public virtual ICollection<DeliveryAncmt> Announcements { get; private set; } = null!;
public int SearchScore(IEnumerable<string> keywords) {
return Utils.GetSearchScore([Description], keywords);
}
}
}

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
namespace Elwig.Models.Entities {
[Table("delivery_schedule_wine_variety"), PrimaryKey("Year", "DsNr", "SortId")]
public class DeliveryScheduleWineVar {
[Column("year")]
public int Year { get; set; }
[Column("dsnr")]
public int DsNr { get; set; }
[Column("sortid")]
public required string SortId { get; set; }
[Column("priority")]
public int Priority { get; set; }
[ForeignKey("Year, DsNr")]
public virtual DeliverySchedule Schedule { get; private set; } = null!;
[ForeignKey("SortId")]
public virtual WineVar Variety { get; private set; } = null!;
}
}

@ -14,52 +14,45 @@ namespace Elwig.Models.Entities {
[Column("predecessor_mgnr")]
public int? PredecessorMgNr { get; set; }
[Column("name")]
public required string Name { get; set; }
[Column("prefix")]
public string? Prefix { get; set; }
[Column("given_name")]
public required string GivenName { get; set; }
public string? GivenName { get; set; }
[Column("middle_names")]
public string? MiddleName { get; set; }
[NotMapped]
public string[] MiddleNames {
get => (MiddleName != null) ? MiddleName.Split(" ") : [];
set => MiddleName = (value.Length > 0) ? string.Join(" ", value) : null;
}
[Column("family_name")]
public required string FamilyName { get; set; }
[Column("suffix")]
public string? Suffix { get; set; }
public string Name =>
(Prefix != null ? Prefix + " " : "") +
GivenName + " " +
(MiddleName != null ? MiddleName + " " : "") +
FamilyName +
(Suffix != null ? " " + Suffix : "");
[Column("attn")]
public string? ForTheAttentionOf { get; set; }
public string ShortName => GivenName + " " + FamilyName;
public string AdministrativeName => AdministrativeName1 + " " + AdministrativeName2;
public string AdministrativeName1 => FamilyName.Replace('ß', 'ẞ').ToUpper();
public string AdministrativeName2 =>
(Prefix != null ? Prefix + " " : "") +
GivenName +
(MiddleName != null ? " " + MiddleName : "") +
(Suffix != null ? " " + Suffix : "");
[NotMapped]
public string FullName => IsJuridicalPerson ? Name : string.Join(" ", ((string?[])[Prefix, GivenName, MiddleName, Name, Suffix]).Where(s => !string.IsNullOrWhiteSpace(s)));
[NotMapped]
public string ShortName => (!string.IsNullOrWhiteSpace(GivenName) ? $"{GivenName} " : "") + Name;
[NotMapped]
public string AdministrativeName => AdministrativeName1 + (!string.IsNullOrWhiteSpace(AdministrativeName2) ? $" {AdministrativeName2}" : "");
[NotMapped]
public string AdministrativeName1 => IsJuridicalPerson ? Name : Name.Replace('ß', 'ẞ').ToUpper();
[NotMapped]
public string? AdministrativeName2 => IsJuridicalPerson ? null : string.Join(" ", ((string?[])[Prefix, GivenName, MiddleName, Suffix]).Where(s => !string.IsNullOrWhiteSpace(s)));
[Column("birthday")]
public string? Birthday { get; set; }
[Column("entry_date")]
public string? EntryDateString { get; set; }
[NotMapped]
public DateOnly? EntryDate {
get => EntryDateString != null ? DateOnly.ParseExact(EntryDateString, "yyyy-MM-dd") : null;
@ -68,7 +61,6 @@ namespace Elwig.Models.Entities {
[Column("exit_date")]
public string? ExitDateString { get; set; }
[NotMapped]
public DateOnly? ExitDate {
get => ExitDateString != null ? DateOnly.ParseExact(ExitDateString, "yyyy-MM-dd") : null;
@ -90,6 +82,9 @@ namespace Elwig.Models.Entities {
[Column("ustid_nr")]
public string? UstIdNr { get; set; }
[Column("juridical_pers")]
public bool IsJuridicalPerson { get; set; }
[Column("volllieferant")]
public bool IsVollLieferant { get; set; }
@ -146,18 +141,27 @@ namespace Elwig.Models.Entities {
[ForeignKey("DefaultKgNr")]
public virtual WbKg? DefaultWbKg { get; private set; }
[NotMapped]
public AT_Kg? DefaultKg => DefaultWbKg?.AtKg;
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("ZwstId")]
public virtual Branch? Branch { get; private set; }
[InverseProperty(nameof(AreaCom.Member))]
public virtual ICollection<AreaCom> AreaCommitments { get; private set; } = null!;
public IQueryable<AreaCom> ActiveAreaCommitments(AppDbContext ctx) {
return ctx.AreaCommitments.Where(c => c.MgNr == MgNr).Where(Utils.ActiveAreaCommitments());
public IQueryable<AreaCom> ActiveAreaCommitments(AppDbContext ctx, int? year = null) {
return ctx.AreaCommitments.Where(c => c.MgNr == MgNr).Where(Utils.ActiveAreaCommitments(year ?? Utils.CurrentYear));
}
[InverseProperty(nameof(BillingAddr.Member))]
@ -179,8 +183,8 @@ namespace Elwig.Models.Entities {
public int SearchScore(IEnumerable<string> keywords) {
return Utils.GetSearchScore([
FamilyName, MiddleName, GivenName,
BillingAddress?.Name,
Name, MiddleName, GivenName,
BillingAddress?.FullName,
Comment,
], keywords);
}

@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
using System.ComponentModel.DataAnnotations.Schema;

@ -1,4 +1,4 @@
using Elwig.Helpers;
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
@ -11,12 +11,31 @@ namespace Elwig.Models.Entities {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("amount")]
public long AmountValue { get; set; }
[Column("mod_abs")]
public long? ModAbsValue { get; set; }
[NotMapped]
public decimal Amount {
get => Utils.DecFromDb(AmountValue, 2);
set => AmountValue = Utils.DecToDb(value, 2);
public decimal? ModAbs {
get => ModAbsValue != null ? Utils.DecFromDb(ModAbsValue.Value, 2) : null;
set => ModAbsValue = value != null ? Utils.DecToDb(value.Value, 2) : null;
}
[Column("mod_rel")]
public double? ModRelValue { get; set; }
[NotMapped]
public decimal? ModRel {
get => ModRelValue != null ? (decimal)ModRelValue.Value : null;
set => ModRelValue = value != null ? (double)value.Value : null;
}
[Column("mod_comment")]
public string? ModComment { get; set; }
[Column("amount")]
public long? AmountValue { get; set; }
[NotMapped]
public decimal? Amount {
get => AmountValue != null ? Utils.DecFromDb(AmountValue.Value, 2) : null;
set => AmountValue = value != null ? Utils.DecToDb(value.Value, 2) : null;
}
[Column("comment")]

@ -35,9 +35,9 @@ namespace Elwig.Models.Entities {
public bool TestVariant { get; set; }
[Column("calc_time")]
public int? CalcTimeUnix { get; set; }
public long? CalcTimeUnix { get; set; }
[NotMapped]
public DateTime? CalcTime => CalcTimeUnix != null ? DateTimeOffset.FromUnixTimeSeconds((long)CalcTimeUnix).UtcDateTime.ToLocalTime() : null;
public DateTime? CalcTime => CalcTimeUnix != null ? DateTimeOffset.FromUnixTimeSeconds(CalcTimeUnix.Value).LocalDateTime : null;
[Column("comment")]
public string? Comment { get; set; }
@ -45,6 +45,16 @@ namespace Elwig.Models.Entities {
[Column("data")]
public required string Data { get; set; }
[Column("ctime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long CTime { get; private set; }
[NotMapped]
public DateTime CreatedTimestamp => DateTimeOffset.FromUnixTimeSeconds(CTime).LocalDateTime;
[Column("mtime"), DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public long MTime { get; private set; }
[NotMapped]
public DateTime ModifiedTimestamp => DateTimeOffset.FromUnixTimeSeconds(MTime).LocalDateTime;
[ForeignKey("Year")]
public virtual Season Season { get; private set; } = null!;

@ -2,7 +2,7 @@ using Elwig.Models.Entities;
namespace Elwig.Models {
public interface IAddress {
string Name { get; }
string FullName { get; }
string Address { get; }
PostalDest PostalDest { get; }
}

@ -1,4 +1,4 @@
namespace Elwig.Models {
namespace Elwig.Models {
public interface IDelivery {
int Weight { get; }
double Kmw { get; }

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->

@ -1,4 +1,4 @@
-- schema version 23 to 24
-- schema version 23 to 24
ALTER TABLE modifier DROP COLUMN standard;
ALTER TABLE modifier DROP COLUMN quick_select;

@ -0,0 +1,4 @@
-- schema version 24 to 25
ALTER TABLE delivery_part RENAME COLUMN weighing_id TO weighing_data;
UPDATE delivery_part SET weighing_data = '{"id":"' || weighing_data || '","nr":' || SUBSTR(weighing_data, INSTR(weighing_data, '/') + 1) || '}';

@ -0,0 +1,91 @@
-- schema version 25 to 26
CREATE TABLE delivery_schedule (
year INTEGER NOT NULL,
dsnr INTEGER NOT NULL,
date TEXT NOT NULL CHECK (date LIKE year || '-%' AND date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$'),
zwstid TEXT NOT NULL,
description TEXT NOT NULL,
max_weight INTEGER,
ancmt_from INTEGER,
ancmt_to INTEGER,
CONSTRAINT pk_delivery_schedule PRIMARY KEY (year, dsnr),
CONSTRAINT fk_delivery_schedule_season FOREIGN KEY (year) REFERENCES season (year)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_delivery_schedule_branch FOREIGN KEY (zwstid) REFERENCES branch (zwstid)
ON UPDATE CASCADE
ON DELETE RESTRICT
) STRICT;
CREATE TABLE delivery_schedule_wine_variety (
year INTEGER NOT NULL,
dsnr INTEGER NOT NULL,
sortid TEXT NOT NULL,
priority INTEGER NOT NULL DEFAULT 1,
CONSTRAINT pk_delivery_schedule_wine_variety PRIMARY KEY (year, dsnr, sortid),
CONSTRAINT fk_delivery_schedule_wine_variety_delivery_schedule FOREIGN KEY (year, dsnr) REFERENCES delivery_schedule (year, dsnr)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT fk_delivery_schedule_wine_variety_wine_variety FOREIGN KEY (sortid) REFERENCES wine_variety (sortid)
ON UPDATE CASCADE
ON DELETE RESTRICT
) STRICT;
CREATE TABLE delivery_announcement (
year INTEGER NOT NULL,
dsnr INTEGER NOT NULL,
mgnr INTEGER NOT NULL,
sortid TEXT NOT NULL,
weight INTEGER NOT NULL,
type TEXT NOT NULL,
ctime INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
mtime INTEGER NOT NULL DEFAULT (UNIXEPOCH()),
CONSTRAINT pk_delivery_announcement PRIMARY KEY (year, dsnr, mgnr, sortid),
CONSTRAINT fk_delivery_announcement_delivery_schedule FOREIGN KEY (year, dsnr) REFERENCES delivery_schedule (year, dsnr)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT fk_delivery_announcement_member FOREIGN KEY (mgnr) REFERENCES member (mgnr)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT fk_delivery_announcement_wine_variety FOREIGN KEY (sortid) REFERENCES wine_variety (sortid)
ON UPDATE CASCADE
ON DELETE RESTRICT
) STRICT;
CREATE TRIGGER t_delivery_announcement_i_ctime
AFTER INSERT ON delivery_announcement FOR EACH ROW
WHEN NEW.ctime != UNIXEPOCH()
BEGIN
UPDATE delivery_announcement SET ctime = UNIXEPOCH() WHERE (year, dsnr, mgnr, sortid) = (NEW.year, NEW.dsnr, NEW.mgnr, NEW.sortid);
END;
CREATE TRIGGER t_delivery_announcement_u_ctime
BEFORE UPDATE ON delivery_announcement FOR EACH ROW
WHEN OLD.ctime != NEW.ctime
BEGIN
SELECT RAISE(ABORT, 'It is not allowed to change ctime');
END;
CREATE TRIGGER t_delivery_announcement_i_mtime
AFTER INSERT ON delivery_announcement FOR EACH ROW
WHEN NEW.mtime != UNIXEPOCH()
BEGIN
UPDATE delivery_announcement SET mtime = UNIXEPOCH() WHERE (year, dsnr, mgnr, sortid) = (NEW.year, NEW.dsnr, NEW.mgnr, NEW.sortid);
END;
CREATE TRIGGER t_delivery_announcement_u_mtime
AFTER UPDATE ON delivery_announcement FOR EACH ROW
WHEN NEW.mtime != UNIXEPOCH()
BEGIN
UPDATE delivery_announcement SET mtime = UNIXEPOCH() WHERE (year, dsnr, mgnr, sortid) = (NEW.year, NEW.dsnr, NEW.mgnr, NEW.sortid);
END;

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