Compare commits

...

67 Commits

Author SHA1 Message Date
f97dfc3c72 Bump version to 0.10.1
All checks were successful
Test / Run tests (push) Successful in 2m7s
Deploy / Build and Deploy (push) Successful in 2m59s
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
All checks were successful
Test / Run tests (push) Successful in 2m8s
Deploy / Build and Deploy (push) Successful in 2m37s
2024-08-13 15:23:58 +02:00
4d89a17e80 Billing: Allow users to add custom member modifiers before VAT
All checks were successful
Test / Run tests (push) Successful in 2m8s
2024-08-13 14:43:12 +02:00
f52c11b91e PaymentVariantSummary: Subtract member modifiers from sum
All checks were successful
Test / Run tests (push) Successful in 2m20s
2024-08-12 15:44:04 +02:00
f48c6a02cb [#54] Member: Add IsJuridicalPerson
All checks were successful
Test / Run tests (push) Successful in 2m49s
2024-08-12 15:18:34 +02:00
025ff08d84 [#14] Documents: Add DeliveryAncmtList
All checks were successful
Test / Run tests (push) Successful in 2m35s
2024-08-10 15:45:37 +02:00
b091bd0ec3 [#14] Windows: Add DeliveryAncmtWindow
All checks were successful
Test / Run tests (push) Successful in 2m5s
2024-08-09 22:11:47 +02:00
804a17911c [#14] Windows: Add DeliveryScheduleAdminWindow
All checks were successful
Test / Run tests (push) Successful in 2m58s
2024-08-09 17:45:14 +02:00
170cfda37e Windows: Minor cleanups 2024-08-09 17:44:48 +02:00
2d737e2780 [#14] Models: Add DeliveryAncmt 2024-08-09 17:44:21 +02:00
2333077aa5 Billing: Include predecessors in Treuebonus for WGM
All checks were successful
Test / Run tests (push) Successful in 2m29s
2024-08-08 19:09:38 +02:00
9127cd3f03 MemberAdminWindow: Fix area commitment transfer for new members
All checks were successful
Test / Run tests (push) Successful in 2m5s
2024-08-08 17:35:58 +02:00
036a0dc978 App: Use Version class
All checks were successful
Test / Run tests (push) Successful in 2m28s
2024-08-02 20:48:53 +02:00
7749f6ab45 Bump version to 0.9.3
All checks were successful
Test / Run tests (push) Successful in 2m29s
Deploy / Build and Deploy (push) Successful in 2m38s
2024-08-02 12:22:52 +02:00
cf05a0c658 DeliveryJournalData: Add delivery and member branch to excel export
All checks were successful
Test / Run tests (push) Successful in 2m52s
2024-08-02 11:42:27 +02:00
5567d9f25a Bump version to 0.9.2
All checks were successful
Test / Run tests (push) Successful in 2m7s
Deploy / Build and Deploy (push) Successful in 2m50s
2024-08-01 16:06:13 +02:00
4403754ada MemberAdminWindow: Fix error when saving telephone numbers
All checks were successful
Test / Run tests (push) Successful in 2m23s
2024-08-01 15:57:49 +02:00
8db6007264 MainWindow: Fix closing behaviour when other windows are open
All checks were successful
Test / Run tests (push) Successful in 2m24s
2024-08-01 13:49:04 +02:00
944270744a Bump version to 0.9.1
All checks were successful
Test / Run tests (push) Successful in 2m53s
Deploy / Build and Deploy (push) Successful in 2m36s
2024-08-01 09:12:33 +02:00
10ee1d6548 [#3] MemberService: Export area commitments with members 2024-07-30 17:00:29 +02:00
db4de5b5fe [#3] Windows: Add option to export selected member or delivery only
All checks were successful
Test / Run tests (push) Successful in 2m23s
2024-07-30 14:24:53 +02:00
f69d2809f3 MailWindow: Allow users to send emails without documents
All checks were successful
Test / Run tests (push) Successful in 2m14s
2024-07-30 12:57:07 +02:00
1c2e0baa68 [#3] 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
All checks were successful
Test / Run tests (push) Successful in 2m39s
2024-07-30 00:07:12 +02:00
91717f8efb Services: Add also messages for TaskCanceledException
All checks were successful
Test / Run tests (push) Successful in 2m21s
2024-07-29 23:35:03 +02:00
7786fb421a DeliveryAdminWindow: Fix scale button ordering
All checks were successful
Test / Run tests (push) Successful in 2m54s
2024-07-29 23:12:00 +02:00
12d2aeecaf Bump version to 0.9.0
All checks were successful
Test / Run tests (push) Successful in 2m50s
Deploy / Build and Deploy (push) Successful in 2m40s
2024-07-28 22:13:17 +02:00
1bc0d67d26 [#3] Services: Add 'Check internet connection' message
All checks were successful
Test / Run tests (push) Successful in 2m46s
2024-07-28 09:35:01 +02:00
38315cd928 [#3] Services: Update 'upload successful' message
All checks were successful
Test / Run tests (push) Successful in 2m24s
2024-07-28 00:27:21 +02:00
53d3affefe DeliveryConfirmation: Use checkboxes for 'gerebelt'
All checks were successful
Test / Run tests (push) Successful in 2m2s
2024-07-27 21:55:20 +02:00
4c3f0c40fa DeliveryAdminWindow: Handle members without default KG correctly
All checks were successful
Test / Run tests (push) Successful in 2m44s
2024-07-27 20:46:13 +02:00
662862090e [#3] ElwigData: Fix import error when no deliveries are present
All checks were successful
Test / Run tests (push) Successful in 2m31s
2024-07-27 19:44:22 +02:00
a5164e286f [#3] Services: Use .elwig.zip as export extension 2024-07-27 19:44:22 +02:00
ae1e985656 [#3] MainWindow: Add functionality to import data from files 2024-07-27 19:44:22 +02:00
36c1bd35a7 [#3] 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 [#3] ElwigData: Fix importing of duplicate data
All checks were successful
Test / Run tests (push) Successful in 2m52s
2024-07-27 16:57:23 +02:00
935b31f6e3 DeliveryAdminWindow: Force user to input gerebelt gewogen input
All checks were successful
Test / Run tests (push) Successful in 2m2s
2024-07-26 19:47:47 +02:00
f09753ccc2 Remove byte order marks
All checks were successful
Test / Run tests (push) Successful in 2m4s
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
All checks were successful
Test / Run tests (push) Successful in 2m19s
2024-07-26 16:15:51 +02:00
b49c9c65b1 [#3] Elwig: Add user-friendly sync method
All checks were successful
Test / Run tests (push) Successful in 2m57s
2024-07-26 14:58:15 +02:00
b6afb94246 MemberListData: Include contact information like phone numbers and email addresses
All checks were successful
Test / Run tests (push) Successful in 2m25s
2024-07-24 19:28:34 +02:00
8e9f2f4e90 WeighingTests: Add Haugsdorf and Sitzendorf
All checks were successful
Test / Run tests (push) Successful in 2m46s
2024-07-24 16:42:38 +02:00
d741ba92dc Printing/Pdf: Set CreateNoWindow to true
All checks were successful
Test / Run tests (push) Successful in 2m6s
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 [#26] MemberAdminWindow: Rearrange status bar items 2024-07-23 20:22:59 +02:00
bb77a4e79a [#26] AreaComAdminWindow: Overhaul status bar and search filters 2024-07-23 20:22:59 +02:00
46ea0f29ff [#26] MemberAdminWindow: Add tooltip to area commitment status bar 2024-07-23 20:22:59 +02:00
4229fbbef6 [#26] AreaComService: Add GenerateToolTip() 2024-07-23 20:22:59 +02:00
8dde1cb3f4 [#10] 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 [#10] MemberAdminWindow: Add tooltip for delivieries 2024-07-23 20:22:59 +02:00
60359935a4 [#10] DeliveryAdminWindow: Move generation of tooltips to DeliveryService 2024-07-23 20:22:59 +02:00
49e988f71a [#10] AreaComAdminWindow: Implement MVVM 2024-07-23 20:22:59 +02:00
2a8de18772 [#10] DeliveryAdminWindow: Implement MVVM 2024-07-23 20:22:59 +02:00
af80e827b7 [#10] MemberAdminWindow: Implement MVVM 2024-07-23 20:22:59 +02:00
9a2fa3ee3d Bump version to 0.8.9
All checks were successful
Deploy / Build and Deploy (push) Successful in 2m37s
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
All checks were successful
Test / Run tests (push) Successful in 2m12s
2024-07-23 11:08:42 +02:00
187 changed files with 7465 additions and 2699 deletions

View File

@ -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

View File

@ -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}
---------------------------------------

View File

@ -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);
}

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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}}">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"

View File

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

View File

@ -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"

View File

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

View File

@ -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"

View File

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

View File

@ -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"

View File

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

View File

@ -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)));
}
}

View File

@ -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);
}
}
}}

View File

@ -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) {

View File

@ -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) {
}
}
}

View File

@ -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>

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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) {

View File

@ -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 = "";
}
}

View File

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

View File

@ -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>

View File

@ -1,4 +1,4 @@

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

View File

@ -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) :

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -7,7 +7,7 @@
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.8.8</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" />

View File

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

View File

@ -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();

View File

@ -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) {

View File

@ -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();

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -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);

View File

@ -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}

View File

@ -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;

View File

@ -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,

View File

@ -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" : "");

View File

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

View File

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

View File

@ -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}");

View File

@ -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));
}
}

View File

@ -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,

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.IO.Ports;
using System.Text;
@ -69,13 +69,14 @@ namespace Elwig.Helpers.Weighing {
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,
};
}

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

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

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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")]

View File

@ -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")]

View File

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

View File

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

View File

@ -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;
}
}
}

View File

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

View File

@ -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;

View File

@ -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")]

View File

@ -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() : [];
}
}
}

View File

@ -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")]

View File

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

View File

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

View File

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

View File

@ -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!;

View File

@ -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; }

View File

@ -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!;

View File

@ -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());

View File

@ -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);
}
}
}

View File

@ -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!;

View File

@ -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);
}
}
}

View File

@ -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!;
}
}

View File

@ -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);
}

View File

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

View File

@ -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")]

View File

@ -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!;

View File

@ -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; }
}

View File

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

View File

@ -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.
-->

View File

@ -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;

View File

@ -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) || '}';

View File

@ -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