Compare commits

..
121 Commits
Author SHA1 Message Date
lorenz.stechauner d91e8c7e99 Bump version to 1.2.0.0
Deploy / Build and Deploy (push) Successful in 2m5s
2026-10-03 00:50:31 +02:00
lorenz.stechauner 09642e643c ClientParameters: Add option to hide business share history on MemberDataSheet
Test / Run tests (push) Successful in 2m16s
2026-10-03 00:07:04 +02:00
lorenz.stechauner b262e17293 Elwig: Update dependencies 2026-10-02 23:48:11 +02:00
lorenz.stechauner 4f4d026b7b AppDbUpdater: Fix update to v42 2026-10-02 23:48:11 +02:00
lorenz.stechauner abb9e5a577 MemberAdminWindow: Add pending member movement export 2026-10-02 23:47:56 +02:00
lorenz.stechauner 0e586cc438 DeliveryAdminWindow: Invalidate non-positive weight
Test / Run tests (push) Successful in 2m12s
2026-10-02 10:31:47 +02:00
lorenz.stechauner 829ad106cf DeliverySplittingDialog: Use color for wine variety
Test / Run tests (push) Successful in 3m3s
2026-10-02 09:26:46 +02:00
lorenz.stechauner ba7c37fd70 Helpers/Export: Code cleanup
Test / Run tests (push) Successful in 3m6s
2026-10-01 12:29:47 +02:00
lorenz.stechauner 66a1185dc3 MemberAdminWindow: Swap postal dest and Ort.Name
Test / Run tests (push) Successful in 2m38s
2026-09-30 15:41:47 +02:00
lorenz.stechauner db8afa59b7 Changelog: Use '-' instead of '*'
Test / Run tests (push) Successful in 2m25s
2026-09-30 12:14:30 +02:00
lorenz.stechauner 3d96c31a70 Elwig: Rename AreaCom to AreaCmt 2026-09-30 12:14:30 +02:00
lorenz.stechauner a18d687f18 [#67] OrganicService: Use ConcurrentDictionary 2026-09-30 12:14:30 +02:00
lorenz.stechauner 5f2788eb9e Elwig: Rename buchführend, funktionär, volllieferant, and gebunden 2026-09-30 12:14:25 +02:00
lorenz.stechauner 4988081bfd Tests: Add PaymentVariantServiceTest 2026-09-30 11:54:33 +02:00
lorenz.stechauner 03872ca5c2 SystecITScale: Fix typo in error message 2026-09-30 11:54:26 +02:00
lorenz.stechauner b886abc632 Test: Fix E2ETests 2026-09-29 22:30:29 +02:00
lorenz.stechauner cba949d2eb App: Only backup when not in debug mode
Test / Run tests (push) Successful in 2m19s
2026-09-29 21:12:15 +02:00
lorenz.stechauner c2bc5b221a ViewModels: Use partial properties 2026-09-29 21:08:25 +02:00
lorenz.stechauner 5845693f17 [#67] OrganicService: Use any valid certificate
Test / Run tests (push) Successful in 2m6s
2026-09-29 19:31:14 +02:00
lorenz.stechauner aea7c39734 Tests: Update dependencies
Test / Run tests (push) Successful in 2m29s
2026-09-29 19:25:41 +02:00
lorenz.stechauner c54a998c96 Elwig: Update dependencies 2026-09-29 19:25:06 +02:00
lorenz.stechauner 5bb84944bc [#67] DeliveryAdminWindow: Check if member IsOrganic and has valid certs when cultivation IsOrganic
Test / Run tests (push) Successful in 2m13s
2026-09-29 19:10:18 +02:00
lorenz.stechauner 34b40535a6 [#67] DeliveryAncmtAdminWindow: Check if member IsOrganic when delivery schedule IsOrganic 2026-09-29 19:04:47 +02:00
lorenz.stechauner 3dca099248 [#67] Elwig: Add OrganicService 2026-09-29 19:04:13 +02:00
lorenz.stechauner 155a599878 App: Add ShutdownNow() and ShutdownGracefully() 2026-09-29 19:04:12 +02:00
lorenz.stechauner fda337c3cb Helpers: Add Normalizer to normalize street names in addresses 2026-09-29 19:04:12 +02:00
lorenz.stechauner d8126e254d BusinessDocument: Do not show billing address name when UseBillingAddress is false 2026-09-29 19:04:12 +02:00
lorenz.stechauner 96441a6701 [#67] BusinessDocument: Add OrganicOperatorId to footer 2026-09-29 19:04:01 +02:00
lorenz.stechauner 03867bcd74 [#67] BusinessDocument: Add OrganicAuthorityCode and OrganicOperatorId to member Aside 2026-09-29 19:03:53 +02:00
lorenz.stechauner 83dfde9203 Entities/MemberHistory: Add CompanyHasSigned 2026-09-29 19:03:51 +02:00
lorenz.stechauner 805c5efda8 [#67] ClientParameters: Add ClientOrganicOperatorId 2026-09-29 19:03:44 +02:00
lorenz.stechauner e86edc080a [#67] Entities/WineCult: Add IsOrganic 2026-09-29 19:03:36 +02:00
lorenz.stechauner 0d5d1d4d3d [#67] Entities/Member: Add OrganicOperatorId 2026-09-29 19:03:23 +02:00
lorenz.stechauner ffd3dc9631 [#67] Entities/Member: Add OrganicAuthorityCode 2026-09-29 19:03:07 +02:00
lorenz.stechauner d592fef352 BusinessDocument: Use more columns in Aside table
Test / Run tests (push) Successful in 2m5s
2026-09-22 11:19:41 +02:00
lorenz.stechauner f2b84053e6 DeliveryNote: Remove right padding for weight cells
Test / Run tests (push) Successful in 1m51s
2026-09-22 10:54:23 +02:00
lorenz.stechauner 7fef895491 DeliveryService: Add red/white distinction to tool tips
Test / Run tests (push) Successful in 1m53s
2026-09-16 17:28:38 +02:00
lorenz.stechauner 0e9121c462 TareCorrectionDialog: Allow actual tare to be null 2026-09-16 17:23:12 +02:00
lorenz.stechauner 15e191d74e Export/Database: Do not delete import source files 2026-09-16 17:12:52 +02:00
lorenz.stechauner abf06c558f Bump version to 1.1.2.2
Test / Run tests (push) Successful in 2m8s
Deploy / Build and Deploy (push) Successful in 2m55s
2026-09-14 17:10:23 +02:00
lorenz.stechauner 6436762e8d DeliveryAdminWindow: Show CorrectTareButton also for unloading type box
Test / Run tests (push) Successful in 2m11s
2026-09-14 16:40:16 +02:00
lorenz.stechauner 6441fa4f50 MemberAdminWindow: Add details in header and tool tips
Test / Run tests (push) Successful in 2m3s
2026-09-14 15:20:34 +02:00
lorenz.stechauner 6bed370eba Tests: Update dependencies
Test / Run tests (push) Successful in 2m13s
2026-09-14 14:53:39 +02:00
lorenz.stechauner 4cc33b56c8 Elwig: Update dependencies 2026-09-14 14:53:26 +02:00
lorenz.stechauner f4cc443521 DeliveryNote: Fix page breaks in delivery table
Test / Run tests (push) Successful in 2m12s
2026-09-14 14:45:39 +02:00
lorenz.stechauner 8ba051198f DeliveryAdminWindow: Add button to correct tare 2026-09-14 14:43:24 +02:00
lorenz.stechauner fea779bd95 Windows: Add filters to Titles in some AdministrationWindows
Test / Run tests (push) Successful in 2m21s
2026-09-12 22:59:57 +02:00
lorenz.stechauner e66780ccd0 DeliveryNote: Fix missing ':' for Comment and Acid 2026-09-12 22:48:28 +02:00
lorenz.stechauner cc01f92005 InteractionService: Add more detailed messages for SqliteException
Test / Run tests (push) Successful in 2m44s
2026-08-29 11:42:09 +02:00
lorenz.stechauner 0a93167ea7 Bump version to 1.1.2.1
Test / Run tests (push) Successful in 1m53s
Deploy / Build and Deploy (push) Successful in 1m51s
2026-08-27 11:33:48 +02:00
lorenz.stechauner 848a6d1ce0 MailWindow: Add Date header to email
Test / Run tests (push) Successful in 1m55s
2026-08-27 11:28:27 +02:00
lorenz.stechauner f0745eae4e Elwig: Fix LINQ queries containing DateTime.Today
Test / Run tests (push) Successful in 2m18s
2026-08-27 11:20:22 +02:00
lorenz.stechauner 8471ac9319 Bump version to 1.1.2.0
Test / Run tests (push) Successful in 2m34s
Deploy / Build and Deploy (push) Successful in 2m11s
2026-08-26 23:05:34 +02:00
lorenz.stechauner 7add96dfd5 Elwig: Update dependencies
Test / Run tests (push) Successful in 2m18s
2026-08-26 16:28:29 +02:00
lorenz.stechauner 2360402124 Tests: Update dependencies 2026-08-26 16:28:14 +02:00
lorenz.stechauner cadac4302d App: Add option to restore database when a DB error occurs at startup
Test / Run tests (push) Successful in 2m12s
2026-08-26 12:01:03 +02:00
lorenz.stechauner c1690bf104 DeliveryService: Fix GetFilterToday and add tests
Test / Run tests (push) Successful in 2m16s
2026-08-26 10:57:02 +02:00
lorenz.stechauner ca5b719630 Elwig: Add BackupService
Test / Run tests (push) Successful in 2m48s
2026-08-26 10:39:50 +02:00
lorenz.stechauner 163fe0fac8 Bump version to 1.1.1.2
Test / Run tests (push) Successful in 2m41s
Deploy / Build and Deploy (push) Successful in 1m52s
2026-08-25 20:36:03 +02:00
lorenz.stechauner edf3678154 Export/ElwigData: Fix detection of KGs without Großlage
Test / Run tests (push) Successful in 1m54s
2026-08-25 18:19:41 +02:00
lorenz.stechauner f450dc35b1 Weighing: Add option to opt-out of automatic datetime sync at startup 2026-08-25 18:19:31 +02:00
lorenz.stechauner 0c4611024f App: Add default weighing mode for Pamhagen
Test / Run tests (push) Successful in 1m57s
2026-08-25 16:31:33 +02:00
lorenz.stechauner 46e596ae23 Utils: Replace Utils.Today with DateTime.Today and adapt DeliveryService filters
Test / Run tests (push) Successful in 2m51s
2026-08-25 16:30:38 +02:00
lorenz.stechauner 3366cc25eb DeliveryAdminWindow: Change modifier handling for Matzen
Test / Run tests (push) Successful in 2m12s
2026-08-24 22:11:52 +02:00
lorenz.stechauner ff71e9fb7f DeliveryNote: Fix alignment of weights 2026-08-24 21:49:10 +02:00
lorenz.stechauner 7d5d7d9307 Bump version to 1.1.1.1
Test / Run tests (push) Successful in 2m20s
Deploy / Build and Deploy (push) Successful in 2m0s
2026-08-24 00:51:00 +02:00
lorenz.stechauner b468bbce31 MainWindow: Add individual commitments to Area Commitment export
Test / Run tests (push) Successful in 2m37s
2026-08-24 00:39:30 +02:00
lorenz.stechauner a79631c586 Bump version to 1.1.1.0
Test / Run tests (push) Successful in 3m10s
Deploy / Build and Deploy (push) Successful in 1m52s
2026-08-18 23:58:31 +02:00
lorenz.stechauner 20b6e11b7f Export/Bki: Honor vollablieferer flag for member
Test / Run tests (push) Successful in 2m44s
2026-08-18 16:26:29 +02:00
lorenz.stechauner 77e812f5e8 Bump version 1.1.0.3
Deploy / Build and Deploy (push) Successful in 2m1s
2026-08-17 23:53:32 +02:00
lorenz.stechauner 58bfb4f56d Tests: Update dependencies
Test / Run tests (push) Successful in 2m4s
2026-08-17 23:38:46 +02:00
lorenz.stechauner e6e140dc08 Elwig: Update dependencies 2026-08-17 23:38:43 +02:00
lorenz.stechauner eaddf0c3cb SyncService: Add distinction main/non-main device
Test / Run tests (push) Successful in 2m26s
2026-08-17 23:29:31 +02:00
lorenz.stechauner fc5ad82686 Printing: Print all copies as one single print job 2026-08-17 17:50:33 +02:00
lorenz.stechauner b76367f73a Bump version to 1.1.0.2
Test / Run tests (push) Successful in 1m56s
Deploy / Build and Deploy (push) Successful in 1m51s
2026-08-11 18:27:22 +02:00
lorenz.stechauner 76ea3513ac MainWindow: Delay email sending
Test / Run tests (push) Successful in 2m4s
2026-08-11 18:25:16 +02:00
lorenz.stechauner 07b197958d Printing: Try to duplex print with manual dialog
Test / Run tests (push) Successful in 2m12s
2026-08-11 18:09:51 +02:00
lorenz.stechauner 470c6e09e5 Printing: Change fit-to-printable-are to scale 1:1
Test / Run tests (push) Successful in 2m40s
2026-08-11 17:25:49 +02:00
lorenz.stechauner 20b19775b1 Printing: Use –FPDF_RenderPage instead of FPDF_RenderPageBitmap to allow vector graphics
Test / Run tests (push) Successful in 2m38s
2026-08-10 18:35:49 +02:00
lorenz.stechauner f13a836ebd Printing: Fix slight translocation to the bottom right
Test / Run tests (push) Successful in 3m2s
2026-08-10 13:10:10 +02:00
lorenz.stechauner 2bf926c84c Bump version to 1.1.0.1
Test / Run tests (push) Successful in 2m15s
Deploy / Build and Deploy (push) Successful in 1m59s
2026-08-05 17:17:27 +02:00
lorenz.stechauner a8cc3c9cb9 Elwig: Update dependencies
Test / Run tests (push) Successful in 1m52s
2026-08-05 10:33:47 +02:00
lorenz.stechauner 88616b2c0b MemberAdminWindow: Add icons to deliveries and area commitments buttons
Test / Run tests (push) Successful in 1m58s
2026-08-03 14:20:24 +02:00
lorenz.stechauner e6712732cd [#82] BaseDataWindow: Fixed error handling after first error to allow saving changes
Test / Run tests (push) Successful in 2m8s
2026-08-03 14:18:20 +02:00
lorenz.stechauner cfabf99c99 [#83] PaymentVariantsWindow: Automatically calculate when saving variant
Test / Run tests (push) Successful in 1m57s
2026-08-03 11:26:28 +02:00
lorenz.stechauner 668030fbff Tests: Update dependencies 2026-08-03 11:26:28 +02:00
lorenz.stechauner b445928c4d Elwig: Update dependencies 2026-08-03 11:26:28 +02:00
lorenz.stechauner 2a3999f300 Weighing: Set Date/Time of scales at startup 2026-08-03 11:26:23 +02:00
lorenz.stechauner 309f118739 Weighing: Differentiate between scale and terminal 2026-08-03 10:56:36 +02:00
lorenz.stechauner 0d8cdd5d6e Bump version to 1.1.0.0
Test / Run tests (push) Successful in 1m54s
Deploy / Build and Deploy (push) Successful in 1m42s
2026-07-09 01:43:38 +02:00
lorenz.stechauner 2350b55cea Tests: Update dependencies
Test / Run tests (push) Successful in 2m6s
2026-07-09 01:01:38 +02:00
lorenz.stechauner e8789aff37 Elwig: Update dependencies 2026-07-09 01:01:38 +02:00
lorenz.stechauner 918555f2b8 [#20] MemberAdminWindow: Add MemberBusinessSharesAdminWindow to manage member shares 2026-07-09 01:01:28 +02:00
lorenz.stechauner 1169ba5101 ChartWindow: Fix warnings and avoid null-forgiving operator
Test / Run tests (push) Successful in 2m10s
2026-07-06 19:11:39 +02:00
lorenz.stechauner 76da392f5f [#20][#80] MemberAdminWindow: Update business share status bar tool tip
Test / Run tests (push) Successful in 2m1s
2026-07-06 18:18:46 +02:00
lorenz.stechauner 000117dc67 CreditNoteData: Add AccountingNr of member
Test / Run tests (push) Successful in 3m6s
2026-07-06 11:03:38 +02:00
lorenz.stechauner 314a0d37a8 [#20] MemberDataSheet: Show members business share history 2026-07-03 12:02:44 +02:00
lorenz.stechauner 765edbdae6 [#20][#80] MemberAdminWindow: Add functionality to edit other business shares 2026-07-03 12:02:44 +02:00
lorenz.stechauner 9a0a422c8a [#20] Entities/MemberHistory: Add DeductYear to simplify billing 2026-07-03 12:02:37 +02:00
lorenz.stechauner a71c41af5c AppDbContext: Add Member.Branch AutoInclude to fix MemberDataSheet
Test / Run tests (push) Successful in 2m52s
2026-07-02 12:17:32 +02:00
lorenz.stechauner 62346a63a8 [#80] Elwig: Add client parameter to switch between simple and red/white business share mode
Test / Run tests (push) Successful in 2m20s
2026-07-01 17:09:55 +02:00
lorenz.stechauner feee6ad1ec [#20][#80] Elwig: Update member_history and add different types of shares
Test / Run tests (push) Successful in 2m0s
2026-07-01 16:22:01 +02:00
lorenz.stechauner 4ebe07f579 AppDbUpdater: Fix handling of foreign key errors
Test / Run tests (push) Successful in 3m4s
2026-07-01 00:36:04 +02:00
lorenz.stechauner beacba6bd9 Services: Add Utils.RunForeground() to factor Task.Run and try-catch-Blocks out
Test / Run tests (push) Successful in 2m2s
2026-06-30 02:32:24 +02:00
lorenz.stechauner 69efca1cc3 Tests: Use Assert.EnterMultipleScope instead of Assert.Multiple
Test / Run tests (push) Successful in 2m6s
2026-06-29 02:33:36 +02:00
lorenz.stechauner fcd0555e4d Elwig: Add InteractionService to centrally manage MessageBox and SaveFileDialogs
Test / Run tests (push) Successful in 2m50s
2026-06-29 02:28:34 +02:00
lorenz.stechauner b93e987685 Bump version to 1.0.5.6
Test / Run tests (push) Successful in 2m24s
Deploy / Build and Deploy (push) Successful in 1m58s
2026-06-25 13:07:10 +02:00
lorenz.stechauner 1ad97a78ff ClientParameters: Add client Seewinkel
Test / Run tests (push) Successful in 3m13s
2026-06-25 12:33:20 +02:00
lorenz.stechauner 1261be001c BaseDataWindow: Remove transactions inside *Save() functions to avoid nesting them 2026-06-25 12:32:46 +02:00
lorenz.stechauner 15de07a4c3 BillingData: Remove quality entry when using –FromGraphEntries
Test / Run tests (push) Successful in 2m34s
2026-06-24 15:51:37 +02:00
lorenz.stechauner 5cbad9a195 Bump version to 1.0.5.5
Test / Run tests (push) Successful in 6m43s
Deploy / Build and Deploy (push) Successful in 2m10s
2026-06-24 14:36:44 +02:00
lorenz.stechauner 4e027a9add Elwig: Update dependencies
Test / Run tests (push) Successful in 4m40s
2026-06-24 14:24:25 +02:00
lorenz.stechauner f32ff945ec BillingData: Preserve all settings when using FromGraphEntries() 2026-06-24 14:24:15 +02:00
lorenz.stechauner ba3f66591e BilllingVariant: Allow for credits with negative value 2026-06-24 13:20:46 +02:00
lorenz.stechauner 3f65b2350b PaymentVariantService: Fix crash in GenerateEbics()
Test / Run tests (push) Successful in 2m48s
2026-06-24 10:36:28 +02:00
lorenz.stechauner 7a6a77419b Bump version to 1.0.5.4
Test / Run tests (push) Successful in 2m17s
Deploy / Build and Deploy (push) Successful in 2m6s
2026-06-22 20:23:46 +02:00
lorenz.stechauner 7a993585c3 Elwig: Update dependencies
Test / Run tests (push) Successful in 2m3s
2026-06-22 20:08:18 +02:00
lorenz.stechauner d0ce377d92 AppDbContext: Fix FetchMembers when using for specific MgNr
Test / Run tests (push) Successful in 2m59s
2026-06-22 20:06:34 +02:00
lorenz.stechauner f8b3147c72 Tests: Update dependencies
Test / Run tests (push) Successful in 2m52s
2026-06-18 12:25:22 +02:00
lorenz.stechauner b4ab8349b4 Elwig: Update dependencies 2026-06-18 12:25:09 +02:00
lorenz.stechauner 5db5876905 Installer: Bundle and ship source code together with binaries
Test / Run tests (push) Successful in 3m14s
2026-05-01 15:10:19 +02:00
190 changed files with 11353 additions and 3966 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ name: Test
on: on:
push: push:
branches: ["**"] branches: ["**"]
paths: ["Elwig/**", "Tests/**", "Installer/Files/*.exe", ".gitea/workflows/test.yaml"] paths: ["Elwig/**", "Tests/**", ".gitea/workflows/test.yaml"]
jobs: jobs:
test: test:
name: Run tests name: Run tests
+1
View File
@@ -7,3 +7,4 @@ Tests/Resources/Sql/Create.sql
*.exe *.exe
!WinziPrint.exe !WinziPrint.exe
*.sqlite3 *.sqlite3
*.zip
+627 -354
View File
File diff suppressed because it is too large Load Diff
+1 -8
View File
@@ -16,14 +16,7 @@
<dependency> <dependency>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly> </dependentAssembly>
</dependency> </dependency>
</assembly> </assembly>
+1 -8
View File
@@ -2,18 +2,11 @@
x:Class="Elwig.App" x:Class="Elwig.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:Elwig.Controls" xmlns:ctrl="clr-namespace:Elwig.Controls">
Exit="Application_Exit">
<Application.Resources> <Application.Resources>
<ctrl:BoolToStringConverter x:Key="BoolToStarConverter" FalseValue="" TrueValue="*"/> <ctrl:BoolToStringConverter x:Key="BoolToStarConverter" FalseValue="" TrueValue="*"/>
<ctrl:WidthToMarginConverter x:Key="WidthToMarginConverter"/> <ctrl:WidthToMarginConverter x:Key="WidthToMarginConverter"/>
<DataTemplate x:Key="PostalDestTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Dest}"/>
<TextBlock Text="{Binding Ort.Name, StringFormat='{} ({0})'}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="KgNrTemplate"> <DataTemplate x:Key="KgNrTemplate">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Name}"/>
+86 -43
View File
@@ -5,6 +5,7 @@ using Elwig.Helpers.Export;
using Elwig.Helpers.Printing; using Elwig.Helpers.Printing;
using Elwig.Helpers.Weighing; using Elwig.Helpers.Weighing;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Elwig.Services;
using Elwig.Windows; using Elwig.Windows;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
@@ -17,16 +18,18 @@ using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input;
using System.Windows.Threading; using System.Windows.Threading;
namespace Elwig { namespace Elwig {
public partial class App : Application { public partial class App : Application {
protected static App CurrentApp; public static App CurrentApp;
public static int NumWindows => CurrentApp.Windows.Count; public static int NumWindows => CurrentApp.Windows.Count;
public static bool ForceShutdown { get; private set; } = false; public static bool ForceShutdown { get; set; } = false;
private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) }; private readonly DispatcherTimer _autoUpdateTimer = new() { Interval = TimeSpan.FromHours(1) };
private readonly DispatcherTimer _autoBackupTimer = new() { Interval = TimeSpan.FromMinutes(10) };
public readonly SerialPortWatcher SerialPortWatcher = new(); public readonly SerialPortWatcher SerialPortWatcher = new();
public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig"); public static readonly string DataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Elwig");
@@ -73,7 +76,7 @@ namespace Elwig {
Config = new(Path.GetFullPath(args[1])); Config = new(Path.GetFullPath(args[1]));
} }
ContextTimer.Tick += (object? sender, EventArgs evt) => { ContextTimer.Tick += (sender, evt) => {
var ch = CurrentLastWrite; var ch = CurrentLastWrite;
if (ch > LastChanged) { if (ch > LastChanged) {
LastChanged = ch; LastChanged = ch;
@@ -91,12 +94,16 @@ namespace Elwig {
try { try {
await AppDbUpdater.CheckDb(); await AppDbUpdater.CheckDb();
} catch (Exception e) { } catch (FileNotFoundException exc) {
InteractionService.ShowException("Datenbank nicht gefunden", "Die Datenbank konnte nicht gefunden werden", exc);
} catch (Exception exc) {
if (Config.UpdateUrl != null && Utils.HasInternetConnectivity()) { if (Config.UpdateUrl != null && Utils.HasInternetConnectivity()) {
await CheckForUpdates(); await CheckForUpdates();
} }
MessageBox.Show($"Invalid Database:\n\n{e.Message}", "Invalid Database", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException("Fehlerhafte Datenbank", "Schwerwiegender Fehler in der Datenbank, unbedingt den Programmierern melden!", exc);
Shutdown(); if (InteractionService.AskQuestion("Backup wiederherstellen", "Soll die Datenbank aus einem Backup wiederhergestellt werden?\n\nAchtung: Das kann zu Datenverlust führen!", false))
await BackupService.RestoreDatabase();
ShutdownNow();
return; return;
} }
@@ -105,14 +112,15 @@ namespace Elwig {
Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = []; Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = [];
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
branches = ctx.FetchBranches() branches = await ctx.FetchBranches()
.ToDictionaryAsync(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr)) .ToDictionaryAsync(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
.GetAwaiter().GetResult();
try { try {
Client = new(ctx); Client = new(ctx);
} catch (Exception e) { } catch (Exception exc) {
MessageBox.Show($"Fehler beim Laden der Mandantendaten:\n\n{e.Message}", "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException("Fehler", "Fehler beim Laden der Mandantendaten, unbedingt den Programmierern melden!", exc);
Shutdown(); if (InteractionService.AskQuestion("Backup wiederherstellen", "Soll die Datenbank aus einem Backup wiederhergestellt werden?\n\nAchtung: Das kann zu Datenverlust führen!", false))
await BackupService.RestoreDatabase();
ShutdownNow();
return; return;
} }
BranchNum = branches.Count; BranchNum = branches.Count;
@@ -123,9 +131,23 @@ namespace Elwig {
return Task.CompletedTask; return Task.CompletedTask;
}); });
Utils.RunBackground("PDF Initialization", () => Pdf.Init()); Utils.RunBackground("PDF Initialization", async () => Pdf.Init());
Utils.RunBackground("JSON Schema Initialization", BillingData.Init); Utils.RunBackground("JSON Schema Initialization", BillingData.Init);
if (Config.BackupAuto && !Config.Debug) {
Utils.RunBackground("Automatisches Backup", async () => {
Directory.CreateDirectory(Config.BackupPath);
await Task.Delay(500);
await BackupService.TryDatabaseBackup();
});
_autoBackupTimer.Tick += new EventHandler((sender, evt) => {
Utils.RunBackground("Automatisches Backup", async () => {
await BackupService.TryDatabaseBackup();
});
});
_autoBackupTimer.Start();
}
if (Config.UpdateAuto && Config.UpdateUrl != null) { if (Config.UpdateAuto && Config.UpdateUrl != null) {
if (Utils.HasInternetConnectivity()) { if (Utils.HasInternetConnectivity()) {
Utils.RunBackground("Auto Updater", async () => { Utils.RunBackground("Auto Updater", async () => {
@@ -144,28 +166,33 @@ namespace Elwig {
var list = new List<IScale>(); var list = new List<IScale>();
foreach (var s in Config.Scales) { foreach (var s in Config.Scales) {
try { try {
list.Add(Scale.FromConfig(s)); var scale = Scale.FromConfig(s);
} catch (Exception e) { if (s.SyncTime && scale is ICommandScale cmd) {
try {
await cmd.SetDateAndTime(DateTime.Now);
} catch { }
}
list.Add(scale);
} catch (Exception exc) {
list.Add(new InvalidScale(s.Id)); list.Add(new InvalidScale(s.Id));
if (s.Required) if (s.Required)
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler", InteractionService.ShowException("Waagenfehler", $"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden", exc);
MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
Scales = list; Scales = list;
if (Config.Branch != null) { if (Config.Branch != null) {
if (!branches.ContainsKey(Config.Branch.ToLower())) { if (!branches.ContainsKey(Config.Branch.ToLower())) {
MessageBox.Show("Ungültige Zweigstelle in Konfigurationsdatei!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowError("Ungültige Zweigstelle", "Ungültige Zweigstelle in Konfigurationsdatei!");
Shutdown(); ShutdownNow();
} else { } else {
SetBranch(branches[Config.Branch.ToLower()]); SetBranch(branches[Config.Branch.ToLower()]);
} }
} else if (branches.Count == 1) { } else if (branches.Count == 1) {
SetBranch(branches.First().Value); SetBranch(branches.First().Value);
} else { } else {
MessageBox.Show("Erkennen der lokalen Zweigstelle nicht möglich!", "Ungültige Zweigstelle", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowError("Ungültige Zweigstelle", "Erkennen der lokalen Zweigstelle nicht möglich!");
Shutdown(); ShutdownNow();
} }
if (Config.WeighingMode == null) { if (Config.WeighingMode == null) {
@@ -173,7 +200,7 @@ namespace Elwig {
Config.WeighingMode = WeighingMode.Net; Config.WeighingMode = WeighingMode.Net;
} else if (Client.IsHaugsdorf || Client.IsSitzendorf) { } else if (Client.IsHaugsdorf || Client.IsSitzendorf) {
Config.WeighingMode = WeighingMode.Box; Config.WeighingMode = WeighingMode.Box;
} else if (Client.IsBaden || Client.IsGrInzersdorf) { } else if (Client.IsBaden || Client.IsGrInzersdorf || Client.IsPamhagen) {
Config.WeighingMode = WeighingMode.Gross; Config.WeighingMode = WeighingMode.Gross;
} }
} }
@@ -184,12 +211,29 @@ namespace Elwig {
window.Show(); window.Show();
} }
private async void Application_Exit(object sender, ExitEventArgs evt) { public static void ShutdownNow() {
ForceShutdown = true;
Current.Shutdown();
}
public async void ShutdownGracefully() {
Mouse.OverrideCursor = Cursors.AppStarting;
if (Config.BackupAuto && !Config.Debug) {
try {
await BackupService.TryDatabaseBackup();
} catch (Exception exc) {
InteractionService.ShowException("Automatisches Backup", exc);
}
}
SerialPortWatcher.Dispose(); SerialPortWatcher.Dispose();
foreach (var s in EventScales) { foreach (var s in EventScales) {
s.Dispose(); s.Dispose();
} }
await Pdf.Cleanup(); Pdf.Cleanup();
Shutdown();
} }
public static void SetBranch(Branch b) { public static void SetBranch(Branch b) {
@@ -254,14 +298,13 @@ namespace Elwig {
if (Scales[i] is InvalidScale) { if (Scales[i] is InvalidScale) {
try { try {
Scales[i] = Scale.FromConfig(s); Scales[i] = Scale.FromConfig(s);
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information); InteractionService.ShowInformation($"Waage {s.Id}", $"Verbindung zu Waage {s.Id} wieder hergestellt!");
} catch (Exception e) { } catch (Exception exc) {
Scales[i] = new InvalidScale(s.Id); Scales[i] = new InvalidScale(s.Id);
MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler", InteractionService.ShowException("Waagenfehler", $"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden", exc);
MessageBoxButton.OK, MessageBoxImage.Error);
} }
} else if (Scales[i] is IEventScale) { } else if (Scales[i] is IEventScale) {
MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information); InteractionService.ShowInformation($"Waage {s.Id}", $"Verbindung zu Waage {s.Id} wieder hergestellt!");
} }
} }
} }
@@ -272,7 +315,7 @@ namespace Elwig {
for (var i = 0; i < Config.Scales.Count; i++) { for (var i = 0; i < Config.Scales.Count; i++) {
var s = Config.Scales[i]; var s = Config.Scales[i];
if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) { if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) {
MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning); InteractionService.ShowWarning($"Waagen {s.Id}", $"Verbindung zu Waage {s.Id} unterbrochen!");
if (Scales[i] is ICommandScale) { if (Scales[i] is ICommandScale) {
try { try {
Scales[i].Dispose(); Scales[i].Dispose();
@@ -301,17 +344,14 @@ namespace Elwig {
await MainDispatcher.BeginInvoke(() => { await MainDispatcher.BeginInvoke(() => {
var d = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size); var d = new UpdateDialog(latest.Value.Version, latest.Value.Url, latest.Value.Size);
if (d.ShowDialog() == true) { if (d.ShowDialog() == true) {
ForceShutdown = true; ShutdownNow();
Current.Shutdown();
} }
}); });
} else if (showAlert) { } else if (showAlert) {
if (latest == null) { if (latest == null) {
MessageBox.Show("Informationen konnten nicht abgerufen werden!", "Nach Updates suchen", InteractionService.ShowError("Nach Updates suchen", "Informationen konnten nicht abgerufen werden!");
MessageBoxButton.OK, MessageBoxImage.Error);
} else { } else {
MessageBox.Show($"Elwig ist auf dem aktuellsten Stand! (Version: {latest.Value.Version})", "Nach Updates suchen", InteractionService.ShowInformation("Nach Updates suchen", $"Elwig ist auf dem aktuellsten Stand! (Version: {latest.Value.Version})");
MessageBoxButton.OK, MessageBoxImage.Information);
} }
} }
} }
@@ -321,11 +361,10 @@ namespace Elwig {
await Task.Run(async () => { await Task.Run(async () => {
await Database.Import(filename); await Database.Import(filename);
}); });
MessageBox.Show("Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!", "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Information); InteractionService.ShowInformation("Datenbank ersetzen", "Das Ersetzen war erfolgreich!\n\nBitte starten Sie Elwig neu!");
ForceShutdown = true; ShutdownNow();
Current.Shutdown();
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show("Fehler beim Ersetzen:\n\n" + exc.Message, "Datenbank ersetzen", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException("Datenbank ersetzen", "Fehler beim Ersetzen", exc);
} }
} }
@@ -347,19 +386,23 @@ namespace Elwig {
return FocusWindow<DeliveryAdminWindow>(() => new(true), w => w.ViewModel.IsReceipt); return FocusWindow<DeliveryAdminWindow>(() => new(true), w => w.ViewModel.IsReceipt);
} }
public static MemberBusinessSharesAdminWindow FocusMemberBusinessSharesWindow(int mgnr) {
return FocusWindow<MemberBusinessSharesAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember?.MgNr == mgnr);
}
public static DeliveryAdminWindow FocusMemberDeliveries(int mgnr) { public static DeliveryAdminWindow FocusMemberDeliveries(int mgnr) {
return FocusWindow<DeliveryAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember?.MgNr == mgnr); return FocusWindow<DeliveryAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember?.MgNr == mgnr);
} }
public static AreaComAdminWindow FocusMemberAreaComs(int mgnr) { public static AreaCmtAdminWindow FocusMemberAreaCmts(int mgnr) {
return FocusWindow<AreaComAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember.MgNr == mgnr); return FocusWindow<AreaCmtAdminWindow>(() => new(mgnr), w => w.ViewModel.FilterMember.MgNr == mgnr);
} }
public static BaseDataWindow FocusBaseData() { public static BaseDataWindow FocusBaseData() {
return FocusWindow<BaseDataWindow>(() => new()); return FocusWindow<BaseDataWindow>(() => new());
} }
public static BaseDataWindow FocusBaseDataAreaComType() { public static BaseDataWindow FocusBaseDataAreaCmtType() {
var w = FocusBaseData(); var w = FocusBaseData();
w.AreaCommitmentTypes.Focus(); w.AreaCommitmentTypes.Focus();
return w; return w;
+1 -1
View File
@@ -122,7 +122,7 @@ namespace Elwig.Controls {
private void OnSelectionChanged(object sender, SelectionChangedEventArgs evt) { private void OnSelectionChanged(object sender, SelectionChangedEventArgs evt) {
SelectItemsReverse(); SelectItemsReverse();
var dmp = !string.IsNullOrEmpty(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrEmpty(DisplayMemberPath) ? DisplayMemberPath : null; var dmp = !string.IsNullOrWhiteSpace(ListDisplayMemberPath) ? ListDisplayMemberPath : !string.IsNullOrWhiteSpace(DisplayMemberPath) ? DisplayMemberPath : null;
if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) { if (SelectedItems.Count == ItemsSource.Cast<object>().Count() && AllItemsSelectedContent != null) {
AllItemsSelected = true; AllItemsSelected = true;
} else if (SelectedItems.Count == 0) { } else if (SelectedItems.Count == 0) {
@@ -1,4 +1,4 @@
<Window x:Class="Elwig.Dialogs.AreaComModifyDialog" <Window x:Class="Elwig.Dialogs.AreaCmtModifyDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -4,7 +4,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
namespace Elwig.Dialogs { namespace Elwig.Dialogs {
public partial class AreaComModifyDialog : Window { public partial class AreaCmtModifyDialog : Window {
public int? YearTo { get; set; } public int? YearTo { get; set; }
public bool ModifyRetroactively { get; set; } public bool ModifyRetroactively { get; set; }
@@ -13,7 +13,7 @@ namespace Elwig.Dialogs {
public string OrigYearTo { get; set; } public string OrigYearTo { get; set; }
public string Area { get; set; } public string Area { get; set; }
public AreaComModifyDialog(int? yearFrom, int? yearTo, int area, bool delete, bool forceRetroactive = false) { public AreaCmtModifyDialog(int? yearFrom, int? yearTo, int area, bool delete, bool forceRetroactive = false) {
Area = $"{area:N0}"; Area = $"{area:N0}";
OrigYearFrom = $"{yearFrom}"; OrigYearFrom = $"{yearFrom}";
OrigYearTo = $"{yearTo}"; OrigYearTo = $"{yearTo}";
@@ -1,4 +1,4 @@
<Window x:Class="Elwig.Dialogs.AreaComTransferDialog" <Window x:Class="Elwig.Dialogs.AreaCmtTransferDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -35,11 +35,11 @@
<TextBlock x:Name="QuestionBlock1" TextAlignment="Center" Margin="0,10,0,0" <TextBlock x:Name="QuestionBlock1" TextAlignment="Center" Margin="0,10,0,0"
HorizontalAlignment="Center" VerticalAlignment="Top"> HorizontalAlignment="Center" VerticalAlignment="Top">
Sollen die aktiven Flächenbindungen des angegebenen Vorgängers<LineBreak/> Sollen die aktiven Flächenbindungen des angegebenen Vorgängers<LineBreak/>
übernommen werden? (<Run Text="{Binding AreaComNum}"/> FB, <Run Text="{Binding Area}"/> m²) übernommen werden? (<Run Text="{Binding AreaCmtNum}"/> FB, <Run Text="{Binding Area}"/> m²)
</TextBlock> </TextBlock>
<TextBlock x:Name="QuestionBlock2" TextAlignment="Center" Margin="0,10,0,0" Visibility="Hidden" <TextBlock x:Name="QuestionBlock2" TextAlignment="Center" Margin="0,10,0,0" Visibility="Hidden"
HorizontalAlignment="Center" VerticalAlignment="Top"> HorizontalAlignment="Center" VerticalAlignment="Top">
Sollen die aktiven Flächenbindungen gekündigt werden? (<Run Text="{Binding AreaComNum}"/> FB, <Run Text="{Binding Area}"/> m²) Sollen die aktiven Flächenbindungen gekündigt werden? (<Run Text="{Binding AreaCmtNum}"/> FB, <Run Text="{Binding Area}"/> m²)
</TextBlock> </TextBlock>
<Label x:Name="SeasonLabel" Content="Saison:" Margin="0,50,100,0" <Label x:Name="SeasonLabel" Content="Saison:" Margin="0,50,100,0"
@@ -3,17 +3,17 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
namespace Elwig.Dialogs { namespace Elwig.Dialogs {
public partial class AreaComTransferDialog : Window { public partial class AreaCmtTransferDialog : Window {
public int CancelSeason => SuccessorSeason - 1; public int CancelSeason => SuccessorSeason - 1;
public int SuccessorSeason { get; set; } public int SuccessorSeason { get; set; }
public string AreaComNum { get; set; } public string AreaCmtNum { get; set; }
public string Area { get; set; } public string Area { get; set; }
public AreaComTransferDialog(string name, int areaComNum, int area) { public AreaCmtTransferDialog(string name, int areaCmtNum, int area) {
SuccessorSeason = Utils.FollowingSeason; SuccessorSeason = Utils.FollowingSeason;
AreaComNum = $"{areaComNum:N0}"; AreaCmtNum = $"{areaCmtNum:N0}";
Area = $"{area:N0}"; Area = $"{area:N0}";
InitializeComponent(); InitializeComponent();
SeasonInput.Text = $"{SuccessorSeason}"; SeasonInput.Text = $"{SuccessorSeason}";
@@ -28,9 +28,9 @@ namespace Elwig.Dialogs {
SeasonLabel.Margin = new(0, 40, 100, 0); SeasonLabel.Margin = new(0, 40, 100, 0);
} }
public AreaComTransferDialog(string name, string successorName, int areaComNum, int area) { public AreaCmtTransferDialog(string name, string successorName, int areaCmtNum, int area) {
SuccessorSeason = Utils.FollowingSeason; SuccessorSeason = Utils.FollowingSeason;
AreaComNum = $"{areaComNum:N0}"; AreaCmtNum = $"{areaCmtNum:N0}";
Area = $"{area:N0}"; Area = $"{area:N0}";
InitializeComponent(); InitializeComponent();
SeasonInput.Text = $"{SuccessorSeason}"; SeasonInput.Text = $"{SuccessorSeason}";
+2 -2
View File
@@ -47,9 +47,9 @@
TextChanged="NameInput_TextChanged"/> TextChanged="NameInput_TextChanged"/>
<Label Content="Beim Löschen müssen folgende Daten auch gelöscht werden:" Margin="10,120,10,10"/> <Label Content="Beim Löschen müssen folgende Daten auch gelöscht werden:" Margin="10,120,10,10"/>
<CheckBox x:Name="AreaComInput" Content="Flächenbindungen" Margin="40,145,0,0" <CheckBox x:Name="AreaCmtInput" Content="Flächenbindungen" Margin="40,145,0,0"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed" Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
IsChecked="{Binding DeleteAreaComs}"/> IsChecked="{Binding DeleteAreaCmts}"/>
<CheckBox x:Name="DeliveryInput" Content="Lieferungen" Margin="40,165,0,0" <CheckBox x:Name="DeliveryInput" Content="Lieferungen" Margin="40,165,0,0"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed" Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"
IsChecked="{Binding DeleteDeliveries}"/> IsChecked="{Binding DeleteDeliveries}"/>
+6 -6
View File
@@ -8,16 +8,16 @@ namespace Elwig.Dialogs {
protected string[] NameParts; protected string[] NameParts;
public bool DeleteAreaComs { get; set; } public bool DeleteAreaCmts { get; set; }
public bool DeleteDeliveries { get; set; } public bool DeleteDeliveries { get; set; }
public bool DeletePaymentData { get; set; } public bool DeletePaymentData { get; set; }
public DeleteMemberDialog(int mgnr, string name, int numAreaComs, int numDeliveries, int numCredits) { public DeleteMemberDialog(int mgnr, string name, int numAreaCmts, int numDeliveries, int numCredits) {
NameParts = name.ToLower().Split(' ').Where(p => p.Length > 0).Append($"{mgnr}").ToArray(); NameParts = name.ToLower().Split(' ').Where(p => p.Length > 0).Append($"{mgnr}").ToArray();
InitializeComponent(); InitializeComponent();
Title += " - " + name; Title += " - " + name;
AreaComInput.IsEnabled = numAreaComs != 0; AreaCmtInput.IsEnabled = numAreaCmts != 0;
AreaComInput.Content += $" ({numAreaComs:N0})"; AreaCmtInput.Content += $" ({numAreaCmts:N0})";
DeliveryInput.IsEnabled = numDeliveries != 0; DeliveryInput.IsEnabled = numDeliveries != 0;
DeliveryInput.Content += $" ({numDeliveries:N0})"; DeliveryInput.Content += $" ({numDeliveries:N0})";
PaymentInput.IsEnabled = numCredits != 0; PaymentInput.IsEnabled = numCredits != 0;
@@ -43,7 +43,7 @@ namespace Elwig.Dialogs {
private void Update() { private void Update() {
var t = NameInput.Text.ToLower().Split(' '); var t = NameInput.Text.ToLower().Split(' ');
var nameValid = NameParts.All(t.Contains); var nameValid = NameParts.All(t.Contains);
UpdateCheckBox(AreaComInput); UpdateCheckBox(AreaCmtInput);
UpdateCheckBox(DeliveryInput); UpdateCheckBox(DeliveryInput);
UpdateCheckBox(PaymentInput); UpdateCheckBox(PaymentInput);
if (!nameValid) { if (!nameValid) {
@@ -52,7 +52,7 @@ namespace Elwig.Dialogs {
ControlUtils.ClearInputState(NameInput); ControlUtils.ClearInputState(NameInput);
} }
ConfirmButton.IsEnabled = ConfirmButton.IsEnabled =
(!AreaComInput.IsEnabled || DeleteAreaComs) && (!AreaCmtInput.IsEnabled || DeleteAreaCmts) &&
(!DeliveryInput.IsEnabled || DeleteDeliveries) && (!DeliveryInput.IsEnabled || DeleteDeliveries) &&
(!PaymentInput.IsEnabled || DeletePaymentData) && (!PaymentInput.IsEnabled || DeletePaymentData) &&
nameValid; nameValid;
+1 -1
View File
@@ -74,7 +74,7 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Part.DPNr}" Width="20" TextAlignment="Right" <TextBlock Text="{Binding Part.DPNr}" Width="20" TextAlignment="Right"
VerticalAlignment="Center" Margin="0,0,5,0" FontSize="14"/> VerticalAlignment="Center" Margin="0,0,5,0" FontSize="14"/>
<TextBlock Text="{Binding Part.SortId}" Width="40" TextAlignment="Center" <TextBlock Text="{Binding Part.SortId}" Foreground="{Binding Part.Variety.Color}" Width="40" TextAlignment="Center"
VerticalAlignment="Center" Margin="0,0,0,0" FontSize="14"/> VerticalAlignment="Center" Margin="0,0,0,0" FontSize="14"/>
<TextBlock Text="{Binding Part.Kmw, StringFormat='{}{0:N1}°'}" Width="40" TextAlignment="Right" Padding="0,0,10,0" <TextBlock Text="{Binding Part.Kmw, StringFormat='{}{0:N1}°'}" Width="40" TextAlignment="Right" Padding="0,0,10,0"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -2,7 +2,6 @@ using Elwig.Helpers;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Elwig.Windows; using Elwig.Windows;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
+4 -4
View File
@@ -1,9 +1,11 @@
<Window x:Class="Elwig.Dialogs.ManualWeighingDialog" <Window x:Class="Elwig.Dialogs.ManualWeighingDialog"
AutomationProperties.AutomationId="ManualWeighingDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Dialogs" xmlns:local="clr-namespace:Elwig.Dialogs"
xmlns:ctrl="clr-namespace:Elwig.Controls"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True" ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=WeightInput}" FocusManager.FocusedElement="{Binding ElementName=WeightInput}"
@@ -37,11 +39,9 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Content="Gewicht:" Margin="10,20,10,10"/> <Label Content="Gewicht:" Margin="10,20,10,10"/>
<Grid Grid.Column="1" Width="70" Height="25" Margin="0,20,10,10" HorizontalAlignment="Left" VerticalAlignment="Top"> <ctrl:UnitTextBox x:Name="WeightInput" Grid.Column="1" Width="62" Height="25" Unit="kg"
<TextBox x:Name="WeightInput" TextAlignment="Right" Padding="2,2,17,2" Margin="0,20,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="14" Padding="2"
TextChanged="WeightInput_TextChanged"/> TextChanged="WeightInput_TextChanged"/>
<Label Content="kg" Margin="0,4,3,0" HorizontalAlignment="Right" FontSize="10" Padding="2,4,2,4"/>
</Grid>
<Label Content="Grund:" Margin="10,50,10,10"/> <Label Content="Grund:" Margin="10,50,10,10"/>
<TextBox x:Name="ReasonInput" Grid.Column="1" Margin="0,50,10,10" <TextBox x:Name="ReasonInput" Grid.Column="1" Margin="0,50,10,10"
+2 -2
View File
@@ -6,8 +6,8 @@ using System.Windows.Controls;
namespace Elwig.Dialogs { namespace Elwig.Dialogs {
public partial class ManualWeighingDialog : Window { public partial class ManualWeighingDialog : Window {
public int Weight = 0; public int Weight { get; private set; }
public string? Reason = null; public string? Reason { get; private set; }
public ManualWeighingDialog(string? reason = null) { public ManualWeighingDialog(string? reason = null) {
InitializeComponent(); InitializeComponent();
+55
View File
@@ -0,0 +1,55 @@
<Window x:Class="Elwig.Dialogs.TareCorrectionDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Elwig.Dialogs"
xmlns:ctrl="clr-namespace:Elwig.Controls"
ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True"
WindowStartupLocation="CenterOwner"
FocusManager.FocusedElement="{Binding ElementName=TargetTareInput}"
Title="Tara korrigieren" Height="170" Width="300">
<Window.Resources>
<Style TargetType="Label">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Padding" Value="2,4,2,4"/>
<Setter Property="Height" Value="25"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Label Content="Ist-Tara:" Margin="10,20,0,10" Grid.Column="1"/>
<ctrl:UnitTextBox x:Name="ActualTareInput" Grid.Column="2" Width="56" Height="25" Unit="kg" IsReadOnly="True"
Margin="0,20,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="14" Padding="2"
TextChanged="TareInput_TextChanged"/>
<Label Content="Soll-Tara:" Margin="10,50,0,10" Grid.Column="1"/>
<ctrl:UnitTextBox x:Name="TargetTareInput" Grid.Column="2" Width="56" Height="25" Unit="kg"
Margin="0,50,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="14" Padding="2"
TextChanged="TareInput_TextChanged"/>
<Button x:Name="ConfirmButton" Content="Bestätigen" Margin="10,10,115,10" Grid.ColumnSpan="3" IsEnabled="False" IsDefault="True"
Click="ConfirmButton_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Margin="10,10,10,10" Grid.ColumnSpan="3" IsCancel="True"/>
</Grid>
</Window>
@@ -0,0 +1,38 @@
using Elwig.Helpers;
using System.Windows;
using System.Windows.Controls;
namespace Elwig.Dialogs {
public partial class TareCorrectionDialog : Window {
public int ActualTare { get; private set; }
public int TargetTare { get; private set; }
public TareCorrectionDialog(int? actualTare, int? targetTare = null) {
InitializeComponent();
ActualTare = actualTare ?? 0;
TargetTare = targetTare ?? actualTare ?? 0;
ActualTareInput.IsReadOnly = actualTare != null;
ActualTareInput.Text = ActualTare.ToString();
TargetTareInput.Text = TargetTare.ToString();
TargetTareInput.SelectAll();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
DialogResult = true;
ActualTare = int.Parse(ActualTareInput.Text);
TargetTare = int.Parse(TargetTareInput.Text);
Close();
}
private void UpdateButtons() {
ConfirmButton.IsEnabled = ActualTareInput.Text.Length > 0 && TargetTareInput.Text.Length > 0;
}
private void TareInput_TextChanged(object sender, TextChangedEventArgs evt) {
if (sender is not TextBox b) return;
Validator.CheckInteger(b, true, 4);
UpdateButtons();
}
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Services;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@@ -51,7 +52,7 @@ namespace Elwig.Dialogs {
File.Delete(fileName); File.Delete(fileName);
return; return;
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException(exc);
} }
Process.Start(fileName); Process.Start(fileName);
DialogResult = true; DialogResult = true;
+54 -21
View File
@@ -18,6 +18,7 @@ namespace Elwig.Documents {
public class BusinessDocument : Document { public class BusinessDocument : Document {
public Member Member; public Member Member;
public MemberHistoryPoint MemberShares;
public string? Location; public string? Location;
public bool IncludeSender = false; public bool IncludeSender = false;
public bool UseBillingAddress = false; public bool UseBillingAddress = false;
@@ -29,28 +30,33 @@ namespace Elwig.Documents {
get { get {
IAddress addr = (Member.BillingAddress != null && UseBillingAddress) ? Member.BillingAddress : Member; IAddress addr = (Member.BillingAddress != null && UseBillingAddress) ? Member.BillingAddress : Member;
var plz = addr.PostalDest.AtPlz; var plz = addr.PostalDest.AtPlz;
return string.Join("\n", ((string?[])[Member.BillingAddress?.FullName, Member.AdministrativeName, Member.ForTheAttentionOf, addr.Address, $"{plz?.Plz} {plz?.Ort.Name.Split(",")[0]}", addr.PostalDest.Country.Name]).Where(s => !string.IsNullOrWhiteSpace(s))); return string.Join("\n", ((string?[])[addr is BillingAddr ? addr.FullName : null, Member.AdministrativeName, Member.ForTheAttentionOf, addr.Address, $"{plz?.Plz} {plz?.Ort.Name.Split(",")[0]}", addr.PostalDest.Country.Name]).Where(s => !string.IsNullOrWhiteSpace(s)));
} }
} }
protected Cell NewAsideCell(Paragraph text, int colspan = 1, bool isName = false) { protected Cell NewAsideCell(Paragraph text, int colspan = 1, bool isName = false, bool right = false, bool center = false) {
var cell = NewCell(text, colspan: colspan).SetPaddingsMM(0.25f, 0.5f, 0.25f, isName ? 1 : 0); var cell = NewCell(text, colspan: colspan).SetPaddingsMM(0.25f, center ? 0 : right ? 1 : 0.5f, 0.25f, isName ? 1 : 0);
if (colspan > 1) { if (colspan == 26) {
cell.SetTextAlignment(TextAlignment.CENTER).SetFont(BF) cell.SetTextAlignment(TextAlignment.CENTER).SetFont(BF)
.SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)) .SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0))
.SetBorderTop(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness)) .SetBorderTop(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness))
.SetPaddingsMM(0.5f, 1, 0.5f, 1); .SetPaddingsMM(0.5f, 1, 0.5f, 1);
} else if (isName) {
cell.SetBorderLeft(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness));
} }
if (right) cell.SetTextAlignment(TextAlignment.RIGHT);
if (center) cell.SetTextAlignment(TextAlignment.CENTER);
return cell; return cell;
} }
protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false) { protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false, bool right = false, bool center = false) {
return NewAsideCell(new KernedParagraph(text, 10), colspan, isName); return NewAsideCell(new KernedParagraph(text, 10), colspan, isName, right, center);
} }
public BusinessDocument(string title, Member m, DateOnly? dateFrom, bool includeSender = false) : public BusinessDocument(string title, Member m, DateOnly? dateFrom, bool includeSender = false) :
base(title) { base(title) {
Member = m; Member = m;
MemberShares = new(m.Shares, m.SharesRed, m.SharesWhite, m.SharesDormant);
Location = App.BranchLocation; Location = App.BranchLocation;
IncludeSender = includeSender; IncludeSender = includeSender;
DateFrom = dateFrom; DateFrom = dateFrom;
@@ -58,16 +64,27 @@ namespace Elwig.Documents {
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf); base.BeforeRenderBody(doc, pdf);
var uid = new KernedParagraph(Member.UstIdNr ?? "-", 10); Aside = new Table(ColsMM(2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5))
if (!Member.IsBuchführend) uid.Add(Normal(" ")).Add(Italic("(pauschaliert)")); .SetWidth(UnitValue.CreatePointValue(65 * PtInMM)).SetFixedLayout()
Aside = new Table(ColsMM(22.5, 42.5))
.SetFont(NF).SetFontSize(10)
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
.SetFont(NF).SetFontSize(10)
.SetBorder(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness)) .SetBorder(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness))
.AddCell(NewAsideCell("Mitglied", 2)) .AddCell(NewAsideCell("Mitglied", 26));
.AddCell(NewAsideCell("Mitglieds-Nr.:", isName: true)).AddCell(NewAsideCell($"{Member.MgNr}")) if (Member.IsOrganic) {
.AddCell(NewAsideCell("Betriebs-Nr.:", isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "")) Aside
.AddCell(NewAsideCell("UID:", isName: true)).AddCell(NewAsideCell(uid)); .AddCell(NewAsideCell("Mitgl.-Nr.:", 8, isName: true)).AddCell(NewAsideCell($"{Member.MgNr}", 4, right: true))
.AddCell(NewAsideCell("Bio-KSt.:", 6, isName: true)).AddCell(NewAsideCell(Member.OrganicAuthorityCode ?? "-", 8, right: true, center: Member.OrganicAuthorityCode == null))
.AddCell(NewAsideCell("Betr.-Nr.:", 6, isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "-", 6, right: true, center: Member.LfbisNr == null))
.AddCell(NewAsideCell("OOC-ID:", 6, isName: true)).AddCell(NewAsideCell(Member.OrganicOperatorId ?? "-", 8, right: true, center: Member.OrganicOperatorId == null))
.AddCell(NewAsideCell("UID:", 6, isName: true)).AddCell(NewAsideCell(Member.UstIdNr ?? "-", 10, right: true, center: Member.UstIdNr == null))
.AddCell(NewAsideCell(new KernedParagraph("", 10).Add(Italic(Member.IsAccounting ? "(buchführend)" : "(pauschaliert)")), 10, center: true));
} else {
Aside
.AddCell(NewAsideCell("Mitglieds-Nr.:", 12, isName: true)).AddCell(NewAsideCell($"{Member.MgNr}", 4, right: true)).AddCell(NewAsideCell("", 10))
.AddCell(NewAsideCell("Betriebs-Nr.:", 10, isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "-", 6, right: true, center: Member.LfbisNr == null)).AddCell(NewAsideCell("", 10))
.AddCell(NewAsideCell("UID:", 6, isName: true)).AddCell(NewAsideCell(Member.UstIdNr ?? "-", 10, right: true, center: Member.UstIdNr == null))
.AddCell(NewAsideCell(new KernedParagraph("", 10).Add(Italic(Member.IsAccounting ? "(buchführend)" : "(pauschaliert)")), 10, center: true));
}
} }
protected void RenderAddress(Canvas canvas, Rectangle pageSize) { protected void RenderAddress(Canvas canvas, Rectangle pageSize) {
@@ -120,11 +137,15 @@ namespace Elwig.Documents {
return new KernedParagraph(10) return new KernedParagraph(10)
.AddAll(Utils.GenerateFooter("\n", " \u00b7 ") .AddAll(Utils.GenerateFooter("\n", " \u00b7 ")
.Item(c.NameFull).NextLine() .Item(c.NameFull).NextLine()
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine() .Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").NextGroup()
.Item(c.EmailAddress != null ? link1 : null) .Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextGroup()
.Item(c.Website != null ? link2 : null) .Item(c.EmailAddress != null ? link1 : null).NextLine()
.Item("Betriebs-Nr.", c.LfbisNr).Item("Bio-KSt.", c.OrganicAuthority).NextLine() .Item(c.Website != null ? link2 : null).NextGroup()
.Item("Betriebs-Nr.", c.LfbisNr).NextGroup()
.Item("Bio-KSt.", c.OrganicAuthorityCode).NextGroup()
.Item("OOC-ID", c.OrganicOperatorId).NextLine()
.Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban) .Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban)
.RebalanceLines()
.ToLeafElements()); .ToLeafElements());
} }
@@ -263,7 +284,7 @@ namespace Elwig.Documents {
} }
protected Table NewBucketTable( protected Table NewBucketTable(
Season season, Dictionary<string, MemberBucket> buckets, int deliveredWeight, Season season, Dictionary<string, MemberBucket> buckets, int deliveredWeightRed, int deliveredWeightWhite,
bool includeDelivery = true, bool includePayment = false, bool includeDelivery = true, bool includePayment = false,
bool isTiny = false, IEnumerable<string>? filter = null bool isTiny = false, IEnumerable<string>? filter = null
) { ) {
@@ -316,9 +337,21 @@ namespace Elwig.Documents {
.Where(b => !fbVars.Contains(b.Key)) .Where(b => !fbVars.Contains(b.Key))
.OrderBy(b => b.Value.Name); .OrderBy(b => b.Value.Name);
if (MemberShares.Shares != 0 || (MemberShares.SharesRed == 0 && MemberShares.SharesWhite == 0)) {
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny)); tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny));
tbl.AddCells(FormatRow(Member.BusinessShares * season.MinKgPerBusinessShare, Member.BusinessShares * season.MaxKgPerBusinessShare, tbl.AddCells(FormatRow(MemberShares.Shares * (season.MinKgPerShare ?? 0), MemberShares.Shares * (season.MaxKgPerShare ?? 0),
deliveredWeight, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny)); deliveredWeightRed + deliveredWeightWhite, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
}
if (MemberShares.SharesRed != 0 || MemberShares.SharesWhite != 0) {
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA (rot)", isTiny: isTiny));
tbl.AddCells(FormatRow(MemberShares.SharesRed * (season.MinKgPerShareRed ?? season.MinKgPerShare ?? 0), MemberShares.SharesRed * (season.MaxKgPerShareRed ?? season.MaxKgPerShare ?? 0),
deliveredWeightRed, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA (weiß)", isTiny: isTiny));
tbl.AddCells(FormatRow(MemberShares.SharesWhite * (season.MinKgPerShareWhite ?? season.MinKgPerShare ?? 0), MemberShares.SharesWhite * (season.MaxKgPerShareWhite ?? season.MaxKgPerShare ?? 0),
deliveredWeightWhite, isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny));
}
if (fbs.Any()) { if (fbs.Any()) {
tbl.AddCell(NewBucketSubHdr("Flächenbindungen" + (vtr.Any() ? " (inkl. Verträge)" : "") + ":", includePayment ? 8 : 7, isTiny: isTiny)); tbl.AddCell(NewBucketSubHdr("Flächenbindungen" + (vtr.Any() ? " (inkl. Verträge)" : "") + ":", includePayment ? 8 : 7, isTiny: isTiny));
+19 -14
View File
@@ -86,19 +86,24 @@ namespace Elwig.Documents {
} }
if (ConsiderTotalPenalty) { if (ConsiderTotalPenalty) {
var total = _data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value); var weights = _data.Rows.Select(r => new {r.Type, Weight = r.Buckets.Sum(b => b.Value)}).GroupBy(r => r.Type).ToDictionary(r => r.Key, g => g.Sum(r => r.Weight));
var totalUnderDelivery = total - Member.BusinessShares * season.MinKgPerBusinessShare; var red = weights.GetValueOrDefault("R", 0);
MemberTotalUnderDelivery = totalUnderDelivery < 0 ? totalUnderDelivery * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerBsAmount * Math.Floor(-(decimal)totalUnderDelivery / season.MinKgPerBusinessShare) ?? 0) : 0; var white = weights.GetValueOrDefault("W", 0);
var total = red + white;
var underDeliveryTotal = total - Member.Shares * (season.MinKgPerShare ?? 0);
var underDeliveryRed = red - Member.SharesRed * (season.MinKgPerShareRed ?? season.MinKgPerShare ?? 0);
var underDeliveryWhite = white - Member.SharesWhite * (season.MinKgPerShareWhite ?? season.MinKgPerShare ?? 0);
MemberTotalUnderDelivery =
(underDeliveryTotal < 0 ? underDeliveryTotal * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerShareAmount * Math.Floor(-(decimal)underDeliveryTotal / (season.MinKgPerShare ?? 0)) ?? 0) : 0) +
(underDeliveryRed < 0 ? underDeliveryRed * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerShareAmount * Math.Floor(-(decimal)underDeliveryRed / (season.MinKgPerShareRed ?? season.MinKgPerShare ?? 0)) ?? 0) : 0) +
(underDeliveryWhite < 0 ? underDeliveryWhite * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) - (season.PenaltyPerShareAmount * Math.Floor(-(decimal)underDeliveryWhite / (season.MinKgPerShareWhite ?? season.MinKgPerShare ?? 0)) ?? 0) : 0);
if (total == 0) if (total == 0)
MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerBsNone * Member.BusinessShares ?? 0); MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0) + (season.PenaltyPerShareNone * (Member.Shares + Member.SharesRed + Member.SharesWhite) ?? 0);
} }
if (ConsiderAutoBusinessShares) { if (ConsiderAutoBusinessShares) {
var fromDate = $"{season.Year}-01-01";
var toDate = $"{season.Year}-12-31";
MemberAutoBusinessShares = await ctx.MemberHistory MemberAutoBusinessShares = await ctx.MemberHistory
.Where(h => h.MgNr == Member.MgNr && h.Type == "auto") .Where(h => h.ToMgNr == Member.MgNr && h.DeductYear == season.Year)
.Where(h => h.DateString.CompareTo(fromDate) >= 0 && h.DateString.CompareTo(toDate) <= 0) .SumAsync(h => h.Shares);
.SumAsync(h => h.BusinessShares);
MemberAutoBusinessSharesAmount = MemberAutoBusinessShares * (-season.BusinessShareValue ?? 0); MemberAutoBusinessSharesAmount = MemberAutoBusinessShares * (-season.BusinessShareValue ?? 0);
} }
if (ConsiderContractPenalties) { if (ConsiderContractPenalties) {
@@ -120,10 +125,10 @@ namespace Elwig.Documents {
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf); base.BeforeRenderBody(doc, pdf);
Aside?.AddCell(NewAsideCell("Gutschrift", 2)) Aside?.AddCell(NewAsideCell("Gutschrift", 26))
.AddCell(NewAsideCell("TG-Nr.:", isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-")) .AddCell(NewAsideCell("Tr.-Gutschr.-Nr.:", 12, isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-", 14))
.AddCell(NewAsideCell("Datum:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}")) .AddCell(NewAsideCell("Datum:", 12, isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}", 14))
.AddCell(NewAsideCell("Überw. am:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}")); .AddCell(NewAsideCell("Überwiesen am:", 12, isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}", 14));
} }
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
@@ -197,7 +202,7 @@ namespace Elwig.Documents {
penalty += MemberTotalUnderDelivery; penalty += MemberTotalUnderDelivery;
} }
if (MemberAutoBusinessSharesAmount != 0) { if (MemberAutoBusinessSharesAmount != 0) {
tbl2.AddCells(FormatRow($"Autom. Nachz. von GA ({MemberAutoBusinessShares})", MemberAutoBusinessSharesAmount, add: true)); tbl2.AddCells(FormatRow($"Nachzeichnung von GA ({MemberAutoBusinessShares})", MemberAutoBusinessSharesAmount, add: true));
penalty += MemberAutoBusinessSharesAmount; penalty += MemberAutoBusinessSharesAmount;
} }
if (CustomPayment?.Amount != null) { if (CustomPayment?.Amount != null) {
+14 -9
View File
@@ -18,7 +18,8 @@ namespace Elwig.Documents {
private readonly int _year; private readonly int _year;
public Season? Season; public Season? Season;
public int MemberDeliveredWeight; public int MemberDeliveredWeightRed;
public int MemberDeliveredWeightWhite;
public DeliveryConfirmationDeliveryData? Data; public DeliveryConfirmationDeliveryData? Data;
public string? Text = App.Client.TextDeliveryConfirmation; public string? Text = App.Client.TextDeliveryConfirmation;
public Dictionary<string, MemberBucket> MemberBuckets = []; public Dictionary<string, MemberBucket> MemberBuckets = [];
@@ -29,17 +30,21 @@ namespace Elwig.Documents {
_year = year; _year = year;
ShowDateAndLocation = true; ShowDateAndLocation = true;
UseBillingAddress = true; UseBillingAddress = true;
DocumentId = $"Anl.-Best. {_year}/{m.MgNr}"; DocumentId = $"Anlfrg.-Bstng. {_year}/{m.MgNr}";
Data = data; Data = data;
} }
protected override async Task LoadData(AppDbContext ctx) { protected override async Task LoadData(AppDbContext ctx) {
await base.LoadData(ctx); await base.LoadData(ctx);
Season = await ctx.FetchSeasons(_year).SingleOrDefaultAsync() ?? throw new ArgumentException("Invalid season"); Season = await ctx.FetchSeasons(_year).SingleOrDefaultAsync() ?? throw new ArgumentException("Invalid season");
MemberDeliveredWeight = await ctx.Deliveries var weights = await ctx.Deliveries
.Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr) .Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr)
.SelectMany(d => d.Parts) .SelectMany(d => d.Parts)
.SumAsync(p => p.Weight); .GroupBy(p => p.Variety.Type)
.ToDictionaryAsync(g => g.Key, g => g.Sum(p => p.Weight));
MemberDeliveredWeightRed = weights.GetValueOrDefault("R", 0);
MemberDeliveredWeightWhite = weights.GetValueOrDefault("W", 0);
MemberShares = await ctx.GetMemberHistory(Season.Year, Member.MgNr);
MemberBuckets = await ctx.GetMemberBuckets(Season.Year, Member.MgNr); MemberBuckets = await ctx.GetMemberBuckets(Season.Year, Member.MgNr);
MemberStats = await AppDbContext.GetMemberStats(Season.Year, Member.MgNr); MemberStats = await AppDbContext.GetMemberStats(Season.Year, Member.MgNr);
Data ??= await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, Season.Year, Member); Data ??= await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, Season.Year, Member);
@@ -50,10 +55,10 @@ namespace Elwig.Documents {
base.BeforeRenderBody(doc, pdf); base.BeforeRenderBody(doc, pdf);
var firstDay = Data.Rows.MinBy(r => r.Date)?.Date; var firstDay = Data.Rows.MinBy(r => r.Date)?.Date;
var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date; var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date;
Aside?.AddCell(NewAsideCell("Saison", 2)) Aside?.AddCell(NewAsideCell("Saison", 26))
.AddCell(NewAsideCell("Lieferungen:", isName: true)).AddCell(NewAsideCell($"{Data.Rows.DistinctBy(r => r.LsNr).Count():N0} (Teil-Lfrg.: {Data.RowNum:N0})")) .AddCell(NewAsideCell("Lieferungen:", 12, isName: true)).AddCell(NewAsideCell($"{Data.Rows.DistinctBy(r => r.LsNr).Count():N0} (Teil-Lfrg.: {Data.RowNum:N0})", 14))
.AddCell(NewAsideCell("Zeitraum:", isName: true)).AddCell(NewAsideCell(firstDay == null || lastDay == null ? "-" : firstDay == lastDay ? $"{firstDay:dd.MM.} (1 Tag)" : $"{firstDay:dd.MM.}\u2013{lastDay:dd.MM.} ({lastDay?.DayNumber - firstDay?.DayNumber + 1:N0} Tage)")) .AddCell(NewAsideCell("Zeitraum:", 8, isName: true)).AddCell(NewAsideCell(firstDay == null || lastDay == null ? "-" : firstDay == lastDay ? $"{firstDay:dd.MM.} (1 Tag)" : $"{firstDay:dd.MM.}\u2013{lastDay:dd.MM.} ({lastDay?.DayNumber - firstDay?.DayNumber + 1:N0} Tage)", 18, center: true))
.AddCell(NewAsideCell("Menge:", isName: true)).AddCell(NewAsideCell($"{MemberStats.Sum(s => s.Weight):N0} kg")); .AddCell(NewAsideCell("Menge:", 12, isName: true)).AddCell(NewAsideCell($"{MemberStats.Sum(s => s.Weight):N0} kg", 14));
} }
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
@@ -63,7 +68,7 @@ namespace Elwig.Documents {
doc.Add(NewDeliveryListTable(Data)); doc.Add(NewDeliveryListTable(Data));
doc.Add(NewWeightsTable(MemberStats) doc.Add(NewWeightsTable(MemberStats)
.SetMarginTopMM(10).SetKeepTogether(true)); .SetMarginTopMM(10).SetKeepTogether(true));
doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeight, includePayment: true) doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, includePayment: true)
.SetMarginTopMM(10).SetKeepTogether(true)); .SetMarginTopMM(10).SetKeepTogether(true));
if (Text != null) { if (Text != null) {
+39 -22
View File
@@ -19,7 +19,8 @@ namespace Elwig.Documents {
public Delivery Delivery; public Delivery Delivery;
public string? Text; public string? Text;
public int MemberDeliveredWeight; public int MemberDeliveredWeightRed;
public int MemberDeliveredWeightWhite;
public Dictionary<string, MemberBucket> MemberBuckets = []; public Dictionary<string, MemberBucket> MemberBuckets = [];
// 0 - none // 0 - none
@@ -59,18 +60,22 @@ namespace Elwig.Documents {
protected override async Task LoadData(AppDbContext ctx) { protected override async Task LoadData(AppDbContext ctx) {
await base.LoadData(ctx); await base.LoadData(ctx);
MemberDeliveredWeight = await ctx.DeliveryParts var weights = await ctx.DeliveryParts
.Where(d => d.Year == Delivery.Year && d.Delivery.MgNr == Member.MgNr) .Where(d => d.Year == Delivery.Year && d.Delivery.MgNr == Member.MgNr)
.SumAsync(p => p.Weight); .GroupBy(p => p.Variety.Type)
.ToDictionaryAsync(g => g.Key, g => g.Sum(p => p.Weight));
MemberDeliveredWeightRed = weights.GetValueOrDefault("R", 0);
MemberDeliveredWeightWhite = weights.GetValueOrDefault("W", 0);
MemberShares = await ctx.GetMemberHistory(Delivery.Year, Member.MgNr);
MemberBuckets = await ctx.GetMemberBuckets(Delivery.Year, Member.MgNr) ?? []; MemberBuckets = await ctx.GetMemberBuckets(Delivery.Year, Member.MgNr) ?? [];
} }
protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) { protected override void BeforeRenderBody(iText.Layout.Document doc, PdfDocument pdf) {
base.BeforeRenderBody(doc, pdf); base.BeforeRenderBody(doc, pdf);
Aside?.AddCell(NewAsideCell("Lieferung", 2)) Aside?.AddCell(NewAsideCell("Lieferung", 26))
.AddCell(NewAsideCell("LS-Nr.:", isName: true)).AddCell(NewAsideCell(Delivery.LsNr)) .AddCell(NewAsideCell("Lieferschein-Nr.:", 12, isName: true)).AddCell(NewAsideCell(Delivery.LsNr, 14))
.AddCell(NewAsideCell("Datum/Zeit:", isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}")) .AddCell(NewAsideCell("Datum/Zeit:", 12, isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}", 14))
.AddCell(NewAsideCell("Zweigstelle:", isName: true)).AddCell(NewAsideCell(Delivery.Branch.Name)); .AddCell(NewAsideCell("Zweigstelle:", 12, isName: true)).AddCell(NewAsideCell(Delivery.Branch.Name, 14));
} }
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
@@ -81,7 +86,7 @@ namespace Elwig.Documents {
doc.Add(new KernedParagraph($"Anmerkung zur Lieferung: {Delivery.Comment}", 10).SetMarginsMM(5, 0, 0, 0)); doc.Add(new KernedParagraph($"Anmerkung zur Lieferung: {Delivery.Comment}", 10).SetMarginsMM(5, 0, 0, 0));
} }
if (DisplayStats > 0) { if (DisplayStats > 0) {
doc.Add(NewBucketTable(Delivery.Season, MemberBuckets, MemberDeliveredWeight, isTiny: true, doc.Add(NewBucketTable(Delivery.Season, MemberBuckets, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, isTiny: true,
filter: DisplayStats > 2 ? null : DisplayStats == 1 ? [] : Delivery.Parts.Select(p => p.SortId).Distinct().ToList()) filter: DisplayStats > 2 ? null : DisplayStats == 1 ? [] : Delivery.Parts.Select(p => p.SortId).Distinct().ToList())
.SetKeepTogether(true) .SetKeepTogether(true)
.SetMarginsMM(5, 0, 0, 0)); .SetMarginsMM(5, 0, 0, 0));
@@ -116,7 +121,7 @@ namespace Elwig.Documents {
} }
protected Table NewDeliveryTable() { protected Table NewDeliveryTable() {
var tbl = new Table(ColsMM(10, 21, 25, 19.5, 19.5, 30, 12.5, 12.5, 15), true) var tbl = new Table(ColsMM(10, 21, 25, 20, 20, 30, 12.5, 12.5, 14), true)
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
.SetBorder(Border.NO_BORDER).SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); .SetBorder(Border.NO_BORDER).SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
@@ -125,15 +130,16 @@ namespace Elwig.Documents {
.AddHeaderCell(NewTh("Attribut", colspan: 2, rowspan: 2, left: true)) .AddHeaderCell(NewTh("Attribut", colspan: 2, rowspan: 2, left: true))
.AddHeaderCell(NewTh("Qualitätsstufe", rowspan: 2, left: true)) .AddHeaderCell(NewTh("Qualitätsstufe", rowspan: 2, left: true))
.AddHeaderCell(NewTh("Gradation", colspan: 2)) .AddHeaderCell(NewTh("Gradation", colspan: 2))
.AddHeaderCell(NewTh("Menge")) .AddHeaderCell(NewTh("Menge").SetPaddingRight(0))
.AddHeaderCell(NewTh("[°Oe]", 8)) .AddHeaderCell(NewTh("[°Oe]", 8))
.AddHeaderCell(NewTh("[°KMW]", 8)) .AddHeaderCell(NewTh("[°KMW]", 8))
.AddHeaderCell(NewTh("[kg]", 8)); .AddHeaderCell(NewTh("[kg]", 8).SetPaddingRight(0));
foreach (var part in Delivery.Parts.OrderBy(p => p.DPNr)) { foreach (var part in Delivery.Parts.OrderBy(p => p.DPNr)) {
var sub = new Table(ColsMM(10, 21, 25, 19.5, 19.5, 30, 12.5, 12.5, 15), true) var sub = new Table(ColsMM(10, 21, 25, 20, 20, 30, 12.5, 12.5, 14), true)
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
.SetKeepTogether(true);
int rowspan = 1 + (part.Cultivation != null ? 1 : 0) + 1 + part.Modifiers.Count() + 1 + (part.Comment != null ? 1 : 0) + (part.Temperature != null || part.Acid != null ? 1 : 0); int rowspan = 1 + (part.Cultivation != null ? 1 : 0) + 1 + part.Modifiers.Count() + 1 + (part.Comment != null ? 1 : 0) + (part.Temperature != null || part.Acid != null ? 1 : 0);
sub.AddCell(NewDeliveryMainTd($"{part.DPNr:N0}", center: true)) sub.AddCell(NewDeliveryMainTd($"{part.DPNr:N0}", center: true))
@@ -142,11 +148,14 @@ namespace Elwig.Documents {
.AddCell(NewDeliveryMainTd(part.Quality.Name)) .AddCell(NewDeliveryMainTd(part.Quality.Name))
.AddCell(NewDeliveryMainTd($"{part.Oe:N0}", center: true)) .AddCell(NewDeliveryMainTd($"{part.Oe:N0}", center: true))
.AddCell(NewDeliveryMainTd($"{part.Kmw:N1}", center: true)) .AddCell(NewDeliveryMainTd($"{part.Kmw:N1}", center: true))
.AddCell(NewDeliveryMainTd($"{part.Weight:N0}", center: true)); .AddCell(NewDeliveryMainTd($"{part.Weight:N0}", right: true).SetPaddingRight(0));
if (part.Cultivation != null) { if (part.Cultivation != null) {
var cult = new KernedParagraph(8); var cult = new KernedParagraph(8);
cult.Add(Italic("Bewirtschaftung:")).Add(Normal(" " + part.Cultivation.Name + (part.Cultivation.Description != null ? $" ({part.Cultivation.Description})" : ""))); cult.Add(Italic("Bewirtschaftung:")).Add(Normal(" " + part.Cultivation.Name + (part.Cultivation.Description != null ? $" ({part.Cultivation.Description})" : "")));
if (part.Cultivation.IsOrganic) {
cult.Add(" \u2013 ").Add(Italic("Kontrollstelle:")).Add(Normal($" {Member.OrganicAuthorityCode ?? "?"}"));
}
sub.AddCell(NewTd()) sub.AddCell(NewTd())
.AddCell(NewTd(cult, colspan: 5)) .AddCell(NewTd(cult, colspan: 5))
.AddCell(NewTd(colspan: 3)); .AddCell(NewTd(colspan: 3));
@@ -176,8 +185,8 @@ namespace Elwig.Documents {
} }
} else { } else {
var info = part.WeighingInfo; var info = part.WeighingInfo;
weighing.Add(Italic("Waage:")) weighing.Add(Italic("Waage/Terminal:"))
.Add(Normal(" " + (part.ScaleId ?? "?") + ", ")) .Add(Normal(" " + (info.ScaleId ?? "?") + "/" + (part.ScaleTerminalId ?? "?") + ", "))
.Add(Italic("ID:")) .Add(Italic("ID:"))
.Add(Normal(" " + (info.Id ?? "?"))); .Add(Normal(" " + (info.Id ?? "?")));
if (info.Date != null || info.Time != null) if (info.Date != null || info.Time != null)
@@ -191,9 +200,17 @@ namespace Elwig.Documents {
.Add(Italic("Brutto:")) .Add(Italic("Brutto:"))
.Add(Normal($" {info.Gross:N0} kg \u2013 ")) .Add(Normal($" {info.Gross:N0} kg \u2013 "))
.Add(Italic("Tara:")) .Add(Italic("Tara:"))
.Add(Normal($" {info.Tare:N0} kg \u2013 ")) .Add(Normal($" {info.Tare:N0} kg "));
if (info.TareCorrection != null && info.TareCorrection != 0)
weighing.Add(Bold($"(Korr.: {Utils.GetSign(info.TareCorrection.Value)}{Math.Abs(info.TareCorrection.Value)} kg)")).Add(" ");
weighing
.Add(Normal("\u2013 "))
.Add(Italic("Netto:")) .Add(Italic("Netto:"))
.Add(Normal($" {info.Net:N0} kg \u2013 ")) .Add(Normal($" {info.Net:N0} kg "));
if (info.TareCorrection != null && info.TareCorrection != 0)
weighing.Add(Normal($"({Utils.GetSign(-info.TareCorrection.Value)}{Math.Abs(info.TareCorrection.Value)} kg) "));
weighing
.Add(Normal("\u2013 "))
.Add(Italic(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen")); .Add(Italic(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen"));
} else { } else {
weighing.Add(" ") weighing.Add(" ")
@@ -205,7 +222,7 @@ namespace Elwig.Documents {
.AddCell(NewCell(colspan: 3)); .AddCell(NewCell(colspan: 3));
if (part.Comment != null) { if (part.Comment != null) {
sub.AddCell(NewCell()) sub.AddCell(NewCell())
.AddCell(NewCell(new KernedParagraph(8).Add(Italic("Anmerkung")).Add(Normal(" " + part.Comment)), colspan: 5)) .AddCell(NewCell(new KernedParagraph(8).Add(Italic("Anmerkung:")).Add(Normal(" " + part.Comment)), colspan: 5))
.AddCell(NewCell(colspan: 3)); .AddCell(NewCell(colspan: 3));
} }
if (part.Temperature != null || part.Acid != null) { if (part.Temperature != null || part.Acid != null) {
@@ -215,7 +232,7 @@ namespace Elwig.Documents {
if (part.Temperature != null && part.Acid != null) if (part.Temperature != null && part.Acid != null)
p.Add(Normal(", ")); p.Add(Normal(", "));
if (part.Acid != null) if (part.Acid != null)
p.Add(Italic("Säure")).Add(Normal($" {part.Acid:N1} g/l")); p.Add(Italic("Säure:")).Add(Normal($" {part.Acid:N1} g/l"));
sub.AddCell(NewCell()) sub.AddCell(NewCell())
.AddCell(NewCell(p, colspan: 5)) .AddCell(NewCell(p, colspan: 5))
.AddCell(NewCell(colspan: 3)); .AddCell(NewCell(colspan: 3));
@@ -224,10 +241,10 @@ namespace Elwig.Documents {
} }
if (Delivery.Parts.Count > 1) { if (Delivery.Parts.Count > 1) {
tbl.AddCell(NewTd("Gesamt:", 12, bold: true, borderTop: true, colspan: 6).SetPaddingsMM(1, 1, 1, 1)) tbl.AddCell(NewTd("Gesamt:", 12, bold: true, borderTop: true, colspan: 6).SetPaddingsMM(1, 1, 1, 0))
.AddCell(NewTd($"{Delivery.Oe:N0}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)) .AddCell(NewTd($"{Delivery.Oe:N0}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1))
.AddCell(NewTd($"{Delivery.Kmw:N1}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)) .AddCell(NewTd($"{Delivery.Kmw:N1}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1))
.AddCell(NewTd($"{Delivery.Weight:N0}", 12, bold: true, right: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); .AddCell(NewTd($"{Delivery.Weight:N0}", 12, bold: true, right: true, borderTop: true).SetPaddingsMM(1, 0, 1, 1));
} }
return tbl; return tbl;
+2 -2
View File
@@ -57,7 +57,7 @@ namespace Elwig.Documents {
public Document(string title) { public Document(string title) {
Title = title; Title = title;
Author = App.Client.NameFull; Author = App.Client.NameFull;
Date = DateOnly.FromDateTime(Utils.Today); Date = DateOnly.FromDateTime(DateTime.Today);
} }
~Document() { ~Document() {
@@ -252,7 +252,7 @@ namespace Elwig.Documents {
public async Task Print(int copies = 1) { public async Task Print(int copies = 1) {
if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet"); if (PdfPath == null) throw new InvalidOperationException("Pdf file has not been generated yet");
await Pdf.Print(PdfPath, copies, IsDoublePaged); Pdf.Print(PdfPath, copies, IsDoublePaged);
} }
public void Show() { public void Show() {
+184 -24
View File
@@ -1,4 +1,5 @@
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Models;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using iText.Kernel.Colors; using iText.Kernel.Colors;
using iText.Kernel.Pdf; using iText.Kernel.Pdf;
@@ -18,9 +19,11 @@ namespace Elwig.Documents {
public new static string Name => "Stammdatenblatt"; public new static string Name => "Stammdatenblatt";
public Season? Season; public Season? Season;
public int MemberDeliveredWeight; public int MemberDeliveredWeightRed;
public int MemberDeliveredWeightWhite;
public List<MemberHistory> MemberHistory = [];
public Dictionary<string, MemberBucket> MemberBuckets = []; public Dictionary<string, MemberBucket> MemberBuckets = [];
public List<AreaCom> ActiveAreaCommitments = []; public List<AreaCmt> ActiveAreaCommitments = [];
public MemberDataSheet(Member m) : public MemberDataSheet(Member m) :
base($"{Name} {m.AdministrativeName}", m, DateOnly.FromDateTime(m.ModifiedAt)) { base($"{Name} {m.AdministrativeName}", m, DateOnly.FromDateTime(m.ModifiedAt)) {
@@ -30,35 +33,51 @@ namespace Elwig.Documents {
public static async Task<MemberDataSheet> Initialize(int mgnr) { public static async Task<MemberDataSheet> Initialize(int mgnr) {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
return new MemberDataSheet(await ctx.FetchMembers(mgnr, true, true).SingleAsync()); return new MemberDataSheet(await ctx.FetchMembers(mgnr, includeContactInfo: true).SingleAsync());
} }
protected override async Task LoadData(AppDbContext ctx) { protected override async Task LoadData(AppDbContext ctx) {
await base.LoadData(ctx); await base.LoadData(ctx);
Season = await ctx.FetchSeasons().FirstOrDefaultAsync() ?? throw new ArgumentException("Invalid season"); Season = await ctx.FetchSeasons().FirstOrDefaultAsync() ?? throw new ArgumentException("Invalid season");
MemberHistory = await ctx.MemberHistory
.Where(h => h.FromMgNr == Member.MgNr || h.ToMgNr == Member.MgNr)
.OrderBy(h => h.DateNoticeString).ThenBy(h => h.HistNr)
.ToListAsync();
MemberBuckets = await ctx.GetMemberBuckets(Utils.CurrentYear, Member.MgNr); MemberBuckets = await ctx.GetMemberBuckets(Utils.CurrentYear, Member.MgNr);
ActiveAreaCommitments = await Member.ActiveAreaCommitments(ctx) ActiveAreaCommitments = await Member.ActiveAreaCommitments(ctx)
.Include(c => c.Contract).ThenInclude(c => c.Revisions) .Include(c => c.Contract).ThenInclude(c => c.Revisions)
.ToListAsync(); .ToListAsync();
MemberDeliveredWeight = await ctx.Deliveries var weights = await ctx.Deliveries
.Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr) .Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr)
.SelectMany(d => d.Parts) .SelectMany(d => d.Parts)
.SumAsync(p => p.Weight); .GroupBy(p => p.Variety.Type)
.ToDictionaryAsync(g => g.Key, g => g.Sum(p => p.Weight));
MemberDeliveredWeightRed = weights.GetValueOrDefault("R", 0);
MemberDeliveredWeightWhite = weights.GetValueOrDefault("W", 0);
} }
protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) {
if (Season == null) throw new Exception("Call LoadData before RenderBody"); if (Season == null) throw new Exception("Call LoadData before RenderBody");
base.RenderBody(doc, pdf); base.RenderBody(doc, pdf);
doc.Add(NewMemberData(Season).SetMarginBottomMM(5)); doc.Add(NewMemberData(Season).SetMarginBottomMM(5));
doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeight, includeDelivery: false)); doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, includeDelivery: false));
if (ActiveAreaCommitments.Count != 0) { if (ActiveAreaCommitments.Count != 0) {
bool firstOnPage = false; bool firstOnPage = false;
if (pdf.GetNumberOfPages() == 1) { if (pdf.GetNumberOfPages() == 1) {
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
firstOnPage = true; firstOnPage = true;
} }
doc.Add(new KernedParagraph(12).Add(Bold($"Flächenbindungen per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 12, 0)); doc.Add(new KernedParagraph(12).Add(Bold($"Flächenbindungen per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 6, 0));
doc.Add(NewAreaComTable(ActiveAreaCommitments)); doc.Add(NewAreaCmtTable(ActiveAreaCommitments));
}
if (App.Client.EnableMemberHistory && App.Client.ModeMemberDataSheetShares > 0) {
bool firstOnPage = false;
if (pdf.GetNumberOfPages() == 1) {
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
firstOnPage = true;
}
doc.Add(new KernedParagraph(12).Add(Bold($"Verlauf der Geschäftsanteile per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 6, 0));
doc.Add(NewMemberBusinessSharesTable(MemberHistory));
} }
} }
@@ -132,32 +151,36 @@ namespace Elwig.Documents {
.AddCell(NewTd(i < subTbl2.Count ? subTbl2[i][1] : "", colspan: 2)); .AddCell(NewTd(i < subTbl2.Count ? subTbl2[i][1] : "", colspan: 2));
} }
var shares = (Member.Shares != 0 || (Member.SharesRed == 0 && Member.SharesWhite == 0) ? $"{Member.Shares:N0}" : "") +
(Member.SharesRed != 0 || Member.SharesWhite != 0 ? (Member.Shares != 0 ? " / " : "") + $"{Member.SharesRed:N0} (rot) / {Member.SharesWhite:N0} (weiß)" : "") +
(Member.SharesDormant != 0 ? $" / {Member.SharesDormant:N0} (ruhend)" : "");
tbl.AddCell(NewDataHdr("Betrieb", colspan: 6)) tbl.AddCell(NewDataHdr("Betrieb", colspan: 6))
.AddCell(NewDataTh("Betriebs-Nr.:")).AddCell(NewTd(Member.LfbisNr)) .AddCell(NewDataTh("Betriebs-Nr.:")).AddCell(NewTd(Member.LfbisNr))
.AddCell(NewDataTh("UID:", colspan: 2)).AddCell(NewTd(Member.UstIdNr, colspan: 2)) .AddCell(NewDataTh("UID:", colspan: 2)).AddCell(NewTd(Member.UstIdNr, colspan: 2))
.AddCell(NewDataTh("Stammgemeinde:")).AddCell(NewTd(Member.DefaultKg?.Name)) .AddCell(NewDataTh("Stammgemeinde:")).AddCell(NewTd(Member.DefaultKg?.Name))
.AddCell(NewDataTh("Buchführend:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsBuchführend ? "Ja " : "Nein ", 10) .AddCell(NewDataTh("Buchführend:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsAccounting ? "Ja " : "Nein ", 10)
.Add(Normal($"({(Member.IsBuchführend ? season.VatNormal : season.VatFlatrate) * 100:N0}% USt.)", 8)), colspan: 2)) .Add(Normal($"({(Member.IsAccounting ? season.VatNormal : season.VatFlatrate) * 100:N0}% USt.)", 8)), colspan: 2))
.AddCell(NewDataTh("(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)", 8, colspan: 2)) .AddCell(NewDataTh("(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)", 8, colspan: 2))
.AddCell(NewDataTh("Bio:", colspan: 2)).AddCell(NewTd(Member.IsOrganic ? "Ja" : "Nein", colspan: 2)) .AddCell(NewDataTh("Bio:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsOrganic ? "Ja" : "Nein", 10)
.Add(Normal(Member.OrganicAuthorityCode != null || Member.OrganicOperatorId != null ? $" ({Member.OrganicAuthorityCode}{(Member.OrganicAuthorityCode != null && Member.OrganicOperatorId != null ? ", " : "")}{Member.OrganicOperatorId})" : "", 8)), colspan: 2))
.AddCell(NewDataHdr("Genossenschaft", colspan: 6)) .AddCell(NewDataHdr("Genossenschaft", colspan: 6))
.AddCell(NewDataTh("Status:")).AddCell(NewTd(new KernedParagraph(Member.IsActive ? "Aktiv " : "Nicht aktiv ", 10) .AddCell(NewDataTh("Status:")).AddCell(NewTd(new KernedParagraph(Member.IsActive ? "Aktiv " : "Nicht aktiv ", 10)
.Add(Normal("(" + (Member.ExitDate != null ? $"{Member.EntryDate:dd.MM.yyyy}\u2013{Member.ExitDate:dd.MM.yyyy}" : $"seit {Member.EntryDate:dd.MM.yyyy}") + ")", 8)))) .Add(Normal("(" + (Member.ExitDate != null ? $"{Member.EntryDate:dd.MM.yyyy}\u2013{Member.ExitDate:dd.MM.yyyy}" : $"seit {Member.EntryDate:dd.MM.yyyy}") + ")", 8))))
.AddCell(NewDataTh("Geschäftsanteile:", colspan: 2)).AddCell(NewTd($"{Member.BusinessShares:N0}", colspan: 2)) .AddCell(NewDataTh("Geschäftsanteile:", colspan: 2)).AddCell(NewTd(shares, colspan: 2))
.AddCell(NewDataTh("Stamm-Zweigstelle:")).AddCell(NewTd(Member.Branch?.Name)) .AddCell(NewDataTh("Stamm-Zweigstelle:")).AddCell(NewTd(Member.Branch?.Name))
.AddCell(NewDataTh("Volllieferant:", colspan: 2)).AddCell(NewTd(Member.IsVollLieferant ? "Ja" : "Nein", colspan: 2)) .AddCell(NewDataTh("Vollablieferer:", colspan: 2)).AddCell(NewTd(Member.IsFullDelivery ? "Ja" : "Nein", colspan: 2))
.AddCell(NewDataTh("Zusendungen per\u2026")).AddCell(NewTd(new KernedParagraph(10) .AddCell(NewDataTh("Zusendungen per\u2026")).AddCell(NewTd(new KernedParagraph(10)
.Add(Italic("Post:")).Add(Normal(Member.ContactViaPost ? " Ja \u2013 " : " Nein \u2013 ")) .Add(Italic("Post:")).Add(Normal(Member.ContactViaPost ? " Ja \u2013 " : " Nein \u2013 "))
.Add(Italic("E-Mail:")).Add(Normal(Member.ContactViaEmail ? " Ja" : " Nein")))) .Add(Italic("E-Mail:")).Add(Normal(Member.ContactViaEmail ? " Ja" : " Nein"))))
.AddCell(NewDataTh("Funktionär:", colspan: 2)).AddCell(NewTd(Member.IsFunktionär ? "Ja" : "Nein", colspan: 2)); .AddCell(NewDataTh("Funktionär:", colspan: 2)).AddCell(NewTd(Member.IsOfficer ? "Ja" : "Nein", colspan: 2));
return tbl; return tbl;
} }
protected Table NewAreaComTable(IEnumerable<AreaCom> activeAreaComs) { protected Table NewAreaCmtTable(IEnumerable<AreaCmt> activeAreaCmts) {
var areaComs = activeAreaComs.GroupBy(a => a.AreaComType).Select(group => new { var areaCmts = activeAreaCmts.GroupBy(a => a.AreaCmtType).Select(group => new {
Type = group.Key, Type = group.Key,
AreaComs = group.OrderBy(c => c.Contract.Kg.AtKg.Name).ToList(), AreaCmts = group.OrderBy(c => c.Contract.Kg.AtKg.Name).ToList(),
Size = group.Sum(c => c.Area) Size = group.Sum(c => c.Area)
}).OrderByDescending(a => a.Size).ToList(); }).OrderByDescending(a => a.Size).ToList();
@@ -176,7 +199,7 @@ namespace Elwig.Documents {
.AddHeaderCell(NewTh("[m²]")); .AddHeaderCell(NewTh("[m²]"));
var lastContract = ""; var lastContract = "";
foreach (var contractType in areaComs) { foreach (var contractType in areaCmts) {
tbl.AddCell(NewCell(new KernedParagraph(10).Add(BoldItalic($"{contractType.Type.WineVar.Name} {(contractType.Type.WineAttr != null ? "(" + contractType.Type.WineAttr + ")" : "")}")), colspan: 3) tbl.AddCell(NewCell(new KernedParagraph(10).Add(BoldItalic($"{contractType.Type.WineVar.Name} {(contractType.Type.WineAttr != null ? "(" + contractType.Type.WineAttr + ")" : "")}")), colspan: 3)
.SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER)) .SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER))
.AddCell(NewCell(new KernedParagraph(10).Add(Bold($"{contractType.Size:N0}")).SetTextAlignment(TextAlignment.RIGHT)) .AddCell(NewCell(new KernedParagraph(10).Add(Bold($"{contractType.Size:N0}")).SetTextAlignment(TextAlignment.RIGHT))
@@ -184,23 +207,160 @@ namespace Elwig.Documents {
.AddCell(NewCell(colspan: 2) .AddCell(NewCell(colspan: 2)
.SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER)); .SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER));
foreach (var areaCom in contractType.AreaComs) { foreach (var areaCmt in contractType.AreaCmts) {
var c = areaCom.Contract; var c = areaCmt.Contract;
tbl.AddCell(NewTd(new KernedParagraph(10).Add(Normal($"{c.Kg.AtKg.Name} ")).Add(Normal($"({c.Kg.AtKg.KgNr:00000})", 8)))) tbl.AddCell(NewTd(new KernedParagraph(10).Add(Normal($"{c.Kg.AtKg.Name} ")).Add(Normal($"({c.Kg.AtKg.KgNr:00000})", 8))))
.AddCell(NewTd(c.Rd?.Name)) .AddCell(NewTd(c.Rd?.Name))
.AddCell(NewTd(Regex.Replace(areaCom.GstNr.Replace(",", ", ").Replace("-", "\u2013"), @"\s+", " "), 10)) .AddCell(NewTd(Regex.Replace(areaCmt.GstNr.Replace(",", ", ").Replace("-", "\u2013"), @"\s+", " "), 10))
.AddCell(NewTd($"{areaCom.Area:N0}", right: true)) .AddCell(NewTd($"{areaCmt.Area:N0}", right: true))
.AddCell(NewTd(areaCom.WineCult?.Name, center: true)) .AddCell(NewTd(areaCmt.WineCult?.Name, center: true))
.AddCell(NewTd(c.YearTo == null ? (c.YearFrom == null ? "unbefristet" : $"ab {c.YearFrom}") : (c.YearFrom == null ? $"bis {c.YearTo}" : $"{c.YearFrom}–{c.YearTo}"), center: true)); .AddCell(NewTd(c.YearTo == null ? (c.YearFrom == null ? "unbefristet" : $"ab {c.YearFrom}") : (c.YearFrom == null ? $"bis {c.YearTo}" : $"{c.YearFrom}–{c.YearTo}"), center: true));
lastContract = contractType.Type.DisplayName; lastContract = contractType.Type.DisplayName;
} }
} }
tbl.AddCell(NewTd("Gesamt:", 12, colspan: 2, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); tbl.AddCell(NewTd("Gesamt:", 12, colspan: 2, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
tbl.AddCell(NewTd($"{activeAreaComs.Sum(a => a.Area):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); tbl.AddCell(NewTd($"{activeAreaCmts.Sum(a => a.Area):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
tbl.AddCell(NewTd(colspan: 2, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); tbl.AddCell(NewTd(colspan: 2, borderTop: true).SetPaddingsMM(1, 1, 1, 1));
return tbl; return tbl;
} }
protected Cell[] NewBusinessSharesCells(MemberHistoryPoint p, bool isDiff = true) {
if (App.Client.HasRedWhite) {
return [
NewTd(isDiff ? (p.SharesRed == 0 ? "" : p.SharesRed < 0 ? $"–{-p.SharesRed:N0}" : $"+{p.SharesRed:N0}") : $"{p.SharesRed:N0}",
right: true, bold: !isDiff, borderTop: !isDiff),
NewTd(isDiff ? (p.SharesWhite == 0 ? "" : p.SharesWhite < 0 ? $"–{-p.SharesWhite:N0}" : $"+{p.SharesWhite:N0}") : $"{p.SharesWhite:N0}",
right: true, bold: !isDiff, borderTop: !isDiff),
NewTd(isDiff ? (p.SharesDormant == 0 ? "" : p.SharesDormant < 0 ? $"–{-p.SharesDormant:N0}" : $"+{p.SharesDormant:N0}") : $"{p.SharesDormant:N0}",
right: true, bold: !isDiff, borderTop: !isDiff),
];
} else {
return [
NewTd(isDiff ? (p.Shares == 0 ? "" : p.Shares < 0 ? $"–{-p.Shares:N0}" : $"+{p.Shares:N0}") : $"{p.Shares:N0}",
right: true, bold: !isDiff, borderTop: !isDiff),
NewTd(isDiff ? (p.SharesDormant == 0 ? "" : p.SharesDormant < 0 ? $"–{-p.SharesDormant:N0}" : $"+{p.SharesDormant:N0}") : $"{p.SharesDormant:N0}",
right : true, bold: !isDiff, borderTop: !isDiff),
];
}
}
protected Table NewMemberBusinessSharesTable(IEnumerable<MemberHistory> history) {
var tbl = new Table(ColsMM(18, 93, App.Client.HasRedWhite ? 18 : 12, App.Client.HasRedWhite ? 12 : 18, 12, 12), true)
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE)
.SetBorder(Border.NO_BORDER)
.SetFontSize(10);
var tw = App.Client.HasRedWhite ? 1 : 2;
tbl.AddHeaderCell(NewTh("Meldung am", rowspan: 2))
.AddHeaderCell(NewTh("Text", rowspan: 2, colspan: tw, left: true))
.AddHeaderCell(NewTh("Wirksam ab", rowspan: 2))
.AddHeaderCell(NewTh("Geschäftsanteile", colspan: App.Client.HasRedWhite ? 3 : 2));
if (App.Client.HasRedWhite) {
tbl.AddHeaderCell(NewTh("rot"))
.AddHeaderCell(NewTh("weiß"))
.AddHeaderCell(NewTh("ruhend"));
} else {
tbl.AddHeaderCell(NewTh("normal"))
.AddHeaderCell(NewTh("ruhend"));
}
if (!history.Any() || Member.EntryDate <= history.First().DateNotice) {
tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
.AddCell(NewTd("Eintritt", colspan: tw, bold: true))
.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
.AddCells(NewBusinessSharesCells(new()));
}
var recorded = history.Aggregate(new MemberHistoryPoint(), (s, h) => new(
s.Shares + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.Normal ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.Normal && h.FromType == BusinessShareType.Normal ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.Normal ? h.Shares : 0),
s.SharesRed + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.Red ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.Red && h.FromType == BusinessShareType.Red ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.Red ? h.Shares : 0),
s.SharesWhite + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.White ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.White && h.FromType == BusinessShareType.White ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.White ? h.Shares : 0),
s.SharesDormant + (h.ToMgNr == Member.MgNr && h.ToType == BusinessShareType.Dormant ? h.Shares : 0) - (h.ToMgNr == h.FromMgNr && h.ToType == BusinessShareType.Dormant && h.FromType == BusinessShareType.Dormant ? 0 : h.FromMgNr == Member.MgNr && h.FromType == BusinessShareType.Dormant ? h.Shares : 0)));
var cur = new MemberHistoryPoint(Member.Shares - recorded.Shares, Member.SharesRed - recorded.SharesRed, Member.SharesWhite - recorded.SharesWhite, Member.SharesDormant - recorded.SharesDormant);
if (cur.Shares != 0 || cur.SharesRed != 0 || cur.SharesWhite != 0 || cur.SharesDormant != 0) {
tbl.AddCell(NewTd())
.AddCell(NewTd("Nicht in Software erfasst", colspan: tw, italic: true))
.AddCell(NewTd())
.AddCells(NewBusinessSharesCells(cur));
if (history.Any()) {
tbl.AddCell(NewTd(borderTop: true))
.AddCell(NewTd($"Bis Lese {history.First().DateNotice.Year - 1}", borderTop: true, colspan: tw, bold: true))
.AddCell(NewTd(borderTop: true))
.AddCells(NewBusinessSharesCells(cur, isDiff: false));
}
}
DateOnly? lastDate = null;
foreach (var row in history) {
int s = 0, r = 0, w = 0, d = 0;
if (row.FromMgNr == Member.MgNr && !(row.FromMgNr == row.ToMgNr && row.FromType == row.ToType)) {
switch (row.FromType) {
case BusinessShareType.Normal: s -= row.Shares; break;
case BusinessShareType.Red: r -= row.Shares; break;
case BusinessShareType.White: w -= row.Shares; break;
case BusinessShareType.Dormant: d -= row.Shares; break;
}
}
if (row.ToMgNr == Member.MgNr) {
switch (row.ToType) {
case BusinessShareType.Normal: s += row.Shares; break;
case BusinessShareType.Red: r += row.Shares; break;
case BusinessShareType.White: w += row.Shares; break;
case BusinessShareType.Dormant: d += row.Shares; break;
}
}
if (lastDate < Member.EntryDate && row.DateNotice <= Member.EntryDate) {
tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
.AddCell(NewTd("Eintritt", colspan: tw, bold: true))
.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
.AddCells(NewBusinessSharesCells(new()));
}
if (lastDate != null && row.DateNotice.Year != lastDate?.Year) {
tbl.AddCell(NewTd(borderTop: true))
.AddCell(NewTd(lastDate?.Year == row.DateNotice.Year - 1 ? $"Lese {lastDate?.Year}" : $"Lese {lastDate?.Year}–{row.DateNotice.Year - 1}", borderTop: true, colspan: tw, bold: true))
.AddCell(NewTd(borderTop: true))
.AddCells(NewBusinessSharesCells(cur, isDiff: false));
}
if (lastDate < Member.ExitDate && row.DateNotice <= Member.ExitDate) {
tbl.AddCell(NewTd($"{Member.ExitDate:dd.MM.yyyy}"))
.AddCell(NewTd("Austritt", colspan: tw, bold: true))
.AddCell(NewTd($"31.12.{Member.ExitDate.Value.Year + 1}")) // FIXME
.AddCells(NewBusinessSharesCells(new()));
}
cur = new(cur.Shares + s, cur.SharesRed + r, cur.SharesWhite + w, cur.SharesDormant + d);
tbl.AddCell(NewTd($"{row.DateNotice:dd.MM.yyyy}"))
.AddCell(NewTd(row.FormatReasonPhrase(Member.MgNr), colspan: tw))
.AddCell(NewTd($"{row.DateEffective:dd.MM.yyyy}"))
.AddCells(NewBusinessSharesCells(new(s, r, w, d)));
lastDate = row.DateNotice;
}
if (Member.EntryDate != null && history.Any() && Member.EntryDate > lastDate) {
tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
.AddCell(NewTd("Eintritt", colspan: tw, bold: true))
.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}"))
.AddCells(NewBusinessSharesCells(new()));
}
if (Member.ExitDate != null && (!history.Any() || Member.ExitDate > lastDate)) {
tbl.AddCell(NewTd($"{Member.ExitDate:dd.MM.yyyy}"))
.AddCell(NewTd("Austritt", colspan: tw, bold: true))
.AddCell(NewTd($"31.12.{Member.ExitDate.Value.Year + 1}")) // FIXME
.AddCells(NewBusinessSharesCells(new()));
}
tbl.AddCell(NewTd(borderTop: true))
.AddCell(NewTd(lastDate == null ? "Aktuell" : $"Ab Lese {lastDate?.Year}", borderTop: true, colspan: tw, bold: true))
.AddCell(NewTd(borderTop: true))
.AddCells(NewBusinessSharesCells(cur, isDiff: false));
return tbl;
}
} }
} }
+15 -15
View File
@@ -16,14 +16,14 @@ namespace Elwig.Documents {
public string Filter; public string Filter;
public List<MemberListRow> Members; public List<MemberListRow> Members;
public string[] AreaComFilters; public string[] AreaCmtFilters;
public bool FilterAreaComs => AreaComFilters.Length > 0; public bool FilterAreaCmts => AreaCmtFilters.Length > 0;
public MemberList(string filter, IEnumerable<MemberListRow> members) : public MemberList(string filter, IEnumerable<MemberListRow> members) :
base(Name) { base(Name) {
Filter = filter; Filter = filter;
Members = [.. members]; Members = [.. members];
AreaComFilters = [..members AreaCmtFilters = [..members
.SelectMany(m => m.AreaCommitmentsFiltered) .SelectMany(m => m.AreaCommitmentsFiltered)
.Select(c => c.VtrgId) .Select(c => c.VtrgId)
.Distinct() .Distinct()
@@ -46,11 +46,11 @@ namespace Elwig.Documents {
} }
protected Table NewMemberTable(List<MemberListRow> members) { protected Table NewMemberTable(List<MemberListRow> members) {
var tbl = new Table(AreaComFilters.Length > 1 ? ColsMM(8, 38, 36, 8, 18, 12, 5, 16, 12, 12) : ColsMM(8, 42, 40, 8, 20, 12, 5, 18, 12), true) var tbl = new Table(AreaCmtFilters.Length > 1 ? ColsMM(8, 38, 36, 8, 18, 12, 5, 16, 12, 12) : ColsMM(8, 42, 40, 8, 20, 12, 5, 18, 12), true)
.SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout()
.SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE);
var headerSpan = FilterAreaComs ? 3 : 2; var headerSpan = FilterAreaCmts ? 3 : 2;
tbl.AddHeaderCell(NewTh("Nr.", rowspan: headerSpan)) tbl.AddHeaderCell(NewTh("Nr.", rowspan: headerSpan))
.AddHeaderCell(NewTh("Name", rowspan: headerSpan, left: true)) .AddHeaderCell(NewTh("Name", rowspan: headerSpan, left: true))
.AddHeaderCell(NewTh("Adresse", rowspan: headerSpan, left: true)) .AddHeaderCell(NewTh("Adresse", rowspan: headerSpan, left: true))
@@ -59,21 +59,21 @@ namespace Elwig.Documents {
.AddHeaderCell(NewTh("Betr.-Nr.", rowspan: headerSpan)) .AddHeaderCell(NewTh("Betr.-Nr.", rowspan: headerSpan))
.AddHeaderCell(NewTh("GA", rowspan: headerSpan).SetPaddingLeft(0).SetPaddingRight(0)) .AddHeaderCell(NewTh("GA", rowspan: headerSpan).SetPaddingLeft(0).SetPaddingRight(0))
.AddHeaderCell(NewTh("Stamm-KG", rowspan: headerSpan, left: true)) .AddHeaderCell(NewTh("Stamm-KG", rowspan: headerSpan, left: true))
.AddHeaderCell(NewTh("Geb. Fl.", colspan: FilterAreaComs ? AreaComFilters.Length : 1)); .AddHeaderCell(NewTh("Geb. Fl.", colspan: FilterAreaCmts ? AreaCmtFilters.Length : 1));
if (FilterAreaComs) { if (FilterAreaCmts) {
foreach (var vtrgId in AreaComFilters) { foreach (var vtrgId in AreaCmtFilters) {
tbl.AddHeaderCell(NewTh(vtrgId)); tbl.AddHeaderCell(NewTh(vtrgId));
} }
} }
for (int i = 0; i < (FilterAreaComs ? AreaComFilters.Length : 1); i++) { for (int i = 0; i < (FilterAreaCmts ? AreaCmtFilters.Length : 1); i++) {
tbl.AddHeaderCell(NewTh("[m²]")); tbl.AddHeaderCell(NewTh("[m²]"));
} }
string? lastBranch = members.Select(m => m.Branch).Distinct().Count() == 1 ? null : ""; string? lastBranch = members.Select(m => m.Branch).Distinct().Count() == 1 ? null : "";
foreach (var m in members) { foreach (var m in members) {
if (lastBranch != null && m.Branch != lastBranch) { if (lastBranch != null && m.Branch != lastBranch) {
tbl.AddCell(NewCell(colspan: 8 + Math.Max(AreaComFilters.Length, 1)).SetHeightMM(5).SetKeepWithNext(true)); tbl.AddCell(NewCell(colspan: 8 + Math.Max(AreaCmtFilters.Length, 1)).SetHeightMM(5).SetKeepWithNext(true));
tbl.AddCell(NewCell(new KernedParagraph(m.Branch ?? "", 16).SetFont(BF), colspan: 8 + Math.Max(AreaComFilters.Length, 1)) tbl.AddCell(NewCell(new KernedParagraph(m.Branch ?? "", 16).SetFont(BF), colspan: 8 + Math.Max(AreaCmtFilters.Length, 1))
.SetPaddingsMM(1, 2, 1, 2) .SetPaddingsMM(1, 2, 1, 2)
.SetBorder(new SolidBorder(BorderThickness)) .SetBorder(new SolidBorder(BorderThickness))
.SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0))); .SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)));
@@ -86,10 +86,10 @@ namespace Elwig.Documents {
.AddCell(NewTd($"{m.Plz}", 8)) .AddCell(NewTd($"{m.Plz}", 8))
.AddCell(NewTd(m.Locality, 6)) .AddCell(NewTd(m.Locality, 6))
.AddCell(NewTd(m.LfbisNr ?? "", 8)) .AddCell(NewTd(m.LfbisNr ?? "", 8))
.AddCell(NewTd($"{m.BusinessShares:N0}", 8, right: true).SetPaddingLeft(0).SetPaddingRight(0)) .AddCell(NewTd($"{m.SharesActive:N0}", 8, right: true).SetPaddingLeft(0).SetPaddingRight(0))
.AddCell(NewTd(m.DefaultKg ?? "", 6)); .AddCell(NewTd(m.DefaultKg ?? "", 6));
if (AreaComFilters.Length > 0) { if (AreaCmtFilters.Length > 0) {
foreach (var v in AreaComFilters) { foreach (var v in AreaCmtFilters) {
tbl.AddCell(NewTd($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}", 8, right: true)); tbl.AddCell(NewTd($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}", 8, right: true));
} }
} else { } else {
@@ -101,7 +101,7 @@ namespace Elwig.Documents {
.AddCell(NewTd(m.BillingAddress ?? "", 8)) .AddCell(NewTd(m.BillingAddress ?? "", 8))
.AddCell(NewTd($"{m.BillingPlz}", 8)) .AddCell(NewTd($"{m.BillingPlz}", 8))
.AddCell(NewTd(m.BillingLocality ?? "", 6)) .AddCell(NewTd(m.BillingLocality ?? "", 6))
.AddCell(NewTd(colspan: 3 + Math.Max(AreaComFilters.Length, 1))); .AddCell(NewTd(colspan: 3 + Math.Max(AreaCmtFilters.Length, 1)));
} }
} }
+23 -23
View File
@@ -108,16 +108,16 @@ namespace Elwig.Documents {
var totalSum = _credits.Sum(p => p.Amount); var totalSum = _credits.Sum(p => p.Amount);
var weiRows = data.Rows.Where(r => r.QualityLevel == "Wein"); var weiRows = data.Rows.Where(r => r.QualityLevel == "Wein");
var minWei = weiRows.Min(r => r.Ungeb.MinPrice); var minWei = weiRows.Min(r => r.UnCtrd.MinPrice);
var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice); var maxWei = weiRows.Max(r => r.UnCtrd.MaxPrice);
var quwRows = data.Rows.Where(r => r.QualityLevel != "Wein"); var quwRows = data.Rows.Where(r => r.QualityLevel != "Wein");
var minPrice = quwRows.Min(r => r.Ungeb.MinPrice); var minPrice = quwRows.Min(r => r.UnCtrd.MinPrice);
var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice); var maxPrice = quwRows.Max(r => r.UnCtrd.MaxPrice);
var gebRows = data.Rows var ctrdRows = data.Rows
.Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null) .Where(r => r.Ctrd.MaxPrice != null && r.UnCtrd.MinPrice != null)
.Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice); .Select(r => r.Ctrd.MaxPrice - r.UnCtrd.MinPrice);
var minGeb = gebRows.Min(); var minCtrd = ctrdRows.Min();
var maxGeb = gebRows.Max(); var maxCtrd = ctrdRows.Max();
tbl.AddCell(NewSectionHdr("Allgemein", colspan: 5)) tbl.AddCell(NewSectionHdr("Allgemein", colspan: 5))
.AddCell(NewSectionHdr("Berücksichtigt", colspan: 3, borderLeft: true)) .AddCell(NewSectionHdr("Berücksichtigt", colspan: 3, borderLeft: true))
@@ -139,7 +139,7 @@ namespace Elwig.Documents {
.AddCell(NewSectionTh("Datum/Überw.:", overflow: true)) .AddCell(NewSectionTh("Datum/Überw.:", overflow: true))
.AddCell(NewTd($"{Variant.Date:dd.MM.yyyy} / {Variant.TransferDate:dd.MM.yyyy}", colspan: 4, center: true)) .AddCell(NewTd($"{Variant.Date:dd.MM.yyyy} / {Variant.TransferDate:dd.MM.yyyy}", colspan: 4, center: true))
.AddCell(NewSectionTh("Automatische Nachzeichnung der GA:", colspan: 2, borderLeft: true)) .AddCell(NewSectionTh("(Automatische) Nachzeichnung von GA:", colspan: 2, borderLeft: true))
.AddCell(NewTd(BillingData.ConsiderAutoBusinessShares ? "Ja" : "Nein", center: true)) .AddCell(NewTd(BillingData.ConsiderAutoBusinessShares ? "Ja" : "Nein", center: true))
.AddCell(NewSectionTh("Berechnung:")) .AddCell(NewSectionTh("Berechnung:"))
@@ -197,7 +197,7 @@ namespace Elwig.Documents {
.AddCell(NewTd(CurrencySymbol)) .AddCell(NewTd(CurrencySymbol))
.AddCell(NewTd($"{Math.Abs(vat):N2}", right: true)) .AddCell(NewTd($"{Math.Abs(vat):N2}", right: true))
.AddCell(NewSectionTh("Gebunden-Zuschlag:", borderLeft: true)) .AddCell(NewSectionTh("Gebunden-Zuschlag:", borderLeft: true))
.AddCell(NewTd(minGeb == null && maxGeb == null ? "-" : minGeb != maxGeb ? $"{minGeb:N4}\u2013{maxGeb:N4} {CurrencySymbol}/kg" : minGeb == 0 ? "-" : $"{minGeb:N4} {CurrencySymbol}/kg", colspan: 2, center: true)) .AddCell(NewTd(minCtrd == null && maxCtrd == null ? "-" : minCtrd != maxCtrd ? $"{minCtrd:N4}\u2013{maxCtrd:N4} {CurrencySymbol}/kg" : minCtrd == 0 ? "-" : $"{minCtrd:N4} {CurrencySymbol}/kg", colspan: 2, center: true))
.AddCell(NewSectionTh("Bruttosumme:", colspan: 2)) .AddCell(NewSectionTh("Bruttosumme:", colspan: 2))
.AddCell(NewTd(borderTop: true)) .AddCell(NewTd(borderTop: true))
@@ -211,21 +211,21 @@ namespace Elwig.Documents {
.AddCell(NewTd(CurrencySymbol)) .AddCell(NewTd(CurrencySymbol))
.AddCell(NewTd($"{Math.Abs(totalMods):N2}", right: true)) .AddCell(NewTd($"{Math.Abs(totalMods):N2}", right: true))
.AddCell(NewSectionTh("Menge (ungebunden):", borderLeft: true, borderTop: true)) .AddCell(NewSectionTh("Menge (ungebunden):", borderLeft: true, borderTop: true))
.AddCell(NewTd($"{data.Rows.Sum(r => r.Ungeb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)) .AddCell(NewTd($"{data.Rows.Sum(r => r.UnCtrd.Weight):N0} kg", colspan: 2, right: true, borderTop: true))
.AddCell(NewSectionTh("Bereits berücksichtigte Abzüge:", colspan: 2)) .AddCell(NewSectionTh("Bereits berücksichtigte Abzüge:", colspan: 2))
.AddCell(NewTd(Utils.GetSign(considered))) .AddCell(NewTd(Utils.GetSign(considered)))
.AddCell(NewTd(CurrencySymbol)) .AddCell(NewTd(CurrencySymbol))
.AddCell(NewTd($"{Math.Abs(considered):N2}", right: true)) .AddCell(NewTd($"{Math.Abs(considered):N2}", right: true))
.AddCell(NewSectionTh("Menge (gebunden):", borderLeft: true)) .AddCell(NewSectionTh("Menge (gebunden):", borderLeft: true))
.AddCell(NewTd($"{data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0} kg", colspan: 2, right: true)) .AddCell(NewTd($"{data.Rows.Sum(r => r.Ctrd.Weight + r.LowCtrd.Weight):N0} kg", colspan: 2, right: true))
.AddCell(NewSectionTh("Auszahlungsbetrag:", colspan: 2)) .AddCell(NewSectionTh("Auszahlungsbetrag:", colspan: 2))
.AddCell(NewTd(borderTop: true)) .AddCell(NewTd(borderTop: true))
.AddCell(NewTd(CurrencySymbol, borderTop: true)) .AddCell(NewTd(CurrencySymbol, borderTop: true))
.AddCell(NewTd($"{totalSum:N2}", right: true, borderTop: true)) .AddCell(NewTd($"{totalSum:N2}", right: true, borderTop: true))
.AddCell(NewSectionTh("Gesamtmenge:", borderLeft: true)) .AddCell(NewSectionTh("Gesamtmenge:", borderLeft: true))
.AddCell(NewTd($"{data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)); .AddCell(NewTd($"{data.Rows.Sum(r => r.UnCtrd.Weight + r.LowCtrd.Weight + r.Ctrd.Weight):N0} kg", colspan: 2, right: true, borderTop: true));
return tbl; return tbl;
} }
@@ -290,22 +290,22 @@ namespace Elwig.Documents {
.ToList(); .ToList();
var border = lastHdr != null; var border = lastHdr != null;
tbl.AddCell(NewTd(hdr, colspan: 2, bold: true, italic: true, borderTop: border)) tbl.AddCell(NewTd(hdr, colspan: 2, bold: true, italic: true, borderTop: border))
.AddCell(NewTd($"{rows.Sum(r => r.Ungeb.Weight):N0}", right: true, bold: true, borderTop: border)) .AddCell(NewTd($"{rows.Sum(r => r.UnCtrd.Weight):N0}", right: true, bold: true, borderTop: border))
.AddCell(NewTd(borderTop: border)) .AddCell(NewTd(borderTop: border))
.AddCell(NewTd($"{rows.Sum(r => r.LowGeb.Weight):N0}", right: true, bold: true, borderTop: border)) .AddCell(NewTd($"{rows.Sum(r => r.LowCtrd.Weight):N0}", right: true, bold: true, borderTop: border))
.AddCell(NewTd(borderTop: border)) .AddCell(NewTd(borderTop: border))
.AddCell(NewTd($"{rows.Sum(r => r.Geb.Weight):N0}", right: true, bold: true, borderTop: border)) .AddCell(NewTd($"{rows.Sum(r => r.Ctrd.Weight):N0}", right: true, bold: true, borderTop: border))
.AddCell(NewTd(borderTop: border)) .AddCell(NewTd(borderTop: border))
.AddCell(NewTd($"{rows.Sum(r => r.Amount):N2}", right: true, bold: true, borderTop: border)); .AddCell(NewTd($"{rows.Sum(r => r.Amount):N2}", right: true, bold: true, borderTop: border));
} }
tbl.AddCell(NewTd(row.QualityLevel)) tbl.AddCell(NewTd(row.QualityLevel))
.AddCell(NewTd($"{row.Oe:N0}", center: true)) .AddCell(NewTd($"{row.Oe:N0}", center: true))
.AddCell(NewTd(row.Ungeb.Weight != 0 ? $"{row.Ungeb.Weight:N0}" : "-", right: true)) .AddCell(NewTd(row.UnCtrd.Weight != 0 ? $"{row.UnCtrd.Weight:N0}" : "-", right: true))
.AddCell(NewTd(row.Ungeb.MaxPrice != null ? $"{row.Ungeb.MaxPrice:N4}" : "-", right: true)) .AddCell(NewTd(row.UnCtrd.MaxPrice != null ? $"{row.UnCtrd.MaxPrice:N4}" : "-", right: true))
.AddCell(NewTd(row.LowGeb.Weight != 0 ? $"{row.LowGeb.Weight:N0}" : "-", right: true)) .AddCell(NewTd(row.LowCtrd.Weight != 0 ? $"{row.LowCtrd.Weight:N0}" : "-", right: true))
.AddCell(NewTd(row.LowGeb.MaxPrice != null ? $"{row.LowGeb.MaxPrice:N4}" : "-", right: true)) .AddCell(NewTd(row.LowCtrd.MaxPrice != null ? $"{row.LowCtrd.MaxPrice:N4}" : "-", right: true))
.AddCell(NewTd(row.Geb.Weight != 0 ? $"{row.Geb.Weight:N0}" : "-", right: true)) .AddCell(NewTd(row.Ctrd.Weight != 0 ? $"{row.Ctrd.Weight:N0}" : "-", right: true))
.AddCell(NewTd(row.Geb.MaxPrice != null ? $"{row.Geb.MaxPrice:N4}" : "-", right: true)) .AddCell(NewTd(row.Ctrd.MaxPrice != null ? $"{row.Ctrd.MaxPrice:N4}" : "-", right: true))
.AddCell(NewTd($"{row.Amount:N2}", right: true)); .AddCell(NewTd($"{row.Amount:N2}", right: true));
lastHdr = hdr; lastHdr = hdr;
} }
+14 -14
View File
@@ -9,10 +9,10 @@
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon> <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>1.0.5.3</Version> <Version>1.2.0.0</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages> <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>App.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -23,21 +23,21 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="bblanchon.PDFium.Win32" Version="149.0.7802" /> <PackageReference Include="bblanchon.PDFium.Win32" Version="156.0.8076" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="itext" Version="9.6.0" /> <PackageReference Include="itext" Version="9.8.0" />
<PackageReference Include="itext.bouncy-castle-adapter" Version="9.6.0" /> <PackageReference Include="itext.bouncy-castle-adapter" Version="9.8.0" />
<PackageReference Include="LinqKit" Version="1.3.11" /> <PackageReference Include="LinqKit" Version="1.3.11" />
<PackageReference Include="MailKit" Version="4.16.0" /> <PackageReference Include="MailKit" Version="4.18.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.7" /> <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="10.0.12" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3912.50" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.4258.31" />
<PackageReference Include="NJsonSchema" Version="11.6.1" /> <PackageReference Include="NJsonSchema" Version="11.6.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.1.58" /> <PackageReference Include="ScottPlot.WPF" Version="5.1.59" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
<PackageReference Include="System.IO.Hashing" Version="10.0.7" /> <PackageReference Include="System.IO.Hashing" Version="10.0.12" />
<PackageReference Include="System.IO.Ports" Version="10.0.7" /> <PackageReference Include="System.IO.Ports" Version="10.0.12" />
<PackageReference Include="System.Management" Version="10.0.7" /> <PackageReference Include="System.Management" Version="10.0.12" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+108 -28
View File
@@ -1,10 +1,10 @@
using Elwig.Models;
using Elwig.Models.Dtos; using Elwig.Models.Dtos;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Elwig.Services;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ScottPlot.TickGenerators.Financial;
using ScottPlot.TickGenerators.TimeUnits;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
@@ -12,12 +12,11 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Converters;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public record struct AreaComBucket(int Area, int Obligation, int Right); public record struct MemberHistoryPoint(int Shares, int SharesRed, int SharesWhite, int SharesDormant);
public record struct AreaCmtBucket(int Area, int Obligation, int Right);
public record struct UnderDelivery(int Weight, int Diff); public record struct UnderDelivery(int Weight, int Diff);
public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int DeliveryTotal, int Payment); public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int DeliveryTotal, int Payment);
public record struct MemberStat(string Variety, string Discr, int Weight); public record struct MemberStat(string Variety, string Discr, int Weight);
@@ -45,14 +44,14 @@ namespace Elwig.Helpers {
public DbSet<WineAttr> WineAttributes { get; private set; } public DbSet<WineAttr> WineAttributes { get; private set; }
public DbSet<WineCult> WineCultivations { get; private set; } public DbSet<WineCult> WineCultivations { get; private set; }
public DbSet<Branch> Branches { get; private set; } public DbSet<Branch> Branches { get; private set; }
public DbSet<AreaComType> AreaCommitmentTypes { get; private set; } public DbSet<AreaCmtType> AreaCommitmentTypes { get; private set; }
public DbSet<Member> Members { get; private set; } public DbSet<Member> Members { get; private set; }
public DbSet<BillingAddr> BillingAddresses { get; private set; } public DbSet<BillingAddr> BillingAddresses { get; private set; }
public DbSet<MemberTelNr> MemberTelephoneNrs { get; private set; } public DbSet<MemberTelNr> MemberTelephoneNrs { get; private set; }
public DbSet<MemberEmailAddr> MemberEmailAddrs { get; private set; } public DbSet<MemberEmailAddr> MemberEmailAddrs { get; private set; }
public DbSet<MemberHistory> MemberHistory { get; private set; } public DbSet<MemberHistory> MemberHistory { get; private set; }
public DbSet<AreaComContract> AreaCommitmentContracts { get; private set; } public DbSet<AreaCmtContract> AreaCommitmentContracts { get; private set; }
public DbSet<AreaCom> AreaCommitments { get; private set; } public DbSet<AreaCmt> AreaCommitments { get; private set; }
public DbSet<Season> Seasons { get; private set; } public DbSet<Season> Seasons { get; private set; }
public DbSet<DeliverySchedule> DeliverySchedules { get; private set; } public DbSet<DeliverySchedule> DeliverySchedules { get; private set; }
public DbSet<DeliveryScheduleWineVar> DeliveryScheduleWineVarieties { get; private set; } public DbSet<DeliveryScheduleWineVar> DeliveryScheduleWineVarieties { get; private set; }
@@ -69,9 +68,10 @@ namespace Elwig.Helpers {
public DbSet<DeliveryPartBucket> DeliveryPartBuckets { get; private set; } public DbSet<DeliveryPartBucket> DeliveryPartBuckets { get; private set; }
public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; } public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; } public DbSet<AreaCmtUnderDeliveryRowSingle> AreaCmtUnderDeliveryRows { get; private set; }
public DbSet<MemberDeliveryPerVarietyRowSingle> MemberDeliveryPerVariantRows { get; private set; } public DbSet<MemberDeliveryPerVarietyRowSingle> MemberDeliveryPerVariantRows { get; private set; }
public DbSet<MemberAreaComsRowSingle> MemberAreaComsRows { get; private set; } public DbSet<MemberAreaCmtTotalsRowSingle> MemberAreaCmtTotalsRows { get; private set; }
public DbSet<MemberAreaCmtsRow> MemberAreaCmtsRows { get; private set; }
public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; } public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; } public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; } public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; }
@@ -133,8 +133,8 @@ namespace Elwig.Helpers {
.OrderBy(m => m.Name).ThenBy(m => m.GivenName).ThenBy(m => m.MgNr) .OrderBy(m => m.Name).ThenBy(m => m.GivenName).ThenBy(m => m.MgNr)
.AsSplitQuery()); .AsSplitQuery());
private static readonly Func<AppDbContext, int?, IAsyncEnumerable<AreaCom>> _compiledQueryAreaCommitments = private static readonly Func<AppDbContext, int?, IAsyncEnumerable<AreaCmt>> _compiledQueryAreaCommitments =
EF.CompileAsyncQuery<AppDbContext, int?, AreaCom>((ctx, fbnr) => ctx.AreaCommitments EF.CompileAsyncQuery<AppDbContext, int?, AreaCmt>((ctx, fbnr) => ctx.AreaCommitments
.Where(c => fbnr == null || c.FbNr == fbnr) .Where(c => fbnr == null || c.FbNr == fbnr)
.OrderBy(c => c.FbNr).ThenBy(c => c.RevNr)); .OrderBy(c => c.FbNr).ThenBy(c => c.RevNr));
@@ -149,7 +149,8 @@ namespace Elwig.Helpers {
.Include(s => s.Modifiers) .Include(s => s.Modifiers)
.OrderByDescending(s => s.Year)); .OrderByDescending(s => s.Year));
private readonly Dictionary<int, Dictionary<int, Dictionary<string, AreaComBucket>>> _memberAreaCommitmentBuckets = []; private readonly Dictionary<int, Dictionary<int, MemberHistoryPoint>> _memberHistory = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, AreaCmtBucket>>> _memberAreaCommitmentBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = []; private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBucketsStrict = []; private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBucketsStrict = [];
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = []; private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = [];
@@ -162,24 +163,33 @@ namespace Elwig.Helpers {
LogFile = new(file) { LogFile = new(file) {
AutoFlush = true AutoFlush = true
}; };
} catch (Exception e) { } catch (Exception exc) {
MessageBox.Show($"Unable to open database log file:\n\n{e.Message}", "Database Log", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException("Database Log", $"Unable to open database log file", exc);
} }
} }
SavedLastWriteTime = LastWriteTime; SavedLastWriteTime = LastWriteTime;
SavedChanges += OnSavedChanges; SavedChanges += OnSavedChanges;
} }
private static void CreateFunctions(SqliteConnection cnx) {
cnx.CreateFunction<string, string?, bool?>("REGEXP", (pattern, value) => {
return value == null ? null : Regex.IsMatch(value, pattern);
}, true);
cnx.CreateFunction<string?, string?, string?>("NORMALIZE_ADDRESS", (address, locality) => {
return string.IsNullOrWhiteSpace(address) ? null : Normalizer.NormalizeAddress(address, locality);
}, true);
}
public static SqliteConnection Connect(string? connectionString = null) { public static SqliteConnection Connect(string? connectionString = null) {
var cnx = new SqliteConnection(connectionString ?? ConnectionString); var cnx = new SqliteConnection(connectionString ?? ConnectionString);
cnx.CreateFunction<string, string?, bool?>("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); CreateFunctions(cnx);
cnx.Open(); cnx.Open();
return cnx; return cnx;
} }
public static async Task<SqliteConnection> ConnectAsync(string? connectionString = null) { public static async Task<SqliteConnection> ConnectAsync(string? connectionString = null) {
var cnx = new SqliteConnection(connectionString ?? ConnectionString); var cnx = new SqliteConnection(connectionString ?? ConnectionString);
cnx.CreateFunction<string, string?, bool?>("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); CreateFunctions(cnx);
await cnx.OpenAsync(); await cnx.OpenAsync();
return cnx; return cnx;
} }
@@ -198,6 +208,7 @@ namespace Elwig.Helpers {
modelBuilder.Entity<PostalDest>().Navigation(p => p.AtPlz).AutoInclude(); modelBuilder.Entity<PostalDest>().Navigation(p => p.AtPlz).AutoInclude();
modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.AtPlz).AutoInclude(); modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.AtPlz).AutoInclude();
modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.Ort).AutoInclude(); modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.Ort).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.Branch).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.DefaultWbKg).AutoInclude(); modelBuilder.Entity<Member>().Navigation(m => m.DefaultWbKg).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.Country).AutoInclude(); modelBuilder.Entity<Member>().Navigation(m => m.Country).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.PostalDest).AutoInclude(); modelBuilder.Entity<Member>().Navigation(m => m.PostalDest).AutoInclude();
@@ -219,13 +230,13 @@ namespace Elwig.Helpers {
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Kg).AutoInclude(); modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Kg).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Rd).AutoInclude(); modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Rd).AutoInclude();
modelBuilder.Entity<DeliveryPartModifier>().Navigation(m => m.Modifier).AutoInclude(); modelBuilder.Entity<DeliveryPartModifier>().Navigation(m => m.Modifier).AutoInclude();
modelBuilder.Entity<AreaComContract>().Navigation(c => c.Kg).AutoInclude(); modelBuilder.Entity<AreaCmtContract>().Navigation(c => c.Kg).AutoInclude();
modelBuilder.Entity<AreaComContract>().Navigation(c => c.Rd).AutoInclude(); modelBuilder.Entity<AreaCmtContract>().Navigation(c => c.Rd).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.Contract).AutoInclude(); modelBuilder.Entity<AreaCmt>().Navigation(c => c.Contract).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.WineCult).AutoInclude(); modelBuilder.Entity<AreaCmt>().Navigation(c => c.WineCult).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.AreaComType).AutoInclude(); modelBuilder.Entity<AreaCmt>().Navigation(c => c.AreaCmtType).AutoInclude();
modelBuilder.Entity<AreaComType>().Navigation(c => c.WineVar).AutoInclude(); modelBuilder.Entity<AreaCmtType>().Navigation(c => c.WineVar).AutoInclude();
modelBuilder.Entity<AreaComType>().Navigation(c => c.WineAttr).AutoInclude(); modelBuilder.Entity<AreaCmtType>().Navigation(c => c.WineAttr).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Credit).AutoInclude(); modelBuilder.Entity<PaymentMember>().Navigation(c => c.Credit).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Member).AutoInclude(); modelBuilder.Entity<PaymentMember>().Navigation(c => c.Member).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Variant).AutoInclude(); modelBuilder.Entity<PaymentMember>().Navigation(c => c.Variant).AutoInclude();
@@ -233,6 +244,11 @@ namespace Elwig.Helpers {
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Schedule).AutoInclude(); modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Schedule).AutoInclude();
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Variety).AutoInclude(); modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Variety).AutoInclude();
modelBuilder.Entity<DeliverySchedule>().Navigation(s => s.Branch).AutoInclude(); modelBuilder.Entity<DeliverySchedule>().Navigation(s => s.Branch).AutoInclude();
modelBuilder.Entity<DeliverySchedule>().Navigation(s => s.Attribute).AutoInclude();
modelBuilder.Entity<DeliverySchedule>().Navigation(s => s.Cultivation).AutoInclude();
modelBuilder.Entity<MemberHistory>().Navigation(s => s.FromMember).AutoInclude();
modelBuilder.Entity<MemberHistory>().Navigation(s => s.ToMember).AutoInclude();
modelBuilder.Entity<MemberHistory>().Navigation(s => s.Currency).AutoInclude();
} }
public override void Dispose() { public override void Dispose() {
@@ -312,6 +328,10 @@ namespace Elwig.Helpers {
return (await DeliverySchedules.Where(s => s.Year == year).Select(v => (int?)v.DsNr).MaxAsync() ?? 0) + 1; return (await DeliverySchedules.Where(s => s.Year == year).Select(v => (int?)v.DsNr).MaxAsync() ?? 0) + 1;
} }
public async Task<int> NextHistNr() {
return (await MemberHistory.Select(c => (int?)c.HistNr).MaxAsync() ?? 0) + 1;
}
public IAsyncEnumerable<Branch> FetchBranches(string? zwstid = null, bool includeWithoutMembers = true) { public IAsyncEnumerable<Branch> FetchBranches(string? zwstid = null, bool includeWithoutMembers = true) {
return _compiledQueryBranches.Invoke(this, zwstid, includeWithoutMembers); return _compiledQueryBranches.Invoke(this, zwstid, includeWithoutMembers);
} }
@@ -338,9 +358,9 @@ namespace Elwig.Helpers {
public IAsyncEnumerable<Member> FetchMembers(int? mgnr = null, bool includeNotActive = false, bool includeContactInfo = false) { public IAsyncEnumerable<Member> FetchMembers(int? mgnr = null, bool includeNotActive = false, bool includeContactInfo = false) {
if (includeContactInfo) { if (includeContactInfo) {
return _compiledQueryMembersContactInfo.Invoke(this, mgnr, includeNotActive); return _compiledQueryMembersContactInfo.Invoke(this, mgnr, mgnr != null || includeNotActive);
} else { } else {
return _compiledQueryMembers.Invoke(this, mgnr, includeNotActive); return _compiledQueryMembers.Invoke(this, mgnr, mgnr != null || includeNotActive);
} }
} }
@@ -400,10 +420,26 @@ namespace Elwig.Helpers {
} }
} }
private async Task FetchMemberHistory(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();
var history = new Dictionary<int, MemberHistoryPoint>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"SELECT mgnr, shares, shares_red, shares_white, shares_dormant FROM v_member_history WHERE year = {year}";
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync()) {
var mgnr = reader.GetInt32(0);
history[mgnr] = new(reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3), reader.GetInt32(4));
}
}
if (ownCnx) await cnx.DisposeAsync();
_memberHistory[year] = history;
}
private async Task FetchMemberAreaCommitmentBuckets(int year, SqliteConnection? cnx = null) { private async Task FetchMemberAreaCommitmentBuckets(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null; var ownCnx = cnx == null;
cnx ??= await ConnectAsync(); cnx ??= await ConnectAsync();
var buckets = new Dictionary<int, Dictionary<string, AreaComBucket>>(); var buckets = new Dictionary<int, Dictionary<string, AreaCmtBucket>>();
using (var cmd = cnx.CreateCommand()) { using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"SELECT mgnr, bucket, area, min_kg, max_kg FROM v_area_commitment_bucket WHERE year = {year}"; cmd.CommandText = $"SELECT mgnr, bucket, area, min_kg, max_kg FROM v_area_commitment_bucket WHERE year = {year}";
using var reader = await cmd.ExecuteReaderAsync(); using var reader = await cmd.ExecuteReaderAsync();
@@ -490,7 +526,51 @@ namespace Elwig.Helpers {
_memberUnderDelivery[year] = buckets; _memberUnderDelivery[year] = buckets;
} }
public async Task<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) { public async Task<MemberHistoryPoint> GetMemberHistory(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberHistory.ContainsKey(year))
await FetchMemberHistory(year, cnx);
return _memberHistory[year].GetValueOrDefault(mgnr, new());
}
public async Task<Dictionary<int, SortedDictionary<DateOnly, MemberHistoryPoint>>> GetMemberHistory() {
var today = DateOnly.FromDateTime(DateTime.Today).AddDays(1);
var history = new Dictionary<int, SortedDictionary<DateOnly, MemberHistoryPoint>>();
var members = await Members.ToListAsync();
foreach (var m in members) {
history[m.MgNr] = new() {
[today] = new(m.Shares, m.SharesRed, m.SharesWhite, m.SharesDormant),
};
}
var hist = await MemberHistory.OrderByDescending(h => h.DateNoticeString).ToListAsync();
foreach (var h in hist) {
if (h.FromMgNr is int from) {
var latest = history[from].Last().Value;
switch (h.FromType) {
case BusinessShareType.Normal: latest.Shares += h.Shares; break;
case BusinessShareType.Red: latest.SharesRed += h.Shares; break;
case BusinessShareType.White: latest.SharesWhite += h.Shares; break;
case BusinessShareType.Dormant: latest.SharesDormant += h.Shares; break;
}
history[from][h.DateNotice] = latest;
}
if (h.ToMgNr is int to) {
var latest = history[to].Last().Value;
switch (h.ToType) {
case BusinessShareType.Normal: latest.Shares -= h.Shares; break;
case BusinessShareType.Red: latest.SharesRed -= h.Shares; break;
case BusinessShareType.White: latest.SharesWhite -= h.Shares; break;
case BusinessShareType.Dormant: latest.SharesDormant -= h.Shares; break;
}
history[to][h.DateNotice] = latest;
}
}
return history;
}
public async Task<Dictionary<string, AreaCmtBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberAreaCommitmentBuckets.ContainsKey(year)) if (!_memberAreaCommitmentBuckets.ContainsKey(year))
await FetchMemberAreaCommitmentBuckets(year, cnx); await FetchMemberAreaCommitmentBuckets(year, cnx);
return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, []); return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, []);
+103 -28
View File
@@ -1,44 +1,59 @@
using Elwig.Services;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public static class AppDbUpdater { public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat! // Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 39; public static readonly int RequiredSchemaVersion = 42;
private static int VersionOffset = 0; private static int VersionOffset = 0;
public static async Task<Version> CheckDb() { public static async Task<Version> CheckDb() {
using var cnx = AppDbContext.Connect(); if (!File.Exists(App.Config.DatabaseFile))
throw new FileNotFoundException($"Die Datei \"{App.Config.DatabaseFile}\" exisitiert nicht");
var applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0; long? applId, schemaVers;
if (applId != 0x454C5747) throw new Exception($"Invalid application_id in database (0x{applId:X08})"); using (var cnx = await AppDbContext.ConnectAsync()) {
var schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0; await cnx.QuickCheck();
applId = (long?)await cnx.ExecuteScalar("PRAGMA application_id") ?? 0;
if (applId != 0x454C5747)
throw new FileFormatException($"Invalid application_id in database (0x{applId:X08})");
schemaVers = (long?)await cnx.ExecuteScalar("PRAGMA schema_version") ?? 0;
VersionOffset = (int)(schemaVers % 100); VersionOffset = (int)(schemaVers % 100);
if (VersionOffset != 0) { if (VersionOffset != 0) {
// schema was modified manually/externally // schema was modified manually/externally
// TODO issue warning // TODO issue warning
} }
await UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion); }
await UpdateDbSchema((int)(schemaVers / 100), RequiredSchemaVersion);
Version v;
using (var cnx = await AppDbContext.ConnectAsync()) {
var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0; var userVers = (long?)await cnx.ExecuteScalar("PRAGMA user_version") ?? 0;
var v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF)); v = new Version((int)(userVers >> 24), (int)((userVers >> 16) & 0xFF), (int)((userVers >> 8) & 0xFF), (int)(userVers & 0xFF));
if (App.Version > v) { if (App.Version > v) {
long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision; long vers = (App.Version.Major << 24) | (App.Version.Minor << 16) | (App.Version.Build << 8) | App.Version.Revision;
await cnx.ExecuteBatch($"PRAGMA user_version = {vers}"); await cnx.ExecuteBatch($"PRAGMA user_version = {vers}");
} }
}
return v; return v;
} }
private static async Task UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) { private static async Task UpdateDbSchema(int fromVersion, int toVersion) {
if (fromVersion == toVersion) { if (fromVersion == toVersion) {
return; return;
} else if (fromVersion > toVersion) { } else if (fromVersion > toVersion) {
@@ -48,43 +63,103 @@ namespace Elwig.Helpers {
} }
var asm = Assembly.GetExecutingAssembly(); var asm = Assembly.GetExecutingAssembly();
(int From, int To, string Name)[] scripts = asm.GetManifestResourceNames() (int From, int To, string Name)[] scripts = [.. asm.GetManifestResourceNames()
.Where(n => n.StartsWith("Elwig.Resources.Sql.")) .Where(n => n.StartsWith("Elwig.Resources.Sql."))
.Select(n => { .Select(n => {
var p = n.Split(".")[^2].Split("-"); var p = n.Split(".")[^2].Split("-");
return (int.Parse(p[0]), int.Parse(p[1]), n); return (int.Parse(p[0]), int.Parse(p[1]), n);
}) })
.OrderBy(s => s.Item1).ThenBy(s => s.Item2) .OrderBy(s => s.Item1).ThenBy(s => s.Item2)];
.ToArray();
List<string> toExecute = []; List<(int ToVersion, string File)> toExecute = [];
var vers = fromVersion; var vers = fromVersion;
while (vers < toVersion) { while (vers < toVersion) {
var (_, to, name) = scripts.Where(s => s.From == vers).Last(); var (_, to, name) = scripts.Last(s => s.From == vers);
toExecute.Add(name); toExecute.Add((to, name));
vers = to; vers = to;
} }
if (toExecute.Count == 0) if (toExecute.Count == 0)
return; return;
await cnx.ExecuteBatch(""" var backup = Path.ChangeExtension(App.Config.DatabaseFile, $".v{fromVersion}.sqlite3");
PRAGMA locking_mode = EXCLUSIVE; File.Copy(App.Config.DatabaseFile, backup, true);
BEGIN EXCLUSIVE; try {
"""); using var cnx = await AppDbContext.ConnectAsync();
foreach (var script in toExecute) { await cnx.ExecuteBatch("PRAGMA locking_mode = EXCLUSIVE");
await cnx.IntegrityCheck();
await cnx.ForeignKeyCheck();
foreach (var (to, script) in toExecute) {
await cnx.ExecuteEmbeddedScript(asm, script); await cnx.ExecuteEmbeddedScript(asm, script);
if (to == 42) {
await UpdateDbSchema_41_To_42(cnx);
} }
var violations = await cnx.ForeignKeyCheck();
if (violations.Length > 0) {
throw new Exception($"Foreign key violations ({violations.Length}):\n" + string.Join("\n", violations
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
} }
await cnx.ExecuteBatch($""" await cnx.IntegrityCheck();
COMMIT; await cnx.ForeignKeyCheck();
VACUUM;
PRAGMA schema_version = {toVersion * 100 + VersionOffset}; await cnx.ExecuteBatch("VACUUM");
"""); await cnx.ExecuteBatch($"PRAGMA schema_version = {toVersion * 100 + VersionOffset}");
} catch (Exception) {
File.Move(backup, App.Config.DatabaseFile, true);
throw;
} finally {
File.Delete(backup);
}
}
private static async Task UpdateDbSchema_41_To_42(SqliteConnection cnx) {
List<(int MgNr, string LfbisNr, string Name, string Address, int Plz)> members = [];
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = """
SELECT m.mgnr, m.lfbis_nr,
COALESCE(a.name, (COALESCE(m.prefix || ' ', '') || COALESCE(m.given_name || ' ', '') || m.name || COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, ''))),
COALESCE(a.address, m.address),
COALESCE(aplz.plz, mplz.plz)
FROM member m
JOIN postal_dest mp ON (mp.country, mp.id) = (m.country, m.postal_dest)
JOIN AT_plz_dest ma ON (ma.country, ma.id) = (mp.country, mp.id)
JOIN AT_plz mplz ON mplz.plz = ma.plz
LEFT JOIN member_billing_address a ON a.mgnr = m.mgnr
LEFT JOIN postal_dest ap ON (ap.country, ap.id) = (a.country, a.postal_dest)
LEFT JOIN AT_plz_dest aa ON (aa.country, aa.id) = (ap.country, mp.id)
LEFT JOIN AT_plz aplz ON aplz.plz = aa.plz
WHERE organic
""";
using var reader = await cmd.ExecuteReaderAsync();
var header = await reader.GetColumnSchemaAsync();
while (await reader.ReadAsync()) {
members.Add((reader.GetInt32(0), reader.GetString(1), reader.GetString(2), reader.GetString(3), reader.GetInt32(4)));
}
}
if (members.Count > 0)
InteractionService.ShowInformation("Datenbank-Update", "Es wird versucht, die Bio-Kontrollstellen-Informationen der Mitglieder automatisch abzurufen. Dies könnte einen kurzen Moment dauern.");
try {
Mouse.OverrideCursor = Cursors.Wait;
foreach (var m in members) {
string? oocId = null;
string? authCode = null;
var certs = await OrganicService.FetchTracesCertificates(lfbisNr: m.LfbisNr, address: (m.Name, m.Address, m.Plz.ToString()));
if (certs.Length > 0) {
oocId = certs[0].OperatorId;
authCode = certs[0].AuthorityCode;
}
if (oocId != null || authCode != null) {
oocId = oocId == null ? "NULL" : $"'{oocId}'";
authCode = authCode == null ? "NULL" : $"'{authCode}'";
await cnx.ExecuteBatch($"UPDATE member SET ooc_id = {oocId}, org_auth_code = {authCode} WHERE mgnr = {m.MgNr}");
}
}
} finally {
Mouse.OverrideCursor = null;
}
} }
} }
} }
+27 -43
View File
@@ -13,32 +13,32 @@ namespace Elwig.Helpers.Billing {
protected readonly Season Season; protected readonly Season Season;
protected readonly Dictionary<string, string> Attributes; protected readonly Dictionary<string, string> Attributes;
protected readonly Dictionary<string, (decimal?, decimal?)> Modifiers; protected readonly Dictionary<string, (decimal?, decimal?)> Modifiers;
protected readonly Dictionary<string, (string, string?, string?, int?, decimal?)> AreaComTypes; protected readonly Dictionary<string, (string, string?, string?, int?, decimal?)> AreaCmtTypes;
protected Billing(int year, Season season, protected Billing(int year, Season season,
Dictionary<string, string> attributes, Dictionary<string, string> attributes,
Dictionary<string, (decimal?, decimal?)> modifiers, Dictionary<string, (decimal?, decimal?)> modifiers,
Dictionary<string, (string, string?, string?, int?, decimal?)> areaComTypes Dictionary<string, (string, string?, string?, int?, decimal?)> areaCmtTypes
) { ) {
Year = year; Year = year;
Season = season; Season = season;
Attributes = attributes; Attributes = attributes;
Modifiers = modifiers; Modifiers = modifiers;
AreaComTypes = areaComTypes; AreaCmtTypes = areaCmtTypes;
} }
protected static async Task<(Season, Dictionary<string, string>, Dictionary<string, (decimal?, decimal?)>, Dictionary<string, (string, string?, string?, int?, decimal?)>)> LoadData(AppDbContext ctx, int year) { protected static async Task<(Season, Dictionary<string, string>, Dictionary<string, (decimal?, decimal?)>, Dictionary<string, (string, string?, string?, int?, decimal?)>)> LoadData(AppDbContext ctx, int year) {
var season = await ctx.FetchSeasons(year).SingleOrDefaultAsync() ?? throw new ArgumentException("Invalid season"); var season = await ctx.FetchSeasons(year).SingleOrDefaultAsync() ?? throw new ArgumentException("Invalid season");
var attributes = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.AttrId, a => a.Name); var attributes = await ctx.FetchWineAttributes().ToDictionaryAsync(a => a.AttrId, a => a.Name);
var modifiers = await ctx.FetchModifiers(year).ToDictionaryAsync(m => m.ModId, m => (m.Abs, m.Rel)); var modifiers = await ctx.FetchModifiers(year).ToDictionaryAsync(m => m.ModId, m => (m.Abs, m.Rel));
var areaComTypes = ctx.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount)); var areaCmtTypes = ctx.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount));
return (season, attributes, modifiers, areaComTypes); return (season, attributes, modifiers, areaCmtTypes);
} }
public static async Task<Billing> Create(int year) { public static async Task<Billing> Create(int year) {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var (season, attributes, modifiers, areaComTypes) = await LoadData(ctx, year); var (season, attributes, modifiers, areaCmtTypes) = await LoadData(ctx, year);
return new Billing(year, season, attributes, modifiers, areaComTypes); return new Billing(year, season, attributes, modifiers, areaCmtTypes);
} }
public async Task FinishSeason() { public async Task FinishSeason() {
@@ -50,56 +50,40 @@ namespace Elwig.Helpers.Billing {
"""); """);
} }
public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceBs = 0, int allowanceKgPerBs = 0, double allowanceRel = 0, int addMinBs = 1) { public async Task AutoAdjustBusinessShares(DateOnly date, int allowanceKg = 0, double allowanceShares = 0, int allowanceKgPerShare = 0, double allowanceRel = 0, int addMinShares = 1) {
if (addMinBs < 1) addMinBs = 1; if (addMinShares < 1) addMinShares = 1;
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch($""" await cnx.ExecuteBatch($"""
UPDATE member DELETE FROM member_history WHERE source = 'elwig' AND reason = '{MemberHistory.REASON_PRESCRIPTION}' AND deduct_year = {Year};
SET business_shares = member.business_shares - h.business_shares INSERT INTO member_history (histnr, from_mgnr, from_type, to_mgnr, to_type, date_notice, date_effective, reason, source, deduct_year, shares, value_per_share, currency, comment)
FROM member_history h SELECT COALESCE((SELECT MAX(histnr) AS histnr FROM member_history), 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr),
WHERE h.date = '{Year}-11-30' AND h.type = 'auto' AND h.mgnr = member.mgnr AND member.active; NULL, NULL, u.mgnr, 1, '{date:yyyy-MM-dd}', '{date:yyyy-MM-dd}', '{MemberHistory.REASON_PRESCRIPTION}', 'elwig', s.year,
CEIL((u.diff - {allowanceKg}.0 - {allowanceKgPerShare}.0 * u.shares) / s.max_kg_per_share
INSERT INTO member_history (mgnr, date, type, business_shares) - {allowanceShares.ToString(CultureInfo.InvariantCulture)}
SELECT u.mgnr, - {allowanceRel.ToString(CultureInfo.InvariantCulture)} * u.shares) AS adjust_shares,
'{date:yyyy-MM-dd}', s.share_value / POW(10, s.precision - 2), s.currency, 'durch automatische Nachzeichnung'
'auto',
CEIL((u.diff - {allowanceKg}.0 - {allowanceKgPerBs}.0 * u.business_shares) / s.max_kg_per_bs
- {allowanceBs.ToString(CultureInfo.InvariantCulture)}
- {allowanceRel.ToString(CultureInfo.InvariantCulture)} * u.business_shares) AS bs
FROM v_total_under_delivery u FROM v_total_under_delivery u
JOIN season s ON s.year = u.year JOIN season s ON s.year = u.year
JOIN member m ON m.mgnr = u.mgnr JOIN member m ON m.mgnr = u.mgnr
WHERE s.year = {Year} AND bs >= {addMinBs} AND m.active WHERE s.year = {Year} AND adjust_shares >= {addMinShares} AND m.active
ON CONFLICT DO UPDATE
SET business_shares = excluded.business_shares;
UPDATE member
SET business_shares = member.business_shares + h.business_shares
FROM member_history h
WHERE h.date = '{Year}-11-30' AND h.type = 'auto' AND h.mgnr = member.mgnr;
"""); """);
} }
public async Task UnAdjustBusinessShares() { public async Task UnAdjustBusinessShares() {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch($""" await cnx.ExecuteBatch($"""
UPDATE member DELETE FROM member_history WHERE source = 'elwig' AND reason = '{MemberHistory.REASON_PRESCRIPTION}' AND deduct_year = {Year};
SET business_shares = member.business_shares - h.business_shares
FROM member_history h
WHERE h.date = '{Year}-11-30' AND h.type = 'auto' AND h.mgnr = member.mgnr AND member.active;
DELETE FROM member_history WHERE date = '{Year}-11-30' AND type = 'auto';
"""); """);
} }
public async Task CalculateBuckets( public async Task CalculateBuckets(
bool? honorGebundenField = null, bool? honorContractedField = null,
bool? allowAttributesIntoLower = null, bool? allowAttributesIntoLower = null,
bool? avoidUnderDeliveries = null, bool? avoidUnderDeliveries = null,
SqliteConnection? cnx = null SqliteConnection? cnx = null
) { ) {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var honorGebunden = honorGebundenField ?? Season.Billing_HonorGebunden; var honorContracted = honorContractedField ?? Season.Billing_HonorContracted;
var allowAttrsIntoLower = allowAttributesIntoLower ?? Season.Billing_AllowAttrsIntoLower; var allowAttrsIntoLower = allowAttributesIntoLower ?? Season.Billing_AllowAttrsIntoLower;
var avoidUnderDlvrs = avoidUnderDeliveries ?? Season.Billing_AvoidUnderDeliveries; var avoidUnderDlvrs = avoidUnderDeliveries ?? Season.Billing_AvoidUnderDeliveries;
var attrVals = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower)); var attrVals = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower));
@@ -112,10 +96,10 @@ namespace Elwig.Helpers.Billing {
var deliveries = new List<(int, int, int, string, int, double, string, string?, string[], bool?)>(); var deliveries = new List<(int, int, int, string, int, double, string, string?, string[], bool?)>();
using (var cmd = cnx.CreateCommand()) { using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $""" cmd.CommandText = $"""
SELECT mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden SELECT mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, contracted
FROM v_delivery FROM v_delivery
WHERE year = {Year} WHERE year = {Year}
ORDER BY mgnr, sortid, abgewertet ASC, {(honorGebunden ? "gebunden IS NOT NULL DESC, gebunden DESC," : "")} ORDER BY mgnr, sortid, abgewertet ASC, {(honorContracted ? "contracted IS NOT NULL DESC, contracted DESC," : "")}
attribute_prio DESC, COALESCE(attrid, '~'), kmw DESC, lsnr, dpnr attribute_prio DESC, COALESCE(attrid, '~'), kmw DESC, lsnr, dpnr
"""; """;
using var reader = await cmd.ExecuteReaderAsync(); using var reader = await cmd.ExecuteReaderAsync();
@@ -131,14 +115,14 @@ namespace Elwig.Helpers.Billing {
} }
int lastMgNr = 0; int lastMgNr = 0;
Dictionary<string, AreaComBucket>? rightsAndObligations = null; Dictionary<string, AreaCmtBucket>? rightsAndObligations = null;
Dictionary<string, int> used = []; Dictionary<string, int> used = [];
foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden) in deliveries) { foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, contracted) in deliveries) {
if (lastMgNr != mgnr) { if (lastMgNr != mgnr) {
rightsAndObligations = await ctx.GetMemberAreaCommitmentBuckets(Year, mgnr); rightsAndObligations = await ctx.GetMemberAreaCommitmentBuckets(Year, mgnr);
used = []; used = [];
} }
if ((honorGebunden && gebunden == false) || if ((honorContracted && contracted == false) ||
rightsAndObligations == null || rightsAndObligations.Count == 0 || rightsAndObligations == null || rightsAndObligations.Count == 0 ||
qualid == "WEI" || qualid == "RSW" || qualid == "LDW") qualid == "WEI" || qualid == "RSW" || qualid == "LDW")
{ {
@@ -156,7 +140,7 @@ namespace Elwig.Helpers.Billing {
foreach (var p in Utils.Permutate(attributes, attributes.Intersect(attrForced))) { foreach (var p in Utils.Permutate(attributes, attributes.Intersect(attrForced))) {
var c = p.Count(); var c = p.Count();
var key = sortid + string.Join("", p); var key = sortid + string.Join("", p);
if (rightsAndObligations.TryGetValue(key, out AreaComBucket value)) { if (rightsAndObligations.TryGetValue(key, out AreaCmtBucket value)) {
int i = (c == 0) ? 1 : 2; int i = (c == 0) ? 1 : 2;
var u = used.GetValueOrDefault(key, 0); var u = used.GetValueOrDefault(key, 0);
var vr = Math.Max(0, Math.Min(value.Right - u, w)); var vr = Math.Max(0, Math.Min(value.Right - u, w));
+35 -33
View File
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -13,7 +14,7 @@ namespace Elwig.Helpers.Billing {
public enum CalculationMode { Elwig, WgMaster } public enum CalculationMode { Elwig, WgMaster }
public enum CurveMode { Oe, Kmw } public enum CurveMode { Oe, Kmw }
public record struct Curve(CurveMode Mode, Dictionary<double, decimal> Normal, Dictionary<double, decimal>? Gebunden); public record struct Curve(CurveMode Mode, Dictionary<double, decimal> Normal, Dictionary<double, decimal>? Contracted);
public static JsonSchema? Schema { get; private set; } public static JsonSchema? Schema { get; private set; }
@@ -92,6 +93,14 @@ namespace Elwig.Helpers.Billing {
Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster; Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster;
} }
public BillingData() {
Data = new JsonObject {
["mode"] = "elwig",
["version"] = 1,
};
Mode = CalculationMode.Elwig;
}
protected static JsonObject ParseJson(string json) { protected static JsonObject ParseJson(string json) {
if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first"); if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first");
try { try {
@@ -107,6 +116,10 @@ namespace Elwig.Helpers.Billing {
return new(ParseJson(json)); return new(ParseJson(json));
} }
public string ToJsonString(JsonSerializerOptions? options = null) {
return Data.ToJsonString(options);
}
protected JsonArray GetCurvesEntry() { protected JsonArray GetCurvesEntry() {
return Data[Mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException(); return Data[Mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException();
} }
@@ -166,10 +179,10 @@ namespace Elwig.Helpers.Billing {
} else { } else {
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
var geb = obj["geb"]; var ctrd = obj["ctrd"] ?? obj["geb"];
if (geb is JsonObject) { if (ctrd is JsonObject) {
c2 = GetCurveData(geb.AsObject(), cMode); c2 = GetCurveData(ctrd.AsObject(), cMode);
} else if (geb?.AsValue().TryGetValue(out decimal v) == true) { } else if (ctrd?.AsValue().TryGetValue(out decimal v) == true) {
var splitVal = GetCurveValueAt(c1, quw); var splitVal = GetCurveValueAt(c1, quw);
c2 = c1.ToDictionary(e => e.Key, e => e.Value + (e.Key >= quw ? v : 0)); c2 = c1.ToDictionary(e => e.Key, e => e.Value + (e.Key >= quw ? v : 0));
c2[quw] = splitVal + v; c2[quw] = splitVal + v;
@@ -261,7 +274,7 @@ namespace Elwig.Helpers.Billing {
protected static JsonNode GraphEntryToJson(GraphEntry entry) { protected static JsonNode GraphEntryToJson(GraphEntry entry) {
try { try {
if (entry.GebundenFlatBonus == null) { if (entry.ContractedFlatBonus == null) {
return JsonValue.Create((decimal)entry.DataGraph.DataY.Distinct().Single()); return JsonValue.Create((decimal)entry.DataGraph.DataY.Distinct().Single());
} }
} catch { } } catch { }
@@ -273,10 +286,10 @@ namespace Elwig.Helpers.Billing {
curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower()); curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower());
if (entry.GebundenFlatBonus != null) { if (entry.ContractedFlatBonus != null) {
curve["geb"] = (decimal)entry.GebundenFlatBonus; curve["ctrd"] = (decimal)entry.ContractedFlatBonus;
} else if (entry.GebundenGraph != null) { } else if (entry.ContractedGraph != null) {
curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower()); curve["ctrd"] = GraphToJson(entry.ContractedGraph, entry.Mode.ToString().ToLower());
} }
return curve; return curve;
@@ -412,7 +425,7 @@ namespace Elwig.Helpers.Billing {
} }
} }
public static JsonObject FromGraphEntries( public static BillingData FromGraphEntries(
IEnumerable<GraphEntry> graphEntries, IEnumerable<GraphEntry> graphEntries,
BillingData? origData = null, BillingData? origData = null,
IEnumerable<RawVaribute>? vaributes = null, IEnumerable<RawVaribute>? vaributes = null,
@@ -446,40 +459,29 @@ namespace Elwig.Helpers.Billing {
} }
} }
CollapsePaymentData(payment, payment.DeepClone().AsObject(), vaributes ?? payment.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultPayment); CollapsePaymentData(payment, payment.DeepClone().AsObject(), vaributes ?? [.. payment.Select(e => new RawVaribute(e.Key))], useDefaultPayment);
CollapsePaymentData(qualityWei, qualityWei.DeepClone().AsObject(), vaributes ?? qualityWei.Select(e => new RawVaribute(e.Key)).ToList(), useDefaultQuality); CollapsePaymentData(qualityWei, qualityWei.DeepClone().AsObject(), vaributes ?? [.. qualityWei.Select(e => new RawVaribute(e.Key))], useDefaultQuality);
var data = new JsonObject {
["mode"] = "elwig",
["version"] = 1,
};
if (origData?.ConsiderDelieryModifiers == true)
data["consider_delivery_modifiers"] = true;
if (origData?.ConsiderContractPenalties == true)
data["consider_contract_penalties"] = true;
if (origData?.ConsiderTotalPenalty == true)
data["consider_total_penalty"] = true;
if (origData?.ConsiderAutoBusinessShares == true)
data["consider_auto_business_shares"] = true;
BillingData data = origData != null && origData.Mode == CalculationMode.Elwig ? new BillingData((JsonObject)origData.Data.DeepClone()) : new BillingData();
if (payment.Count == 0) { if (payment.Count == 0) {
data["payment"] = 0; data.Data["payment"] = 0;
} else if (payment.Count == 1 && payment.First().Key == "default") { } else if (payment.Count == 1 && payment.First().Key == "default") {
data["payment"] = payment.Single().Value?.DeepClone(); data.Data["payment"] = payment.Single().Value?.DeepClone();
} else { } else {
data["payment"] = payment; data.Data["payment"] = payment;
} }
if (qualityWei.Count == 1 && qualityWei.First().Key == "default") { if (qualityWei.Count == 1 && qualityWei.First().Key == "default") {
data["quality"] = new JsonObject() { data.Data["quality"] = new JsonObject() {
["WEI"] = qualityWei.Single().Value?.DeepClone() ["WEI"] = qualityWei.Single().Value?.DeepClone()
}; };
} else if (qualityWei.Count >= 1) { } else if (qualityWei.Count >= 1) {
data["quality"] = new JsonObject() { data.Data["quality"] = new JsonObject() {
["WEI"] = qualityWei ["WEI"] = qualityWei
}; };
} else {
data.Data.Remove("quality");
} }
data["curves"] = curves; data.Data["curves"] = curves;
return data; return data;
} }
+18 -18
View File
@@ -16,9 +16,9 @@ namespace Elwig.Helpers.Billing {
protected BillingVariant(int year, int avnr, Season season, protected BillingVariant(int year, int avnr, Season season,
Dictionary<string, string> attributes, Dictionary<string, string> attributes,
Dictionary<string, (decimal?, decimal?)> modifiers, Dictionary<string, (decimal?, decimal?)> modifiers,
Dictionary<string, (string, string?, string?, int?, decimal?)> areaComTypes, Dictionary<string, (string, string?, string?, int?, decimal?)> areaCmtTypes,
PaymentVar paymentVar, PaymentBillingData data) : PaymentVar paymentVar, PaymentBillingData data) :
base(year, season, attributes, modifiers, areaComTypes) { base(year, season, attributes, modifiers, areaCmtTypes) {
AvNr = avnr; AvNr = avnr;
PaymentVariant = paymentVar; PaymentVariant = paymentVar;
Data = data; Data = data;
@@ -32,16 +32,16 @@ namespace Elwig.Helpers.Billing {
public static async Task<BillingVariant> Create(int year, int avnr) { public static async Task<BillingVariant> Create(int year, int avnr) {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var (season, attributes, modifiers, areaComTypes) = await LoadData(ctx, year); var (season, attributes, modifiers, areaCmtTypes) = await LoadData(ctx, year);
var (paymentVar, data) = await LoadData(ctx, year, avnr); var (paymentVar, data) = await LoadData(ctx, year, avnr);
return new BillingVariant(year, avnr, season, attributes, modifiers, areaComTypes, paymentVar, data); return new BillingVariant(year, avnr, season, attributes, modifiers, areaCmtTypes, paymentVar, data);
} }
public async Task Calculate(bool strictPrices = true, bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) { public async Task Calculate(bool strictPrices = true, bool? honorContracted = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
if (PaymentVariant == null || Data == null) throw new Exception("Call Load before Calculate"); if (PaymentVariant == null || Data == null) throw new Exception("Call Load before Calculate");
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
using var tx = await cnx.BeginTransactionAsync(); using var tx = await cnx.BeginTransactionAsync();
await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx); await CalculateBuckets(honorContracted, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
await DeleteInDb(cnx); await DeleteInDb(cnx);
await SetCalcTime(cnx); await SetCalcTime(cnx);
KeyNotFoundException? exception = null; KeyNotFoundException? exception = null;
@@ -73,10 +73,10 @@ namespace Elwig.Helpers.Billing {
v.avnr, v.avnr,
ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount, ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
IIF(lc.amount < 0, 0, ROUND(lp.amount / POW(10, s.precision - 2))) AS prev_net_amount, IIF(lc.amount < 0, 0, ROUND(lp.amount / POW(10, s.precision - 2))) AS prev_net_amount,
IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat, IIF(m.accounting, s.vat_normal, s.vat_flatrate) AS vat,
ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) + ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) + ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) + ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0)) +
IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0) IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
AS modifiers, AS modifiers,
IIF(lc.amount < 0, 0, lc.modifiers) AS prev_modifiers IIF(lc.amount < 0, 0, lc.modifiers) AS prev_modifiers
@@ -98,7 +98,7 @@ namespace Elwig.Helpers.Billing {
LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr) LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr) LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr)
LEFT JOIN payment_custom x ON (x.year, x.mgnr) = (s.year, m.mgnr) LEFT JOIN payment_custom x ON (x.year, x.mgnr) = (s.year, m.mgnr)
WHERE s.year = {Year} AND v.avnr = {AvNr} AND p.amount > COALESCE(lp.amount, 0); WHERE s.year = {Year} AND v.avnr = {AvNr} AND p.amount != COALESCE(lp.amount, 0);
"""); """);
await cnx.ExecuteBatch($""" await cnx.ExecuteBatch($"""
UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr}); UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
@@ -137,11 +137,11 @@ namespace Elwig.Helpers.Billing {
await cnx.ExecuteBatch($""" await cnx.ExecuteBatch($"""
INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel) INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
SELECT c.year, {AvNr}, s.mgnr, 0, SELECT c.year, {AvNr}, s.mgnr, 0,
ROUND(s.sum * COALESCE(m.abs, 0)), ROUND(s.weight_total * COALESCE(m.abs, 0)),
COALESCE(m.rel, 0) COALESCE(m.rel, 0)
FROM (SELECT {Year} AS year, m.mgnr, FROM (SELECT {Year} AS year, m.mgnr,
ROUND(AVG(COALESCE(a.sum, b.sum)) * {multiplier}) AS baseline, ROUND(AVG(COALESCE(a.weight_total, b.weight_total)) * {multiplier}) AS baseline,
COUNT(*) = {lastYears} AND MIN(COALESCE(a.sum, b.sum)) > 0 AS allowed COUNT(*) = {lastYears} AND MIN(COALESCE(a.weight_total, b.weight_total)) > 0 AS allowed
FROM member m FROM member m
LEFT JOIN v_stat_member a ON a.mgnr = m.mgnr 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")} FULL OUTER JOIN v_stat_member b ON b.mgnr = m.predecessor_mgnr AND b.year = a.year AND {(includePredecessor ? "TRUE" : "FALSE")}
@@ -150,7 +150,7 @@ namespace Elwig.Helpers.Billing {
HAVING allowed) c HAVING allowed) c
JOIN v_stat_member s ON (s.year, s.mgnr) = (c.year, c.mgnr) 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}' LEFT JOIN modifier m ON m.year = c.year AND m.name LIKE '{modName}'
WHERE sum >= baseline WHERE weight_total >= baseline
ON CONFLICT DO UPDATE ON CONFLICT DO UPDATE
SET mod_abs = mod_abs + excluded.mod_abs, SET mod_abs = mod_abs + excluded.mod_abs,
mod_rel = mod_rel + excluded.mod_rel mod_rel = mod_rel + excluded.mod_rel
@@ -170,7 +170,7 @@ namespace Elwig.Helpers.Billing {
protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) { protected async Task CalculatePrices(SqliteConnection cnx, bool strict = true) {
var invalid = new HashSet<string>(); var invalid = new HashSet<string>();
var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>(); var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCmt)>();
using (var cmd = cnx.CreateCommand()) { using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $""" cmd.CommandText = $"""
SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, d.attrid, d.cultid, b.discr, b.value, d.oe, d.kmw, d.qualid, COALESCE(a.area_com, TRUE) SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, d.attrid, d.cultid, b.discr, b.value, d.oe, d.kmw, d.qualid, COALESCE(a.area_com, TRUE)
@@ -195,13 +195,13 @@ namespace Elwig.Helpers.Billing {
foreach (var part in parts) { foreach (var part in parts) {
if (part.Value == 0) if (part.Value == 0)
continue; continue;
var ungeb = part.Discr == "_"; var unctrd = part.Discr == "_";
var payAttrId = (part.Discr is "" or "_") ? null : part.Discr; var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
var attrId = part.AttrAreaCom ? payAttrId : part.AttrId; var attrId = part.AttrAreaCmt ? payAttrId : part.AttrId;
var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom); var ctrd = !unctrd && (payAttrId == attrId || !part.AttrAreaCmt);
decimal price = 0; decimal price = 0;
try { try {
price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw); price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, ctrd, part.Oe, part.Kmw);
} catch (KeyNotFoundException e) { } catch (KeyNotFoundException e) {
invalid.Add(e.Message.Split('\'')[1]); invalid.Add(e.Message.Split('\'')[1]);
} }
+17 -17
View File
@@ -7,7 +7,7 @@ namespace Elwig.Helpers.Billing {
public class GraphEntry { public class GraphEntry {
public const int MinX = 50; public const int MinX = 50;
public const int MinXGeb = 73; public const int MinXCtrd = 73;
public const int MaxX = 120; public const int MaxX = 120;
public int Id { get; set; } public int Id { get; set; }
@@ -15,11 +15,11 @@ namespace Elwig.Helpers.Billing {
public bool Abgewertet { get; set; } public bool Abgewertet { get; set; }
public Graph DataGraph { get; set; } public Graph DataGraph { get; set; }
public Graph? GebundenGraph { get; set; } public Graph? ContractedGraph { get; set; }
public double? GebundenFlatBonus { public double? ContractedFlatBonus {
get { get {
try { try {
var val = GebundenGraph?.DataX.Zip(GebundenGraph.DataY) var val = ContractedGraph?.DataX.Zip(ContractedGraph.DataY)
.Select(e => Math.Round(e.Second - DataGraph.GetPriceAtOe(e.First), Precision)) .Select(e => Math.Round(e.Second - DataGraph.GetPriceAtOe(e.First), Precision))
.Distinct() .Distinct()
.Single(); .Single();
@@ -30,10 +30,10 @@ namespace Elwig.Helpers.Billing {
} }
set { set {
if (value is not double v) return; if (value is not double v) return;
var values = Enumerable.Range(MinXGeb, MaxX - MinXGeb + 1) var values = Enumerable.Range(MinXCtrd, MaxX - MinXCtrd + 1)
.Select(i => Math.Round(DataGraph.GetPriceAtOe(i) + v, Precision)) .Select(i => Math.Round(DataGraph.GetPriceAtOe(i) + v, Precision))
.ToArray(); .ToArray();
GebundenGraph = new Graph(values, Precision, MinXGeb, MaxX); ContractedGraph = new Graph(values, Precision, MinXCtrd, MaxX);
} }
} }
@@ -52,39 +52,39 @@ namespace Elwig.Helpers.Billing {
Vaributes = []; Vaributes = [];
} }
public GraphEntry(int id, int precision, BillingData.CurveMode mode, Dictionary<double, decimal> data, Dictionary<double, decimal>? gebunden) : public GraphEntry(int id, int precision, BillingData.CurveMode mode, Dictionary<double, decimal> data, Dictionary<double, decimal>? contracted) :
this(id, precision, mode) { this(id, precision, mode) {
DataGraph = new Graph(data, precision, MinX, MaxX); DataGraph = new Graph(data, precision, MinX, MaxX);
if (gebunden != null) GebundenGraph = new Graph(gebunden, precision, MinXGeb, MaxX); if (contracted != null) ContractedGraph = new Graph(contracted, precision, MinXCtrd, MaxX);
} }
public GraphEntry(int id, int precision, BillingData.Curve curve, List<Varibute> vaributes) : public GraphEntry(int id, int precision, BillingData.Curve curve, List<Varibute> vaributes) :
this(id, precision, curve.Mode) { this(id, precision, curve.Mode) {
DataGraph = new Graph(curve.Normal, precision, MinX, MaxX); DataGraph = new Graph(curve.Normal, precision, MinX, MaxX);
if (curve.Gebunden != null) if (curve.Contracted != null)
GebundenGraph = new Graph(curve.Gebunden, precision, MinXGeb, MaxX); ContractedGraph = new Graph(curve.Contracted, precision, MinXCtrd, MaxX);
Vaributes = vaributes; Vaributes = vaributes;
} }
private GraphEntry(int id, int precision, BillingData.CurveMode mode, Graph dataGraph, Graph? gebundenGraph, List<Varibute> vaributes) { private GraphEntry(int id, int precision, BillingData.CurveMode mode, Graph dataGraph, Graph? contractedGraph, List<Varibute> vaributes) {
Id = id; Id = id;
Precision = precision; Precision = precision;
Mode = mode; Mode = mode;
DataGraph = dataGraph; DataGraph = dataGraph;
GebundenGraph = gebundenGraph; ContractedGraph = contractedGraph;
Vaributes = vaributes; Vaributes = vaributes;
} }
public void AddGebundenGraph() { public void AddContractedGraph() {
GebundenGraph ??= new Graph(Precision, MinXGeb, MaxX); ContractedGraph ??= new Graph(Precision, MinXCtrd, MaxX);
} }
public void RemoveGebundenGraph() { public void RemoveContractedGraph() {
GebundenGraph = null; ContractedGraph = null;
} }
public GraphEntry Copy(int id) { public GraphEntry Copy(int id) {
return new GraphEntry(id, Precision, Mode, (Graph)DataGraph.Clone(), (Graph?)GebundenGraph?.Clone(), []); return new GraphEntry(id, Precision, Mode, (Graph)DataGraph.Clone(), (Graph?)ContractedGraph?.Clone(), []);
} }
} }
} }
+2 -2
View File
@@ -45,9 +45,9 @@ namespace Elwig.Helpers.Billing {
return dict; return dict;
} }
public decimal CalculatePrice(string sortid, string? attrid, string? cultid, string qualid, bool gebunden, double oe, double kmw) { public decimal CalculatePrice(string sortid, string? attrid, string? cultid, string qualid, bool contracted, double oe, double kmw) {
var curve = GetQualityCurve(qualid, sortid, attrid, cultid) ?? GetCurve(sortid, attrid, cultid); var curve = GetQualityCurve(qualid, sortid, attrid, cultid) ?? GetCurve(sortid, attrid, cultid);
return GetCurveValueAt((gebunden ? curve.Gebunden : null) ?? curve.Normal, curve.Mode == CurveMode.Oe ? oe : kmw); return GetCurveValueAt((contracted ? curve.Contracted : null) ?? curve.Normal, curve.Mode == CurveMode.Oe ? oe : kmw);
} }
private Curve LookupCurve(JsonValue val) { private Curve LookupCurve(JsonValue val) {
+47 -9
View File
@@ -8,16 +8,18 @@ using System.Threading.Tasks;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public class ClientParameters { public class ClientParameters {
public enum Type { Matzen, Winzerkeller, Weinland, Baden }; public enum Type { Matzen, Winzerkeller, Weinland, Baden, Seewinkel };
public bool IsMatzen => Client == Type.Matzen; public bool IsMatzen => Client == Type.Matzen;
public bool IsWinzerkeller => Client == Type.Winzerkeller; public bool IsWinzerkeller => Client == Type.Winzerkeller;
public bool IsWeinland => Client == Type.Weinland; public bool IsWeinland => Client == Type.Weinland;
public bool IsBaden => Client == Type.Baden; public bool IsBaden => Client == Type.Baden;
public bool IsSeewinkel => Client == Type.Seewinkel;
public bool IsWolkersdorf => IsWinzerkeller && App.ZwstId == "W"; public bool IsWolkersdorf => IsWinzerkeller && App.ZwstId == "W";
public bool IsHaugsdorf => IsWinzerkeller && App.ZwstId == "H"; public bool IsHaugsdorf => IsWinzerkeller && App.ZwstId == "H";
public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S"; public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
public bool IsGrInzersdorf => IsWeinland; public bool IsGrInzersdorf => IsWeinland;
public bool IsPamhagen => IsSeewinkel;
public string NameToken; public string NameToken;
public string NameShort; public string NameShort;
@@ -47,13 +49,19 @@ namespace Elwig.Helpers {
public string? Bic; public string? Bic;
public string? UstIdNr; public string? UstIdNr;
public string? LfbisNr; public string? LfbisNr;
public string? OrganicAuthority; public string? OrganicOperatorId;
public string? OrganicAuthorityCode;
public string? PhoneNr; public string? PhoneNr;
public string? FaxNr; public string? FaxNr;
public string? EmailAddress; public string? EmailAddress;
public string? Website; public string? Website;
public bool EnableMemberHistory;
public int ModeBusinessShares;
public bool HasRedWhite => ModeBusinessShares == 1;
public int ModeMemberDataSheetShares;
public int ModeDeliveryNoteStats; public int ModeDeliveryNoteStats;
public int ModeWineQualityStatistics; public int ModeWineQualityStatistics;
public int OrderingMemberList; public int OrderingMemberList;
@@ -73,7 +81,7 @@ namespace Elwig.Helpers {
public int ExportEbicsVersion; public int ExportEbicsVersion;
public int ExportEbicsAddress; public int ExportEbicsAddress;
public (int? AllowanceKg, double? AllowanceBs, int? AllowanceKgPerBs, double? AllowancePercent, int? MinBs) AutoAdjustBs; public (int? AllowanceKg, double? AllowanceShares, int? AllowanceKgPerShare, double? AllowancePercent, int? MinShares) AutoAdjustShares;
public ClientParameters(AppDbContext ctx) : this(ctx.ClientParameters.ToDictionary(e => e.Param, e => e.Value)) { } public ClientParameters(AppDbContext ctx) : this(ctx.ClientParameters.ToDictionary(e => e.Param, e => e.Value)) { }
@@ -93,6 +101,8 @@ namespace Elwig.Helpers {
Client = Type.Weinland; break; Client = Type.Weinland; break;
case "Winzergenossenschaft Baden - Bad Vöslau": case "Winzergenossenschaft Baden - Bad Vöslau":
Client = Type.Baden; break; Client = Type.Baden; break;
case "Winzerkeller Seewinkel":
Client = Type.Seewinkel; break;
}; };
Plz = int.Parse(parameters["CLIENT_PLZ"] ?? ""); Plz = int.Parse(parameters["CLIENT_PLZ"] ?? "");
@@ -106,8 +116,22 @@ namespace Elwig.Helpers {
UstIdNr = parameters.GetValueOrDefault("CLIENT_USTIDNR"); UstIdNr = parameters.GetValueOrDefault("CLIENT_USTIDNR");
Bic = parameters.GetValueOrDefault("CLIENT_BIC"); Bic = parameters.GetValueOrDefault("CLIENT_BIC");
Iban = parameters.GetValueOrDefault("CLIENT_IBAN"); Iban = parameters.GetValueOrDefault("CLIENT_IBAN");
OrganicAuthority = parameters.GetValueOrDefault("CLIENT_ORGANIC_AUTHORITY"); OrganicOperatorId = parameters.GetValueOrDefault("CLIENT_ORGANIC_OPERATORID");
OrganicAuthorityCode = parameters.GetValueOrDefault("CLIENT_ORGANIC_AUTHORITYCODE");
EnableMemberHistory = (parameters.GetValueOrDefault("ENABLE_MEMBERHISTORY")?.ToUpper()) switch {
"1" or "TRUE" or "YES" or "JA" => true,
_ => false,
};
switch (parameters.GetValueOrDefault("MODE_BUSINESSSHARES", "SIMPLE")?.ToUpper()) {
case "SIMPLE": ModeBusinessShares = 0; break;
case "RED_WHITE": ModeBusinessShares = 1; break;
}
switch (parameters.GetValueOrDefault("MODE_MEMBERDATASHEET_SHARES", "FULL")?.ToUpper()) {
case "NONE": ModeMemberDataSheetShares = 0; break;
case "FULL": ModeMemberDataSheetShares = 1; break;
}
switch (parameters.GetValueOrDefault("MODE_DELIVERYNOTE_STATS", "SHORT")?.ToUpper()) { switch (parameters.GetValueOrDefault("MODE_DELIVERYNOTE_STATS", "SHORT")?.ToUpper()) {
case "NONE": ModeDeliveryNoteStats = 0; break; case "NONE": ModeDeliveryNoteStats = 0; break;
case "GA_ONLY": ModeDeliveryNoteStats = 1; break; case "GA_ONLY": ModeDeliveryNoteStats = 1; break;
@@ -172,7 +196,7 @@ namespace Elwig.Helpers {
} }
var autoAdjust = (parameters.GetValueOrDefault("AUTOADJUST_BUSINESSSHARES") ?? "").Split(';'); var autoAdjust = (parameters.GetValueOrDefault("AUTOADJUST_BUSINESSSHARES") ?? "").Split(';');
AutoAdjustBs = autoAdjust.Length == 5 ? ( AutoAdjustShares = autoAdjust.Length == 5 ? (
int.TryParse(autoAdjust[0], out var v1) ? v1 : null, int.TryParse(autoAdjust[0], out var v1) ? v1 : null,
double.TryParse(autoAdjust[1], out var v2) ? v2 : null, double.TryParse(autoAdjust[1], out var v2) ? v2 : null,
int.TryParse(autoAdjust[2], out var v3) ? v3 : null, int.TryParse(autoAdjust[2], out var v3) ? v3 : null,
@@ -185,6 +209,16 @@ namespace Elwig.Helpers {
} }
private IEnumerable<(string, string?)> GetParamValues() { private IEnumerable<(string, string?)> GetParamValues() {
string businessShares = "SIMPLE";
switch (ModeBusinessShares) {
case 0: businessShares = "SIMPLE"; break;
case 1: businessShares = "RED_WHITE"; break;
}
string memberDataSheetShares = "FULL";
switch (ModeMemberDataSheetShares) {
case 0: memberDataSheetShares = "NONE"; break;
case 1: memberDataSheetShares = "FULL"; break;
}
string deliveryNoteStats = "SHORT"; string deliveryNoteStats = "SHORT";
switch (ModeDeliveryNoteStats) { switch (ModeDeliveryNoteStats) {
case 0: deliveryNoteStats = "NONE"; break; case 0: deliveryNoteStats = "NONE"; break;
@@ -231,9 +265,9 @@ namespace Elwig.Helpers {
case 1: exportEbicsAddress = "LINES"; break; case 1: exportEbicsAddress = "LINES"; break;
case 2: exportEbicsAddress = "FULL"; break; case 2: exportEbicsAddress = "FULL"; break;
} }
string autoAdjust = $"{AutoAdjustBs.AllowanceKg};{AutoAdjustBs.AllowanceBs?.ToString(CultureInfo.InvariantCulture)};" + string autoAdjust = $"{AutoAdjustShares.AllowanceKg};{AutoAdjustShares.AllowanceShares?.ToString(CultureInfo.InvariantCulture)};" +
$"{AutoAdjustBs.AllowanceKgPerBs};{AutoAdjustBs.AllowancePercent?.ToString(CultureInfo.InvariantCulture)};" + $"{AutoAdjustShares.AllowanceKgPerShare};{AutoAdjustShares.AllowancePercent?.ToString(CultureInfo.InvariantCulture)};" +
$"{AutoAdjustBs.MinBs}"; $"{AutoAdjustShares.MinShares}";
return [ return [
("CLIENT_NAME_TOKEN", NameToken), ("CLIENT_NAME_TOKEN", NameToken),
("CLIENT_NAME_SHORT", NameShort), ("CLIENT_NAME_SHORT", NameShort),
@@ -251,7 +285,11 @@ namespace Elwig.Helpers {
("CLIENT_USTIDNR", UstIdNr), ("CLIENT_USTIDNR", UstIdNr),
("CLIENT_BIC", Bic), ("CLIENT_BIC", Bic),
("CLIENT_IBAN", Iban), ("CLIENT_IBAN", Iban),
("CLIENT_ORGANIC_AUTHORITY", OrganicAuthority), ("CLIENT_ORGANIC_OPERATORID", OrganicOperatorId),
("CLIENT_ORGANIC_AUTHORITYCODE", OrganicAuthorityCode),
("ENABLE_MEMBERHISTORY", EnableMemberHistory ? "YES" : "NO"),
("MODE_BUSINESSSHARES", businessShares),
("MODE_MEMBERDATASHEET_SHARES", memberDataSheetShares),
("MODE_DELIVERYNOTE_STATS", deliveryNoteStats), ("MODE_DELIVERYNOTE_STATS", deliveryNoteStats),
("MODE_WINEQUALITYSTATISTICS", modeWineQualityStatistics), ("MODE_WINEQUALITYSTATISTICS", modeWineQualityStatistics),
("ORDERING_MEMBERLIST", orderingMemberList), ("ORDERING_MEMBERLIST", orderingMemberList),
+30 -4
View File
@@ -17,10 +17,11 @@ namespace Elwig.Helpers {
public string? Filling; public string? Filling;
public string? Limit; public string? Limit;
public bool Required; public bool Required;
public bool SyncTime;
public string? Log; public string? Log;
public string? _Log; public string? _Log;
public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, bool? required, string? log) { public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, bool? required, bool? syncTime, string? log) {
Id = id; Id = id;
Type = type; Type = type;
Model = model; Model = model;
@@ -29,6 +30,7 @@ namespace Elwig.Helpers {
Filling = filling; Filling = filling;
Limit = limit; Limit = limit;
Required = required ?? true; Required = required ?? true;
SyncTime = syncTime ?? true;
_Log = log; _Log = log;
Log = log != null ? Path.Combine(App.DataPath, log) : null; Log = log != null ? Path.Combine(App.DataPath, log) : null;
} }
@@ -37,19 +39,30 @@ namespace Elwig.Helpers {
public class Config { public class Config {
private static readonly string[] TrueValues = ["1", "true", "yes", "on"]; private static readonly string[] TrueValues = ["1", "true", "yes", "on"];
private static readonly string[] FalseValues = ["0", "false", "no", "off"];
private readonly string FileName; private readonly string FileName;
public bool Debug; public bool Debug;
public string DatabaseFile = App.DataPath + "database.sqlite3"; public string DatabaseFile = Path.Combine(App.DataPath, "database.sqlite3");
public string? DatabaseLog = null; public string? DatabaseLog = null;
public string? Branch = null; public string? Branch = null;
public WeighingMode? WeighingMode; public WeighingMode? WeighingMode;
public bool BackupAuto = true;
public string BackupPath = Path.Combine(App.DataPath, "backups");
public int BackupRetainHours = 36;
public int BackupRetainDays = 8;
public int BackupRetainWeeks = 6;
public int BackupRetainMonths = 18;
public string? UpdateUrl = null; public string? UpdateUrl = null;
public bool UpdateAuto = false; public bool UpdateAuto = false;
public string? SyncUrl = null; public string? SyncUrl = null;
public string SyncUsername = ""; public string SyncUsername = "";
public string SyncPassword = ""; public string SyncPassword = "";
public bool SyncMain = false;
public string? SmtpHost = null; public string? SmtpHost = null;
public int? SmtpPort = null; public int? SmtpPort = null;
@@ -57,9 +70,10 @@ namespace Elwig.Helpers {
public string? SmtpUsername = null; public string? SmtpUsername = null;
public string? SmtpPassword = null; public string? SmtpPassword = null;
public string? SmtpFrom = null; public string? SmtpFrom = null;
public (string Host, int Port, string Mode, string Username, string Password, string From)? Smtp => public double SmtpWait = 0;
public (string Host, int Port, string Mode, string Username, string Password, string From, double Wait)? Smtp =>
SmtpHost == null || SmtpPort == null || SmtpMode == null || SmtpUsername == null || SmtpPassword == null || SmtpFrom == null ? SmtpHost == null || SmtpPort == null || SmtpMode == null || SmtpUsername == null || SmtpPassword == null || SmtpFrom == null ?
null : (SmtpHost, (int)SmtpPort, SmtpMode, SmtpUsername, SmtpPassword, SmtpFrom); null : (SmtpHost, (int)SmtpPort, SmtpMode, SmtpUsername, SmtpPassword, SmtpFrom, SmtpWait);
public IList<ScaleConfig> Scales; public IList<ScaleConfig> Scales;
private readonly List<ScaleConfig> ScaleList = []; private readonly List<ScaleConfig> ScaleList = [];
@@ -80,11 +94,21 @@ namespace Elwig.Helpers {
Debug = TrueValues.Contains(config["general:debug"]?.ToLower()); Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
var weighing = config["general:weighing"]; var weighing = config["general:weighing"];
WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null; WeighingMode = weighing != null && Enum.TryParse<WeighingMode>(weighing, true, out var w) ? w : null;
BackupAuto = !FalseValues.Contains(config["backup:auto"]?.ToLower());
BackupPath = Path.Combine(Path.GetDirectoryName(DatabaseFile) ?? App.DataPath, config["backup:path"] ?? "backups");
BackupRetainHours = int.TryParse(config["backup:retain_hours"], out var hours) ? hours : 36;
BackupRetainDays = int.TryParse(config["backup:retain_days"], out var days) ? days : 8;
BackupRetainWeeks = int.TryParse(config["backup:retain_weeks"], out var weeks) ? weeks : 6;
BackupRetainMonths = int.TryParse(config["backup:retain_months"], out var months) ? months : 18;
UpdateUrl = config["update:url"]; UpdateUrl = config["update:url"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower()); UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
SyncUrl = config["sync:url"]; SyncUrl = config["sync:url"];
SyncUsername = config["sync:username"] ?? ""; SyncUsername = config["sync:username"] ?? "";
SyncPassword = config["sync:password"] ?? ""; SyncPassword = config["sync:password"] ?? "";
SyncMain = TrueValues.Contains(config["sync:main"]?.ToLower());
SmtpHost = config["smtp:host"]; SmtpHost = config["smtp:host"];
SmtpPort = config["smtp:port"]?.All(char.IsAsciiDigit) == true && config["smtp:port"]?.Length > 0 ? int.Parse(config["smtp:port"]!) : null; SmtpPort = config["smtp:port"]?.All(char.IsAsciiDigit) == true && config["smtp:port"]?.Length > 0 ? int.Parse(config["smtp:port"]!) : null;
@@ -92,6 +116,7 @@ namespace Elwig.Helpers {
SmtpUsername = config["smtp:username"]; SmtpUsername = config["smtp:username"];
SmtpPassword = config["smtp:password"]; SmtpPassword = config["smtp:password"];
SmtpFrom = config["smtp:from"]; SmtpFrom = config["smtp:from"];
SmtpWait = double.TryParse(config["smtp:wait"], out var res) ? res : 0;
var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key).Order(); var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key).Order();
ScaleList.Clear(); ScaleList.Clear();
@@ -101,6 +126,7 @@ namespace Elwig.Helpers {
s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"], s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"],
config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"],
config[$"scale.{s}:required"] != null ? TrueValues.Contains(config[$"scale.{s}:required"]?.ToLower()) : null, config[$"scale.{s}:required"] != null ? TrueValues.Contains(config[$"scale.{s}:required"]?.ToLower()) : null,
config[$"scale.{s}:synctime"] != null ? !FalseValues.Contains(config[$"scale.{s}:synctime"]?.ToLower()) : null,
config[$"scale.{s}:log"] config[$"scale.{s}:log"]
)); ));
} }
+7 -11
View File
@@ -6,9 +6,9 @@ using System;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
using Row = Tuple<(string?, string?, string?, string?, string, int, string, int), (string, int, string, string, string, int, string, double, double)>; using Row = Tuple<(string?, string?, string?, string?, string, int, string, bool, int), (string, int, string, string, string, int, string, double, double)>;
public class Bki : Csv<Row> { public class Bki : CsvFile<Row> {
private readonly string _clientData; private readonly string _clientData;
@@ -29,7 +29,7 @@ namespace Elwig.Helpers.Export {
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
using var cmd = cnx.CreateCommand(); using var cmd = cnx.CreateCommand();
cmd.CommandText = $""" cmd.CommandText = $"""
SELECT lfbis_nr, name, other_names, billing_name, address, plz, ort, area, SELECT lfbis_nr, name, other_names, billing_name, address, plz, ort, vollablieferer, area,
date, weight, type, sortid, qualid, year, hkid, kmw, oe date, weight, type, sortid, qualid, year, hkid, kmw, oe
FROM v_bki_delivery FROM v_bki_delivery
WHERE year = {year} WHERE year = {year}
@@ -38,28 +38,24 @@ namespace Elwig.Helpers.Export {
List<Row> rows = []; List<Row> rows = [];
while (await r.ReadAsync()) { while (await r.ReadAsync()) {
rows.Add(new( rows.Add(new(
(r.IsDBNull(0) ? null : r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetInt32(7)), (r.IsDBNull(0) ? null : r.GetString(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2), r.IsDBNull(3) ? null : r.GetString(3), r.GetString(4), r.GetInt32(5), r.GetString(6), r.GetBoolean(7), r.GetInt32(8)),
(r.GetString(8), r.GetInt32(9), r.GetString(10), r.GetString(11), r.GetString(12), r.GetInt32(13), r.GetString(14), r.GetDouble(15), r.GetDouble(16)) (r.GetString(9), r.GetInt32(10), r.GetString(11), r.GetString(12), r.GetString(13), r.GetInt32(14), r.GetString(15), r.GetDouble(16), r.GetDouble(17))
)); ));
} }
await ExportAsync(rows); await ExportAsync(rows);
} }
public void Export(int year) {
ExportAsync(year).GetAwaiter().GetResult();
}
public override string FormatRow(Row row) { public override string FormatRow(Row row) {
var (member, delivery) = row; var (member, delivery) = row;
var (lfBisNr, familyName, name, billingName, address, plz, ort, area) = member; var (lfBisNr, familyName, name, billingName, address, plz, ort, vollablieferer, area) = member;
var (date, weight, type, sortid, qualid, year, hkid, kmw, oe) = delivery; var (date, weight, type, sortid, qualid, year, hkid, kmw, oe) = delivery;
var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName); var (n1, n2) = billingName == null ? (familyName, name) : Utils.SplitName(billingName, familyName);
var (a1, a2) = Utils.SplitAddress(address); var (a1, a2) = Utils.SplitAddress(address);
var memberData = $"{lfBisNr};{n1};{n2};{a1};\t{a2};{plz};{ort}"; var memberData = $"{lfBisNr};{n1};{n2};{a1};\t{a2};{plz};{ort}";
var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}"; var deliveryData = $"{string.Join(".", date.Split("-").Reverse())};{weight};TB;{(type == "W" ? "J" : "")};{(type == "R" ? "J" : "")};{sortid};;;{qualid};{year};{hkid};{kmw:0.0};{oe:0}";
var vollData = $"N;;;{area / 10_000.0}"; var vollData = $"{(vollablieferer ? "J" : "N")};{(vollablieferer ? area / 10_000.0 : null):N4};{(vollablieferer ? area / 10_000.0 : null):N4};{area / 10_000.0:N4}";
return $"{_clientData};{memberData};{deliveryData};{vollData}"; return $"{_clientData};{memberData};{deliveryData};{vollData}";
} }
@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
public abstract class Csv<T> : IExporter<T> { public abstract class CsvFile<T> : IExporter<T> {
public static string FileExtension => "csv"; public static string FileExtension => "csv";
@@ -15,11 +15,11 @@ namespace Elwig.Helpers.Export {
protected readonly char Separator; protected readonly char Separator;
protected string? Header; protected string? Header;
public Csv(string filename, char separator = ';') : public CsvFile(string filename, char separator = ';') :
this(filename, separator, Utils.UTF8) { this(filename, separator, Utils.UTF8) {
} }
public Csv(string filename, char separator, Encoding encoding) { public CsvFile(string filename, char separator, Encoding encoding) {
_writer = new StreamWriter(filename, false, encoding); _writer = new StreamWriter(filename, false, encoding);
Separator = separator; Separator = separator;
} }
@@ -29,11 +29,6 @@ namespace Elwig.Helpers.Export {
_writer.Dispose(); _writer.Dispose();
} }
public ValueTask DisposeAsync() {
GC.SuppressFinalize(this);
return _writer.DisposeAsync();
}
public async Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null) { public async Task ExportAsync(IEnumerable<T> data, IProgress<double>? progress = null) {
progress?.Report(0.0); progress?.Report(0.0);
int count = data.Count() + 2, i = 0; int count = data.Count() + 2, i = 0;
@@ -50,10 +45,6 @@ namespace Elwig.Helpers.Export {
progress?.Report(100.0); progress?.Report(100.0);
} }
public void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
ExportAsync(data, progress).GetAwaiter().GetResult();
}
public string FormatRow(IEnumerable row) { public string FormatRow(IEnumerable row) {
return string.Join(Separator, row); return string.Join(Separator, row);
} }
@@ -61,7 +52,7 @@ namespace Elwig.Helpers.Export {
public abstract string FormatRow(T row); public abstract string FormatRow(T row);
} }
public class CsvSimple : Csv<IEnumerable<object?>> { public class CsvSimple : CsvFile<IEnumerable<object?>> {
public CsvSimple(string filename, char separator, Encoding encoding) : public CsvSimple(string filename, char separator, Encoding encoding) :
base(filename, separator, encoding) { base(filename, separator, encoding) {
+25 -9
View File
@@ -18,9 +18,11 @@ namespace Elwig.Helpers.Export {
} }
public static async Task ExportSqlite(string filename, bool zipFile) { public static async Task ExportSqlite(string filename, bool zipFile) {
var tmp = filename + ".tmp";
try {
File.Delete(tmp);
if (zipFile) { if (zipFile) {
File.Delete(filename); using var zip = ZipFile.Open(tmp, ZipArchiveMode.Create);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression); var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
@@ -46,14 +48,20 @@ namespace Elwig.Helpers.Export {
var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize); var db = zip.CreateEntryFromFile(App.Config.DatabaseFile, "database.sqlite3", CompressionLevel.SmallestSize);
} else { } else {
File.Copy(App.Config.DatabaseFile, filename, true); File.Copy(App.Config.DatabaseFile, tmp);
}
File.Move(tmp, filename, true);
} finally {
File.Delete(tmp);
} }
} }
public static async Task ExportSql(string filename, bool zipFile) { public static async Task ExportSql(string filename, bool zipFile) {
var tmp = filename + ".tmp";
try {
File.Delete(tmp);
if (zipFile) { if (zipFile) {
File.Delete(filename); using var zip = ZipFile.Open(tmp, ZipArchiveMode.Create);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression); var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) { using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
@@ -82,10 +90,14 @@ namespace Elwig.Helpers.Export {
await ExportSql(writer); await ExportSql(writer);
} }
} else { } else {
using var stream = File.OpenWrite(filename); using var stream = File.OpenWrite(tmp);
using var writer = new StreamWriter(stream, Utils.UTF8); using var writer = new StreamWriter(stream, Utils.UTF8);
await ExportSql(writer); await ExportSql(writer);
} }
File.Move(tmp, filename, true);
} finally {
File.Delete(tmp);
}
} }
public static async Task ExportSql(StreamWriter writer) { public static async Task ExportSql(StreamWriter writer) {
@@ -200,7 +212,7 @@ namespace Elwig.Helpers.Export {
} }
} }
public static async Task ImportSqlite(string filename, bool zipFile = false) { public static async Task ImportSqlite(string filename, bool zipFile = false, bool keepSource = true) {
if (zipFile) { if (zipFile) {
var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3"); var newName = Path.ChangeExtension(App.Config.DatabaseFile, ".new.sqlite3");
try { try {
@@ -209,7 +221,7 @@ namespace Elwig.Helpers.Export {
foreach (var entry in zip.Entries) { foreach (var entry in zip.Entries) {
if (entry.Name.EndsWith(".sqlite3")) { if (entry.Name.EndsWith(".sqlite3")) {
entry.ExtractToFile(newName); entry.ExtractToFile(newName);
await ImportSqlite(newName); await ImportSqlite(newName, keepSource: false);
return; return;
} }
} }
@@ -221,7 +233,11 @@ namespace Elwig.Helpers.Export {
var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3"); var oldName = Path.ChangeExtension(App.Config.DatabaseFile, ".old.sqlite3");
File.Move(App.Config.DatabaseFile, oldName, true); File.Move(App.Config.DatabaseFile, oldName, true);
if (keepSource) {
File.Copy(filename, App.Config.DatabaseFile, false);
} else {
File.Move(filename, App.Config.DatabaseFile, false); File.Move(filename, App.Config.DatabaseFile, false);
}
using var cnx = await AppDbContext.ConnectAsync(); using var cnx = await AppDbContext.ConnectAsync();
await cnx.ExecuteBatch("VACUUM"); await cnx.ExecuteBatch("VACUUM");
@@ -234,7 +250,7 @@ namespace Elwig.Helpers.Export {
using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) { using (var cnx = await AppDbContext.ConnectAsync($"Data Source=\"{newName}\"; Mode=ReadWriteCreate; Foreign Keys=False; Cache=Default; Pooling=False")) {
await cnx.ExecuteBatch(await reader.ReadToEndAsync()); await cnx.ExecuteBatch(await reader.ReadToEndAsync());
} }
await ImportSqlite(newName); await ImportSqlite(newName, keepSource: false);
} finally { } finally {
if (File.Exists(newName)) File.Delete(newName); if (File.Exists(newName)) File.Delete(newName);
} }
+1 -10
View File
@@ -9,7 +9,7 @@ using System.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
public class Ebics(PaymentVar variant, string filename, int version, Ebics.AddressMode mode = Ebics.AddressMode.Full) : IBankingExporter { public class Ebics(PaymentVar variant, string filename, int version, Ebics.AddressMode mode = Ebics.AddressMode.Full) : IExporter<Transaction> {
public enum AddressMode { Omit = 0, Lines = 1, Full = 2 } public enum AddressMode { Omit = 0, Lines = 1, Full = 2 }
@@ -28,15 +28,6 @@ namespace Elwig.Helpers.Export {
Writer.Dispose(); Writer.Dispose();
} }
public ValueTask DisposeAsync() {
GC.SuppressFinalize(this);
return Writer.DisposeAsync();
}
public void Export(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
ExportAsync(transactions, progress).GetAwaiter().GetResult();
}
public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) { public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) {
if (transactions.Any(tx => tx.Amount < 0)) { if (transactions.Any(tx => tx.Amount < 0)) {
throw new ArgumentException("Tranaction amount may not be negative"); throw new ArgumentException("Tranaction amount may not be negative");
-15
View File
@@ -1,15 +0,0 @@
using Elwig.Models.Dtos;
using System;
namespace Elwig.Helpers.Export {
public class Elba : Csv<Transaction>, IBankingExporter {
public static new string FileExtension => "elba";
public Elba(string filename) : base(filename) { }
public override string FormatRow(Transaction row) {
throw new NotImplementedException();
}
}
}
+178 -84
View File
@@ -1,4 +1,6 @@
using Elwig.Models;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Elwig.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -8,7 +10,6 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
public static class ElwigData { public static class ElwigData {
@@ -34,14 +35,16 @@ namespace Elwig.Helpers.Export {
public static async Task Import(IEnumerable<string> filenames, ImportMode mode) { public static async Task Import(IEnumerable<string> filenames, ImportMode mode) {
try { try {
Dictionary<string, Branch> branches; Dictionary<string, Branch> branches;
Dictionary<int, AT_Kg> kgs;
Dictionary<int, int> currentDids; Dictionary<int, int> currentDids;
Dictionary<string, int> currentLsNrs; Dictionary<string, int> currentLsNrs;
Dictionary<int, List<WbRd>> currentWbRde; Dictionary<int, List<WbRd>> currentWbRde;
Dictionary<int, AT_Kg> kgs; Dictionary<int, WbKg> currentWbKgs;
List<WbGl> currentWbGls; List<WbGl> currentWbGls;
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
branches = await ctx.FetchBranches().ToDictionaryAsync(b => b.ZwstId); branches = await ctx.FetchBranches().ToDictionaryAsync(b => b.ZwstId);
kgs = await ctx.Katastralgemeinden.ToDictionaryAsync(k => k.KgNr);
currentDids = await ctx.Deliveries currentDids = await ctx.Deliveries
.GroupBy(d => d.Year) .GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId)); .ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));
@@ -50,8 +53,8 @@ namespace Elwig.Helpers.Export {
currentWbRde = await ctx.WbRde currentWbRde = await ctx.WbRde
.GroupBy(r => r.KgNr) .GroupBy(r => r.KgNr)
.ToDictionaryAsync(g => g.Key, g => g.ToList()); .ToDictionaryAsync(g => g.Key, g => g.ToList());
currentWbKgs = await ctx.WbKgs.ToDictionaryAsync(k => k.KgNr);
currentWbGls = await ctx.WbGls.ToListAsync(); currentWbGls = await ctx.WbGls.ToListAsync();
kgs = await ctx.Katastralgemeinden.Include(k => k.WbKg).ToDictionaryAsync(k => k.KgNr);
} }
var data = new List<( var data = new List<(
@@ -59,8 +62,9 @@ namespace Elwig.Helpers.Export {
List<BillingAddr> BillingAddresses, List<BillingAddr> BillingAddresses,
List<MemberTelNr> TelephoneNumbers, List<MemberTelNr> TelephoneNumbers,
List<MemberEmailAddr> EmailAddresses, List<MemberEmailAddr> EmailAddresses,
List<AreaCom> AreaCommitments, List<MemberHistory> MemberHistory,
List<AreaComContract> Contracts, List<AreaCmt> AreaCommitments,
List<AreaCmtContract> Contracts,
List<WbRd> Riede, List<WbRd> Riede,
List<WbKg> WbKgs, List<WbKg> WbKgs,
List<WbGl> WbGls, List<WbGl> WbGls,
@@ -71,12 +75,12 @@ namespace Elwig.Helpers.Export {
var metaData = new List<(string FileName, string ZwstId, string Device, var metaData = new List<(string FileName, string ZwstId, string Device,
int? MemberNum, string? MemberFilters, int? MemberNum, string? MemberFilters,
int? AreaComNum, string? AreaComFilters, int? AreaCmtNum, string? AreaCmtFilters,
int? DeliveryNum, string? DeliveryFilters)>(); int? DeliveryNum, string? DeliveryFilters)>();
foreach (var filename in filenames) { foreach (var filename in filenames) {
try { try {
data.Add(new([], [], [], [], [], [], [], new([], [], [], [], [], new() { data.Add(new([], [], [], [], [], [], [], new([], [], [], [], [], [], new() {
["member"] = [], ["member"] = [],
["area_commitment_contract"] = [], ["area_commitment_contract"] = [],
["area_commitment"] = [], ["area_commitment"] = [],
@@ -101,14 +105,14 @@ namespace Elwig.Helpers.Export {
var meta = await JsonNode.ParseAsync(metaJson!.Open()); var meta = await JsonNode.ParseAsync(metaJson!.Open());
var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>(); var memberCount = meta!["members"]?["count"]?.AsValue().GetValue<int>();
var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray(); var memberFilters = meta!["members"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var areaComCount = meta!["area_commitment_contracts"]?["count"]?.AsValue().GetValue<int>(); var areaCmtCount = meta!["area_commitment_contracts"]?["count"]?.AsValue().GetValue<int>();
var areaComFilters = meta!["area_commitment_contracts"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray(); var areaCmtFilters = meta!["area_commitment_contracts"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>(); var deliveryCount = meta!["deliveries"]?["count"]?.AsValue().GetValue<int>();
var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray(); var deliveryFilters = meta!["deliveries"]?["filters"]?.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
metaData.Add((Path.GetFileName(filename), metaData.Add((Path.GetFileName(filename),
meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(), meta["zwstid"]!.AsValue().GetValue<string>(), meta["device"]!.AsValue().GetValue<string>(),
memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null, memberCount, memberFilters != null ? string.Join(" / ", memberFilters) : null,
areaComCount, areaComFilters != null ? string.Join(" / ", areaComFilters) : null, areaCmtCount, areaCmtFilters != null ? string.Join(" / ", areaCmtFilters) : null,
deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null)); deliveryCount, deliveryFilters != null ? string.Join(" / ", deliveryFilters) : null));
var wbKgsJson = zip.GetEntry("wb_kgs.json"); var wbKgsJson = zip.GetEntry("wb_kgs.json");
@@ -119,6 +123,7 @@ namespace Elwig.Helpers.Export {
var obj = JsonNode.Parse(line)!.AsObject(); var obj = JsonNode.Parse(line)!.AsObject();
var (k, g) = obj.ToWbKg(currentWbGls); var (k, g) = obj.ToWbKg(currentWbGls);
r.WbKgs.Add(k); r.WbKgs.Add(k);
currentWbKgs[k.KgNr] = k;
if (g != null) { if (g != null) {
currentWbGls[g.GlNr] = g; currentWbGls[g.GlNr] = g;
r.WbGls.Add(g); r.WbGls.Add(g);
@@ -132,7 +137,7 @@ namespace Elwig.Helpers.Export {
string? line; string? line;
while ((line = await reader.ReadLineAsync()) != null) { while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject(); var obj = JsonNode.Parse(line)!.AsObject();
var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs); var (m, b, telNrs, emailAddrs, timestamps) = obj.ToMember(kgs, currentWbKgs);
r.Members.Add(m); r.Members.Add(m);
if (b != null) r.BillingAddresses.Add(b); if (b != null) r.BillingAddresses.Add(b);
r.TelephoneNumbers.AddRange(telNrs); r.TelephoneNumbers.AddRange(telNrs);
@@ -142,22 +147,33 @@ namespace Elwig.Helpers.Export {
} }
} }
// legacy area commitments var historyJson = zip.GetEntry("member_history.json");
var areaComsJson = zip.GetEntry("area_commitments.json"); if (historyJson != null) {
if (areaComsJson != null) { using var reader = new StreamReader(historyJson.Open(), Utils.UTF8);
using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
string? line; string? line;
while ((line = await reader.ReadLineAsync()) != null) { while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject(); var obj = JsonNode.Parse(line)!.AsObject();
var (contract, areaCom, wbrd, timestamps) = obj.ToAreaCom(currentWbRde); var h = obj.ToMemberHistory();
r.MemberHistory.Add(h);
}
}
// legacy area commitments
var areaCmtsJson = zip.GetEntry("area_commitments.json");
if (areaCmtsJson != null) {
using var reader = new StreamReader(areaCmtsJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (contract, areaCmt, wbrd, timestamps) = obj.ToAreaCmt(kgs, currentWbKgs, currentWbRde);
r.Contracts.Add(contract); r.Contracts.Add(contract);
r.AreaCommitments.Add(areaCom); r.AreaCommitments.Add(areaCmt);
if (wbrd != null) { if (wbrd != null) {
r.Riede.Add(wbrd); r.Riede.Add(wbrd);
} }
if (timestamps.HasValue) { if (timestamps.HasValue) {
r.Timestamps["area_commitment_contract"].Add((contract.FbNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt)); r.Timestamps["area_commitment_contract"].Add((contract.FbNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
r.Timestamps["area_commitment"].Add((areaCom.FbNr, areaCom.RevNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt)); r.Timestamps["area_commitment"].Add((areaCmt.FbNr, areaCmt.RevNr, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
} }
} }
} }
@@ -168,17 +184,17 @@ namespace Elwig.Helpers.Export {
string? line; string? line;
while ((line = await reader.ReadLineAsync()) != null) { while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject(); var obj = JsonNode.Parse(line)!.AsObject();
var (contract, areaComs, wbrd, timestamps) = obj.ToAreaComContract(currentWbRde); var (contract, areaCmts, wbrd, timestamps) = obj.ToAreaCmtContract(kgs, currentWbKgs, currentWbRde);
r.Contracts.Add(contract); r.Contracts.Add(contract);
r.AreaCommitments.AddRange(areaComs.Select(v => v.Item1)); r.AreaCommitments.AddRange(areaCmts.Select(v => v.Item1));
if (wbrd != null) { if (wbrd != null) {
r.Riede.Add(wbrd); r.Riede.Add(wbrd);
} }
if (timestamps.HasValue) if (timestamps.HasValue)
r.Timestamps["area_commitment_contract"].Add((contract.FbNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt)); r.Timestamps["area_commitment_contract"].Add((contract.FbNr, 0, 0, timestamps.Value.CreatedAt, timestamps.Value.ModifiedAt));
foreach (var (areaCom, ts) in areaComs) { foreach (var (areaCmt, ts) in areaCmts) {
if (!ts.HasValue) continue; if (!ts.HasValue) continue;
r.Timestamps["area_commitment"].Add((areaCom.FbNr, areaCom.RevNr, 0, ts.Value.CreatedAt, ts.Value.ModifiedAt)); r.Timestamps["area_commitment"].Add((areaCmt.FbNr, areaCmt.RevNr, 0, ts.Value.CreatedAt, ts.Value.ModifiedAt));
} }
} }
} }
@@ -189,7 +205,7 @@ namespace Elwig.Helpers.Export {
string? line; string? line;
while ((line = await reader.ReadLineAsync()) != null) { while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject(); var obj = JsonNode.Parse(line)!.AsObject();
var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbRde); var (d, parts, mods, rde, timestamps) = obj.ToDelivery(currentLsNrs, currentDids, kgs, currentWbKgs, currentWbRde);
r.Deliveries.Add(d); r.Deliveries.Add(d);
r.DeliveryParts.AddRange(parts.Select(p => p.Item1)); r.DeliveryParts.AddRange(parts.Select(p => p.Item1));
r.Modifiers.AddRange(mods); r.Modifiers.AddRange(mods);
@@ -208,30 +224,27 @@ namespace Elwig.Helpers.Export {
exc is FileNotFoundException || exc is FileNotFoundException ||
exc is IOException) { exc is IOException) {
data.RemoveAt(data.Count - 1); data.RemoveAt(data.Count - 1);
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen.\n\n" + exc.Message; InteractionService.ShowException("Fehler beim Importieren", $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden und wird übersprungen", exc);
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
await AddImportedFiles(Path.GetFileName(filename)); await AddImportedFiles(Path.GetFileName(filename));
} catch (Exception exc) { } catch (Exception exc) {
data.RemoveAt(data.Count - 1); data.RemoveAt(data.Count - 1);
var str = $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?\n\n" + exc.Message; if (InteractionService.AskException("Fehler beim Importieren", $"Die Elwig-Export-Datei '{Path.GetFileName(filename)}' konnte nicht verarbeitet werden. Soll sie in Zukunft übersprungen werden?", exc)) {
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
var r = MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No);
if (r == MessageBoxResult.Yes) {
await AddImportedFiles(Path.GetFileName(filename)); await AddImportedFiles(Path.GetFileName(filename));
} }
} }
} }
var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>(); var importedMembers = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>();
var importedAreaComs = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>(); var importedAreaCmts = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>();
var importedDeliveries = new List<(string FileName, string ZwstId, string Device, int New, int Overwritten, int NotImported, string? Filters)>(); 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, contracts, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) { foreach (var ((members, billingAddresses, telephoneNumbers, emailAddresses, history, areaCommitments, contracts, riede, wbKgs, wbGls, deliveries, deliveryParts, modifiers, timestamps), meta) in data.Zip(metaData)) {
var branch = branches[meta.ZwstId]; var branch = branches[meta.ZwstId];
var device = meta.Device; var device = meta.Device;
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var tx = await ctx.Database.BeginTransactionAsync();
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
var kgnrs = wbKgs.Select(k => k.KgNr).ToList(); var kgnrs = wbKgs.Select(k => k.KgNr).ToList();
var duplicateKgNrs = await ctx.WbKgs var duplicateKgNrs = await ctx.WbKgs
@@ -239,6 +252,12 @@ namespace Elwig.Helpers.Export {
.Select(k => k.KgNr) .Select(k => k.KgNr)
.ToListAsync(); .ToListAsync();
var histNrs = history.Select(h => h.HistNr).ToList();
var duplicateHistNrs = await ctx.MemberHistory
.Where(h => histNrs.Contains(h.HistNr))
.Select(h => h.HistNr)
.ToListAsync();
var mgnrs = members.Select(m => m.MgNr).ToList(); var mgnrs = members.Select(m => m.MgNr).ToList();
var duplicateMgNrs = await ctx.Members var duplicateMgNrs = await ctx.Members
.Where(m => mgnrs.Contains(m.MgNr)) .Where(m => mgnrs.Contains(m.MgNr))
@@ -328,6 +347,11 @@ namespace Elwig.Helpers.Export {
importedMembers.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, members.Count - n - o, meta.MemberFilters)); importedMembers.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, members.Count - n - o, meta.MemberFilters));
} }
if (importDuplicateMembers || importNewMembers) {
ctx.UpdateRange(history.Where(h => duplicateHistNrs.Contains(h.HistNr)));
ctx.AddRange(history.Where(h => !duplicateHistNrs.Contains(h.HistNr)));
}
if (importDuplicateContracts) { if (importDuplicateContracts) {
ctx.RemoveRange(ctx.AreaCommitments.IgnoreAutoIncludes().Where(c => duplicateFbNrs.Contains(c.FbNr))); ctx.RemoveRange(ctx.AreaCommitments.IgnoreAutoIncludes().Where(c => duplicateFbNrs.Contains(c.FbNr)));
ctx.UpdateRange(contracts.Where(c => duplicateFbNrs.Contains(c.FbNr))); ctx.UpdateRange(contracts.Where(c => duplicateFbNrs.Contains(c.FbNr)));
@@ -341,7 +365,7 @@ namespace Elwig.Helpers.Export {
ctx.AddRange(riede); ctx.AddRange(riede);
var n = importNewContracts ? contracts.Count - duplicateFbNrs.Count : 0; var n = importNewContracts ? contracts.Count - duplicateFbNrs.Count : 0;
var o = importDuplicateContracts ? duplicateFbNrs.Count : 0; var o = importDuplicateContracts ? duplicateFbNrs.Count : 0;
importedAreaComs.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, contracts.Count - n - o, meta.AreaComFilters)); importedAreaCmts.Add((meta.FileName, meta.ZwstId, meta.Device, n, o, contracts.Count - n - o, meta.AreaCmtFilters));
} }
if (allowedDuplicateLsNrs.Count > 0) { if (allowedDuplicateLsNrs.Count > 0) {
@@ -385,6 +409,8 @@ namespace Elwig.Helpers.Export {
} }
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
await tx.CommitAsync();
var primaryKeys = new Dictionary<string, string>() { var primaryKeys = new Dictionary<string, string>() {
["member"] = "mgnr, 0, 0", ["member"] = "mgnr, 0, 0",
@@ -411,7 +437,7 @@ namespace Elwig.Helpers.Export {
} }
App.HintContextChange(); App.HintContextChange();
MessageBox.Show( InteractionService.ShowInformation("Importieren erfolgreich",
$"Das importieren der Daten war erfolgreich!\n" + $"Das importieren der Daten war erfolgreich!\n" +
$"Folgendes wurde importiert:\n" + $"Folgendes wurde importiert:\n" +
string.Join("\n", [ string.Join("\n", [
@@ -421,8 +447,8 @@ namespace Elwig.Helpers.Export {
$" ({d.New} neu, {d.Overwritten} überschrieben, {d.NotImported} nicht importiert)\n" + $" ({d.New} neu, {d.Overwritten} überschrieben, {d.NotImported} nicht importiert)\n" +
$" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" + $" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" +
$" Filter: {d.Filters}"), $" Filter: {d.Filters}"),
$"Flächenbindungen: {importedAreaComs.Sum(d => d.New + d.Overwritten)}", $"Flächenbindungen: {importedAreaCmts.Sum(d => d.New + d.Overwritten)}",
..importedAreaComs.Select(d => ..importedAreaCmts.Select(d =>
$" {d.FileName} ({d.New + d.Overwritten})\n" + $" {d.FileName} ({d.New + d.Overwritten})\n" +
$" ({d.New} importiert, {d.Overwritten} überschreiben, {d.NotImported} nicht importiert)\n" + $" ({d.New} importiert, {d.Overwritten} überschreiben, {d.NotImported} nicht importiert)\n" +
$" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" + $" Zweigstelle: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" +
@@ -433,38 +459,33 @@ namespace Elwig.Helpers.Export {
$" ({d.New} neu, {d.Overwritten} überschr., {d.NotImported} nicht importiert)\n" + $" ({d.New} neu, {d.Overwritten} überschr., {d.NotImported} nicht importiert)\n" +
$" Zwst.: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" + $" Zwst.: {branches[d.ZwstId].Name} (Gerät {d.Device})\n" +
$" Filter: {d.Filters}") $" Filter: {d.Filters}")
]), ]));
"Importieren erfolgreich",
MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception exc) { } catch (Exception exc) {
var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\nEvtl. muss die Datenbank manuell auf dieses Gerät kopieren werden.\n\n" + exc.Message; InteractionService.ShowException("Fehler beim Importieren", "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\nEvtl. muss die Datenbank manuell auf dieses Gerät kopieren werden", exc);
if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
MessageBox.Show(str, "Fehler beim Importieren", MessageBoxButton.OK, MessageBoxImage.Error);
} }
GC.Collect(); GC.Collect();
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
} }
private static bool ImportQuestion(string branch, string device, string subject, bool duplicate, int number) { private static bool ImportQuestion(string branch, string device, string subject, bool duplicate, int number) {
return MessageBox.Show( return InteractionService.AskQuestion($"{subject} importieren",
$"Sollen {number} {(duplicate ? "" : "neue ")}{subject} durch die Zweigstelle\n" + $"Sollen {number} {(duplicate ? "" : "neue ")}{subject} durch die Zweigstelle\n" +
$"{branch} (Gerät {device}) {(duplicate ? "überschrieben" : "importiert")} werden?", $"{branch} (Gerät {device}) {(duplicate ? "überschrieben" : "importiert")} werden?", true);
$"{subject} importieren",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes
) == MessageBoxResult.Yes;
} }
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) { public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<MemberHistory> history, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
return new ElwigExport { return new ElwigExport {
Members = (members, filters), Members = (members, filters),
History = (history, ["von exportierten Mitgliedern"]),
WbKgs = (wbKgs, ["von exportierten Mitgliedern"]), WbKgs = (wbKgs, ["von exportierten Mitgliedern"]),
}.Export(filename); }.Export(filename);
} }
public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<AreaComContract> areaComs, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) { public static Task Export(string filename, IEnumerable<Member> members, IEnumerable<MemberHistory> history, IEnumerable<AreaCmtContract> areaCmts, IEnumerable<WbKg> wbKgs, IEnumerable<string> filters) {
return new ElwigExport { return new ElwigExport {
Members = (members, filters), Members = (members, filters),
AreaComs = (areaComs, ["von exportierten Mitgliedern"]), History = (history, ["von exportierten Mitgliedern"]),
AreaCmts = (areaCmts, ["von exportierten Mitgliedern"]),
WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]), WbKgs = (wbKgs, ["von exportierten Mitgliedern und Flächenbindungen"]),
}.Export(filename); }.Export(filename);
} }
@@ -479,7 +500,8 @@ namespace Elwig.Helpers.Export {
public class ElwigExport { public class ElwigExport {
public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; } public (IEnumerable<WbKg> WbKgs, IEnumerable<string> Filters)? WbKgs { get; set; }
public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; } public (IEnumerable<Member> Members, IEnumerable<string> Filters)? Members { get; set; }
public (IEnumerable<AreaComContract> AreaComs, IEnumerable<string> Filters)? AreaComs { get; set; } public (IEnumerable<MemberHistory> History, IEnumerable<string> Filters)? History { get; set; }
public (IEnumerable<AreaCmtContract> AreaCmts, IEnumerable<string> Filters)? AreaCmts { get; set; }
public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; } public (IEnumerable<Delivery> Deliveries, IEnumerable<string> Filters)? Deliveries { get; set; }
public async Task Export(string filename) { public async Task Export(string filename) {
@@ -498,22 +520,26 @@ namespace Elwig.Helpers.Export {
["zwstid"] = App.ZwstId, ["zwstid"] = App.ZwstId,
["device"] = Environment.MachineName, ["device"] = Environment.MachineName,
}; };
if (WbKgs != null) { if (WbKgs != null)
obj["wb_kgs"] = new JsonObject { obj["wb_kgs"] = new JsonObject {
["count"] = WbKgs.Value.WbKgs.Count(), ["count"] = WbKgs.Value.WbKgs.Count(),
["filters"] = new JsonArray(WbKgs.Value.Filters.Select(f => (JsonNode)f).ToArray()), ["filters"] = new JsonArray(WbKgs.Value.Filters.Select(f => (JsonNode)f).ToArray()),
}; };
}
if (Members != null) if (Members != null)
obj["members"] = new JsonObject { obj["members"] = new JsonObject {
["count"] = Members.Value.Members.Count(), ["count"] = Members.Value.Members.Count(),
["filters"] = new JsonArray(Members.Value.Filters.Select(f => (JsonNode)f).ToArray()), ["filters"] = new JsonArray(Members.Value.Filters.Select(f => (JsonNode)f).ToArray()),
}; };
if (AreaComs != null) if (History != null)
obj["member_history"] = new JsonObject {
["count"] = History.Value.History.Count(),
["filters"] = new JsonArray(History.Value.Filters.Select(f => (JsonNode)f).ToArray()),
};
if (AreaCmts != null)
obj["area_commitment_contracts"] = new JsonObject { obj["area_commitment_contracts"] = new JsonObject {
["count"] = AreaComs.Value.AreaComs.Count(), ["count"] = AreaCmts.Value.AreaCmts.Count(),
["revisions"] = AreaComs.Value.AreaComs.Sum(c => c.Revisions.Count), ["revisions"] = AreaCmts.Value.AreaCmts.Sum(c => c.Revisions.Count),
["filters"] = new JsonArray(AreaComs.Value.Filters.Select(f => (JsonNode)f).ToArray()), ["filters"] = new JsonArray(AreaCmts.Value.Filters.Select(f => (JsonNode)f).ToArray()),
}; };
if (Deliveries != null) if (Deliveries != null)
obj["deliveries"] = new JsonObject { obj["deliveries"] = new JsonObject {
@@ -539,10 +565,17 @@ namespace Elwig.Helpers.Export {
await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(m.ToJson().ToJsonString(Utils.JsonOpts));
} }
} }
if (AreaComs != null) { if (History != null) {
var json = zip.CreateEntry("member_history.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var h in History.Value.History) {
await writer.WriteLineAsync(h.ToJson().ToJsonString(Utils.JsonOpts));
}
}
if (AreaCmts != null) {
var json = zip.CreateEntry("area_commitment_contracts.json", CompressionLevel.SmallestSize); var json = zip.CreateEntry("area_commitment_contracts.json", CompressionLevel.SmallestSize);
using var writer = new StreamWriter(json.Open(), Utils.UTF8); using var writer = new StreamWriter(json.Open(), Utils.UTF8);
foreach (var c in AreaComs.Value.AreaComs) { foreach (var c in AreaCmts.Value.AreaCmts) {
await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts)); await writer.WriteLineAsync(c.ToJson().ToJsonString(Utils.JsonOpts));
} }
} }
@@ -597,16 +630,21 @@ namespace Elwig.Helpers.Export {
["birthday"] = m.Birthday, ["birthday"] = m.Birthday,
["entry_date"] = m.EntryDate != null ? $"{m.EntryDate:yyyy-MM-dd}" : null, ["entry_date"] = m.EntryDate != null ? $"{m.EntryDate:yyyy-MM-dd}" : null,
["exit_date"] = m.ExitDate != null ? $"{m.ExitDate:yyyy-MM-dd}" : null, ["exit_date"] = m.ExitDate != null ? $"{m.ExitDate:yyyy-MM-dd}" : null,
["business_shares"] = m.BusinessShares, ["shares"] = m.Shares,
["shares_red"] = m.SharesRed,
["shares_white"] = m.SharesWhite,
["shares_dormant"] = m.SharesDormant,
["accounting_nr"] = m.AccountingNr, ["accounting_nr"] = m.AccountingNr,
["zwstid"] = m.ZwstId, ["zwstid"] = m.ZwstId,
["lfbis_nr"] = m.LfbisNr, ["lfbis_nr"] = m.LfbisNr,
["ustid_nr"] = m.UstIdNr, ["ustid_nr"] = m.UstIdNr,
["ooc_id"] = m.OrganicOperatorId,
["org_auth_code"] = m.OrganicAuthorityCode,
["juridical_pers"] = m.IsJuridicalPerson, ["juridical_pers"] = m.IsJuridicalPerson,
["volllieferant"] = m.IsVollLieferant, ["full_delivery"] = m.IsFullDelivery,
["buchführend"] = m.IsBuchführend, ["accounting"] = m.IsAccounting,
["organic"] = m.IsOrganic, ["organic"] = m.IsOrganic,
["funktionär"] = m.IsFunktionär, ["officer"] = m.IsOfficer,
["active"] = m.IsActive, ["active"] = m.IsActive,
["deceased"] = m.IsDeceased, ["deceased"] = m.IsDeceased,
["iban"] = m.Iban, ["iban"] = m.Iban,
@@ -646,11 +684,11 @@ namespace Elwig.Helpers.Export {
}; };
} }
public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs) { public static (Member, BillingAddr?, List<MemberTelNr>, List<MemberEmailAddr>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToMember(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs) {
var mgnr = json["mgnr"]!.AsValue().GetValue<int>(); var mgnr = json["mgnr"]!.AsValue().GetValue<int>();
var kgnr = json["default_kgnr"]?.AsValue().GetValue<int>(); var kgnr = json["default_kgnr"]?.AsValue().GetValue<int>();
if (kgnr != null && !kgs.Values.Any(k => k.WbKg?.KgNr == kgnr)) { if (kgnr != null && !currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new ArgumentException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten → Herkunftshierarchie)"); throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
} }
var createdAt = json["created_at"]?.AsValue().GetValue<string>(); var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>(); var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
@@ -666,16 +704,21 @@ namespace Elwig.Helpers.Export {
Birthday = json["birthday"]?.AsValue().GetValue<string>(), Birthday = json["birthday"]?.AsValue().GetValue<string>(),
EntryDateString = json["entry_date"]?.AsValue().GetValue<string>(), EntryDateString = json["entry_date"]?.AsValue().GetValue<string>(),
ExitDateString = json["exit_date"]?.AsValue().GetValue<string>(), ExitDateString = json["exit_date"]?.AsValue().GetValue<string>(),
BusinessShares = json["business_shares"]?.AsValue().GetValue<int>() ?? 0, Shares = json["shares"]?.AsValue().GetValue<int>() ?? json["business_shares"]?.AsValue().GetValue<int>() ?? 0,
SharesRed = json["shares_red"]?.AsValue().GetValue<int>() ?? 0,
SharesWhite = json["shares_white"]?.AsValue().GetValue<int>() ?? 0,
SharesDormant = json["shares_dormant"]?.AsValue().GetValue<int>() ?? 0,
AccountingNr = json["accounting_nr"]?.AsValue().GetValue<string>(), AccountingNr = json["accounting_nr"]?.AsValue().GetValue<string>(),
ZwstId = json["zwstid"]?.AsValue().GetValue<string>(), ZwstId = json["zwstid"]?.AsValue().GetValue<string>(),
LfbisNr = json["lfbis_nr"]?.AsValue().GetValue<string>(), LfbisNr = json["lfbis_nr"]?.AsValue().GetValue<string>(),
UstIdNr = json["ustid_nr"]?.AsValue().GetValue<string>(), UstIdNr = json["ustid_nr"]?.AsValue().GetValue<string>(),
OrganicOperatorId = json["ooc_id"]?.AsValue().GetValue<string>(),
OrganicAuthorityCode = json["org_auth_code"]?.AsValue().GetValue<string>(),
IsJuridicalPerson = json["juridical_pers"]?.AsValue().GetValue<bool>() ?? false, IsJuridicalPerson = json["juridical_pers"]?.AsValue().GetValue<bool>() ?? false,
IsVollLieferant = json["volllieferant"]?.AsValue().GetValue<bool>() ?? false, IsFullDelivery = json["full_delivery"]?.AsValue().GetValue<bool>() ?? json["volllieferant"]?.AsValue().GetValue<bool>() ?? false,
IsBuchführend = json["buchführend"]?.AsValue().GetValue<bool>() ?? false, IsAccounting = json["accounting"]?.AsValue().GetValue<bool>() ?? json["buchführend"]?.AsValue().GetValue<bool>() ?? false,
IsOrganic = json["organic"]?.AsValue().GetValue<bool>() ?? false, IsOrganic = json["organic"]?.AsValue().GetValue<bool>() ?? false,
IsFunktionär = json["funktionär"]?.AsValue().GetValue<bool>() ?? false, IsOfficer = json["officer"]?.AsValue().GetValue<bool>() ?? json["funktionär"]?.AsValue().GetValue<bool>() ?? false,
IsActive = json["active"]?.AsValue().GetValue<bool>() ?? false, IsActive = json["active"]?.AsValue().GetValue<bool>() ?? false,
IsDeceased = json["deceased"]?.AsValue().GetValue<bool>() ?? false, IsDeceased = json["deceased"]?.AsValue().GetValue<bool>() ?? false,
Iban = json["iban"]?.AsValue().GetValue<string>(), Iban = json["iban"]?.AsValue().GetValue<string>(),
@@ -711,7 +754,49 @@ namespace Elwig.Helpers.Export {
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None))); DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
} }
public static JsonObject ToJson(this AreaComContract c) { public static JsonObject ToJson(this MemberHistory h) {
return new JsonObject {
["histnr"] = h.HistNr,
["from_mgnr"] = h.FromMgNr,
["from_type"] = h.FromType?.ToString().ToLower(),
["to_mgnr"] = h.ToMgNr,
["to_type"] = h.ToType?.ToString().ToLower(),
["date_notice"] = h.DateNoticeString,
["date_effective"] = h.DateEffectiveString,
["reason"] = h.Reason,
["source"] = h.Source,
["shares"] = h.Shares,
["signed_company"] = h.CompanyHasSigned,
["signed_member"] = h.MemberHasSigned,
["value_per_share"] = h.ValuePerShare,
["currency"] = h.CurrencyCode,
["deduct_year"] = h.DeductYear,
["comment"] = h.Comment,
};
}
public static MemberHistory ToMemberHistory(this JsonNode json) {
return new MemberHistory {
HistNr = json["histnr"]!.AsValue().GetValue<int>(),
FromMgNr = json["from_mgnr"]?.AsValue().GetValue<int>(),
FromType = Enum.TryParse<BusinessShareType>(json["from_type"]?.AsValue().GetValue<string>(), true, out var from) ? from : null,
ToMgNr = json["to_mgnr"]?.AsValue().GetValue<int>(),
ToType = Enum.TryParse<BusinessShareType>(json["to_type"]?.AsValue().GetValue<string>(), true, out var to) ? to : null,
DateNoticeString = json["date_notice"]!.AsValue().GetValue<string>(),
DateEffectiveString = json["date_effective"]!.AsValue().GetValue<string>(),
Reason = json["reason"]!.AsValue().GetValue<string>(),
Source = json["source"]!.AsValue().GetValue<string>(),
Shares = json["shares"]!.AsValue().GetValue<int>(),
CompanyHasSigned = json["signed_company"]?.AsValue().GetValue<bool>() ?? false,
MemberHasSigned = json["signed_member"]?.AsValue().GetValue<bool>() ?? false,
ValuePerShare = json["value_per_share"]?.AsValue().GetValue<decimal>(),
CurrencyCode = json["currency"]?.AsValue().GetValue<string>(),
DeductYear = json["deduct_year"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
};
}
public static JsonObject ToJson(this AreaCmtContract c) {
return new JsonObject { return new JsonObject {
["fbnr"] = c.FbNr, ["fbnr"] = c.FbNr,
["kgnr"] = c.KgNr, ["kgnr"] = c.KgNr,
@@ -723,7 +808,7 @@ namespace Elwig.Helpers.Export {
}; };
} }
private static JsonObject ToJson(this AreaCom c) { private static JsonObject ToJson(this AreaCmt c) {
return new JsonObject { return new JsonObject {
["revnr"] = c.RevNr, ["revnr"] = c.RevNr,
["mgnr"] = c.MgNr, ["mgnr"] = c.MgNr,
@@ -738,10 +823,13 @@ namespace Elwig.Helpers.Export {
}; };
} }
public static (AreaComContract, List<(AreaCom, (DateTime CreatedAt, DateTime ModifiedAt)?)>, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaComContract(this JsonNode json, Dictionary<int, List<WbRd>> riede) { public static (AreaCmtContract, List<(AreaCmt, (DateTime CreatedAt, DateTime ModifiedAt)?)>, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCmtContract(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs, Dictionary<int, List<WbRd>> riede) {
var kgnr = json["kgnr"]!.AsValue().GetValue<int>(); var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
var ried = json["ried"]?.AsValue().GetValue<string>(); var ried = json["ried"]?.AsValue().GetValue<string>();
var fbnr = json["fbnr"]!.AsValue()!.GetValue<int>(); var fbnr = json["fbnr"]!.AsValue()!.GetValue<int>();
if (!currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
}
WbRd? rd = null; WbRd? rd = null;
bool newRd = false; bool newRd = false;
if (ried != null) { if (ried != null) {
@@ -760,16 +848,16 @@ namespace Elwig.Helpers.Export {
} }
var createdAt = json["created_at"]?.AsValue().GetValue<string>(); var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>(); var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaComContract { return (new AreaCmtContract {
FbNr = fbnr, FbNr = fbnr,
KgNr = kgnr, KgNr = kgnr,
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(), RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(), Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now, ImportedAt = DateTime.Now,
}, [.. json["revisions"]!.AsArray().Select(r => r!.AsObject()).Select<JsonObject, (AreaCom, (DateTime, DateTime)?)>(r => { }, [.. json["revisions"]!.AsArray().Select(r => r!.AsObject()).Select<JsonObject, (AreaCmt, (DateTime, DateTime)?)>(r => {
var createdAt = json["created_at"]?.AsValue().GetValue<string>(); var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>(); var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaCom { return (new AreaCmt {
FbNr = fbnr, FbNr = fbnr,
RevNr = r["revnr"]!.AsValue().GetValue<int>(), RevNr = r["revnr"]!.AsValue().GetValue<int>(),
MgNr = r["mgnr"]!.AsValue().GetValue<int>(), MgNr = r["mgnr"]!.AsValue().GetValue<int>(),
@@ -789,9 +877,12 @@ namespace Elwig.Helpers.Export {
DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None))); DateTime.ParseExact(modifiedAt, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None)));
} }
public static (AreaComContract, AreaCom, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCom(this JsonNode json, Dictionary<int, List<WbRd>> riede) { public static (AreaCmtContract, AreaCmt, WbRd?, (DateTime CreatedAt, DateTime ModifiedAt)?) ToAreaCmt(this JsonNode json, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs, Dictionary<int, List<WbRd>> riede) {
var kgnr = json["kgnr"]!.AsValue().GetValue<int>(); var kgnr = json["kgnr"]!.AsValue().GetValue<int>();
var ried = json["ried"]?.AsValue().GetValue<string>(); var ried = json["ried"]?.AsValue().GetValue<string>();
if (!currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
}
WbRd? rd = null; WbRd? rd = null;
bool newRd = false; bool newRd = false;
if (ried != null) { if (ried != null) {
@@ -810,13 +901,13 @@ namespace Elwig.Helpers.Export {
} }
var createdAt = json["created_at"]?.AsValue().GetValue<string>(); var createdAt = json["created_at"]?.AsValue().GetValue<string>();
var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>(); var modifiedAt = json["modified_at"]?.AsValue().GetValue<string>();
return (new AreaComContract { return (new AreaCmtContract {
FbNr = json["fbnr"]!.AsValue().GetValue<int>(), FbNr = json["fbnr"]!.AsValue().GetValue<int>(),
KgNr = kgnr, KgNr = kgnr,
RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(), RdNr = rd?.RdNr ?? json["rdnr"]?.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(), Comment = json["comment"]?.AsValue().GetValue<string>(),
ImportedAt = DateTime.Now, ImportedAt = DateTime.Now,
}, new AreaCom { }, new AreaCmt {
FbNr = json["fbnr"]!.AsValue().GetValue<int>(), FbNr = json["fbnr"]!.AsValue().GetValue<int>(),
RevNr = 1, RevNr = 1,
MgNr = json["mgnr"]!.AsValue().GetValue<int>(), MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
@@ -870,17 +961,17 @@ namespace Elwig.Helpers.Export {
}; };
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck; if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked; if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden; if (p.IsContracted != null) obj["contracted"] = p.IsContracted;
if (p.Unloading != null) obj["unloading"] = p.Unloading; if (p.Unloading != null) obj["unloading"] = p.Unloading;
if (p.Temperature != null) obj["temperature"] = p.Temperature; if (p.Temperature != null) obj["temperature"] = p.Temperature;
if (p.Acid != null) obj["acid"] = p.Acid; if (p.Acid != null) obj["acid"] = p.Acid;
if (p.ScaleId != null) obj["scale_id"] = p.ScaleId; if (p.ScaleTerminalId != null) obj["scale_terminal"] = p.ScaleTerminalId;
if (p.WeighingData != null) obj["weighing_data"] = JsonNode.Parse(p.WeighingData); if (p.WeighingData != null) obj["weighing_data"] = JsonNode.Parse(p.WeighingData);
if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason; if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason;
return obj; return obj;
} }
public static (Delivery, List<(DeliveryPart, (DateTime CreatedAt, DateTime ModifiedAt)?)>, List<DeliveryPartModifier>, List<WbRd>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids, Dictionary<int, AT_Kg> kgs, Dictionary<int, List<WbRd>> riede) { public static (Delivery, List<(DeliveryPart, (DateTime CreatedAt, DateTime ModifiedAt)?)>, List<DeliveryPartModifier>, List<WbRd>, (DateTime CreatedAt, DateTime ModifiedAt)?) ToDelivery(this JsonNode json, Dictionary<string, int> currentLsNrs, Dictionary<int, int> currentDids, Dictionary<int, AT_Kg> kgs, Dictionary<int, WbKg> currentWbKgs, Dictionary<int, List<WbRd>> riede) {
var year = json["year"]!.AsValue().GetValue<int>(); var year = json["year"]!.AsValue().GetValue<int>();
var lsnr = json["lsnr"]!.AsValue().GetValue<string>(); var lsnr = json["lsnr"]!.AsValue().GetValue<string>();
var did = currentLsNrs.GetValueOrDefault(lsnr, -1); var did = currentLsNrs.GetValueOrDefault(lsnr, -1);
@@ -906,6 +997,9 @@ namespace Elwig.Helpers.Export {
}, [.. json["parts"]!.AsArray().Select(p => p!.AsObject()).Select<JsonObject, (DeliveryPart, (DateTime, DateTime)?)>(p => { }, [.. json["parts"]!.AsArray().Select(p => p!.AsObject()).Select<JsonObject, (DeliveryPart, (DateTime, DateTime)?)>(p => {
var kgnr = p["kgnr"]?.AsValue().GetValue<int>(); var kgnr = p["kgnr"]?.AsValue().GetValue<int>();
var ried = p["ried"]?.AsValue().GetValue<string>(); var ried = p["ried"]?.AsValue().GetValue<string>();
if (kgnr != null && !currentWbKgs.Values.Any(k => k.KgNr == kgnr)) {
throw new KeyNotFoundException($"Für KG {(kgs.TryGetValue(kgnr.Value, out var k) ? k.Name : "?")} ({kgnr:00000}) ist noch keine Großlage festgelegt!\n(Stammdaten \u2192 Herkunftshierarchie)");
}
WbRd? rd = null; WbRd? rd = null;
if (ried != null && kgnr != null) { if (ried != null && kgnr != null) {
var rde = riede.GetValueOrDefault(kgnr.Value, []); var rde = riede.GetValueOrDefault(kgnr.Value, []);
@@ -941,11 +1035,11 @@ namespace Elwig.Helpers.Export {
Comment = p["comment"]?.AsValue().GetValue<string>(), Comment = p["comment"]?.AsValue().GetValue<string>(),
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false, IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(), IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(), IsContracted = p["contracted"]?.AsValue().GetValue<bool>() ?? p["gebunden"]?.AsValue().GetValue<bool>(),
Unloading = p["unloading"]?.AsValue().GetValue<string>() ?? ((p["lesewagen"]?.AsValue().GetValue<bool>() ?? false) ? DeliveryPart.Pumped : null), Unloading = p["unloading"]?.AsValue().GetValue<string>() ?? ((p["lesewagen"]?.AsValue().GetValue<bool>() ?? false) ? DeliveryPart.Pumped : null),
Temperature = p["temperature"]?.AsValue().GetValue<double>(), Temperature = p["temperature"]?.AsValue().GetValue<double>(),
Acid = p["acid"]?.AsValue().GetValue<double>(), Acid = p["acid"]?.AsValue().GetValue<double>(),
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(), ScaleTerminalId = p["scale_terminal"]?.AsValue().GetValue<string>() ?? p["scale_id"]?.AsValue().GetValue<string>(),
WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts), WeighingData = p["weighing_data"]?.AsObject().ToJsonString(Utils.JsonOpts),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(), WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
}, createdAt == null || modifiedAt == null ? null : }, createdAt == null || modifiedAt == null ? null :
-8
View File
@@ -1,8 +0,0 @@
using Elwig.Models.Dtos;
namespace Elwig.Helpers.Export {
/// <summary>
/// Interface for exporting banking data
/// </summary>
public interface IBankingExporter : IExporter<Transaction> { }
}
+4 -2
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Helpers.Export { namespace Elwig.Helpers.Export {
public interface IExporter<T> : IDisposable, IAsyncDisposable { public interface IExporter<T> : IDisposable {
/// <summary> /// <summary>
/// The default file extension of the exported files to be used (whithout a preceding ".") /// The default file extension of the exported files to be used (whithout a preceding ".")
/// </summary> /// </summary>
@@ -14,7 +14,9 @@ namespace Elwig.Helpers.Export {
/// </summary> /// </summary>
/// <param name="data">The data to be exported</param> /// <param name="data">The data to be exported</param>
/// <param name="progress">The progress object to report to</param> /// <param name="progress">The progress object to report to</param>
void Export(IEnumerable<T> data, IProgress<double>? progress = null); void Export(IEnumerable<T> data, IProgress<double>? progress = null) {
ExportAsync(data, progress).GetAwaiter().GetResult();
}
/// <summary> /// <summary>
/// Asynchronosly export the given data to the given file. /// Asynchronosly export the given data to the given file.
@@ -109,8 +109,13 @@ namespace Elwig.Helpers.Export {
<style:paragraph-properties fo:text-align="center"/> <style:paragraph-properties fo:text-align="center"/>
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/> <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style> </style:style>
<style:style style:name="sectionheader" style:family="table-cell" style:parent-style-name="default">
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/>
<style:paragraph-properties fo:text-align="left"/>
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold" fo:font-size="14pt"/>
</style:style>
<style:style style:name="th" style:family="table-cell" style:parent-style-name="default"> <style:style style:name="th" style:family="table-cell" style:parent-style-name="default">
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle"/> <style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle" fo:wrap-option="wrap"/>
<style:paragraph-properties fo:text-align="center"/> <style:paragraph-properties fo:text-align="center"/>
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/> <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style> </style:style>
@@ -211,17 +216,39 @@ namespace Elwig.Helpers.Export {
} }
} }
await Content.WriteAsync( await Content.WriteAsync($"""
$" <table:table-row>\r\n" + <table:table-row>
FormatCell(table.FullName, colSpan: totalSpan, style: "header") + {FormatCell(table.FullName, colSpan: totalSpan, style: "header")}
$" </table:table-row>\r\n" + </table:table-row>
$" <table:table-row>\r\n" + <table:table-row>
FormatCell(table.Subtitle, colSpan: totalSpan, style: "subheader") + {FormatCell(table.Subtitle, colSpan: totalSpan, style: "subheader")}
$" </table:table-row>\r\n" + </table:table-row>
$" <table:table-row>\r\n" + <table:table-row>
$" <table:table-cell table:number-columns-repeated=\"{totalSpan}\"/>\r\n" + <table:table-cell table:number-columns-repeated="{totalSpan}"/>
$" </table:table-row>\r\n" + </table:table-row>
$" <table:table-row>\r\n"); """);
var firstSection = true;
foreach (var sec in table.GetData()) {
if (!firstSection) {
await Content.WriteAsync($"""
<table:table-row>
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
</table:table-row>
<table:table-row>
<table:table-cell table:number-columns-repeated="{totalSpan}"/>
</table:table-row>
""");
}
if (sec.Name != null) {
await Content.WriteAsync($"""
<table:table-row>
{FormatCell(sec.Name, colSpan: totalSpan, style: "sectionheader")}
</table:table-row>
""");
}
await Content.WriteAsync(" <table:table-row>\r\n");
foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) { foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) {
var hasUnits = units.Length > 0; var hasUnits = units.Length > 0;
await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th")); await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th"));
@@ -238,9 +265,11 @@ namespace Elwig.Helpers.Export {
} }
await Content.WriteAsync(" </table:table-row>\r\n"); await Content.WriteAsync(" </table:table-row>\r\n");
foreach (var row in table.GetData()) { foreach (var row in sec.Rows) {
await FormatRow(row, table.ColumnUnits, mergeSubRowCells); await FormatRow(row, table.ColumnUnits, mergeSubRowCells);
} }
firstSection = false;
}
await Content.WriteAsync(" </table:table>\r\n"); await Content.WriteAsync(" </table:table>\r\n");
} }
@@ -314,7 +343,8 @@ namespace Elwig.Helpers.Export {
} }
c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString(CultureInfo.InvariantCulture)}\"{add}><text:p>{data}</text:p></{ct}>"; c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString(CultureInfo.InvariantCulture)}\"{add}><text:p>{data}</text:p></{ct}>";
} else { } else {
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{SecurityElement.Escape(data.ToString())}</text:p></{ct}>"; var lines = string.Join("", data.ToString()?.Split('\n').Select(l => $"<text:p>{SecurityElement.Escape(l)}</text:p>") ?? []);
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}>{lines}</{ct}>";
} }
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-columns-repeated=\"{colSpan - 1}\"/>\r\n" : ""); return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-columns-repeated=\"{colSpan - 1}\"/>\r\n" : "");
+3 -10
View File
@@ -13,7 +13,9 @@ namespace Elwig.Helpers.Export {
private readonly StreamWriter _writer; private readonly StreamWriter _writer;
public VCard(string filename) : this(filename, Utils.UTF8) { } public VCard(string filename) :
this(filename, Utils.UTF8) {
}
public VCard(string filename, Encoding encoding) { public VCard(string filename, Encoding encoding) {
_writer = new StreamWriter(filename, false, encoding); _writer = new StreamWriter(filename, false, encoding);
@@ -24,11 +26,6 @@ namespace Elwig.Helpers.Export {
_writer.Dispose(); _writer.Dispose();
} }
public ValueTask DisposeAsync() {
GC.SuppressFinalize(this);
return _writer.DisposeAsync();
}
public async Task ExportAsync(IEnumerable<Member> data, IProgress<double>? progress = null) { public async Task ExportAsync(IEnumerable<Member> data, IProgress<double>? progress = null) {
progress?.Report(0.0); progress?.Report(0.0);
int count = data.Count() + 1, i = 0; int count = data.Count() + 1, i = 0;
@@ -58,10 +55,6 @@ namespace Elwig.Helpers.Export {
progress?.Report(100.0); progress?.Report(100.0);
} }
public void Export(IEnumerable<Member> data, IProgress<double>? progress = null) {
ExportAsync(data, progress).GetAwaiter().GetResult();
}
private static string? Escape(string? text) { private static string? Escape(string? text) {
return text?.Replace("\\", "\\\\").Replace(",", "\\,").Replace(";", "\\;").Replace("\n", "\\n").Replace("\r", ""); return text?.Replace("\\", "\\\\").Replace(",", "\\,").Replace(";", "\\;").Replace("\n", "\\n").Replace("\r", "");
} }
+71 -3
View File
@@ -1,14 +1,19 @@
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.IO.Hashing; using System.IO.Hashing;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Elwig.Helpers { namespace Elwig.Helpers {
public static partial class Extensions { public static partial class Extensions {
@@ -131,11 +136,39 @@ namespace Elwig.Helpers {
return await cmd.ExecuteScalarAsync(); return await cmd.ExecuteScalarAsync();
} }
public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(this SqliteConnection cnx) { public static async Task QuickCheck(this SqliteConnection cnx) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA quick_check";
using var reader = await cmd.ExecuteReaderAsync();
var list = new List<string>();
while (await reader.ReadAsync()) {
list.Add(reader.GetString(0));
}
if (list.Count != 1 || list[0] != "ok") {
throw new FileFormatException($"Integrity problems ({list.Count}):\n" + string.Join("\n", list.Take(50)));
}
}
public static async Task IntegrityCheck(this SqliteConnection cnx) {
using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA integrity_check";
using var reader = await cmd.ExecuteReaderAsync();
var list = new List<string>();
while (await reader.ReadAsync()) {
list.Add(reader.GetString(0));
}
if (list.Count != 1 || list[0] != "ok") {
throw new FileFormatException($"Integrity problems ({list.Count}):\n" + string.Join("\n", list.Take(50)));
}
}
public static async Task ForeignKeyCheck(this SqliteConnection cnx) {
using var cmd = cnx.CreateCommand(); using var cmd = cnx.CreateCommand();
cmd.CommandText = "PRAGMA foreign_key_check"; cmd.CommandText = "PRAGMA foreign_key_check";
using var reader = await cmd.ExecuteReaderAsync(); using var reader = await cmd.ExecuteReaderAsync();
var list = new List<(string, long, string, long)>(); var list = new List<(string Table, long RowId, string Parent, long FkId)>();
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
var table = reader.GetString(0); var table = reader.GetString(0);
var rowid = reader.GetInt64(1); var rowid = reader.GetInt64(1);
@@ -143,7 +176,42 @@ namespace Elwig.Helpers {
var fkid = reader.GetInt64(3); var fkid = reader.GetInt64(3);
list.Add((table, rowid, parent, fkid)); list.Add((table, rowid, parent, fkid));
} }
return [.. list];
if (list.Count > 0) {
throw new InvalidDataException($"Foreign key violations ({list.Count}):\n" + string.Join("\n", list.Take(50)
.Select(v => $"{v.Table} - {v.RowId} - {v.Parent} - {v.FkId}")));
}
}
public static IEnumerable<T> Join<T>(this IEnumerable<T> src, Func<T> separatorFactory) {
var srcArr = src.ToArray();
for (int i = 0; i < srcArr.Length; i++) {
yield return srcArr[i];
if (i < srcArr.Length - 1) {
yield return separatorFactory();
}
}
}
public static void AddToolTipCell(this Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false, Brush? color = null) {
var tb = new TextBlock() {
Text = text,
TextAlignment = alignRight ? TextAlignment.Right : alignCenter ? TextAlignment.Center : TextAlignment.Left,
Margin = new(0, 12 * row, 0, 0),
FontWeight = bold ? FontWeights.Bold : FontWeights.Normal,
};
if (color != null) tb.Foreground = color;
tb.SetValue(Grid.ColumnProperty, col);
tb.SetValue(Grid.ColumnSpanProperty, colSpan);
grid.Children.Add(tb);
}
public static (int Year, int Week) GetYearAndWeek(this DateTime date) {
return (ISOWeek.GetYear(date), ISOWeek.GetWeekOfYear(date));
}
public static (int Year, int Tertial) GetYearAndTertial(this DateTime date) {
return (date.Month <= 7 ? date.Year - 1 : date.Year, date.Month == 12 || date.Month <= 3 ? 2 : date.Month <= 7 ? 3 : 1);
} }
} }
} }
+303
View File
@@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Elwig.Helpers {
public static partial class Normalizer {
[GeneratedRegex(@"(?=(?:(?:bundes)?straße|gasse|weg|platz|allee|ring|zeile?|steig|hof|markt|kai|ufer|brücke|tor|stiege|promenade|gang|siedlung)\b)|[\s-]+", RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)]
private static partial Regex GeneratedStreetNameWordsRegex();
private static readonly Regex StreetNameWordsRegex = GeneratedStreetNameWordsRegex();
private static readonly HashSet<string> StreetTypes = [
"Straße", "Gasse", "Weg", "Platz", "Allee", "Ring", "Zeile", "Zeil", "Steig", "Hof",
"Markt", "Kai", "Ufer", "Brücke", "Tor", "Stiege", "Promenade", "Gang", "Siedlung"];
private static readonly HashSet<string> StreetArticles = ["der", "die", "das", "des", "dem", "den"];
private static readonly HashSet<string> StreetPrepositions = [
"an", "am", "auf", "aus", "bei", "beim", "hinter", "hinterm", "in", "im", "ins", "nach",
"neben", "über", "unter", "von", "vom", "van", "vor", "zu", "zum", "zur", "zwischen",
"durch", "mit"];
private static readonly HashSet<string> StreetNumbers = [ "Zwei", "Drei", "Vier", "Fünf", "Sechs", "Sieben" ];
private static readonly HashSet<string> StreetAdjectives = [
"alter", "alte", "alten", "altem", "altes",
"äußere", "äußere", "äußeren", "äußerem", "äußeres",
// "breiter", "breite", "breiten", "breitem", "breites",
"dritter", "dritte", "dritten", "drittem", "drittes",
"erster", "erste", "ersten", "erstem", "erstes",
"großer", "große", "großen", "großem", "großes",
"hoher", "hohe", "hohen", "hohem", "hohes",
"innerer", "innere", "inneren", "innerem", "inneres",
"kleiner", "kleine", "kleinen", "kleinem", "kleines",
"kurzer", "kurze", "kurzen", "kurzem", "kurzes",
"langer", "lange", "langen", "langem", "langes",
"linker", "linke", "linken", "linkem", "linkes",
"mittlerer", "mittlere", "mittleren", "mittlerem", "mittleres",
"neuer", "neue", "neuen", "neuem", "neues",
"oberer", "obere", "oberen", "oberem", "oberes",
"rechter", "rechte", "rechten", "rechtem", "rechtes",
"schmaler", "schmale", "schmalen", "schmalem", "schmales",
"schöner", "schöne", "schönen", "schönem", "schönes",
"unterer", "untere", "unteren", "unterem", "unteres",
"hinterer", "hintere", "hinteren", "hinterem", "hinteres",
"vorderer", "vordere", "vorderen", "vorderem", "vorderes",
"runder", "runde", "runden", "rundem", "rundes",
"kalter", "kalte", "kalten", "kaltem", "kaltes"];
private static readonly HashSet<string> StreetLastNames = [
"Habsburger", "Lothringer", "Leitzenberger", "Luyderer", "Preiner", "Radinger", "Seefelder", "Staribacher", "Gschmeidler",
"Hannover", "Münster", "Salzgitter", "Speyer", "Trier"];
private static readonly HashSet<string> StreetPersonTitles = [
"St.", "Familie",
"Dr.", "DDr.", "Hofrat", "Baurat", "Kommerzialrat", "Dir.", "Ing.", "Prof.", "Mag.", "DI", "OSR", "Präsident",
"Landeshauptmann", "LH", "Bürgermeister", "Bgm.", "Minister",
"Kaiser", "Kaiserin", "König", "Königin", "Erzherzog", "Erzherzogin", "Herzog", "Fürst", "Graf", "Gräfin", "Markgraf", "Kronprinz", "Prinz", "Ritter", "Freiherr", "Lord",
"Abt", "Bischof", "Propst", "Dechant", "Kardinal", "Monsignore", "Pfarrer", "Pater", "Prälat",
"Oberst"];
private static readonly HashSet<string> StreetGivenNames = [
"Adalbert", "Adam", "Adele", "Adelheid", "Adolf", "Adolph", "Adrian", "Albert", "Albrecht", "Alexander", "Alfons", "Alfred", "Alois", "Aloisia",
"Andreas", "Andrea", "Andre", "Anna", "Anton", "Arnold", "Arthur", "August", "Augustin", "Augustine", "Agnes", "Angelika", "Ada", "Adelheid", "Anni", "Anita",
"Barbara", "Beate", "Beatrix", "Benedikt", "Bernhard", "Berta", "Bertha", "Berthold", "Brüder", "Bruno", "Brigitte",
"Camillo", "Carl", "Caspar", "Catharina", "Christian", "Christine", "Christof", "Christoph", "Clara", "Clemens", "Conrad", "Claus", "Camillo", "Claudia",
"Daniel", "Dietmar", "Dietrich",
"Edgar", "Edi", "Edith", "Edmund", "Egon", "Elsa", "Eduard", "Emmerich", "Erhard", "Erich", "Ernst", "Erwin", "Eugen", "Ewald", "Eberhard",
"Elisabeth", "Emanuel", "Emerich", "Emil", "Emma", "Engelbert", "Elias",
"Franz", "Ferdinand", "Friedrich", "Felix", "Florian", "Fritz", "Franziska", "Fred",
"Georg", "Gregor", "Gustav", "Gabriel", "Gebrüder", "Gabriele", "Gerhard", "Gerhart", "Gertrude", "Gottfried", "Gottlieb", "Grete",
"Günther", "Günter",
"Hans", "Hanns", "Heinrich", "Heinz", "Helmut", "Helmuth", "Hermann", "Herta", "Herbert", "Hubert", "Harald", "Horst", "Helene", "Helga",
"Hieronymus", "Hilde", "Hugo",
"Isabelle", "Ignaz", "Ilse", "Ingeborg", "Isidor",
"Josef", "Josefine", "Joseph", "Johann", "Johanna", "Johan", "Johannes", "Hannes", "Harry", "Jakob", "Jacob", "Jan", "Julian", "Julius", "Julia", "Jürgen",
"Karl", "Kasimir", "Konrad", "Klemens", "Klara", "Katharina", "Kurt", "Kaspar", "Klaus", "Koloman", "Konstantin", "Killian",
"Leopold", "Leo", "Lorenz", "Lothar", "Louise", "Ludmilla", "Ludwig", "Lukas", "Ladislaus", "Lambert", "Laurenz", "Liese",
"Lise", "Lilly", "Lina", "Lothar", "Lotte", "Luise", "Leonhard", "Lotte", "Martha",
"Maria", "Marie", "Michael", "Martin", "Max", "Maximilian", "Manuel", "Manuela", "Moritz", "Marianne", "Manfred", "Marc",
"Mathias", "Matthias", "Matthäus",
"Margarete", "Margarethe",
"Nico", "Niko", "Niklas", "Niclas", "Nikolaus", "Norbert",
"Otto", "Ottokar", "Oskar", "Olga", "Oswald", "Othmar",
"Paul", "Pauline", "Patrick", "Pepi", "Peter", "Philipp", "Philip", "Petrus",
"Robert", "Rudolf", "Richard", "Ruprecht", "Raffael", "Rolf", "Roman", "Rosa", "Rosalia", "Rupert", "Raimund",
"Sigmund", "Siegfried", "Simon", "Stanislaus", "Stefan", "Stephan", "Sepp", "Samuel", "Sebastian", "Susi",
"Theodor", "Thomas", "Theresia", "Thaddäus", "Theo", "Theophil", "Tina", "Titus", "Toni", "Trude",
"Ullrich", "Ulrich", "Udo",
"Viktor", "Vinzenz", "Victor",
"Wolfgang", "Willibald", "Wilhelm", "Willhelm", "Wilhelmine", "Walter", "Walther", "Wenzel", "Werner", "Willi",
"Zacharias"];
private static readonly HashSet<string> LocalityNames = [
"Groß-Enzersdorf",
"Groß Burgstall", "Klein Eberharts", "Klein-Engersdorf", "Groß-Enzersdorf", "Klein-Erlauf",
"Groß Gerungs", "Klein Gundholz", "Klein-Harras", "Klein-Heinrichschlag", "Groß-Höbarten",
"Klein-Jetzelsdorf", "Klein-Mariazell", "Groß Meinharts", "Klein-Meiseldorf", "Groß-Neusiedl", "Klein-Neusiedl", "Klein-Pöchlarn",
"Groß-Radischen", "Klein-Radischen", "Groß Raßberg", "Klein Reinprechts", "Klein-Reinprechtsdorf", "Groß-Reipersdorf", "Klein-Ruprechts",
"Groß-Schollach", "Klein-Schollach", "Groß-Schweinbarth", "Groß-Siegharts", "Klein-Siegharts", "Groß Sierning",
"Klein-Ulrichschlag", "Klein Weinberg", "Klein Wetzles", "Klein-Wien",
];
private static bool IsGivenName(this string word) {
return StreetGivenNames.Contains(word);
}
private static bool IsFamilyName(this string word) {
if (StreetLastNames.Contains(word))
return true;
return false;
}
private static bool IsPersonalName(this string word) {
return word.IsGivenName() || word.IsFamilyName();
}
private static bool IsGeographicalDerivativeName(this string word) {
if (!word.EndsWith("er") || word.IsPersonalName() || word == "Weißer")
return false;
return word.StartsWith("St. ") || word.StartsWith("Ober ") || word.StartsWith("Unter ") || word.StartsWith("Maria") ||
word.EndsWith("sdorfer") || word.EndsWith("ndorfer") || word.EndsWith("ldorfer") ||
word.EndsWith("felder") ||
word.EndsWith("sthaler") || word.EndsWith("nthaler") || word.EndsWith("lthaler") ||
word.EndsWith("dauer") || word.EndsWith("lauer") || word.EndsWith("mauer") || word.EndsWith("nauer") || word.EndsWith("ßauer") ||
word.EndsWith("lbacher") || word.EndsWith("nbacher") || word.EndsWith("ßbacher") ||
word.EndsWith("ringer") || word.EndsWith("singer") ||
word.EndsWith("sbrunner") || word.EndsWith("nbrunner") || word.EndsWith("lbrunner") || word.EndsWith("abrunner") || word.EndsWith("ingbrunner") ||
word.EndsWith("sburger") || word.EndsWith("nburger") || word.EndsWith("mburger") || word.EndsWith("lburger") || word.EndsWith("neuburger") ||
word.EndsWith("berger") ||
word.EndsWith("winkler") ||
word.EndsWith("lsteiner") || word.EndsWith("nsteiner") ||
word.EndsWith("städter") ||
word.EndsWith("stättner") || word.EndsWith("stettner") ||
word.EndsWith("brücker") ||
word.EndsWith("further") || word.EndsWith("furter") ||
word.EndsWith("aaer") ||
word.EndsWith("seer") || word.EndsWith("leer") ||
word.EndsWith("tzner") ||
word.EndsWith("zeller") ||
word.EndsWith("rther") ||
word.EndsWith("sdner") ||
word.EndsWith("gerner") || word.EndsWith("berner") ||
word.EndsWith("frieder") ||
word.EndsWith("litzer") || word.EndsWith("mitzer") || word.EndsWith("eutzer") ||
word.EndsWith("loner") ||
word.EndsWith("skirchner") || word.EndsWith("nkirchner") || word.EndsWith("ingkirchner") ||
word.EndsWith("ziger") ||
word.EndsWith("ließer") ||
word.EndsWith("gerer") ||
word.EndsWith("kreuzer") ||
word == "Wiener" || word == "Brünner" || word == "Znaimer" || word == "Triester" || word == "Prager" || word == "Berliner" || word == "Basler" ||
word == "Retzer" || word == "Tullner" || word == "Badner" ||
word == "Gruber" || word == "Sulzer" || word == "Harraser" || word == "Dörfleser" || word == "Neusiedler" ||
word == "Dornauer" || word == "Siebenhauser" || word == "Alser" ||
word == "Bockenheimer";
}
public static string NormalizeAddress(string address, string? locality = null) {
var idx = address.IndexOf(',');
if (idx >= 0) address = address[..idx];
var (name, nr) = Utils.SplitAddress(address);
return (NormalizeStreetName(name, locality) + " " + nr).Trim();
}
public static string NormalizeStreetName(string name, string? locality = null) {
name = name.Trim().ToLowerInvariant();
if (name == "") {
return "";
} else if (name == "nr.") {
if (locality == null)
return "Nr.";
name = locality.Trim().ToLowerInvariant();
}
name = name.Replace("hautp", "haupt")
.Replace("leop.", "leopold ").Replace("ferd.", "ferdinand ").Replace("gottfr.", "gottfried ")
.Replace("heinr.", "heinrich").Replace("joh.", "johann ").Replace("jos.", "josef ")
.Replace("msgr.", "monsignore ").Replace("pf.", "pfarrer ")
.Replace("präs.", "präsident ");
name = name.Replace("str.", "straße").Replace("strasse", "straße")
.Replace("bruecke", "brücke").Replace("pl.", "platz")
.Replace("gr.", "groß").Replace("kl.", "klein")
.Replace("sankt-", "st.").Replace("sankt ", "st.")
.Replace("wr.", "wiener ")
.Replace(".", ". ");
List<string> words = [.. StreetNameWordsRegex.Split(name)
.Where(w => !string.IsNullOrWhiteSpace(w))
.Select(w => StreetArticles.Contains(w) || StreetPrepositions.Contains(w) ? w : string.Concat(w[0..1].ToUpper(), w.AsSpan(1)))
.Select(w => w == "Ddr." ? "DDr." : w == "Lh" || w == "Osr" ? w.ToUpper() : w)
];
if (words[0] == "Di") words[0] = "DI";
var temp = "|" + string.Join('|', words) + "|";
temp = temp.Replace("|Dipl.|Ing.|", "|DI|").Replace("|Dipl|Ing.|", "|DI|")
.Replace("|Kaiser|Ebersdorf|", "|Kaiser-Ebersdorf|")
.Replace("|Kalk|Deutz|", "|Kalk-Deutz|").Replace("|Kalk|Deutzer|", "|Kalk-Deutzer|")
.Replace("|Hohe|Wand|", "|Hohe Wand|").Replace("|Hohen|Wand|", "|Hohen Wand|").Replace("|Hohewand|", "|Hohe Wand|")
.Replace("|Alten|Markt|", "|Altenmarkt|")
.Replace("|Kirch|Hof|", "|Kirchhof|").Replace("|Bisc|Hof|", "|Bischof|")
.Replace("|Wiener|Neustadt|", "|Wiener Neustadt|").Replace("|Wiener|Neustädter|", "|Wiener Neustädter|")
.Replace("|St.|Pölten|", "|St. Pölten|").Replace("|St.|Pöltner|", "|St. Pöltner|")
.Replace("|Gross|", "|Groß|")
.Replace("|Nr|", "|Nr.|").Replace("|Und|", "|und|");
words = [.. temp.Trim('|').Split('|')];
if (words.Count > 1 && (words[0] == "Groß" || words[0] == "Klein") && !StreetTypes.Contains(words[1])) {
var w = words[0] + words[1].ToLowerInvariant();
words.RemoveRange(0, 2);
words.Insert(0, w);
}
if (words[0].StartsWith("Groß") || words[0].StartsWith("Klein")) {
var repl = LocalityNames.FirstOrDefault(n => n.Replace(" ", "").Replace("-", "").Equals(words[0], StringComparison.InvariantCultureIgnoreCase));
if (repl != null) words[0] = repl;
}
if (words.Count > 2 && words[^2] == "in") {
// remove suffix "in ..."
words.RemoveRange(words.Count - 2, 2);
}
char sep = name.Contains('-') ? '-' : ' ';
if (words.Count <= 1) {
// single word
} else if (StreetTypes.Contains(words[0]) && !StreetTypes.Contains(words[^1])) {
// "Straße der ..." / "Straße des ..."
sep = ' ';
} else if (StreetTypes.Contains(words[^1])) {
// "... Straße" / "...straße"
var type = words[^1];
var isCompound = true;
var isAdjective = StreetAdjectives.Contains(words[^2].ToLowerInvariant());
var isGeographical = false;
var isPerson = false;
var geo = words.FindLastIndex(IsGeographicalDerivativeName);
if (geo >= 0 && geo < words.Count - 2 && words[^2] != "Haupt" && words[^2] != "Bundes" && words[^2] != "Keller") {
isPerson = true;
isGeographical = true;
} else if (words.Any(w => w.Contains(' ') && !w.IsGeographicalDerivativeName())) {
isPerson = true;
isGeographical = true;
} else if (StreetNumbers.Contains(words[0])) {
isPerson = true;
isGeographical = true;
} else if (!StreetPrepositions.Contains(words[0]) && !StreetArticles.Contains(words[0]) && words.FindIndex(StreetPrepositions.Contains) > 0) {
isPerson = true;
isGeographical = true;
}
if (words.Count(w => w.Contains('.')) >= 2 || words.Contains("van") || words.Contains("von") || words.Contains("St.") ||
StreetPersonTitles.Contains(words[0]) || words.Any(w => w.IsGivenName()))
{
isPerson = true;
}
if (!isPerson && isAdjective || StreetArticles.Contains(words[^2]) || StreetPrepositions.Contains(words[^2])) {
isCompound = false;
}
if (!isPerson && words[^2].EndsWith("er") && !StreetLastNames.Contains(words[^2])) {
isGeographical = IsGeographicalDerivativeName(words[^2]);
} else if (words.Count >= 3 && words.Contains("Bad")) {
isPerson = true;
isGeographical = true;
}
sep = isPerson ? '-' : ' ';
if (isPerson && words.Count == 2 && !isGeographical) {
isPerson = false;
isCompound = true;
}
if (isGeographical || isPerson) {
isCompound = false;
}
if (isCompound) {
var w = words[^2] + type.ToLowerInvariant();
words.RemoveRange(words.Count - 2, 2);
words.Add(w);
}
if (isPerson) {
words = [.. words.Select(w => w.Replace(' ', '-'))];
}
} else {
// unknown
}
if (sep == '-' && StreetPrepositions.Contains(words[0]) && words[0] != "von" && words[0] != "van") {
sep = ' ';
int i;
for (i = 0; StreetPrepositions.Contains(words[i]) && i < words.Count; i++);
var word = string.Join('-', words.Skip(i));
words = [.. words.Take(i).Append(word)];
}
var norm = string.Join(sep, words);
return string.Concat(norm[0..1].ToUpperInvariant(), norm.AsSpan(1));
}
}
}
+8 -23
View File
@@ -1,21 +1,17 @@
using Elwig.Services;
using Elwig.Windows; using Elwig.Windows;
using System; using System;
using System.Drawing.Printing;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Printing { namespace Elwig.Helpers.Printing {
public static class Pdf { public static class Pdf {
public static Task Init(Action? evtHandler = null) { public static void Init(Action? evtHandler = null) {
PdfiumNative.FPDF_InitLibrary(); PdfiumNative.FPDF_InitLibrary();
evtHandler?.Invoke(); evtHandler?.Invoke();
return Task.CompletedTask;
} }
public static Task Cleanup() { public static void Cleanup() {
PdfiumNative.FPDF_DestroyLibrary(); PdfiumNative.FPDF_DestroyLibrary();
return Task.CompletedTask;
} }
public static void Show(TempFile file, string title) { public static void Show(TempFile file, string title) {
@@ -32,24 +28,13 @@ namespace Elwig.Helpers.Printing {
}); });
} }
public static async Task Print(string path, int copies = 1, bool doublePaged = false) { public static void Print(string path, int copies = 1, bool doublePaged = false) {
await Print(path, new() {
Copies = (short)copies,
Collate = true,
Duplex = doublePaged ? Duplex.Vertical : Duplex.Simplex,
});
}
public static Task Print(string path, PrinterSettings settings) {
try { try {
using var printDoc = new PdfPrintDocument(path) { var printer = new PdfPrinter();
PrinterSettings = settings, printer.Print(path, copies, doublePaged);
}; } catch (Exception exc) {
printDoc.Print(); InteractionService.ShowException("Fehler beim Drucken", "Beim Drucken ist ein Fehler aufgetreten", exc);
} catch (Exception e) {
MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken", MessageBoxButton.OK, MessageBoxImage.Error);
} }
return Task.CompletedTask;
} }
} }
} }
-102
View File
@@ -1,102 +0,0 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.Runtime.InteropServices;
namespace Elwig.Helpers.Printing {
public class PdfPrintDocument : PrintDocument {
private readonly IntPtr _handle;
private readonly int _pageCount;
private readonly double _dpi;
private int _currentPage;
public PdfPrintDocument(string path, string? password = null, double dpi = 300.0) :
base() {
_handle = PdfiumNative.FPDF_LoadDocument(path, password);
_pageCount = PdfiumNative.FPDF_GetPageCount(_handle);
_dpi = dpi;
}
protected override void Dispose(bool disposing) {
PdfiumNative.FPDF_CloseDocument(_handle);
base.Dispose(disposing);
}
protected override void OnBeginPrint(PrintEventArgs evt) {
_currentPage = (PrinterSettings.FromPage != 0) ? (PrinterSettings.FromPage - 1) : 0;
base.OnBeginPrint(evt);
}
protected override void OnPrintPage(PrintPageEventArgs evt) {
if (_currentPage < _pageCount) {
IntPtr page = PdfiumNative.FPDF_LoadPage(_handle, _currentPage);
double width = PdfiumNative.FPDF_GetPageWidth(page);
double height = PdfiumNative.FPDF_GetPageHeight(page);
int pixelWidth = (int)(width / 72.0 * _dpi);
int pixelHeight = (int)(height / 72.0 * _dpi);
IntPtr bitmap = PdfiumNative.FPDFBitmap_Create(pixelWidth, pixelHeight, 1);
PdfiumNative.FPDF_RenderPageBitmap(bitmap, page, 0, 0, pixelWidth, pixelHeight, 0, 0);
IntPtr buffer = PdfiumNative.FPDFBitmap_GetBuffer(bitmap);
int stride = PdfiumNative.FPDFBitmap_GetStride(bitmap);
using (var bmp = new Bitmap(pixelWidth, pixelHeight, stride, PixelFormat.Format32bppArgb, buffer)) {
evt.Graphics?.DrawImage(bmp, evt.PageBounds);
}
_currentPage++;
PdfiumNative.FPDFBitmap_Destroy(bitmap);
PdfiumNative.FPDF_ClosePage(page);
}
evt.HasMorePages = _currentPage < ((PrinterSettings.ToPage == 0) ? _pageCount : Math.Min(PrinterSettings.ToPage, _pageCount));
base.OnPrintPage(evt);
}
}
internal partial class PdfiumNative {
[LibraryImport("pdfium.dll")]
public static partial void FPDF_InitLibrary();
[LibraryImport("pdfium.dll")]
public static partial void FPDF_DestroyLibrary();
[LibraryImport("pdfium.dll", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr FPDF_LoadDocument(string filePath, string? password);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_CloseDocument(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial int FPDF_GetPageCount(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDF_LoadPage(IntPtr document, int pageIndex);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_ClosePage(IntPtr page);
[LibraryImport("pdfium.dll")]
public static partial double FPDF_GetPageWidth(IntPtr page);
[LibraryImport("pdfium.dll")]
public static partial double FPDF_GetPageHeight(IntPtr page);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDFBitmap_Create(int width, int height, int alpha);
[LibraryImport("pdfium.dll")]
public static partial void FPDFBitmap_Destroy(IntPtr bitmap);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDFBitmap_GetBuffer(IntPtr bitmap);
[LibraryImport("pdfium.dll")]
public static partial int FPDFBitmap_GetStride(IntPtr bitmap);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_RenderPageBitmap(IntPtr bitmap, IntPtr page, int start_x, int start_y, int size_x, int size_y, int rotate, int flags);
}
}
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.IO;
using System.Printing;
namespace Elwig.Helpers.Printing {
public class PdfPrinter {
public string PrinterName { get; init; }
public PdfPrinter(string? printerName = null) {
PrinterName = printerName ?? GetDefaultPrinterName() ?? throw new InvalidOperationException("No default Windows printer is configured.");
}
public void Print(string pdfPath, int copies = 1, bool doublePaged = false) {
if (!File.Exists(pdfPath)) throw new FileNotFoundException("PDF file not found.", pdfPath);
IntPtr document = PdfiumNative.FPDF_LoadDocument(pdfPath, null);
if (document == IntPtr.Zero) throw new InvalidOperationException("PDFium could not open the PDF.");
try {
int pageCount = PdfiumNative.FPDF_GetPageCount(document);
if (pageCount <= 0)
return;
using var printer = new Win32Printer.Printer(PrinterName);
printer.TrySetDuplex(doublePaged ? Duplexing.TwoSidedLongEdge : Duplexing.OneSided);
printer.StartDocument(Path.GetFileName(pdfPath));
bool documentStarted = true;
try {
for (int i = 0; i < copies; i++) {
for (int j = 0; j < pageCount; j++) {
PrintPage(document, printer, j);
}
}
printer.EndDocument();
documentStarted = false;
} finally {
if (documentStarted) printer.AbortDocument();
}
} finally {
PdfiumNative.FPDF_CloseDocument(document);
}
}
private static void PrintPage(IntPtr document, Win32Printer.Printer printer, int pageIndex) {
if (!PdfiumNative.FPDF_GetPageSizeByIndex(document, pageIndex, out double pageWidthPt, out double pageHeightPt)) {
throw new InvalidOperationException($"Unable to obtain PDF page {pageIndex} size.");
}
IntPtr page = PdfiumNative.FPDF_LoadPage(document, pageIndex);
if (page == IntPtr.Zero)
throw new InvalidOperationException($"Unable to load PDF page {pageIndex}.");
try {
int renderWidth = (int)Math.Round(pageWidthPt / 72.0 * printer.DpiX);
int renderHeight = (int)Math.Round(pageHeightPt / 72.0 * printer.DpiY);
int offsetX = (printer.PrintableWidth - renderWidth) / 2;
int offsetY = (printer.PrintableHeight - renderHeight) / 2;
printer.StartPage();
try {
PdfiumNative.FPDF_RenderPage(printer.Hdc, page, offsetX, offsetY, renderWidth, renderHeight, 0, PdfiumNative.FPDF_PRINTING | PdfiumNative.FPDF_ANNOT);
} finally {
printer.EndPage();
}
} finally {
PdfiumNative.FPDF_ClosePage(page);
}
}
private static string? GetDefaultPrinterName() {
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\Windows");
string? printer = key?.GetValue("Device") as string;
if (string.IsNullOrWhiteSpace(printer))
return null;
// Device has the form:
// Printer Name,winspool,PORT
int comma = printer.IndexOf(',');
return comma >= 0 ? printer[..comma] : printer;
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using System.Runtime.InteropServices;
namespace Elwig.Helpers.Printing {
internal partial class PdfiumNative {
public const int FPDF_ANNOT = 0x01;
public const int FPDF_PRINTING = 0x800;
[LibraryImport("pdfium.dll")]
public static partial void FPDF_InitLibrary();
[LibraryImport("pdfium.dll")]
public static partial void FPDF_DestroyLibrary();
[LibraryImport("pdfium.dll", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr FPDF_LoadDocument(string filePath, string? password);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_CloseDocument(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial int FPDF_GetPageCount(IntPtr document);
[LibraryImport("pdfium.dll")]
public static partial IntPtr FPDF_LoadPage(IntPtr document, int pageIndex);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_ClosePage(IntPtr page);
[LibraryImport("pdfium.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool FPDF_GetPageSizeByIndex(IntPtr document, int pageIndex, out double width, out double height);
[LibraryImport("pdfium.dll")]
public static partial void FPDF_RenderPage(IntPtr hdc, IntPtr page, int startX, int startY, int sizeX, int sizeY, int rotate, int flags);
}
}
+239
View File
@@ -0,0 +1,239 @@
using System;
using System.ComponentModel;
using System.Printing;
using System.Runtime.InteropServices;
namespace Elwig.Helpers.Printing {
public partial class Win32Printer {
private const int DM_OUT_BUFFER = 2;
private const int HORZRES = 8;
private const int VERTRES = 10;
private const int LOGPIXELSX = 88;
private const int LOGPIXELSY = 90;
private const int PHYSICALWIDTH = 110;
private const int PHYSICALHEIGHT = 111;
private const int PHYSICALOFFSETX = 112;
private const int PHYSICALOFFSETY = 113;
private const int DM_DUPLEX = 0x1000;
private const short DMDUP_SIMPLEX = 1;
private const short DMDUP_VERTICAL = 2;
private const short DMDUP_HORIZONTAL = 3;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DOCINFO {
public int cbSize;
[MarshalAs(UnmanagedType.LPWStr)]
public string? lpszDocName;
[MarshalAs(UnmanagedType.LPWStr)]
public string? lpszOutput;
[MarshalAs(UnmanagedType.LPWStr)]
public string? lpszDatatype;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEVMODE {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public int dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
}
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool OpenPrinter(string pPrinterName, out IntPtr phPrinter, IntPtr pDefault);
[DllImport("winspool.drv", SetLastError = true)]
private static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int DocumentProperties(IntPtr hwnd, IntPtr hPrinter, string pDeviceName, IntPtr pDevModeOutput, IntPtr pDevModeInput, int fMode);
[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr CreateDC(string? pDriver, string? pDevice, string? pOutput, IntPtr pInitData);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int GetDeviceCaps(IntPtr hdc, int index);
[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int StartDoc(IntPtr hdc, ref DOCINFO lpdi);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int EndDoc(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int AbortDoc(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int StartPage(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern int EndPage(IntPtr hdc);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GlobalFree(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern bool GlobalUnlock(IntPtr hMem);
public class Printer : IDisposable {
private readonly string _printerName;
private readonly IntPtr _printerHandle;
private readonly IntPtr _devMode;
private readonly IntPtr _hdc;
public IntPtr Hdc => _hdc;
public int PrintableWidth => GetDeviceCaps(_hdc, HORZRES);
public int PrintableHeight => GetDeviceCaps(_hdc, VERTRES);
public int DpiX => GetDeviceCaps(_hdc, LOGPIXELSX);
public int DpiY => GetDeviceCaps(_hdc, LOGPIXELSY);
public int PhysicalWidth => GetDeviceCaps(_hdc, PHYSICALWIDTH);
public int PhysicalHeight => GetDeviceCaps(_hdc, PHYSICALHEIGHT);
public int PhysicalOffsetX => GetDeviceCaps(_hdc, PHYSICALOFFSETX);
public int PhysicalOffsetY => GetDeviceCaps(_hdc, PHYSICALOFFSETY);
public Printer(string printerName) {
_printerName = printerName;
if (!OpenPrinter(_printerName, out _printerHandle, IntPtr.Zero)) {
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to open printer '{printerName}'.");
}
try {
_devMode = CreateDevMode();
_hdc = CreateDC(null, _printerName, null, _devMode);
if (_hdc == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Unable to create printer DC for '{_printerName}'.");
} catch {
if (_devMode != IntPtr.Zero) Marshal.FreeHGlobal(_devMode);
ClosePrinter(_printerHandle);
throw;
}
}
public bool TrySetDuplex(Duplexing duplex) {
var devMode = Marshal.PtrToStructure<DEVMODE>(_devMode);
switch (duplex) {
case Duplexing.OneSided: devMode.dmDuplex = DMDUP_SIMPLEX; break;
case Duplexing.TwoSidedShortEdge: devMode.dmDuplex = DMDUP_HORIZONTAL; break;
case Duplexing.TwoSidedLongEdge: devMode.dmDuplex = DMDUP_VERTICAL; break;
}
if ((devMode.dmFields & DM_DUPLEX) != 0) {
Marshal.StructureToPtr(devMode, _devMode, false);
return true;
} else {
devMode.dmFields |= DM_DUPLEX;
Marshal.StructureToPtr(devMode, _devMode, false);
return false;
}
}
private IntPtr CreateDevMode() {
int size = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, IntPtr.Zero, IntPtr.Zero, 0);
if (size <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "DocumentProperties failed.");
IntPtr devMode = Marshal.AllocHGlobal(size);
int result = DocumentProperties(IntPtr.Zero, _printerHandle, _printerName, devMode, IntPtr.Zero, DM_OUT_BUFFER);
if (result < 0) {
Marshal.FreeHGlobal(devMode);
throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to obtain printer DEVMODE.");
}
return devMode;
}
public void StartDocument(string documentName) {
var docInfo = new DOCINFO {
cbSize = Marshal.SizeOf<DOCINFO>(),
lpszDocName = documentName,
lpszDatatype = null,
lpszOutput = null
};
if (StartDoc(_hdc, ref docInfo) <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "StartDoc failed.");
}
public void StartPage() {
if (Win32Printer.StartPage(_hdc) <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "StartPage failed.");
}
public void EndPage() {
if (Win32Printer.EndPage(_hdc) <= 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "EndPage failed.");
}
public void EndDocument() {
if (EndDoc(_hdc) <= 0)
throw new Win32Exception( Marshal.GetLastWin32Error(), "EndDoc failed.");
}
public void AbortDocument() {
AbortDoc(_hdc);
}
public void Dispose() {
if (_hdc != IntPtr.Zero)
DeleteDC(_hdc);
if (_devMode != IntPtr.Zero)
Marshal.FreeHGlobal(_devMode);
if (_printerHandle != IntPtr.Zero)
ClosePrinter(_printerHandle);
}
}
}
}
+80 -38
View File
@@ -3,12 +3,12 @@ using Elwig.Documents;
using Elwig.Helpers.Billing; using Elwig.Helpers.Billing;
using Elwig.Models; using Elwig.Models;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Elwig.Services;
using iText.Layout.Element; using iText.Layout.Element;
using LinqKit; using LinqKit;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using MailKit.Security; using MailKit.Security;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using MimeKit; using MimeKit;
using System; using System;
using System.Collections; using System.Collections;
@@ -31,6 +31,7 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input;
using System.Windows.Markup; using System.Windows.Markup;
namespace Elwig.Helpers { namespace Elwig.Helpers {
@@ -44,7 +45,6 @@ namespace Elwig.Helpers {
public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0); public static int CurrentNextSeason => DateTime.Now.Year - (DateTime.Now.Month <= 3 ? 1 : 0);
public static int CurrentLastSeason => DateTime.Now.Year - (DateTime.Now.Month <= 6 ? 1 : 0); public static int 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 int FollowingSeason => DateTime.Now.Year + (DateTime.Now.Month >= 11 ? 1 : 0);
public static DateTime Today => (DateTime.Now.Hour >= 3) ? DateTime.Today : DateTime.Today.AddDays(-1);
[GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)] [GeneratedRegex("^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\\.5|2|))?$", RegexOptions.Compiled)]
private static partial Regex GeneratedSerialRegex(); private static partial Regex GeneratedSerialRegex();
@@ -209,16 +209,29 @@ namespace Elwig.Helpers {
return Regex.Replace(iban.Trim(), ".{4}", "$0 ").Trim(); return Regex.Replace(iban.Trim(), ".{4}", "$0 ").Trim();
} }
public static void RunBackground(string title, Func<Task> a) { public static void RunBackground(string title, Func<Task> function) {
Task.Run(async () => { Task.Run(async () => {
try { try {
await a(); await function();
} catch (Exception e) { } catch (Exception exc) {
MessageBox.Show(e.ToString(), title, MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException(title, exc);
} }
}); });
} }
public static async Task RunForeground(Func<Task> function) {
var isSTA = Thread.CurrentThread.GetApartmentState() == ApartmentState.STA;
if (isSTA) Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
await function();
} catch (Exception exc) {
InteractionService.ShowException(exc);
}
});
if (isSTA) Mouse.OverrideCursor = null;
}
public static void MailTo(string emailAddress) { public static void MailTo(string emailAddress) {
MailTo([emailAddress]); MailTo([emailAddress]);
} }
@@ -290,6 +303,11 @@ namespace Elwig.Helpers {
return d.ShowDialog() == true ? (d.Weight, d.Reason) : null; return d.ShowDialog() == true ? (d.Weight, d.Reason) : null;
} }
public static int? ShowTareCorrectionDialog(int? actualTare, int? targetTare = null) {
var d = new TareCorrectionDialog(actualTare, targetTare);
return d.ShowDialog() == true ? d.TargetTare - d.ActualTare : null;
}
public static (string?, int[])? ShowDeliverySplittingDialog(Delivery delivery) { public static (string?, int[])? ShowDeliverySplittingDialog(Delivery delivery) {
var d = new DeliverySplittingDialog(delivery); var d = new DeliverySplittingDialog(delivery);
return d.ShowDialog() == true ? (d.MgNr?.ToString() ?? d.LsNr, d.Weights ?? []) : null; return d.ShowDialog() == true ? (d.MgNr?.ToString() ?? d.LsNr, d.Weights ?? []) : null;
@@ -305,7 +323,7 @@ namespace Elwig.Helpers {
} }
public class Footer { public class Footer {
private readonly List<List<object>> Items = [[]]; private readonly List<List<List<object>>> Items = [[[]]];
private readonly string LineBreak; private readonly string LineBreak;
private readonly string Separator; private readonly string Separator;
@@ -316,7 +334,7 @@ namespace Elwig.Helpers {
public Footer Item(object? text) { public Footer Item(object? text) {
if (text == null) return this; if (text == null) return this;
Items[^1].Add(text); Items[^1][^1].Add(text);
return this; return this;
} }
@@ -324,13 +342,38 @@ namespace Elwig.Helpers {
return text == null ? this : Item($"{name}: {text}"); return text == null ? this : Item($"{name}: {text}");
} }
public Footer NextGroup() {
Items[^1].Add([]);
return this;
}
public Footer NextLine() { public Footer NextLine() {
Items.Add([]); Items.Add([[]]);
return this;
}
public Footer RebalanceLines() {
for (int i = 0; i < Items.Count - 1; i++) {
var groups = Items[i];
var nextGroups = Items[i + 1];
if (groups.Count <= 1) continue;
var g = groups[^1];
var gl = g.Sum(i => (i is Text txt ? txt.GetText().Length : i.ToString()?.Length ?? 0) + Separator.Length);
var l1a = groups.SelectMany(g => g).Sum(i => (i is Text txt ? txt.GetText().Length : i.ToString()?.Length ?? 0) + Separator.Length) - Separator.Length;
var l2a = nextGroups.SelectMany(g => g).Sum(i => (i is Text txt ? txt.GetText().Length : i.ToString()?.Length ?? 0) + Separator.Length) - Separator.Length;
var l1b = l1a - gl;
var l2b = l2a + gl;
if (Math.Abs(l1b - l2b) <= Math.Abs(l1a - l2a)) {
Items[i].Remove(g);
Items[i + 1].Insert(0, g);
i--;
}
}
return this; return this;
} }
public override string ToString() { public override string ToString() {
return string.Join(LineBreak, Items.Select(l => string.Join(Separator, l.ToString()))); return string.Join(LineBreak, Items.Select(l => string.Join(Separator, l.SelectMany(g => g).ToString())));
} }
public IList<ILeafElement> ToLeafElements() { public IList<ILeafElement> ToLeafElements() {
@@ -339,11 +382,13 @@ namespace Elwig.Helpers {
foreach (var line in Items) { foreach (var line in Items) {
if (!first1) l.Add(new Text(LineBreak)); if (!first1) l.Add(new Text(LineBreak));
var first2 = true; var first2 = true;
foreach (var item in line) { foreach (var group in line) {
foreach (var item in group) {
if (!first2) l.Add(new Text(Separator)); if (!first2) l.Add(new Text(Separator));
l.Add(item as ILeafElement ?? new Text(item.ToString() ?? "")); l.Add(item as ILeafElement ?? new Text(item.ToString() ?? ""));
first2 = false; first2 = false;
} }
}
first1 = false; first1 = false;
} }
return l; return l;
@@ -443,7 +488,7 @@ namespace Elwig.Helpers {
Timeout = TimeSpan.FromSeconds(5), Timeout = TimeSpan.FromSeconds(5),
}; };
client.DefaultRequestHeaders.UserAgent.Clear(); client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.ParseAdd($"Elwig/{App.Version} ({App.Client.NameToken}, {App.BranchName}, {Environment.MachineName}, {Environment.OSVersion})"); client.DefaultRequestHeaders.UserAgent.ParseAdd($"Elwig/{App.Version} ({App.Client?.NameToken}, {App.BranchName}, {Environment.MachineName}, {Environment.OSVersion})");
client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Clear();
if (accept != null) if (accept != null)
client.DefaultRequestHeaders.Accept.Add(new(accept)); client.DefaultRequestHeaders.Accept.Add(new(accept));
@@ -502,7 +547,7 @@ namespace Elwig.Helpers {
public static async Task<SmtpClient?> GetSmtpClient() { public static async Task<SmtpClient?> GetSmtpClient() {
if (App.Config.Smtp == null) if (App.Config.Smtp == null)
return null; return null;
var (host, port, mode, username, password, _) = App.Config.Smtp.Value; var (host, port, mode, username, password, _, _) = App.Config.Smtp.Value;
var client = new SmtpClient(); var client = new SmtpClient();
await client.ConnectAsync(host, port, mode == "starttls" ? SecureSocketOptions.StartTls : SecureSocketOptions.None); await client.ConnectAsync(host, port, mode == "starttls" ? SecureSocketOptions.StartTls : SecureSocketOptions.None);
await client.AuthenticateAsync(username, password); await client.AuthenticateAsync(username, password);
@@ -522,6 +567,7 @@ namespace Elwig.Helpers {
msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From)); msg.From.Add(new MailboxAddress(App.Client.NameFull, App.Config.Smtp.Value.From));
msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address))); msg.To.AddRange(member.EmailAddresses.OrderBy(a => a.Nr).Select(a => new MailboxAddress(member.AdministrativeName, a.Address)));
msg.Subject = subject; msg.Subject = subject;
msg.Date = DateTime.Now;
var body = new Multipart("mixed") { var body = new Multipart("mixed") {
new TextPart("plain") { Text = text } new TextPart("plain") { Text = text }
}; };
@@ -537,7 +583,7 @@ namespace Elwig.Helpers {
subject, docs.Select(d => d.Title).ToArray() subject, docs.Select(d => d.Title).ToArray()
)]); )]);
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException(exc);
return false; return false;
} finally { } finally {
if (client != null) if (client != null)
@@ -551,8 +597,7 @@ namespace Elwig.Helpers {
public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) { public static async Task ExportDocument(Document doc, ExportMode mode, string? filename = null, (Member Member, string Subject, string Text)? emailData = null) {
if (mode == ExportMode.Print && !App.Config.Debug) { if (mode == ExportMode.Print && !App.Config.Debug) {
if (doc.IsPreview) { if (doc.IsPreview) {
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!", InteractionService.ShowError("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und kann daher nicht ausgedruckt werden!");
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
return; return;
} }
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
@@ -561,8 +606,7 @@ namespace Elwig.Helpers {
await doc.Print(); await doc.Print();
} else if (mode == ExportMode.Email && emailData is (Member, string, string) e) { } else if (mode == ExportMode.Email && emailData is (Member, string, string) e) {
if (doc.IsPreview) { if (doc.IsPreview) {
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!", InteractionService.ShowError("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und kann daher nicht verschickt werden!");
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Error);
return; return;
} }
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
@@ -570,25 +614,18 @@ namespace Elwig.Helpers {
} }
var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]); var success = await SendEmail(e.Member, e.Subject, e.Text, [doc]);
if (success) if (success)
MessageBox.Show("Die E-Mail wurde erfolgreich verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mail im Posteingang des Empfängers aufscheint.", "E-Mail verschickt", InteractionService.ShowInformation("E-Mail verschickt", "Die E-Mail wurde erfolgreich verschickt!\n\nEs kann einige Minuten dauern, bis die E-Mail im Posteingang des Empfängers aufscheint.");
MessageBoxButton.OK, MessageBoxImage.Information);
} else if (mode == ExportMode.SavePdf) { } else if (mode == ExportMode.SavePdf) {
if (doc.IsPreview) { if (doc.IsPreview) {
MessageBox.Show("Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!", InteractionService.ShowWarning("Vorläufiges Dokument", "Dieses Dokument ist als vorläufig markiert und sollte daher nicht langfristig gespeichert werden!");
"Vorläufiges Dokument", MessageBoxButton.OK, MessageBoxImage.Warning);
} }
var d = new SaveFileDialog() { filename = InteractionService.SaveFile(doc.Title, NormalizeFileName(filename ?? doc.Title), "pdf");
FileName = $"{NormalizeFileName(filename ?? doc.Title)}.pdf", if (filename != null) {
DefaultExt = "pdf",
Filter = "PDF-Datei (*.pdf)|*.pdf",
Title = $"{doc.Title} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
await doc.Generate(ctx); await doc.Generate(ctx);
} }
doc.SaveTo(d.FileName); doc.SaveTo(filename);
Process.Start("explorer.exe", d.FileName); if (!App.Config.Debug) Process.Start("explorer.exe", filename);
} }
} else { } else {
using (var ctx = new AppDbContext()) { using (var ctx = new AppDbContext()) {
@@ -625,16 +662,16 @@ namespace Elwig.Helpers {
} }
} }
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments() => ActiveAreaCommitments(CurrentYear); public static Expression<Func<AreaCmt, bool>> ActiveAreaCommitments() => ActiveAreaCommitments(CurrentYear);
public static Expression<Func<AreaCom, bool>> ActiveAreaCommitments(int year) => public static Expression<Func<AreaCmt, bool>> ActiveAreaCommitments(int year) =>
c => (c.YearFrom == null || c.YearFrom <= year) && (c.YearTo == null || c.YearTo >= year); c => (c.YearFrom == null || c.YearFrom <= year) && (c.YearTo == null || c.YearTo >= year);
public static IQueryable<AreaCom> ActiveAreaCommitments(IQueryable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear); public static IQueryable<AreaCmt> ActiveAreaCommitments(IQueryable<AreaCmt> query) => ActiveAreaCommitments(query, CurrentYear);
public static IQueryable<AreaCom> ActiveAreaCommitments(IQueryable<AreaCom> query, int year) => public static IQueryable<AreaCmt> ActiveAreaCommitments(IQueryable<AreaCmt> query, int year) =>
query.Where(ActiveAreaCommitments(year)); query.Where(ActiveAreaCommitments(year));
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query) => ActiveAreaCommitments(query, CurrentYear); public static IEnumerable<AreaCmt> ActiveAreaCommitments(IEnumerable<AreaCmt> query) => ActiveAreaCommitments(query, CurrentYear);
public static IEnumerable<AreaCom> ActiveAreaCommitments(IEnumerable<AreaCom> query, int year) => public static IEnumerable<AreaCmt> ActiveAreaCommitments(IEnumerable<AreaCmt> query, int year) =>
query.Where(c => ActiveAreaCommitments(year).Invoke(c)); query.Where(c => ActiveAreaCommitments(year).Invoke(c));
public static async Task<(DateTime DateTime, string Type, int MgNr, string Name, string[] Addresses, string Subject, string[] Attachments)[]> GetSentMails(DateOnly? fromDate = null, DateOnly? toDate = null) { public static async Task<(DateTime DateTime, string Type, int MgNr, string Name, string[] Addresses, string Subject, string[] Attachments)[]> GetSentMails(DateOnly? fromDate = null, DateOnly? toDate = null) {
@@ -665,7 +702,7 @@ namespace Elwig.Helpers {
p[2], p[2],
int.Parse(p[3]), int.Parse(p[3]),
p[4], p[4],
p[5].Split(',').Select(a => a.Replace(" | ", "\n")).ToArray(), [.. p[5].Split(',').Select(a => a.Replace(" | ", "\n"))],
p[6], p[6],
p[7].Split(',') p[7].Split(',')
)); ));
@@ -707,5 +744,10 @@ namespace Elwig.Helpers {
var filename = Path.Combine(App.MailsPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{NormalizeFileName(subject)}.txt"); var filename = Path.Combine(App.MailsPath, $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{NormalizeFileName(subject)}.txt");
await File.WriteAllTextAsync(filename, $"# {subject}\r\n# Vorgesehene Empfänger: {recipients}\r\n\r\n" + body, Utils.UTF8); await File.WriteAllTextAsync(filename, $"# {subject}\r\n# Vorgesehene Empfänger: {recipients}\r\n\r\n" + body, Utils.UTF8);
} }
public static string GetTitle(string window, IEnumerable<string> filterNames, IEnumerable<string> filter) {
var f = string.Join(" / ", filterNames.Union(filter.Select(f => $"\"{f}\"")));
return window + (f.Length == 0 ? "" : ": " + f) + " - Elwig";
}
} }
} }
+75 -22
View File
@@ -136,8 +136,7 @@ namespace Elwig.Helpers {
return new(false, "PLZ zu kurz"); return new(false, "PLZ zu kurz");
} }
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
int plz = int.Parse(input.Text); if (!int.TryParse(input.Text, out var plz) || ctx.Postleitzahlen.Find(plz) == null) {
if (ctx.Postleitzahlen.Find(plz) == null) {
return new(false, "Ungültige PLZ"); return new(false, "Ungültige PLZ");
} }
return new(true, null); return new(true, null);
@@ -423,8 +422,7 @@ namespace Elwig.Helpers {
} }
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
int nr = int.Parse(input.Text); if (!int.TryParse(input.Text, out var nr) || !ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
if (!ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
return new(false, "Ungültige Mitgliedsnummer"); return new(false, "Ungültige Mitgliedsnummer");
} }
@@ -440,8 +438,7 @@ namespace Elwig.Helpers {
} }
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
int nr = int.Parse(input.Text); if (!int.TryParse(input.Text, out var nr) || nr != m?.MgNr && ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
if (nr != m?.MgNr && ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
return new(false, "Mitgliedsnummer wird bereits verwendet"); return new(false, "Mitgliedsnummer wird bereits verwendet");
} }
@@ -476,7 +473,7 @@ namespace Elwig.Helpers {
return res; return res;
} else if (!required && input.Text.Length == 0) { } else if (!required && input.Text.Length == 0) {
return new(true, null); return new(true, null);
} else if (!ctx.MgNrExists(int.Parse(input.Text)).GetAwaiter().GetResult()) { } else if (!int.TryParse(input.Text, out var nr) ||!ctx.MgNrExists(nr).GetAwaiter().GetResult()) {
return new(false, "Ein Mitglied mit dieser Mitgliedsnummer existiert nicht"); return new(false, "Ein Mitglied mit dieser Mitgliedsnummer existiert nicht");
} }
@@ -537,7 +534,7 @@ namespace Elwig.Helpers {
return (parts[0]?.Length == 4) ? new(true, null) : new(false, "Datum ist ungültig"); return (parts[0]?.Length == 4) ? new(true, null) : new(false, "Datum ist ungültig");
} else if (p == 1) { } else if (p == 1) {
// only month and year provided // only month and year provided
int m = parts[0] != null && parts[0] != "" ? int.Parse(parts[0] ?? "0") : 0; int m = parts[0] != null && parts[0] != "" && int.TryParse(parts[0], out var v) ? v : 0;
if (parts[1]?.Length != 4 || parts[0]?.Length != 2 || m < 1 || m > 12) if (parts[1]?.Length != 4 || parts[0]?.Length != 2 || m < 1 || m > 12)
return new(false, "Datum ist ungültig"); return new(false, "Datum ist ungültig");
return new(true, null); return new(true, null);
@@ -580,7 +577,7 @@ namespace Elwig.Helpers {
} }
} }
public static ValidationResult CheckFbNr(TextBox input, bool required, AreaComContract? c) { public static ValidationResult CheckFbNr(TextBox input, bool required, AreaCmtContract? c) {
var res = CheckInteger(input, required); var res = CheckInteger(input, required);
if (!res.IsValid) { if (!res.IsValid) {
return res; return res;
@@ -589,8 +586,7 @@ namespace Elwig.Helpers {
} }
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
int nr = int.Parse(input.Text); if (!int.TryParse(input.Text, out var nr) || (nr != c?.FbNr && ctx.FbNrExists(nr).GetAwaiter().GetResult())) {
if (nr != c?.FbNr && ctx.FbNrExists(nr).GetAwaiter().GetResult()) {
return new(false, "Flächenbindungsnummer wird bereits verwendet"); return new(false, "Flächenbindungsnummer wird bereits verwendet");
} }
@@ -605,8 +601,7 @@ namespace Elwig.Helpers {
return new(true, null); return new(true, null);
} }
var oe = double.Parse(input.Text); if (!double.TryParse(input.Text, out var oe) || oe < 10 || oe >= 300) {
if (oe < 10 || oe >= 300) {
return new(false, "Ungültiger Oechsle-Wert"); return new(false, "Ungültiger Oechsle-Wert");
} }
@@ -621,14 +616,44 @@ namespace Elwig.Helpers {
return new(true, null); return new(true, null);
} }
var kmw = double.Parse(input.Text); if (!double.TryParse(input.Text, out var kmw) || kmw < 5 || kmw >= 50) {
if (kmw < 5 || kmw >= 50) {
return new(false, "Ungültiger KMW-Wert"); return new(false, "Ungültiger KMW-Wert");
} }
return new(true, null); return new(true, null);
} }
public static ValidationResult CheckOrganicOperatorId(TextBox input, bool required) {
string text = "";
int pos = input.CaretIndex;
for (int i = 0, v = 0; i < input.Text.Length; i++) {
char ch = input.Text[i];
if (char.IsAsciiDigit(ch)) {
if (v == 3 && text[^1] != '-')
text += "-";
v++;
text += ch;
}
if (i == input.CaretIndex - 1) {
pos = text.Length;
} else if (v >= 10) {
break;
}
}
input.Text = text;
input.CaretIndex = pos;
if (text.Length == 0) {
return required ? new(false, "OOC-ID ist nicht optional") : new(true, null);
} else if (!text.StartsWith("040-") || text.Length != 11) {
return new(false, "OOC-ID ist ungültig");
}
return new(true, null);
}
public static ValidationResult CheckOrganicAuthorityCode(TextBox input, bool required) { public static ValidationResult CheckOrganicAuthorityCode(TextBox input, bool required) {
string text = ""; string text = "";
int pos = input.CaretIndex; int pos = input.CaretIndex;
@@ -637,15 +662,18 @@ namespace Elwig.Helpers {
if (v < 2 && char.IsAsciiLetter(ch)) { if (v < 2 && char.IsAsciiLetter(ch)) {
v++; v++;
text += ch; text += ch;
} else if ((v == 2 || v == 6) && ch == '-') { } else if ((v == 2 || v == 5) && ch == '-' && text[^1] != '-') {
v++;
text += ch; text += ch;
} else if (v >= 2 && char.IsLetterOrDigit(ch)) { } else if (v >= 2 && v <= 4 && char.IsLetter(ch)) {
if (v == 2 && text[^1] != '-')
text += "-";
if (text.StartsWith("AT")) { if (text.StartsWith("AT")) {
if (v == 3 && ch == 'B' || v == 4 && ch == 'I' || v == 5 && ch == 'O') { if (v == 2 && ch == 'B' || v == 3 && ch == 'I' || v == 4 && ch == 'O') {
v++; v++;
text += ch; text += ch;
} else if (v > 6 && char.IsAsciiDigit(ch)) { }
} else if (text.StartsWith("DE")) {
if (v == 2 && ch == 'Ö' || v == 3 && ch == 'K' || v == 4 && ch == 'O') {
v++; v++;
text += ch; text += ch;
} }
@@ -653,11 +681,16 @@ namespace Elwig.Helpers {
v++; v++;
text += ch; text += ch;
} }
} else if (v >= 5 && v <= 7 && char.IsAsciiDigit(ch)) {
if (v == 5 && text[^1] != '-')
text += "-";
v++;
text += ch;
} }
if (i == input.CaretIndex - 1) { if (i == input.CaretIndex - 1) {
pos = text.Length; pos = text.Length;
} else if (text.StartsWith("AT") && v >= 10) { } else if ((text.StartsWith("AT") || text.StartsWith("DE")) && v >= 10) {
break; break;
} }
} }
@@ -668,7 +701,7 @@ namespace Elwig.Helpers {
if (text.Length == 0) if (text.Length == 0)
return required ? new(false, "Bio-Kontrollstellen-Code ist nicht optional") : new(true, null); return required ? new(false, "Bio-Kontrollstellen-Code ist nicht optional") : new(true, null);
if (text.StartsWith("AT")) { if (text.StartsWith("AT") || text.StartsWith("DE")) {
if (text.Length != 10) { if (text.Length != 10) {
return new(false, "Bio-Kontrollstellen-Code ist ungültig"); return new(false, "Bio-Kontrollstellen-Code ist ungültig");
} }
@@ -678,5 +711,25 @@ namespace Elwig.Helpers {
return new(true, null); return new(true, null);
} }
public static ValidationResult CheckAddress(TextBox input, bool required, string? locality = null) {
var text = input.Text;
var (name, nr) = Utils.SplitAddress(text);
if (!string.IsNullOrWhiteSpace(nr)) {
var street = Normalizer.NormalizeStreetName(name.Replace(",", ""), locality);
var idx = input.CaretIndex;
input.Text = $"{street} {nr}".Trim();
if (idx >= (text.Length - nr.Length)) {
input.CaretIndex = street.Length + (idx - (text.Length - nr.Length)) + 1;
} else if (idx > 0) {
input.CaretIndex = idx;
}
}
if (text.Length == 0)
return required ? new(false, "Adresse ist nicht optional") : new(true, null);
return new(true, null);
}
} }
} }
+5 -9
View File
@@ -1,17 +1,16 @@
using Elwig.Services;
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Helpers.Weighing { namespace Elwig.Helpers.Weighing {
public class AveryEventScale : Scale, IEventScale, IDisposable { public class AveryEventScale : Scale, IEventScale, IDisposable {
public string Manufacturer => "Avery"; public string Manufacturer => "Avery";
public int InternalScaleNr => 1;
public string Model { get; private set; } public string Model { get; private set; }
public string ScaleId { get; private set; } public string ScaleTerminalId { get; private set; }
public bool IsReady { get; private set; } public bool IsReady { get; private set; }
public bool HasFillingClearance { get; private set; } public bool HasFillingClearance { get; private set; }
@@ -19,13 +18,11 @@ namespace Elwig.Helpers.Weighing {
private bool IsRunning = true; private bool IsRunning = true;
private readonly Thread BackgroundThread; private readonly Thread BackgroundThread;
private readonly string Connection;
public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) : public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) :
base(cnx, null, null, null, log, true, !required) { base(cnx, null, null, null, log, true, !required) {
ScaleId = id; ScaleTerminalId = id;
Model = model; Model = model;
Connection = cnx;
IsReady = true; IsReady = true;
HasFillingClearance = false; HasFillingClearance = false;
Stream.WriteTimeout = -1; Stream.WriteTimeout = -1;
@@ -60,9 +57,8 @@ namespace Elwig.Helpers.Weighing {
} catch (TimeoutException) { } catch (TimeoutException) {
await Task.Delay(500); await Task.Delay(500);
await Reconnect(); await Reconnect();
} catch (Exception ex) { } catch (Exception exc) {
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler", InteractionService.ShowException("Waagenfehler", "Beim Wiegen ist ein Fehler Aufgetreten", exc, showExcType: true);
MessageBoxButton.OK, MessageBoxImage.Error);
} }
} }
} }
@@ -5,18 +5,17 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Helpers.Weighing { namespace Elwig.Helpers.Weighing {
public class GassnerScale : Scale, ICommandScale { public class GassnerEnqScale : Scale, ICommandScale {
public string Manufacturer => "Gassner"; public string Manufacturer => "Gassner";
public int InternalScaleNr => 1;
public string Model { get; private set; } public string Model { get; private set; }
public string ScaleId { get; private set; } public string ScaleTerminalId { get; private set; }
public bool IsReady { get; private set; } public bool IsReady { get; private set; }
public bool HasFillingClearance { get; private set; } public bool HasFillingClearance { get; private set; }
public GassnerScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) : public GassnerEnqScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
base(cnx, empty, filling, limit, log) { base(cnx, empty, filling, limit, log) {
ScaleId = id; ScaleTerminalId = id;
Model = model; Model = model;
IsReady = true; IsReady = true;
HasFillingClearance = false; HasFillingClearance = false;
@@ -24,10 +23,9 @@ namespace Elwig.Helpers.Weighing {
Stream.ReadTimeout = 11000; Stream.ReadTimeout = 11000;
} }
protected Task SendCommand(char command) => SendCommand(Convert.ToByte(command)); protected Task SendCommand(string command) => SendCommand(Encoding.ASCII.GetBytes(command));
protected async Task SendCommand(byte command) { protected async Task SendCommand(byte[] cmd) {
byte[] cmd = [command];
await Stream.WriteAsync(cmd); await Stream.WriteAsync(cmd);
if (LogPath != null) await File.AppendAllTextAsync(LogPath, Encoding.ASCII.GetString(cmd)); if (LogPath != null) await File.AppendAllTextAsync(LogPath, Encoding.ASCII.GetString(cmd));
} }
@@ -53,15 +51,15 @@ namespace Elwig.Helpers.Weighing {
return line[1..^1]; return line[1..^1];
} }
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) { protected async Task<WeighingResult> Weigh(int? internalScaleNr, bool incIdentNr, bool retry = true) {
string record; string record;
try { try {
await SendCommand(incIdentNr ? '\x05' : '?'); await SendCommand(incIdentNr ? "\x05" : "?");
record = await ReceiveResponse(); record = await ReceiveResponse();
} catch (IOException) { } catch (IOException) {
if (!retry || Tcp == null) throw; if (!retry || Tcp == null) throw;
ReconnectTcp(); ReconnectTcp();
return await Weigh(incIdentNr, false); return await Weigh(internalScaleNr, incIdentNr, false);
} }
if (record.Length != 45) if (record.Length != 45)
throw new FormatException("Invalid response from scale: Received record has invalid size"); throw new FormatException("Invalid response from scale: Received record has invalid size");
@@ -80,6 +78,7 @@ namespace Elwig.Helpers.Weighing {
GrossWeight = int.Parse(brutto), GrossWeight = int.Parse(brutto),
TareWeight = int.Parse(tara), TareWeight = int.Parse(tara),
NetWeight = int.Parse(netto), NetWeight = int.Parse(netto),
ScaleId = scaleNr,
WeighingId = identNr, WeighingId = identNr,
FullWeighingId = identNr, FullWeighingId = identNr,
Date = DateOnly.TryParseExact(date, "yyyyMMdd", out var d) ? d : null, Date = DateOnly.TryParseExact(date, "yyyyMMdd", out var d) ? d : null,
@@ -87,12 +86,12 @@ namespace Elwig.Helpers.Weighing {
}; };
} }
public async Task<WeighingResult> Weigh() { public async Task<WeighingResult> Weigh(int? scaleNr = null) {
return await Weigh(true); return await Weigh(scaleNr, true);
} }
public async Task<WeighingResult> GetCurrentWeight() { public async Task<WeighingResult> GetCurrentWeight(int? scaleNr = null) {
return await Weigh(false); return await Weigh(scaleNr, false);
} }
public async Task Empty() { public async Task Empty() {
@@ -126,8 +125,9 @@ namespace Elwig.Helpers.Weighing {
await SetFillingClearance(false); await SetFillingClearance(false);
} }
public Task SetDateAndTime(DateTime dateTime) { public async Task SetDateAndTime(DateTime dateTime) {
throw new NotImplementedException("Für Waagen vom Typ 'Gassner' ist diese Funktion noch nicht implementiert"); await SendCommand($"C{dateTime:yyyyMMddHHmmss}\r");
await ReceiveResponse();
} }
} }
} }
+2 -2
View File
@@ -10,13 +10,13 @@ namespace Elwig.Helpers.Weighing {
/// Get the current weight on the scale without performing a weighing process /// Get the current weight on the scale without performing a weighing process
/// </summary> /// </summary>
/// <returns>Result of the weighing process (probably without a weighing id)</returns> /// <returns>Result of the weighing process (probably without a weighing id)</returns>
Task<WeighingResult> GetCurrentWeight(); Task<WeighingResult> GetCurrentWeight(int? scaleNr = null);
/// <summary> /// <summary>
/// Perform a weighing process /// Perform a weighing process
/// </summary> /// </summary>
/// <returns>Result of the weighing process (including a weighing id)</returns> /// <returns>Result of the weighing process (including a weighing id)</returns>
Task<WeighingResult> Weigh(); Task<WeighingResult> Weigh(int? scaleNr = null);
/// <summary> /// <summary>
/// Empty the scale container or grant clearance to do so /// Empty the scale container or grant clearance to do so
+1 -6
View File
@@ -18,12 +18,7 @@ namespace Elwig.Helpers.Weighing {
/// <summary> /// <summary>
/// Unique identificator of the scale /// Unique identificator of the scale
/// </summary> /// </summary>
string ScaleId { get; } string ScaleTerminalId { get; }
/// <summary>
/// Internal identifying number of the scale in its system
/// </summary>
int InternalScaleNr { get; }
/// <summary> /// <summary>
/// Indicates if the scale is currently processing a request or not /// Indicates if the scale is currently processing a request or not
+1 -2
View File
@@ -5,8 +5,7 @@ namespace Elwig.Helpers.Weighing {
public string Manufacturer => "NONE"; public string Manufacturer => "NONE";
public string Model => "NONE"; public string Model => "NONE";
public string ScaleId => id; public string ScaleTerminalId => id;
public int InternalScaleNr => 0;
public bool IsReady => false; public bool IsReady => false;
public bool HasFillingClearance => false; public bool HasFillingClearance => false;
public int? WeightLimit => null; public int? WeightLimit => null;
+6 -7
View File
@@ -1,9 +1,9 @@
using Elwig.Services;
using System; using System;
using System.IO; using System.IO;
using System.IO.Ports; using System.IO.Ports;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Windows;
namespace Elwig.Helpers.Weighing { namespace Elwig.Helpers.Weighing {
public abstract class Scale : IDisposable { public abstract class Scale : IDisposable {
@@ -28,10 +28,10 @@ namespace Elwig.Helpers.Weighing {
int? limit = config.Limit != null ? int.Parse(config.Limit) : null; int? limit = config.Limit != null ? int.Parse(config.Limit) : null;
if (config.Type == "SysTec-IT") { if (config.Type == "SysTec-IT") {
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log); return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else if (config.Type == "Avery-Async") { } else if (config.Type == "Gassner-Enq" || config.Type == "Gassner") {
return new GassnerEnqScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else if (config.Type == "Avery-Event" || config.Type == "Avery-Async") {
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Log, config.Required); return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Log, config.Required);
} else if (config.Type == "Gassner") {
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else { } else {
throw new ArgumentException($"Invalid scale type: \"{config.Type}\""); throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
} }
@@ -42,11 +42,10 @@ namespace Elwig.Helpers.Weighing {
if (cnx.StartsWith("serial:")) { if (cnx.StartsWith("serial:")) {
try { try {
Serial = Utils.OpenSerialConnection(cnx); Serial = Utils.OpenSerialConnection(cnx);
} catch (Exception e) { } catch (Exception exc) {
if (!softFail) throw; if (!softFail) throw;
if (!failSilent) if (!failSilent)
MessageBox.Show($"Verbindung zu Waage konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler", InteractionService.ShowException("Waagenfehler", "Verbindung zu Waage konnte nicht hergestellt werden", exc, isError: false);
MessageBoxButton.OK, MessageBoxImage.Warning);
} }
Stream = Serial?.BaseStream ?? Stream.Null; Stream = Serial?.BaseStream ?? Stream.Null;
} else if (cnx.StartsWith("tcp:")) { } else if (cnx.StartsWith("tcp:")) {
+11 -11
View File
@@ -8,15 +8,14 @@ namespace Elwig.Helpers.Weighing {
public class SysTecITScale : Scale, ICommandScale { public class SysTecITScale : Scale, ICommandScale {
public string Manufacturer => "SysTec"; public string Manufacturer => "SysTec";
public int InternalScaleNr => 1;
public string Model { get; private set; } public string Model { get; private set; }
public string ScaleId { get; private set; } public string ScaleTerminalId { get; private set; }
public bool IsReady { get; private set; } public bool IsReady { get; private set; }
public bool HasFillingClearance { get; private set; } public bool HasFillingClearance { get; private set; }
public SysTecITScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) : public SysTecITScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
base(cnx, empty, filling, limit, log) { base(cnx, empty, filling, limit, log) {
ScaleId = id; ScaleTerminalId = id;
Model = model; Model = model;
IsReady = true; IsReady = true;
HasFillingClearance = false; HasFillingClearance = false;
@@ -62,7 +61,7 @@ namespace Elwig.Helpers.Weighing {
throw new WeighingException($"Fehler {error}: {msg}"); throw new WeighingException($"Fehler {error}: {msg}");
} else if (error[0] == '3') { } else if (error[0] == '3') {
switch (error[1]) { switch (error[1]) {
case '1': msg = "Übertragunsfehler"; break; case '1': msg = "Übertragungsfehler"; break;
case '2': msg = "Ungültiger Befehl"; break; case '2': msg = "Ungültiger Befehl"; break;
case '3': msg = "Ungültiger Parameter"; break; case '3': msg = "Ungültiger Parameter"; break;
} }
@@ -74,15 +73,15 @@ namespace Elwig.Helpers.Weighing {
return line[1..^3]; return line[1..^3];
} }
protected async Task<WeighingResult> Weigh(bool incIdentNr, bool retry = true) { protected async Task<WeighingResult> Weigh(int internalScalNr, bool incIdentNr, bool retry = true) {
string record; string record;
try { try {
await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}"); await SendCommand(incIdentNr ? $"RN{internalScalNr}" : $"RM{internalScalNr}");
record = await ReceiveResponse(); record = await ReceiveResponse();
} catch (IOException) { } catch (IOException) {
if (!retry || Tcp == null) throw; if (!retry || Tcp == null) throw;
ReconnectTcp(); ReconnectTcp();
return await Weigh(incIdentNr, false); return await Weigh(internalScalNr, incIdentNr, false);
} }
if (record.Length != 62) if (record.Length != 62)
throw new FormatException("Invalid response from scale: Received record has invalid size"); throw new FormatException("Invalid response from scale: Received record has invalid size");
@@ -114,6 +113,7 @@ namespace Elwig.Helpers.Weighing {
GrossWeight = int.Parse(brutto), GrossWeight = int.Parse(brutto),
TareWeight = int.Parse(tara), TareWeight = int.Parse(tara),
NetWeight = int.Parse(netto), NetWeight = int.Parse(netto),
ScaleId = scaleNr,
WeighingId = identNr, WeighingId = identNr,
FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null, FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null,
Date = parsedDate, Date = parsedDate,
@@ -121,12 +121,12 @@ namespace Elwig.Helpers.Weighing {
}; };
} }
public async Task<WeighingResult> Weigh() { public async Task<WeighingResult> Weigh(int? scaleNr = 1) {
return await Weigh(true); return await Weigh(scaleNr ?? 1, true);
} }
public async Task<WeighingResult> GetCurrentWeight() { public async Task<WeighingResult> GetCurrentWeight(int? scaleNr = 1) {
return await Weigh(false); return await Weigh(scaleNr ?? 1, false);
} }
public async Task Empty() { public async Task Empty() {
+7 -1
View File
@@ -21,6 +21,11 @@ namespace Elwig.Helpers.Weighing {
/// </summary> /// </summary>
public int? NetWeight; public int? NetWeight;
/// <summary>
/// Reported id of the scale
/// </summary>
public string? ScaleId;
/// <summary> /// <summary>
/// Weighing id (or IdentNr) provided by the scale /// Weighing id (or IdentNr) provided by the scale
/// </summary> /// </summary>
@@ -44,11 +49,12 @@ namespace Elwig.Helpers.Weighing {
/// <returns>&lt;[GrossWeight-TaraWeight=]NetWeight/WeighingId/Date/Time&gt;</returns> /// <returns>&lt;[GrossWeight-TaraWeight=]NetWeight/WeighingId/Date/Time&gt;</returns>
public override readonly string ToString() { public override readonly string ToString() {
var w = NetWeight != null ? (GrossWeight != null && TareWeight != null ? $"{GrossWeight}-{TareWeight}=" : "") + $"{NetWeight}kg" : ""; var w = NetWeight != null ? (GrossWeight != null && TareWeight != null ? $"{GrossWeight}-{TareWeight}=" : "") + $"{NetWeight}kg" : "";
return $"<{w}/{WeighingId}/{Date:yyyy-MM-dd}/{Time:HH:mm}>"; return $"<{w}/{WeighingId}/{Date:yyyy-MM-dd}/{Time:HH:mm:ss}>";
} }
public readonly JsonObject ToJson() { public readonly JsonObject ToJson() {
var obj = new JsonObject(); var obj = new JsonObject();
if (ScaleId != null) obj["scale"] = ScaleId;
if (FullWeighingId != null) obj["id"] = FullWeighingId; if (FullWeighingId != null) obj["id"] = FullWeighingId;
if (WeighingId != null) obj["nr"] = int.Parse(WeighingId); if (WeighingId != null) obj["nr"] = int.Parse(WeighingId);
if (Date != null) obj["date"] = $"{Date:yyyy-MM-dd}"; if (Date != null) obj["date"] = $"{Date:yyyy-MM-dd}";
+5
View File
@@ -0,0 +1,5 @@
namespace Elwig.Models {
public enum BusinessShareType {
Normal = 1, Red = 2, White = 3, Dormant = 9,
}
}
@@ -5,7 +5,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Models.Dtos { namespace Elwig.Models.Dtos {
public class AreaComUnderDeliveryData : DataTable<AreaComUnderDeliveryRow> { public class AreaCmtUnderDeliveryData : DataTable<AreaCmtUnderDeliveryRow> {
private static readonly (string, string, string?, int)[] FieldNames = [ private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12), ("MgNr", "MgNr.", null, 12),
@@ -21,19 +21,19 @@ namespace Elwig.Models.Dtos {
("UnderDeliveries", "Unterliefert", "kg|%", 34), ("UnderDeliveries", "Unterliefert", "kg|%", 34),
]; ];
public AreaComUnderDeliveryData(IEnumerable<AreaComUnderDeliveryRow> rows, int year) : public AreaCmtUnderDeliveryData(IEnumerable<AreaCmtUnderDeliveryRow> rows, int year) :
base($"Unterlieferungen FB", $"Unterlieferungen laut Flächenbindungen {year}", rows, FieldNames) { base($"Unterlieferungen FB", $"Unterlieferungen laut Flächenbindungen {year}", rows, FieldNames) {
} }
public static async Task<AreaComUnderDeliveryData> ForSeason(DbSet<AreaComUnderDeliveryRowSingle> table, int year) { public static async Task<AreaCmtUnderDeliveryData> ForSeason(DbSet<AreaCmtUnderDeliveryRowSingle> table, int year) {
return new AreaComUnderDeliveryData( return new AreaCmtUnderDeliveryData(
(await FromDbSet(table, year)).GroupBy( (await FromDbSet(table, year)).GroupBy(
r => r.MgNr, r => r.MgNr,
(k, g) => new AreaComUnderDeliveryRow(g) (k, g) => new AreaCmtUnderDeliveryRow(g)
), year); ), year);
} }
private static async Task<IEnumerable<AreaComUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaComUnderDeliveryRowSingle> table, int year) { private static async Task<IEnumerable<AreaCmtUnderDeliveryRowSingle>> FromDbSet(DbSet<AreaCmtUnderDeliveryRowSingle> table, int year) {
return await table.FromSql($""" return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1, SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(m.prefix || ' ', '') || m.given_name ||
@@ -51,7 +51,7 @@ namespace Elwig.Models.Dtos {
} }
} }
public class AreaComUnderDeliveryRow { public class AreaCmtUnderDeliveryRow {
public int MgNr; public int MgNr;
public string Name1; public string Name1;
public string? Name2; public string? Name2;
@@ -66,7 +66,7 @@ namespace Elwig.Models.Dtos {
.Select(v => v.First < v.Second ? ((int?, double?))(v.First - v.Second, v.First * 100.0 / v.Second - 100.0) : (null, null)) .Select(v => v.First < v.Second ? ((int?, double?))(v.First - v.Second, v.First * 100.0 / v.Second - 100.0) : (null, null))
.ToArray(); .ToArray();
public AreaComUnderDeliveryRow(IEnumerable<AreaComUnderDeliveryRowSingle> rows) { public AreaCmtUnderDeliveryRow(IEnumerable<AreaCmtUnderDeliveryRowSingle> rows) {
var f = rows.First(); var f = rows.First();
MgNr = f.MgNr; MgNr = f.MgNr;
Name1 = f.Name1; Name1 = f.Name1;
@@ -82,7 +82,7 @@ namespace Elwig.Models.Dtos {
} }
[Keyless] [Keyless]
public class AreaComUnderDeliveryRowSingle { public class AreaCmtUnderDeliveryRowSingle {
[Column("mgnr")] [Column("mgnr")]
public int MgNr { get; set; } public int MgNr { get; set; }
[Column("name_1")] [Column("name_1")]
+21 -16
View File
@@ -17,6 +17,7 @@ namespace Elwig.Models.Dtos {
("Address", "Adresse", null, 60), ("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10), ("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60), ("Locality", "Ort", null, 60),
("AccountingNr", "BH-Konto", null, 20),
("Iban", "IBAN", null, 45), ("Iban", "IBAN", null, 45),
("TgNr", "TG-Nr.", null, 20), ("TgNr", "TG-Nr.", null, 20),
("Sum", "Zwischens.", "€", 20), ("Sum", "Zwischens.", "€", 20),
@@ -29,7 +30,7 @@ namespace Elwig.Models.Dtos {
("Gross", "Brutto", "€", 20), ("Gross", "Brutto", "€", 20),
("Penalties", "Pönalen FB", "€", 20), ("Penalties", "Pönalen FB", "€", 20),
("Penalty", "Unterl. GA", "€", 20), ("Penalty", "Unterl. GA", "€", 20),
("AutoBs", "GA Nachz.", "€", 20), ("AutoShares", "GA Nachz.", "€", 20),
("Custom", "Weitere", "€", 20), ("Custom", "Weitere", "€", 20),
("Others", "Sonstige", "€", 20), ("Others", "Sonstige", "€", 20),
("Considered", "Berückstgt.", "€", 20), ("Considered", "Berückstgt.", "€", 20),
@@ -53,12 +54,12 @@ namespace Elwig.Models.Dtos {
SELECT m.mgnr, m.name AS name_1, SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2, COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address, m.iban, c.tgnr, s.year, s.precision, p.plz, o.name AS ort, m.address, m.accounting_nr, m.iban, c.tgnr, s.year, s.precision,
p.amount - p.net_amount AS surcharge, p.amount - p.net_amount AS surcharge,
c.net_amount, c.prev_net_amount, c.vat, c.vat_amount, c.gross_amount, c.modifiers, c.prev_modifiers, c.amount, c.net_amount, c.prev_net_amount, c.vat, c.vat_amount, c.gross_amount, c.modifiers, c.prev_modifiers, c.amount,
ROUND(b.total_penalty / POW(10, s.precision - 2)) AS bs_penalty, ROUND(b.total_penalty / POW(10, s.precision - 2)) AS shares_penalty,
ROUND(u.total_penalty / POW(10, 4 - 2)) AS fb_penalty, ROUND(u.total_penalty / POW(10, 4 - 2)) AS ac_penalty,
ROUND(-a.total_amount / POW(10, s.precision - 2)) AS auto_bs, -a.total_amount AS auto_shares,
x.amount AS custom_mod x.amount AS custom_mod
FROM credit c FROM credit c
LEFT JOIN member m ON m.mgnr = c.mgnr LEFT JOIN member m ON m.mgnr = c.mgnr
@@ -83,6 +84,7 @@ namespace Elwig.Models.Dtos {
public string Address; public string Address;
public int Plz; public int Plz;
public string Locality; public string Locality;
public string? AccountingNr;
public string? Iban; public string? Iban;
public string TgNr; public string TgNr;
public decimal Sum; public decimal Sum;
@@ -94,7 +96,7 @@ namespace Elwig.Models.Dtos {
public decimal Gross; public decimal Gross;
public decimal? Penalties; public decimal? Penalties;
public decimal? Penalty; public decimal? Penalty;
public decimal? AutoBs; public decimal? AutoShares;
public decimal? Custom; public decimal? Custom;
public decimal? Others; public decimal? Others;
public decimal? Considered; public decimal? Considered;
@@ -108,6 +110,7 @@ namespace Elwig.Models.Dtos {
Address = row.Address; Address = row.Address;
Plz = row.Plz; Plz = row.Plz;
Locality = row.Locality; Locality = row.Locality;
AccountingNr = row.AccountingNr;
Iban = row.Iban != null ? Utils.FormatIban(row.Iban) : null; Iban = row.Iban != null ? Utils.FormatIban(row.Iban) : null;
TgNr = $"{row.Year}/{row.TgNr}"; TgNr = $"{row.Year}/{row.TgNr}";
Total = Utils.DecFromDb(row.NetAmount, prec1); Total = Utils.DecFromDb(row.NetAmount, prec1);
@@ -122,14 +125,14 @@ namespace Elwig.Models.Dtos {
} }
decimal mod = (row.Modifiers == null) ? 0 : Utils.DecFromDb((long)row.Modifiers, prec1); decimal mod = (row.Modifiers == null) ? 0 : Utils.DecFromDb((long)row.Modifiers, prec1);
if (data.ConsiderContractPenalties) if (data.ConsiderContractPenalties)
Penalties = (row.FbPenalty == null) ? null : Utils.DecFromDb((long)row.FbPenalty, prec1); Penalties = (row.AcPenalty == null) ? null : Utils.DecFromDb((long)row.AcPenalty, prec1);
if (data.ConsiderTotalPenalty) if (data.ConsiderTotalPenalty)
Penalty = (row.BsPenalty == null) ? null : Utils.DecFromDb((long)row.BsPenalty, prec1); Penalty = (row.SharesPenalty == null) ? null : Utils.DecFromDb((long)row.SharesPenalty, prec1);
if (data.ConsiderAutoBusinessShares) if (data.ConsiderAutoBusinessShares)
AutoBs = (row.AutoBs == null) ? null : Utils.DecFromDb((long)row.AutoBs, prec1); AutoShares = (row.AutoShares == null) ? null : Utils.DecFromDb((long)row.AutoShares, prec1);
if (data.ConsiderCustomModifiers) if (data.ConsiderCustomModifiers)
Custom = (row.CustomMod == null) ? null : Utils.DecFromDb((long)row.CustomMod, prec1); Custom = (row.CustomMod == null) ? null : Utils.DecFromDb((long)row.CustomMod, prec1);
mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoBs ?? 0) + (Custom ?? 0); mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoShares ?? 0) + (Custom ?? 0);
Others = (mod == 0) ? null : mod; Others = (mod == 0) ? null : mod;
Gross = Utils.DecFromDb(row.GrossAmount, prec1); Gross = Utils.DecFromDb(row.GrossAmount, prec1);
Considered = (row.PrevModifiers == null || row.PrevModifiers == 0) ? null : -Utils.DecFromDb((long)row.PrevModifiers, prec1); Considered = (row.PrevModifiers == null || row.PrevModifiers == 0) ? null : -Utils.DecFromDb((long)row.PrevModifiers, prec1);
@@ -153,6 +156,8 @@ namespace Elwig.Models.Dtos {
public required string LocalityFull { get; set; } public required string LocalityFull { get; set; }
[NotMapped] [NotMapped]
public string Locality => LocalityFull.Split(",")[0]; public string Locality => LocalityFull.Split(",")[0];
[Column("accounting_nr")]
public string? AccountingNr { get; set; }
[Column("iban")] [Column("iban")]
public string? Iban { get; set; } public string? Iban { get; set; }
[Column("year")] [Column("year")]
@@ -179,12 +184,12 @@ namespace Elwig.Models.Dtos {
public long? PrevModifiers { get; set; } public long? PrevModifiers { get; set; }
[Column("amount")] [Column("amount")]
public long Amount { get; set; } public long Amount { get; set; }
[Column("bs_penalty")] [Column("shares_penalty")]
public long? BsPenalty { get; set; } public long? SharesPenalty { get; set; }
[Column("fb_penalty")] [Column("ac_penalty")]
public long? FbPenalty { get; set; } public long? AcPenalty { get; set; }
[Column("auto_bs")] [Column("auto_shares")]
public long? AutoBs { get; set; } public long? AutoShares { get; set; }
[Column("custom_mod")] [Column("custom_mod")]
public long? CustomMod { get; set; } public long? CustomMod { get; set; }
} }
+11 -11
View File
@@ -42,13 +42,10 @@ namespace Elwig.Models.Dtos {
} }
private static async Task<IEnumerable<CreditNoteDeliveryRowSingle>> FromDbSet(DbSet<CreditNoteDeliveryRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) { private static async Task<IEnumerable<CreditNoteDeliveryRowSingle>> FromDbSet(DbSet<CreditNoteDeliveryRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) {
var y = year?.ToString() ?? "NULL"; return await table.FromSql($"""
var v = avnr?.ToString() ?? "NULL";
var m = mgnr?.ToString() ?? "NULL";
return await table.FromSqlRaw($"""
SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers, SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers,
b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount, b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount,
s.name AS variety, a.name AS attribute, c.name AS cultivation, q.qualid AS qualid, q.name AS quality_level, d.oe, d.kmw, d.net_weight s.name AS variety, s.type AS type, a.name AS attribute, c.name AS cultivation, q.qualid AS qualid, q.name AS quality_level, d.oe, d.kmw, d.net_weight
FROM v_delivery d FROM v_delivery d
JOIN wine_variety s ON s.sortid = d.sortid JOIN wine_variety s ON s.sortid = d.sortid
LEFT JOIN wine_attribute a ON a.attrid = d.attrid LEFT JOIN wine_attribute a ON a.attrid = d.attrid
@@ -59,7 +56,7 @@ namespace Elwig.Models.Dtos {
LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr) LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr)
LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr, pb.avnr) = (b.year, b.did, b.dpnr, b.bktnr, v.avnr) LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr, pb.avnr) = (b.year, b.did, b.dpnr, b.bktnr, v.avnr)
LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr) LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr)
WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL) WHERE b.value > 0 AND (d.year = {year} OR {year} IS NULL) AND (v.avnr = {avnr} OR {avnr} IS NULL) AND (d.mgnr = {mgnr} OR {mgnr} IS NULL)
ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
""").ToListAsync(); """).ToListAsync();
} }
@@ -75,6 +72,7 @@ namespace Elwig.Models.Dtos {
public string LsNr; public string LsNr;
public int DPNr; public int DPNr;
public string Variety; public string Variety;
public string Type;
public string? Attribute; public string? Attribute;
public string? Cultivation; public string? Cultivation;
public string[] Modifiers; public string[] Modifiers;
@@ -97,24 +95,24 @@ namespace Elwig.Models.Dtos {
LsNr = f.LsNr; LsNr = f.LsNr;
DPNr = f.DPNr; DPNr = f.DPNr;
Variety = f.Variety; Variety = f.Variety;
Type = f.Type;
Attribute = f.Attribute; Attribute = f.Attribute;
Cultivation = f.Cultivation; Cultivation = f.Cultivation;
var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',') var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',')
.Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m)) .Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m))
.Where(m => m != null) .Where(m => m != null)
.OrderBy(m => m.Ordering) .OrderBy(m => m!.Ordering)
.ToList(); .ToList();
Modifiers = modifiers.Select(m => m.Name).ToArray(); Modifiers = [.. modifiers.Select(m => m.Name)];
QualId = f.QualId; QualId = f.QualId;
QualityLevel = f.QualityLevel; QualityLevel = f.QualityLevel;
Gradation = (f.Oe, f.Kmw); Gradation = (f.Oe, f.Kmw);
Buckets = rows Buckets = [.. rows
.Where(b => b.Value > 0) .Where(b => b.Value > 0)
.OrderByDescending(b => b.BktNr) .OrderByDescending(b => b.BktNr)
.Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value, .Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
b.Price != null ? season?.DecFromDb((long)b.Price) : null, b.Price != null ? season?.DecFromDb((long)b.Price) : null,
b.Amount != null ? season?.DecFromDb((long)b.Amount) : null)) b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))];
.ToArray();
WeighingModifier = (varData == null || !varData.ConsiderDelieryModifiers) ? 0 : f.NetWeight ? varData.NetWeightModifier : varData.GrossWeightModifier; WeighingModifier = (varData == null || !varData.ConsiderDelieryModifiers) ? 0 : f.NetWeight ? varData.NetWeightModifier : varData.GrossWeightModifier;
Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null; Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null; var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
@@ -161,6 +159,8 @@ namespace Elwig.Models.Dtos {
public long? TotalAmount { get; set; } public long? TotalAmount { get; set; }
[Column("variety")] [Column("variety")]
public required string Variety { get; set; } public required string Variety { get; set; }
[Column("type")]
public required string Type { get; set; }
[Column("attribute")] [Column("attribute")]
public string? Attribute { get; set; } public string? Attribute { get; set; }
[Column("cultivation")] [Column("cultivation")]
+54 -35
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -9,13 +10,14 @@ namespace Elwig.Models.Dtos {
public string Name { get; set; } public string Name { get; set; }
public string FullName { get; set; } public string FullName { get; set; }
public string? Subtitle { get; set; } public string? Subtitle { get; set; }
public IEnumerable<T> Rows { get; private set; } public IEnumerable<(string? Name, IEnumerable<T> Rows)> Sections { get; private set; }
public IEnumerable<T> Rows => Sections.SelectMany(s => s.Rows);
public int RowNum => Rows.Count(); public int RowNum => Rows.Count();
public int ColNum => ColumnNames.Count(); public int ColNum => ColumnNames.Count();
public IEnumerable<(string, Type?)> ColumnDefs => _map.Select(m => (m.Item1, m.Item2?.PropertyType ?? m.Item3?.FieldType)); public IEnumerable<(string Name, Type? Type)> ColumnDefs => _map.Select(m => (m.Name, m.PInfo?.PropertyType ?? m.FInfo?.FieldType));
public IEnumerable<string> ColumnNames => ColumnDefs.Select(m => m.Item1); public IEnumerable<string> ColumnNames => ColumnDefs.Select(m => m.Name);
public IEnumerable<Type?> ColumnTypes => ColumnDefs.Select(m => m.Item2); public IEnumerable<Type?> ColumnTypes => ColumnDefs.Select(m => m.Type);
public IEnumerable<Type?> ColumnFlatTypes { get; private set; } public IEnumerable<Type?> ColumnFlatTypes { get; private set; }
public IEnumerable<int> ColumnSpans { get; private set; } public IEnumerable<int> ColumnSpans { get; private set; }
public IEnumerable<int?> ColumnWidths { get; private set; } public IEnumerable<int?> ColumnWidths { get; private set; }
@@ -23,70 +25,87 @@ namespace Elwig.Models.Dtos {
private readonly PropertyInfo[] _properties; private readonly PropertyInfo[] _properties;
private readonly FieldInfo[] _fields; private readonly FieldInfo[] _fields;
private readonly (string, PropertyInfo?, FieldInfo?)[] _map; private readonly (string Name, PropertyInfo? PInfo, FieldInfo? FInfo)[] _map;
public DataTable(string name, string fullName, string subtitle, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) : public DataTable(string name, string fullName, string subtitle, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
this(name, fullName, rows, colNames) { this(name, fullName, sections, colNames) {
Subtitle = subtitle; Subtitle = subtitle;
} }
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) { public DataTable(string name, string fullName, string subtitle, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
this(name, fullName, subtitle, [(null, rows)], colNames) {
}
public DataTable(string name, string fullName, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) {
_fields = typeof(T).GetFields(); _fields = typeof(T).GetFields();
_properties = typeof(T).GetProperties(); _properties = typeof(T).GetProperties();
colNames ??= _properties.Select(p => p.Name).Union(_fields.Select(f => f.Name)).Select(i => (i, i, (string?)null, (int?)null)).ToList(); colNames ??= [.. _properties.Select(p => p.Name).Union(_fields.Select(f => f.Name)).Select(i => (i, i, (string?)null, (int?)null))];
_map = colNames.Select(n => (n.Item2, _properties.FirstOrDefault(p => p?.Name == n.Item1, null), _fields.FirstOrDefault(f => f?.Name == n.Item1, null))).ToArray(); _map = [.. colNames.Select(n => (n.Name, _properties.FirstOrDefault(p => p?.Name == n.Property, null), _fields.FirstOrDefault(f => f?.Name == n.Property, null)))];
Name = name; Name = name;
FullName = fullName; FullName = fullName;
Rows = rows; Sections = sections;
ColumnFlatTypes = ColumnTypes.SelectMany(type => { ColumnFlatTypes = [.. ColumnTypes.SelectMany(type => {
var elType = type?.GetElementType(); var elType = type?.GetElementType();
return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Select(f => f.FieldType) : return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Select(f => (Type?)f.FieldType) :
type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Select(f => f.FieldType) : type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Select(f => (Type?)f.FieldType) :
[type]; [type];
}).ToList(); })];
ColumnSpans = ColumnTypes.Select(type => { ColumnSpans = [.. ColumnTypes.Select(type => {
var elType = type?.GetElementType(); var elType = type?.GetElementType();
return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Length : return type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Length :
type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Length : 1; type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Length :
}).ToList(); 1;
ColumnWidths = colNames.Select(c => c.Item4).ToList(); })];
ColumnUnits = colNames.Select(c => c.Item3?.Split("|").Select(p => p.Length == 0 ? null : p).ToArray() ?? []).ToList(); ColumnWidths = [.. colNames.Select(c => c.Width)];
ColumnUnits = [.. colNames.Select(c => c.Unit?.Split("|").Select(p => p.Length == 0 ? null : p).ToArray() ?? [])];
} }
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?)>? colNames = null) : public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
this(name, fullName, rows, colNames?.Select(c => (c.Item1, c.Item2, c.Item3, (int?)null))) { this(name, fullName, [(null, rows)], colNames) {
} }
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?)>? colNames = null) : public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit)>? colNames = null) :
this(name, fullName, rows, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)null))) {
}
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int Width)>? colNames = null) :
this(name, fullName, rows, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)c.Width))) {
}
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int? Width)>? colNames = null) :
this(name, name, rows, colNames) { this(name, name, rows, colNames) {
} }
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?, int?)>? colNames = null) : public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit)>? colNames = null) :
this(name, name, rows, colNames) { this(name, name, rows, colNames) {
} }
public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string, string?, int)>? colNames = null) : public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string Property, string Name, string? Unit, int Width)>? colNames = null) :
this(name, name, rows, colNames) { this(name, name, rows, colNames) {
} }
public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string, string?, int)>? colNames = null) : public DataTable(string name, string fullName, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit)>? colNames = null) :
this(name, fullName, rows, colNames?.Select(c => (c.Item1, c.Item2, c.Item3, (int?)c.Item4))) { this(name, fullName, sections, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)null))) {
} }
protected IEnumerable<(string, object?)> GetNamedRowData(T row) { public DataTable(string name, string fullName, IEnumerable<(string? Name, IEnumerable<T> Rows)> sections, IEnumerable<(string Property, string Name, string? Unit, int Width)>? colNames = null) :
return _map.Select(i => (i.Item1, i.Item2?.GetValue(row) ?? i.Item3?.GetValue(row))); this(name, fullName, sections, colNames?.Select(c => (c.Property, c.Name, c.Unit, (int?)c.Width))) {
} }
protected IEnumerable<object?> GetRowData(T row) { protected IEnumerable<(string Name, object? Value)> GetNamedRowValues(T row) {
return GetNamedRowData(row).Select(i => i.Item2); return _map.Select(i => (i.Name, i.PInfo?.GetValue(row) ?? i.FInfo?.GetValue(row)));
} }
public IEnumerable<IEnumerable<object?>> GetData() { protected IEnumerable<object?> GetRowValues(T row) {
return Rows.Select(GetRowData); return GetNamedRowValues(row).Select(i => i.Value);
} }
public IEnumerable<IEnumerable<(string, object?)>> GetNamedData() { public IEnumerable<(string? Name, IEnumerable<IEnumerable<object?>> Rows)> GetData() {
return Rows.Select(GetNamedRowData); return Sections.Select(s => (s.Name, s.Rows.Select(GetRowValues)));
}
public IEnumerable<(string? Name, IEnumerable<IEnumerable<(string Name, object? Value)>> Rows)> GetNamedData() {
return Sections.Select(s => (s.Name, s.Rows.Select(GetNamedRowValues)));
} }
} }
} }
@@ -5,7 +5,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Elwig.Models.Dtos { namespace Elwig.Models.Dtos {
class MemberAreaComsData : DataTable<MemberAreaComsRow> { class MemberAreaCmtTotalsData : DataTable<MemberAreaCmtTotalsRow> {
private static readonly (string, string, string?, int)[] FieldNames = [ private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12), ("MgNr", "MgNr.", null, 12),
@@ -21,19 +21,19 @@ namespace Elwig.Models.Dtos {
("DeliveryRights", "Lieferrecht", "kg", 22), ("DeliveryRights", "Lieferrecht", "kg", 22),
]; ];
public MemberAreaComsData(IEnumerable<MemberAreaComsRow> rows, int year) : public MemberAreaCmtTotalsData(IEnumerable<MemberAreaCmtTotalsRow> rows, int year) :
base($"Flächenbindungen", $"Flächenbindungen pro Mitglied {year}", rows, FieldNames) { base($"Gebundene Fläche", $"Gebundene Fläche pro Mitglied {year}", rows, FieldNames) {
} }
public static async Task<MemberAreaComsData> ForSeason(DbSet<MemberAreaComsRowSingle> table, int year) { public static async Task<MemberAreaCmtTotalsData> ForSeason(DbSet<MemberAreaCmtTotalsRowSingle> table, int year) {
return new MemberAreaComsData( return new MemberAreaCmtTotalsData(
(await FromDbSet(table, year)).GroupBy( (await FromDbSet(table, year)).GroupBy(
r => r.MgNr, r => r.MgNr,
(k, g) => new MemberAreaComsRow(g) (k, g) => new MemberAreaCmtTotalsRow(g)
), year); ), year);
} }
private static async Task<IEnumerable<MemberAreaComsRowSingle>> FromDbSet(DbSet<MemberAreaComsRowSingle> table, int year) { private static async Task<IEnumerable<MemberAreaCmtTotalsRowSingle>> FromDbSet(DbSet<MemberAreaCmtTotalsRowSingle> table, int year) {
return await table.FromSql($""" return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1, SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(m.prefix || ' ', '') || m.given_name ||
@@ -50,7 +50,7 @@ namespace Elwig.Models.Dtos {
} }
} }
public class MemberAreaComsRow { public class MemberAreaCmtTotalsRow {
public int MgNr; public int MgNr;
public string Name1; public string Name1;
public string? Name2; public string? Name2;
@@ -63,7 +63,7 @@ namespace Elwig.Models.Dtos {
public int[] DeliveryObligations; public int[] DeliveryObligations;
public int[] DeliveryRights; public int[] DeliveryRights;
public MemberAreaComsRow(IEnumerable<MemberAreaComsRowSingle> rows) { public MemberAreaCmtTotalsRow(IEnumerable<MemberAreaCmtTotalsRowSingle> rows) {
var f = rows.First(); var f = rows.First();
MgNr = f.MgNr; MgNr = f.MgNr;
Name1 = f.Name1; Name1 = f.Name1;
@@ -71,16 +71,16 @@ namespace Elwig.Models.Dtos {
Address = f.Address; Address = f.Address;
Plz = f.Plz; Plz = f.Plz;
Locality = f.Locality.Split(",")[0]; Locality = f.Locality.Split(",")[0];
SortIds = rows.Select(r => r.VtrgId[..2]).ToArray(); SortIds = [.. rows.Select(r => r.VtrgId[..2])];
AttrIds = rows.Select(r => r.VtrgId[2..]).ToArray(); AttrIds = [.. rows.Select(r => r.VtrgId[2..])];
Areas = rows.Select(r => r.Area).ToArray(); Areas = [.. rows.Select(r => r.Area)];
DeliveryObligations = rows.Select(r => r.MinKg).ToArray(); DeliveryObligations = [.. rows.Select(r => r.MinKg)];
DeliveryRights = rows.Select(r => r.MaxKg).ToArray(); DeliveryRights = [.. rows.Select(r => r.MaxKg)];
} }
} }
[Keyless] [Keyless]
public class MemberAreaComsRowSingle { public class MemberAreaCmtTotalsRowSingle {
[Column("mgnr")] [Column("mgnr")]
public int MgNr { get; set; } public int MgNr { get; set; }
[Column("name_1")] [Column("name_1")]
+87
View File
@@ -0,0 +1,87 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
class MemberAreaCmtsData : DataTable<MemberAreaCmtsRow> {
private static readonly (string, string, string?, int)[] FieldNames = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SortId", "Sorte", null, 12),
("AttrId", "Attribut", null, 16),
("CultId", "Bewirt.", null, 16),
("GstNr", "Parzelle(n)", null, 25),
("Area", "Fläche", "m²", 22),
("MinKg", "Lieferpflicht", "kg", 22),
("MaxKg", "Lieferrecht", "kg", 22),
];
public MemberAreaCmtsData(IEnumerable<MemberAreaCmtsRow> rows, int year) :
base($"Flächenbindungen", $"Flächenbindungen pro Mitglied {year}", rows, FieldNames) {
}
public static async Task<MemberAreaCmtsData> ForSeason(DbSet<MemberAreaCmtsRow> table, int year) {
return new MemberAreaCmtsData(await FromDbSet(table, year), year);
}
private static async Task<IEnumerable<MemberAreaCmtsRow>> FromDbSet(DbSet<MemberAreaCmtsRow> table, int year) {
return await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address,
t.sortid, t.attrid, c.cultid,
c.gstnr, c.area,
CAST(ROUND(area * COALESCE(t.min_kg_per_ha, 0) / 10000.0, 0) AS INTEGER) AS min_kg,
CAST(ROUND(area * MIN(COALESCE(a.max_kg_per_ha, s.max_kg_per_ha), s.max_kg_per_ha) / 10000.0, 0) AS INTEGER) AS max_kg
FROM v_virtual_season s, area_commitment c
JOIN area_commitment_type t ON t.vtrgid = c.vtrgid
LEFT JOIN wine_attribute a ON a.attrid = t.attrid
LEFT JOIN member m ON m.mgnr = c.mgnr
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz
WHERE s.year = {year} AND
(year_from IS NULL OR year_from <= s.year) AND
(year_to IS NULL OR year_to >= s.year)
ORDER BY c.mgnr, t.sortid, t.attrid, c.cultid, c.gstnr;
""").ToListAsync();
}
}
[Keyless]
public class MemberAreaCmtsRow {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("name_1")]
public required string Name1 { get; set; }
[Column("name_2")]
public string? Name2 { get; set; }
[Column("address")]
public required string Address { get; set; }
[Column("plz")]
public int Plz { get; set; }
[Column("ort")]
public required string Locality { get; set; }
[Column("sortid")]
public required string SortId { get; set; }
[Column("attrid")]
public string? AttrId { get; set; }
[Column("cultid")]
public string? CultId { get; set; }
[Column("gstnr")]
public required string GstNr { get; set; }
[Column("area")]
public int Area { get; set; }
[Column("min_kg")]
public int MinKg { get; set; }
[Column("max_kg")]
public int MaxKg { get; set; }
}
}
+18 -12
View File
@@ -19,13 +19,13 @@ namespace Elwig.Models.Dtos {
("Locality", "Ort", null, 60), ("Locality", "Ort", null, 60),
("DefaultKg", "Stammgemeinde", null, 60), ("DefaultKg", "Stammgemeinde", null, 60),
("Branch", "Zweigstelle", null, 40), ("Branch", "Zweigstelle", null, 40),
("BusinessShares", "GA", null, 10), ("SharesActive", "GA", null, 10),
("BillingName", "Rechnungsname", null, 60), ("BillingName", "Rechnungsname", null, 60),
("BillingAddress", "Rechnungsadresse", null, 60), ("BillingAddress", "Rechnungsadresse", null, 60),
("BillingPlz", "PLZ", null, 10), ("BillingPlz", "PLZ", null, 10),
("BillingLocality", "Ort", null, 60), ("BillingLocality", "Ort", null, 60),
("LfbisNr", "Betr.-Nr.", null, 20), ("LfbisNr", "Betr.-Nr.", null, 20),
("IsBuchführend", "buchf.", null, 15), ("IsAccounting", "buchf.", null, 15),
("IsOrganic", "Bio", null, 15), ("IsOrganic", "Bio", null, 15),
("IsActive", "aktiv", null, 15), ("IsActive", "aktiv", null, 15),
("EntryDate", "Eintritt", null, 20), ("EntryDate", "Eintritt", null, 20),
@@ -46,15 +46,15 @@ namespace Elwig.Models.Dtos {
base(MemberList.Name, MemberList.Name, string.Join(" / ", filterNames), rows, FieldNames) { base(MemberList.Name, MemberList.Name, string.Join(" / ", filterNames), rows, FieldNames) {
} }
public static async Task<MemberListData> FromQuery(IQueryable<Member> query, List<string> filterNames, IEnumerable<string> filterAreaCom) { public static async Task<MemberListData> FromQuery(IQueryable<Member> query, List<string> filterNames, IEnumerable<string> filterAreaCmt) {
var areaComs = await query.Include(m => m.AreaCommitments).ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments)); var areaCmts = await query.Include(m => m.AreaCommitments).ToDictionaryAsync(m => m.MgNr, m => Utils.ActiveAreaCommitments(m.AreaCommitments));
return new((await query return new((await query
.Include(m => m.Branch) .Include(m => m.Branch)
.Include(m => m.TelephoneNumbers) .Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses) .Include(m => m.EmailAddresses)
.ToListAsync()).Select(m => new MemberListRow(m, .ToListAsync()).Select(m => new MemberListRow(m,
areaComs[m.MgNr].Sum(c => c.Area), areaCmts[m.MgNr].Sum(c => c.Area),
areaComs[m.MgNr].Where(c => filterAreaCom.Contains(c.VtrgId)).GroupBy(c => c.VtrgId).ToDictionary(g => g.Key, g => g.Sum(c => c.Area)))), areaCmts[m.MgNr].Where(c => filterAreaCmt.Contains(c.VtrgId)).GroupBy(c => c.VtrgId).ToDictionary(g => g.Key, g => g.Sum(c => c.Area)))),
filterNames); filterNames);
} }
} }
@@ -66,7 +66,10 @@ namespace Elwig.Models.Dtos {
public string? Name2; public string? Name2;
public string? DefaultKg; public string? DefaultKg;
public string? Branch; public string? Branch;
public int BusinessShares; public int Shares;
public int SharesRed;
public int SharesWhite;
public int SharesActive;
public string Address; public string Address;
public int Plz; public int Plz;
public string Locality; public string Locality;
@@ -80,7 +83,7 @@ namespace Elwig.Models.Dtos {
public string? Bic; public string? Bic;
public int? AreaCommitment; public int? AreaCommitment;
public (string VtrgId, int Area)[] AreaCommitmentsFiltered; public (string VtrgId, int Area)[] AreaCommitmentsFiltered;
public bool IsBuchführend; public bool IsAccounting;
public bool IsOrganic; public bool IsOrganic;
public bool IsActive; public bool IsActive;
public DateOnly? EntryDate; public DateOnly? EntryDate;
@@ -91,14 +94,17 @@ namespace Elwig.Models.Dtos {
public string? AdditionalContact; public string? AdditionalContact;
public string? Comment; public string? Comment;
public MemberListRow(Member m, int? areaCom = null, Dictionary<string, int>? filtered = null) { public MemberListRow(Member m, int? areaCmt = null, Dictionary<string, int>? filtered = null) {
MgNr = m.MgNr; MgNr = m.MgNr;
Name1 = m.Name; Name1 = m.Name;
AdminName1 = m.AdministrativeName1; AdminName1 = m.AdministrativeName1;
Name2 = m.AdministrativeName2; Name2 = m.AdministrativeName2;
DefaultKg = m.DefaultKg?.Name; DefaultKg = m.DefaultKg?.Name;
Branch = m.Branch?.Name; Branch = m.Branch?.Name;
BusinessShares = m.BusinessShares; Shares = m.Shares;
SharesRed = m.SharesRed;
SharesWhite = m.SharesWhite;
SharesActive = Shares + SharesRed + SharesWhite;
Address = m.Address; Address = m.Address;
Plz = m.PostalDest.AtPlz!.Plz; Plz = m.PostalDest.AtPlz!.Plz;
Locality = m.PostalDest.AtPlz!.Ort.Name; Locality = m.PostalDest.AtPlz!.Ort.Name;
@@ -112,7 +118,7 @@ namespace Elwig.Models.Dtos {
UstIdNr = m.UstIdNr; UstIdNr = m.UstIdNr;
Iban = m.Iban != null ? Utils.FormatIban(m.Iban) : null; Iban = m.Iban != null ? Utils.FormatIban(m.Iban) : null;
Bic = m.Bic; Bic = m.Bic;
IsBuchführend = m.IsBuchführend; IsAccounting = m.IsAccounting;
IsOrganic = m.IsOrganic; IsOrganic = m.IsOrganic;
IsActive = m.IsActive; IsActive = m.IsActive;
EntryDate = m.EntryDate; EntryDate = m.EntryDate;
@@ -127,7 +133,7 @@ namespace Elwig.Models.Dtos {
.Distinct() .Distinct()
.Concat(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).Except([EmailAddress]))); .Concat(m.EmailAddresses.OrderBy(a => a.Nr).Select(a => a.Address).Except([EmailAddress])));
Comment = m.Comment; Comment = m.Comment;
AreaCommitment = areaCom == 0 ? null : areaCom; AreaCommitment = areaCmt == 0 ? null : areaCmt;
AreaCommitmentsFiltered = filtered != null ? filtered.OrderBy(f => f.Key).Select(f => (f.Key, f.Value)).ToArray() : []; AreaCommitmentsFiltered = filtered != null ? filtered.OrderBy(f => f.Key).Select(f => (f.Key, f.Value)).ToArray() : [];
} }
} }
+142
View File
@@ -0,0 +1,142 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Models.Dtos {
public class MemberMovementData : DataTable<MemberMovementDataRow> {
private static readonly (string, string, string?, int)[] FieldNames = [
("Type", "\u2026 durch", null, 30),
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 25),
("Address", "Adresse", null, 50),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 50),
("Date", "Datum", null, 20),
("SharesOld", "GA\nalt", null, 10),
("SharesDiff", "GA\nDiff.", null, 10),
("SharesNew", "GA\nneu", null, 10),
("Costs", "Kosten", "€", 15),
];
private MemberMovementData(string name, IEnumerable<(string? Name, IEnumerable<MemberMovementDataRow> Rows)> sections) :
base("Mitgl.-Bewegungen", name, sections, FieldNames) {
}
public static MemberMovementData FromMemberHistory(string name, IEnumerable<MemberHistory> rows, Dictionary<int, SortedDictionary<DateOnly, MemberHistoryPoint>> history) {
(string? Name, List<MemberMovementDataRow> Rows)[] sections = [
("Beitritt", []),
("Nachzeichnung (GA)", []),
("Überschreibung", []),
("Kündigung (GA)", []),
("Austritt", []),
];
foreach (var h in rows.OrderBy(r => r.DateNotice).ThenBy(r => r.ToMgNr).ThenBy(r => r.FromMgNr)) {
int cat;
if (h.ToMgNr == h.FromMgNr) {
// Umwandlung
if (h.ToType == BusinessShareType.Dormant) {
// Kündigung (GA)
cat = 3;
} else if (h.FromType == h.ToType) {
// Überschreibung
cat = 2;
} else {
throw new NotImplementedException();
}
} else if (h.DateNotice <= h.ToMember?.EntryDate && h.DateNotice >= h.FromMember?.ExitDate) {
// Überschreibung
cat = 2;
} else if (h.DateNotice <= h.ToMember?.EntryDate) {
// Beitritt
cat = 0;
} else if (h.DateNotice >= h.FromMember?.ExitDate) {
// Austritt
cat = 4;
} else if (h.ToMember == null) {
// Auszahlung
continue;
} else {
// Nachzeichnung (GA)
cat = 1;
}
Member a;
Member? b = null;
if (h.FromMember != null && h.ToMember != null) {
if (cat <= 1) {
a = h.ToMember;
b = h.FromMember;
} else {
a = h.FromMember;
b = h.ToMember;
}
} else if (h.FromMember != null) {
a = h.FromMember;
} else if (h.ToMember != null) {
a = h.ToMember;
} else {
throw new ArgumentException();
}
var oldA = history[a.MgNr].GetValueOrDefault(h.DateNotice);
var d = new MemberMovementDataRow {
Type = [h.ReasonPretty],
MgNr = [a.MgNr],
Name1 = [a.Name],
Name2 = [a.AdministrativeName2],
Address = [a.Address],
Plz = [a.PostalDest.AtPlz!.Plz],
Locality = [a.PostalDest.AtPlz.Ort.Name],
Date = h.DateNotice,
SharesOld = [oldA.Shares + oldA.SharesRed + oldA.SharesWhite],
SharesDiff = [cat != 3 && a == h.ToMember ? h.Shares : -h.Shares],
Costs = [h.FromMember == null ? h.TotalValue : null],
};
d.SharesNew = [d.SharesOld[^1] + (h.FromMgNr == h.ToMgNr ? 0 : d.SharesDiff[^1])];
if (b != null) {
var oldB = history[b.MgNr].GetValueOrDefault(h.DateNotice);
d.Type = [..d.Type, "\u2026 " + (cat <= 1 ? "von" : "an")];
d.MgNr = [.. d.MgNr, b.MgNr];
d.Name1 = [.. d.Name1, b.Name];
d.Name2 = [.. d.Name2, b.AdministrativeName2];
d.Address = [.. d.Address, b.Address];
d.Plz = [.. d.Plz, b.PostalDest.AtPlz!.Plz];
d.Locality = [.. d.Locality, b.PostalDest.AtPlz.Ort.Name];
d.SharesOld = [.. d.SharesOld, oldB.Shares + oldB.SharesRed + oldB.SharesWhite];
d.SharesDiff = [.. d.SharesDiff, b == h.ToMember ? h.Shares : -h.Shares];
d.SharesNew = [.. d.SharesNew, d.SharesOld[^1] + d.SharesDiff[^1]];
}
sections[cat].Rows.Add(d);
}
return new MemberMovementData($"Mitglieder-Bewegungen {name}", sections.Select(s => (s.Name, (IEnumerable<MemberMovementDataRow>)s.Rows)));
}
}
public class MemberMovementDataRow {
public string[] Type = [];
public int[] MgNr = [];
public string[] Name1 = [];
public string?[] Name2 = [];
public string[] Address = [];
public int[] Plz = [];
public string[] Locality = [];
public DateOnly Date;
public int?[] SharesOld = [];
public int[] SharesDiff = [];
public int?[] SharesNew = [];
public decimal?[] Costs = [];
}
}
+81 -18
View File
@@ -13,35 +13,74 @@ namespace Elwig.Models.Dtos {
("Address", "Adresse", null, 60), ("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10), ("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60), ("Locality", "Ort", null, 60),
("BusinessShares", "GA", null, 10), ("Shares", "GA", null, 10),
("DeliveryObligation", "Lieferpflicht", "kg", 22), ("DeliveryObligation", "Lieferpflicht", "kg", 22),
("DeliveryRight", "Lieferrecht", "kg", 22), ("DeliveryRight", "Lieferrecht", "kg", 22),
("Weight", "Geliefert", "kg", 22), ("WeightTotal", "Geliefert", "kg", 22),
("OverUnderDelivery", "Über-/Unterliefert", "kg|%", 34), ("OverUnderDelivery", "Über-/Unterliefert", "kg|%", 34),
]; ];
private static readonly (string, string, string?, int)[] FieldNamesRed = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SharesRed", "GA", null, 10),
("DeliveryObligationRed", "Lieferpflicht", "kg", 22),
("DeliveryRightRed", "Lieferrecht", "kg", 22),
("WeightRed", "Geliefert", "kg", 22),
("OverUnderDeliveryRed", "Über-/Unterliefert", "kg|%", 34),
];
private static readonly (string, string, string?, int)[] FieldNamesWhite = [
("MgNr", "MgNr.", null, 12),
("Name1", "Name", null, 40),
("Name2", "Vorname", null, 40),
("Address", "Adresse", null, 60),
("Plz", "PLZ", null, 10),
("Locality", "Ort", null, 60),
("SharesWhite", "GA", null, 10),
("DeliveryObligationWhite", "Lieferpflicht", "kg", 22),
("DeliveryRightWhite", "Lieferrecht", "kg", 22),
("WeightWhite", "Geliefert", "kg", 22),
("OverUnderDeliveryWhite", "Über-/Unterliefert", "kg|%", 34),
];
public OverUnderDeliveryData(IEnumerable<OverUnderDeliveryRow> rows, int year) : public OverUnderDeliveryData(IEnumerable<OverUnderDeliveryRow> rows, int year) :
base($"Über-Unterlieferungen", $"Über- und Unterlieferungen laut gezeichneten Geschäftsanteilen {year}", rows, FieldNames) { base($"Über-Unterlieferungen", $"Über- und Unterlieferungen laut gezeichneten Geschäftsanteilen {year}", rows, FieldNames) {
} }
public static async Task<OverUnderDeliveryData> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) { public OverUnderDeliveryData(IEnumerable<OverUnderDeliveryRow> rows, int year, string mode) :
base($"Über-Unterlieferungen-{(mode == "R" ? "rot" : "weiß")}", $"Über- und Unterlieferungen laut gezeichneten Geschäftsanteilen {(mode == "R" ? "rot" : "weiß")} {year}", rows,
mode == "R" ? FieldNamesRed : FieldNamesWhite) {
}
public static async Task<(OverUnderDeliveryData Total, OverUnderDeliveryData Red, OverUnderDeliveryData White)> ForSeason(DbSet<OverUnderDeliveryRow> table, int year) {
var rows = await table.FromSql($""" var rows = await table.FromSql($"""
SELECT m.mgnr, m.name AS name_1, SELECT m.mgnr, m.name AS name_1,
COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(m.prefix || ' ', '') || m.given_name ||
COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2, COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '') AS name_2,
p.plz, o.name AS ort, m.address, m.business_shares, p.plz, o.name AS ort, m.address, h.shares, h.shares_red, h.shares_white,
m.business_shares * s.min_kg_per_bs AS min_kg, h.shares * COALESCE(s.min_kg_per_share, 0) AS min_kg,
m.business_shares * s.max_kg_per_bs AS max_kg, h.shares * COALESCE(s.max_kg_per_share, 0) AS max_kg,
COALESCE(SUM(d.weight), 0) AS sum h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0) AS min_kg_red,
h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0) AS max_kg_red,
h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0) AS min_kg_white,
h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0) AS max_kg_white,
COALESCE(d.weight_total, 0) AS weight_total,
COALESCE(d.weight_red, 0) AS weight_red,
COALESCE(d.weight_white, 0) AS weight_white
FROM season s, member m FROM season s, member m
LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
LEFT JOIN AT_ort o ON o.okz = p.okz LEFT JOIN AT_ort o ON o.okz = p.okz
LEFT JOIN v_delivery d ON (d.year, d.mgnr) = (s.year, m.mgnr) LEFT JOIN v_member_history h ON (h.year, h.mgnr) = (s.year, m.mgnr)
WHERE s.year = {year} AND (m.active = TRUE OR d.weight > 0) LEFT JOIN v_stat_member d ON (d.year, d.mgnr) = (s.year, m.mgnr)
GROUP BY s.year, m.mgnr WHERE s.year = {year} AND (m.active = TRUE OR d.weight_total > 0)
ORDER BY 100.0 * sum / max_kg, m.mgnr ORDER BY 100.0 * weight_total / (max_kg + max_kg_red + max_kg_white), m.mgnr
""").ToListAsync(); """).ToListAsync();
return new OverUnderDeliveryData(rows, year); return (new OverUnderDeliveryData(rows, year), new OverUnderDeliveryData(rows, year, "R"), new OverUnderDeliveryData(rows, year, "W"));
} }
} }
@@ -61,17 +100,41 @@ namespace Elwig.Models.Dtos {
public required string LocalityFull { get; set; } public required string LocalityFull { get; set; }
[NotMapped] [NotMapped]
public string Locality => LocalityFull.Split(",")[0]; public string Locality => LocalityFull.Split(",")[0];
[Column("business_shares")] [Column("shares")]
public int BusinessShares { get; set; } public int Shares { get; set; }
[Column("shares_red")]
public int SharesRed { get; set; }
[Column("shares_white")]
public int SharesWhite { get; set; }
[Column("min_kg")] [Column("min_kg")]
public int DeliveryObligation { get; set; } public int DeliveryObligation { get; set; }
[Column("max_kg")] [Column("max_kg")]
public int DeliveryRight { get; set; } public int DeliveryRight { get; set; }
[Column("sum")]
public int Weight { get; set; }
[NotMapped] [NotMapped]
public (int? Kg, double? Percent) OverUnderDelivery => public (int? Kg, double? Percent) OverUnderDelivery =>
Weight < DeliveryObligation ? (Weight - DeliveryObligation, Weight * 100.0 / DeliveryObligation - 100.0) : WeightTotal < DeliveryObligation ? (WeightTotal - DeliveryObligation, WeightTotal * 100.0 / DeliveryObligation - 100.0) :
Weight > DeliveryRight ? (Weight - DeliveryRight, Weight * 100.0 / DeliveryRight - 100.0) : (null, null); WeightTotal > DeliveryRight ? (WeightTotal - DeliveryRight, WeightTotal * 100.0 / DeliveryRight - 100.0) : (null, null);
[Column("min_kg_red")]
public int DeliveryObligationRed { get; set; }
[Column("max_kg_red")]
public int DeliveryRightRed { get; set; }
[NotMapped]
public (int? Kg, double? Percent) OverUnderDeliveryRed =>
WeightRed < DeliveryObligationRed ? (WeightRed - DeliveryObligationRed, WeightRed * 100.0 / DeliveryObligationRed - 100.0) :
WeightRed > DeliveryRightRed ? (WeightRed - DeliveryRightRed, WeightRed * 100.0 / DeliveryRightRed - 100.0) : (null, null);
[Column("min_kg_white")]
public int DeliveryObligationWhite { get; set; }
[Column("max_kg_white")]
public int DeliveryRightWhite { get; set; }
[NotMapped]
public (int? Kg, double? Percent) OverUnderDeliveryWhite =>
WeightWhite < DeliveryObligationWhite ? (WeightWhite - DeliveryObligationWhite, WeightWhite * 100.0 / DeliveryObligationWhite - 100.0) :
WeightWhite > DeliveryRightWhite ? (WeightWhite - DeliveryRightWhite, WeightWhite * 100.0 / DeliveryRightWhite - 100.0) : (null, null);
[Column("weight_total")]
public int WeightTotal { get; set; }
[Column("weight_red")]
public int WeightRed { get; set; }
[Column("weight_white")]
public int WeightWhite { get; set; }
} }
} }
+36 -36
View File
@@ -18,9 +18,9 @@ namespace Elwig.Models.Dtos {
("Cultivation", "Bewirt.", null, 20), ("Cultivation", "Bewirt.", null, 20),
("QualityLevel", "Qualitätsstufe", null, 30), ("QualityLevel", "Qualitätsstufe", null, 30),
("Oe", "Gradation", "°Oe", 20), ("Oe", "Gradation", "°Oe", 20),
("Ungeb", "ungebunden", "kg|€/kg|€/kg", 60), ("UnCtrd", "ungebunden", "kg|€/kg|€/kg", 60),
("LowGeb", "attributlos gebunden", "kg|€/kg|€/kg", 60), ("LowCtrd", "attributlos gebunden", "kg|€/kg|€/kg", 60),
("Geb", "gebunden", "kg|€/kg|€/kg", 60), ("Ctrd", "gebunden", "kg|€/kg|€/kg", 60),
("Amount", "Gesamt", "€", 25), ("Amount", "Gesamt", "€", 25),
]; ];
@@ -31,9 +31,9 @@ namespace Elwig.Models.Dtos {
string? Cultivation, string? Cultivation,
string QualityLevel, string QualityLevel,
double Oe, double Oe,
(int Weight, decimal? MinPrice, decimal? MaxPrice) Ungeb, (int Weight, decimal? MinPrice, decimal? MaxPrice) UnCtrd,
(int Weight, decimal? MinPrice, decimal? MaxPrice) LowGeb, (int Weight, decimal? MinPrice, decimal? MaxPrice) LowCtrd,
(int Weight, decimal? MinPrice, decimal? MaxPrice) Geb, (int Weight, decimal? MinPrice, decimal? MaxPrice) Ctrd,
decimal Amount decimal Amount
); );
@@ -44,9 +44,9 @@ namespace Elwig.Models.Dtos {
public static async Task<PaymentVariantSummaryData> ForPaymentVariant(PaymentVar v, DbSet<PaymentVariantSummaryRow> table) { public static async Task<PaymentVariantSummaryData> ForPaymentVariant(PaymentVar v, DbSet<PaymentVariantSummaryRow> table) {
return new(v, (await FromDbSet(table, v.Year, v.AvNr)) return new(v, (await FromDbSet(table, v.Year, v.AvNr))
.Select(r => new PaymentRow(r.Type, r.Variety, r.Attribute, r.Cultivation, r.QualityLevel, r.Oe, .Select(r => new PaymentRow(r.Type, r.Variety, r.Attribute, r.Cultivation, r.QualityLevel, r.Oe,
(r.WeightUngeb, r.MinPriceUngeb != null ? Utils.DecFromDb(r.MinPriceUngeb.Value, v.Season.Precision) : null, r.MaxPriceUngeb != null ? Utils.DecFromDb(r.MaxPriceUngeb.Value, v.Season.Precision) : null), (r.WeightUnCtrd, r.MinPriceUnCtrd != null ? Utils.DecFromDb(r.MinPriceUnCtrd.Value, v.Season.Precision) : null, r.MaxPriceUnCtrd != null ? Utils.DecFromDb(r.MaxPriceUnCtrd.Value, v.Season.Precision) : null),
(r.WeightLowGeb, r.MinPriceLowGeb != null ? Utils.DecFromDb(r.MinPriceLowGeb.Value, v.Season.Precision) : null, r.MaxPriceLowGeb != null ? Utils.DecFromDb(r.MaxPriceLowGeb.Value, v.Season.Precision) : null), (r.WeightLowCtrd, r.MinPriceLowCtrd != null ? Utils.DecFromDb(r.MinPriceLowCtrd.Value, v.Season.Precision) : null, r.MaxPriceLowCtrd != null ? Utils.DecFromDb(r.MaxPriceLowCtrd.Value, v.Season.Precision) : null),
(r.WeightGeb, r.MinPriceGeb != null ? Utils.DecFromDb(r.MinPriceGeb.Value, v.Season.Precision) : null, r.MaxPriceGeb != null ? Utils.DecFromDb(r.MaxPriceGeb.Value, v.Season.Precision) : null), (r.WeightCtrd, r.MinPriceCtrd != null ? Utils.DecFromDb(r.MinPriceCtrd.Value, v.Season.Precision) : null, r.MaxPriceCtrd != null ? Utils.DecFromDb(r.MaxPriceCtrd.Value, v.Season.Precision) : null),
Utils.DecFromDb(r.Amount, v.Season.Precision))) Utils.DecFromDb(r.Amount, v.Season.Precision)))
.ToArray()); .ToArray());
} }
@@ -59,15 +59,15 @@ namespace Elwig.Models.Dtos {
c.name AS cultivation, c.name AS cultivation,
q.name AS quality_level, q.name AS quality_level,
ROUND(kmw * (4.54 + 0.022 * kmw)) AS oe, ROUND(kmw * (4.54 + 0.022 * kmw)) AS oe,
SUM(IIF(w.discr = '_', w.value, 0)) AS weight_ungeb, SUM(IIF(w.discr = '_', w.value, 0)) AS weight_unctrd,
MIN(IIF(w.discr = '_', b.price, NULL)) AS min_price_ungeb, MIN(IIF(w.discr = '_', b.price, NULL)) AS min_price_unctrd,
MAX(IIF(w.discr = '_', b.price, NULL)) AS max_price_ungeb, MAX(IIF(w.discr = '_', b.price, NULL)) AS max_price_unctrd,
SUM(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), w.value, 0)) AS weight_lowgeb, SUM(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), w.value, 0)) AS weight_lowctrd,
MIN(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS min_price_lowgeb, MIN(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS min_price_lowctrd,
MAX(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS max_price_lowgeb, MAX(IIF(w.discr NOT IN (COALESCE(p.attrid, ''), '_'), b.price, NULL)) AS max_price_lowctrd,
SUM(IIF(w.discr = COALESCE(p.attrid, ''), w.value, 0)) AS weight_geb, SUM(IIF(w.discr = COALESCE(p.attrid, ''), w.value, 0)) AS weight_ctrd,
MIN(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS min_price_geb, MIN(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS min_price_ctrd,
MAX(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS max_price_geb, MAX(IIF(w.discr = COALESCE(p.attrid, ''), b.price, NULL)) AS max_price_ctrd,
SUM(b.amount) AS amount SUM(b.amount) AS amount
FROM payment_delivery_part_bucket b FROM payment_delivery_part_bucket b
LEFT JOIN delivery_part_bucket w ON (w.year, w.did, w.dpnr, w.bktnr) = (b.year, b.did, b.dpnr, b.bktnr) LEFT JOIN delivery_part_bucket w ON (w.year, w.did, w.dpnr, w.bktnr) = (b.year, b.did, b.dpnr, b.bktnr)
@@ -97,24 +97,24 @@ namespace Elwig.Models.Dtos {
public required string QualityLevel { get; set; } public required string QualityLevel { get; set; }
[Column("oe")] [Column("oe")]
public double Oe { get; set; } public double Oe { get; set; }
[Column("weight_ungeb")] [Column("weight_unctrd")]
public int WeightUngeb { get; set; } public int WeightUnCtrd { get; set; }
[Column("min_price_ungeb")] [Column("min_price_unctrd")]
public long? MinPriceUngeb { get; set; } public long? MinPriceUnCtrd { get; set; }
[Column("max_price_ungeb")] [Column("max_price_unctrd")]
public long? MaxPriceUngeb { get; set; } public long? MaxPriceUnCtrd { get; set; }
[Column("weight_lowgeb")] [Column("weight_lowctrd")]
public int WeightLowGeb { get; set; } public int WeightLowCtrd { get; set; }
[Column("min_price_lowgeb")] [Column("min_price_lowctrd")]
public long? MinPriceLowGeb { get; set; } public long? MinPriceLowCtrd { get; set; }
[Column("max_price_lowgeb")] [Column("max_price_lowctrd")]
public long? MaxPriceLowGeb { get; set; } public long? MaxPriceLowCtrd { get; set; }
[Column("weight_geb")] [Column("weight_ctrd")]
public int WeightGeb { get; set; } public int WeightCtrd { get; set; }
[Column("min_price_geb")] [Column("min_price_ctrd")]
public long? MinPriceGeb { get; set; } public long? MinPriceCtrd { get; set; }
[Column("max_price_geb")] [Column("max_price_ctrd")]
public long? MaxPriceGeb { get; set; } public long? MaxPriceCtrd { get; set; }
[Column("amount")] [Column("amount")]
public long Amount { get; set; } public long Amount { get; set; }
} }
+6 -6
View File
@@ -13,7 +13,7 @@ namespace Elwig.Models.Dtos {
("AttrId", "Attr.", null, 10), ("AttrId", "Attr.", null, 10),
("CultId", "Bewirt.", null, 15), ("CultId", "Bewirt.", null, 15),
("QualId", "Qual.", null, 15), ("QualId", "Qual.", null, 15),
("Geb", "gebunden", null, 20), ("Contracted", "gebunden", null, 20),
("Weight", "Menge", "kg", 20), ("Weight", "Menge", "kg", 20),
]; ];
@@ -28,12 +28,12 @@ namespace Elwig.Models.Dtos {
private static async Task<IEnumerable<WeightBreakdownRow>> FromDbSet(DbSet<WeightBreakdownRow> table, int year, string? zwstid) { private static async Task<IEnumerable<WeightBreakdownRow>> FromDbSet(DbSet<WeightBreakdownRow> table, int year, string? zwstid) {
zwstid = zwstid == null ? "NULL" : $"'{zwstid}'"; zwstid = zwstid == null ? "NULL" : $"'{zwstid}'";
return await table.FromSqlRaw($""" return await table.FromSqlRaw($"""
SELECT type, sortid, attrid, cultid, v.qualid, geb, SUM(weight) AS weight SELECT type, sortid, attrid, cultid, v.qualid, contracted, SUM(weight) AS weight
FROM v_stat_total v FROM v_stat_total v
LEFT JOIN wine_quality_level q ON q.qualid = v.qualid LEFT JOIN wine_quality_level q ON q.qualid = v.qualid
WHERE year = {year} AND ({zwstid} IS NULL OR zwstid = {zwstid}) WHERE year = {year} AND ({zwstid} IS NULL OR zwstid = {zwstid})
GROUP BY type, sortid, attrid, cultid, v.qualid, geb GROUP BY type, sortid, attrid, cultid, v.qualid, contracted
ORDER BY type DESC, sortid, attrid, cultid, q.min_kmw, geb ORDER BY type DESC, sortid, attrid, cultid, q.min_kmw, contracted
""").ToListAsync(); """).ToListAsync();
} }
} }
@@ -50,8 +50,8 @@ namespace Elwig.Models.Dtos {
public string? CultId { get; set; } public string? CultId { get; set; }
[Column("qualid")] [Column("qualid")]
public required string QualId { get; set; } public required string QualId { get; set; }
[Column("geb")] [Column("contracted")]
public required string Geb { get; set; } public required string Contracted { get; set; }
[Column("weight")] [Column("weight")]
public int Weight { get; set; } public int Weight { get; set; }
} }
+3
View File
@@ -20,6 +20,9 @@ namespace Elwig.Models.Entities {
[Column("dest")] [Column("dest")]
public string Dest { get; private set; } = null!; public string Dest { get; private set; } = null!;
[NotMapped]
public string FullDest => $"{Ort.Name} ({Dest})";
[ForeignKey("Plz")] [ForeignKey("Plz")]
public virtual AT_Plz AtPlz { get; private set; } = null!; public virtual AT_Plz AtPlz { get; private set; } = null!;
@@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Elwig.Models.Entities { namespace Elwig.Models.Entities {
[Table("area_commitment"), PrimaryKey("FbNr", "RevNr")] [Table("area_commitment"), PrimaryKey("FbNr", "RevNr")]
public class AreaCom { public class AreaCmt {
[Column("fbnr")] [Column("fbnr")]
public int FbNr { get; set; } public int FbNr { get; set; }
@@ -65,13 +65,13 @@ namespace Elwig.Models.Entities {
} }
[ForeignKey("FbNr")] [ForeignKey("FbNr")]
public virtual AreaComContract Contract { get; private set; } = null!; public virtual AreaCmtContract Contract { get; private set; } = null!;
[ForeignKey("MgNr")] [ForeignKey("MgNr")]
public virtual Member Member { get; private set; } = null!; public virtual Member Member { get; private set; } = null!;
[ForeignKey("VtrgId")] [ForeignKey("VtrgId")]
public virtual AreaComType AreaComType { get; private set; } = null!; public virtual AreaCmtType AreaCmtType { get; private set; } = null!;
[ForeignKey("CultId")] [ForeignKey("CultId")]
public virtual WineCult? WineCult { get; private set; } public virtual WineCult? WineCult { get; private set; }
@@ -7,7 +7,7 @@ using System.Linq;
namespace Elwig.Models.Entities { namespace Elwig.Models.Entities {
[Table("area_commitment_contract"), PrimaryKey("FbNr")] [Table("area_commitment_contract"), PrimaryKey("FbNr")]
public class AreaComContract { public class AreaCmtContract {
[Column("fbnr")] [Column("fbnr")]
public int FbNr { get; set; } public int FbNr { get; set; }
@@ -58,11 +58,11 @@ namespace Elwig.Models.Entities {
[ForeignKey("KgNr, RdNr")] [ForeignKey("KgNr, RdNr")]
public virtual WbRd? Rd { get; private set; } public virtual WbRd? Rd { get; private set; }
[InverseProperty(nameof(AreaCom.Contract))] [InverseProperty(nameof(AreaCmt.Contract))]
public virtual ICollection<AreaCom> Revisions { get; private set; } = null!; public virtual ICollection<AreaCmt> Revisions { get; private set; } = null!;
[NotMapped] [NotMapped]
public AreaCom? Latest => Revisions.OrderBy(r => r.RevNr).LastOrDefault(); public AreaCmt? Latest => Revisions.OrderBy(r => r.RevNr).LastOrDefault();
[NotMapped] [NotMapped]
public int? YearFrom => Revisions.Any(r => r.YearFrom == null) ? null : Revisions.Min(r => r.YearFrom); public int? YearFrom => Revisions.Any(r => r.YearFrom == null) ? null : Revisions.Min(r => r.YearFrom);
@@ -5,7 +5,7 @@ using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute;
namespace Elwig.Models.Entities { namespace Elwig.Models.Entities {
[Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId", "Discriminator", IsUnique = true)] [Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId", "Discriminator", IsUnique = true)]
public class AreaComType { public class AreaCmtType {
[Column("vtrgid")] [Column("vtrgid")]
public required string VtrgId { get; set; } public required string VtrgId { get; set; }
+8 -6
View File
@@ -91,8 +91,8 @@ namespace Elwig.Models.Entities {
[Column("hand_picked")] [Column("hand_picked")]
public bool? IsHandPicked { get; set; } public bool? IsHandPicked { get; set; }
[Column("gebunden")] [Column("contracted")]
public bool? IsGebunden { get; set; } public bool? IsContracted { get; set; }
[Column("unloading")] [Column("unloading")]
public string? Unloading { get; set; } public string? Unloading { get; set; }
@@ -118,26 +118,28 @@ namespace Elwig.Models.Entities {
[Column("acid")] [Column("acid")]
public double? Acid { get; set; } public double? Acid { get; set; }
[Column("scale_id")] [Column("scale_terminal")]
public string? ScaleId { get; set; } public string? ScaleTerminalId { get; set; }
[Column("weighing_data")] [Column("weighing_data")]
public string? WeighingData { get; set; } public string? WeighingData { get; set; }
[NotMapped] [NotMapped]
public (string? Id, int? Gross, int? Tare, int? Net, DateOnly? Date, TimeOnly? Time) WeighingInfo { public (string? ScaleId, string? Id, int? Gross, int? Tare, int? TareCorrection, int? Net, DateOnly? Date, TimeOnly? Time) WeighingInfo {
get { get {
try { try {
var obj = JsonNode.Parse(WeighingData!)!.AsObject(); var obj = JsonNode.Parse(WeighingData!)!.AsObject();
return ( return (
obj["scale"]?.AsValue().GetValue<string>(),
obj["id"]?.AsValue().GetValue<string>(), obj["id"]?.AsValue().GetValue<string>(),
obj["gross_weight"]?.AsValue().GetValue<int>(), obj["gross_weight"]?.AsValue().GetValue<int>(),
obj["tare_weight"]?.AsValue().GetValue<int>(), obj["tare_weight"]?.AsValue().GetValue<int>(),
obj["tare_correction"]?.AsValue().GetValue<int>(),
obj["net_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, 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 TimeOnly.TryParseExact(obj["time"]?.AsValue().GetValue<string>(), ["HH:mm:ss", "HH:mm"], out var t) ? t : null
); );
} catch { } catch {
return (null, null, null, null, null, null); return (null, null, null, null, null, null, null, null);
} }
} }
} }
@@ -38,6 +38,8 @@ namespace Elwig.Models.Entities {
[Column("cultid")] [Column("cultid")]
public string? CultId { get; set; } public string? CultId { get; set; }
[NotMapped]
public bool IsOrganic => Cultivation?.IsOrganic ?? false;
[Column("max_weight")] [Column("max_weight")]
public int? MaxWeight { get; set; } public int? MaxWeight { get; set; }
+37 -12
View File
@@ -42,6 +42,8 @@ namespace Elwig.Models.Entities {
[NotMapped] [NotMapped]
public string ShortName => (!string.IsNullOrWhiteSpace(GivenName) ? $"{GivenName} " : "") + Name; public string ShortName => (!string.IsNullOrWhiteSpace(GivenName) ? $"{GivenName} " : "") + Name;
[NotMapped] [NotMapped]
public string FullAdministrativeName => $"{AdministrativeName} ({MgNr})";
[NotMapped]
public string AdministrativeName => AdministrativeName1 + (!string.IsNullOrWhiteSpace(AdministrativeName2) ? $" {AdministrativeName2}" : ""); public string AdministrativeName => AdministrativeName1 + (!string.IsNullOrWhiteSpace(AdministrativeName2) ? $" {AdministrativeName2}" : "");
[NotMapped] [NotMapped]
public string AdministrativeName1 => IsJuridicalPerson ? Name : Name.Replace('ß', 'ẞ').ToUpper(); public string AdministrativeName1 => IsJuridicalPerson ? Name : Name.Replace('ß', 'ẞ').ToUpper();
@@ -67,8 +69,20 @@ namespace Elwig.Models.Entities {
set => ExitDateString = value?.ToString("yyyy-MM-dd"); set => ExitDateString = value?.ToString("yyyy-MM-dd");
} }
[Column("business_shares")] [Column("shares")]
public int BusinessShares { get; set; } public int Shares { get; set; }
[Column("shares_red")]
public int SharesRed { get; set; }
[Column("shares_white")]
public int SharesWhite { get; set; }
[Column("shares_dormant")]
public int SharesDormant { get; set; }
[NotMapped]
public int SharesActive => Shares + SharesRed + SharesWhite;
[Column("accounting_nr")] [Column("accounting_nr")]
public string? AccountingNr { get; set; } public string? AccountingNr { get; set; }
@@ -82,20 +96,26 @@ namespace Elwig.Models.Entities {
[Column("ustid_nr")] [Column("ustid_nr")]
public string? UstIdNr { get; set; } public string? UstIdNr { get; set; }
[Column("ooc_id")]
public string? OrganicOperatorId { get; set; }
[Column("org_auth_code")]
public string? OrganicAuthorityCode { get; set; }
[Column("juridical_pers")] [Column("juridical_pers")]
public bool IsJuridicalPerson { get; set; } public bool IsJuridicalPerson { get; set; }
[Column("volllieferant")] [Column("full_delivery")]
public bool IsVollLieferant { get; set; } public bool IsFullDelivery { get; set; }
[Column("buchführend")] [Column("accounting")]
public bool IsBuchführend { get; set; } public bool IsAccounting { get; set; }
[Column("organic")] [Column("organic")]
public bool IsOrganic { get; set; } public bool IsOrganic { get; set; }
[Column("funktionär")] [Column("officer")]
public bool IsFunktionär { get; set; } public bool IsOfficer { get; set; }
[Column("active")] [Column("active")]
public bool IsActive { get; set; } public bool IsActive { get; set; }
@@ -179,19 +199,24 @@ namespace Elwig.Models.Entities {
[ForeignKey("ZwstId")] [ForeignKey("ZwstId")]
public virtual Branch? Branch { get; private set; } public virtual Branch? Branch { get; private set; }
[InverseProperty(nameof(AreaCom.Member))] [InverseProperty(nameof(AreaCmt.Member))]
public virtual ICollection<AreaCom> AreaCommitments { get; private set; } = null!; public virtual ICollection<AreaCmt> AreaCommitments { get; private set; } = null!;
public IQueryable<AreaCom> ActiveAreaCommitments(AppDbContext ctx, int? year = null) { public IQueryable<AreaCmt> ActiveAreaCommitments(AppDbContext ctx, int? year = null) {
return ctx.AreaCommitments.Where(c => c.MgNr == MgNr).Where(Utils.ActiveAreaCommitments(year ?? Utils.CurrentYear)); return ctx.AreaCommitments.Where(c => c.MgNr == MgNr).Where(Utils.ActiveAreaCommitments(year ?? Utils.CurrentYear));
} }
[InverseProperty(nameof(BillingAddr.Member))] [InverseProperty(nameof(BillingAddr.Member))]
public virtual BillingAddr? BillingAddress { get; private set; } public virtual BillingAddr? BillingAddress { get; private set; }
[InverseProperty(nameof(Delivery.Member))] [InverseProperty(nameof(DeliveryAncmt.Member))]
public virtual ICollection<DeliveryAncmt> Announcements { get; private set; } = null!; public virtual ICollection<DeliveryAncmt> Announcements { get; private set; } = null!;
[InverseProperty(nameof(MemberHistory.FromMember))]
public virtual ICollection<MemberHistory> HistoryFrom { get; private set; } = null!;
[InverseProperty(nameof(MemberHistory.ToMember))]
public virtual ICollection<MemberHistory> HistoryTo { get; private set; } = null!;
[InverseProperty(nameof(Delivery.Member))] [InverseProperty(nameof(Delivery.Member))]
public virtual ICollection<Delivery> Deliveries { get; private set; } = null!; public virtual ICollection<Delivery> Deliveries { get; private set; } = null!;
+112 -13
View File
@@ -1,31 +1,130 @@
using Elwig.Helpers;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace Elwig.Models.Entities { namespace Elwig.Models.Entities {
[Table("member_history"), PrimaryKey("MgNr", "DateString", "Type")] [Table("member_history"), PrimaryKey("HistNr")]
public class MemberHistory { public class MemberHistory {
[Column("mgnr")]
public int MgNr { get; set; }
[Column("date")] public const string REASON_PURCHASE = "purchase"; // Kauf
public required string DateString { get; set; } public const string REASON_CONVERT = "convert"; // Änderung
public const string REASON_TRANSFER = "transfer"; // Übertragung
public const string REASON_PRESCRIPTION = "prescription"; // Vorschreibung
public const string REASON_EXCLUSION = "exclusion"; // Ausschluss
public const string REASON_DECEASED = "deceased"; // Tod
public const string REASON_RETIREMENT = "retirement"; // Kündigung/stilllegen
public const string REASON_PAYOUT = "payout"; // Auszahlung
[Column("histnr")]
public int HistNr { get; set; }
[Column("from_mgnr")]
public int? FromMgNr { get; set; }
[Column("from_type")]
public int? FromTypeValue { get; set; }
[NotMapped] [NotMapped]
public DateOnly Date { public BusinessShareType? FromType {
get => DateOnly.ParseExact(DateString, "yyyy-MM-dd"); get => (BusinessShareType?)FromTypeValue;
set => FromTypeValue = (int?)value;
}
[Column("to_mgnr")]
public int? ToMgNr { get; set; }
[Column("to_type")]
public int? ToTypeValue { get; set; }
[NotMapped]
public BusinessShareType? ToType {
get => (BusinessShareType?)ToTypeValue;
set => ToTypeValue = (int?)value;
}
[Column("date_notice")]
public required string DateNoticeString { get; set; }
[NotMapped]
public DateOnly DateNotice {
get => DateOnly.ParseExact(DateNoticeString, "yyyy-MM-dd");
set => value.ToString("yyyy-MM-dd"); set => value.ToString("yyyy-MM-dd");
} }
[Column("type")] [Column("date_effective")]
public required string Type { get; set; } public required string DateEffectiveString { get; set; }
[NotMapped]
public DateOnly DateEffective {
get => DateOnly.ParseExact(DateEffectiveString, "yyyy-MM-dd");
set => value.ToString("yyyy-MM-dd");
}
[Column("business_shares")] [Column("reason")]
public int BusinessShares { get; set; } public required string Reason { get; set; }
[NotMapped]
public string ReasonPretty => Reason switch {
REASON_PURCHASE => "Kauf",
REASON_CONVERT => "Änderung",
REASON_TRANSFER => "Übertragung",
REASON_PRESCRIPTION => "Vorschreibung",
REASON_EXCLUSION => "Ausschluss",
REASON_DECEASED => "Tod",
REASON_RETIREMENT => "Kündigung",
REASON_PAYOUT => "Auszahlung",
_ => Reason,
};
[Column("source")]
public required string Source { get; set; }
[Column("shares")]
public int Shares { get; set; }
[Column("signed_company")]
public bool CompanyHasSigned { get; set; }
[Column("signed_member")]
public bool MemberHasSigned { get; set; }
[Column("value_per_share")]
public long? ValuePerShareValue { get; set; }
[NotMapped]
public decimal? ValuePerShare {
get => ValuePerShareValue != null ? Utils.DecFromDb(ValuePerShareValue.Value, 2) : null;
set => ValuePerShareValue = value != null ? Utils.DecToDb(value.Value, 2) : null;
}
[NotMapped]
public decimal? TotalValue => Shares * ValuePerShare;
[Column("currency")]
public string? CurrencyCode { get; set; }
[Column("deduct_year")]
public int? DeductYear { get; set; }
[Column("comment")] [Column("comment")]
public string? Comment { get; set; } public string? Comment { get; set; }
[ForeignKey("MgNr")] public string FormatReasonPhrase(int mgnr) {
public virtual Member Member { get; private set; } = null!; if (Reason == REASON_TRANSFER) {
if (ToMgNr == mgnr) {
return $"{ReasonPretty} von {FromMember?.FullAdministrativeName ?? "Vorgänger"}" + (Comment != null ? $", {Comment}" : "");
} else if (FromMgNr == mgnr) {
return $"{ReasonPretty} an {ToMember?.FullAdministrativeName ?? "Nachfolger"}" + (Comment != null ? $", {Comment}" : "");
}
}
return ReasonPretty + (Comment != null ? $", {Comment}" : "");
}
[NotMapped]
public int? PoVMgNr { get; set; }
[NotMapped]
public string PoVReasonPhrase => FormatReasonPhrase(PoVMgNr ?? 0);
[ForeignKey("FromMgNr")]
public virtual Member? FromMember { get; private set; } = null!;
[ForeignKey("ToMgNr")]
public virtual Member? ToMember { get; private set; } = null!;
[ForeignKey("CurrencyCode")]
public virtual Currency Currency { get; private set; } = null!;
} }
} }
+25 -16
View File
@@ -25,11 +25,20 @@ namespace Elwig.Models.Entities {
[Column("vat_flatrate")] [Column("vat_flatrate")]
public double VatFlatrate { get; set; } public double VatFlatrate { get; set; }
[Column("min_kg_per_bs")] [Column("min_kg_per_share")]
public int MinKgPerBusinessShare { get; set; } public int? MinKgPerShare { get; set; }
[Column("max_kg_per_share")]
public int? MaxKgPerShare { get; set; }
[Column("max_kg_per_bs")] [Column("min_kg_per_share_red")]
public int MaxKgPerBusinessShare { get; set; } public int? MinKgPerShareRed { get; set; }
[Column("max_kg_per_share_red")]
public int? MaxKgPerShareRed { get; set; }
[Column("min_kg_per_share_white")]
public int? MinKgPerShareWhite { get; set; }
[Column("max_kg_per_share_white")]
public int? MaxKgPerShareWhite { get; set; }
[Column("penalty_per_kg")] [Column("penalty_per_kg")]
public long? PenaltyPerKgValue { get; set; } public long? PenaltyPerKgValue { get; set; }
@@ -55,23 +64,23 @@ namespace Elwig.Models.Entities {
set => PenaltyNoneValue = value != null ? DecToDb(value.Value) : null; set => PenaltyNoneValue = value != null ? DecToDb(value.Value) : null;
} }
[Column("penalty_per_bs_amount")] [Column("penalty_per_share_amount")]
public long? PenaltyPerBsAmountValue { get; set; } public long? PenaltyPerShareAmountValue { get; set; }
[NotMapped] [NotMapped]
public decimal? PenaltyPerBsAmount { public decimal? PenaltyPerShareAmount {
get => PenaltyPerBsAmountValue != null ? DecFromDb(PenaltyPerBsAmountValue.Value) : null; get => PenaltyPerShareAmountValue != null ? DecFromDb(PenaltyPerShareAmountValue.Value) : null;
set => PenaltyPerBsAmountValue = value != null ? DecToDb(value.Value) : null; set => PenaltyPerShareAmountValue = value != null ? DecToDb(value.Value) : null;
} }
[Column("penalty_per_bs_none")] [Column("penalty_per_share_none")]
public long? PenaltyPerBsNoneValue { get; set; } public long? PenaltyPerShareNoneValue { get; set; }
[NotMapped] [NotMapped]
public decimal? PenaltyPerBsNone { public decimal? PenaltyPerShareNone {
get => PenaltyPerBsNoneValue != null ? DecFromDb(PenaltyPerBsNoneValue.Value) : null; get => PenaltyPerShareNoneValue != null ? DecFromDb(PenaltyPerShareNoneValue.Value) : null;
set => PenaltyPerBsNoneValue = value != null ? DecToDb(value.Value) : null; set => PenaltyPerShareNoneValue = value != null ? DecToDb(value.Value) : null;
} }
[Column("bs_value")] [Column("share_value")]
public long? BusinessShareValueValue { get; set; } public long? BusinessShareValueValue { get; set; }
[NotMapped] [NotMapped]
public decimal? BusinessShareValue { public decimal? BusinessShareValue {
@@ -98,7 +107,7 @@ namespace Elwig.Models.Entities {
[Column("calc_mode")] [Column("calc_mode")]
public int CalcMode { get; set; } public int CalcMode { get; set; }
[NotMapped] [NotMapped]
public bool Billing_HonorGebunden { public bool Billing_HonorContracted {
get => (CalcMode & 0x1) != 0; get => (CalcMode & 0x1) != 0;
set => CalcMode = value ? CalcMode | 0x1 : CalcMode & ~0x1; set => CalcMode = value ? CalcMode | 0x1 : CalcMode & ~0x1;
} }
+3
View File
@@ -11,6 +11,9 @@ namespace Elwig.Models.Entities {
[Column("name")] [Column("name")]
public required string Name { get; set; } public required string Name { get; set; }
[Column("organic")]
public bool IsOrganic { get; set; }
[Column("description")] [Column("description")]
public string? Description { get; set; } public string? Description { get; set; }
@@ -92,6 +92,12 @@
{"$ref": "#/definitions/curve_data"} {"$ref": "#/definitions/curve_data"}
] ]
}, },
"ctrd": {
"anyOf": [
{"type": "number"},
{"$ref": "#/definitions/curve_data"}
]
},
"geb": { "geb": {
"anyOf": [ "anyOf": [
{"type": "number"}, {"type": "number"},
+326
View File
@@ -0,0 +1,326 @@
-- schema version 39 to 40
PRAGMA writable_schema = ON;
ALTER TABLE member RENAME COLUMN business_shares TO shares;
ALTER TABLE member ADD COLUMN shares_red INTEGER NOT NULL DEFAULT 0 CHECK (shares_red >= 0);
ALTER TABLE member ADD COLUMN shares_white INTEGER NOT NULL DEFAULT 0 CHECK (shares_white >= 0);
ALTER TABLE member ADD COLUMN shares_dormant INTEGER NOT NULL DEFAULT 0 CHECK (shares_dormant >= 0);
UPDATE sqlite_schema SET sql = REPLACE(sql, 'shares INTEGER NOT NULL DEFAULT 0', 'shares INTEGER NOT NULL DEFAULT 0 CHECK (shares >= 0)')
WHERE type = 'table' AND name = 'member';
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
UPDATE member
SET shares_dormant = shares + shares_red + shares_white,
shares = 0, shares_red = 0, shares_white = 0
WHERE NOT active;
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
CREATE TABLE member_history_new (
histnr INTEGER NOT NULL,
from_mgnr INTEGER,
from_type INTEGER,
to_mgnr INTEGER,
to_type INTEGER,
date_notice TEXT NOT NULL CHECK (date_notice REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$') DEFAULT CURRENT_DATE,
date_effective TEXT NOT NULL CHECK (date_effective REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$') DEFAULT CURRENT_DATE,
reason TEXT NOT NULL CHECK (reason REGEXP '^[a-z_]+$'),
source TEXT NOT NULL CHECK (source REGEXP '^[a-z_]+$'),
shares INTEGER NOT NULL CHECK (shares > 0),
signed INTEGER NOT NULL CHECK (signed IN (TRUE, FALSE)) DEFAULT FALSE,
value_per_share INTEGER DEFAULT NULL,
currency TEXT DEFAULT NULL,
deduct_year INTEGER DEFAULT NULL,
comment TEXT DEFAULT NULL,
CONSTRAINT pk_member_history PRIMARY KEY (histnr),
CONSTRAINT fk_member_history_member_from FOREIGN KEY (from_mgnr) REFERENCES member (mgnr)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_member_history_member_to FOREIGN KEY (to_mgnr) REFERENCES member (mgnr)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_member_history_currency FOREIGN KEY (currency) REFERENCES currency (code)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT c_member_history CHECK ((from_mgnr IS NOT NULL OR to_mgnr IS NOT NULL) AND
((from_mgnr IS NULL AND from_type IS NULL) OR (from_mgnr IS NOT NULL AND from_type IS NOT NULL)) AND
((to_mgnr IS NULL AND to_type IS NULL) OR (to_mgnr IS NOT NULL AND to_type IS NOT NULL)))
) STRICT;
INSERT INTO member_history_new (histnr, from_mgnr, from_type, to_mgnr, to_type, date_notice, date_effective, reason, source, shares, value_per_share, currency, deduct_year, comment)
SELECT ROW_NUMBER() OVER(ORDER BY h.date, h.mgnr), NULL, NULL, h.mgnr, 1, h.date, h.date,
IIF(h.type = 'auto', 'prescription', h.type), 'elwig', h.business_shares, s.bs_value / POW(10, s.precision - 2), s.currency,
IIF(h.type = 'auto', s.year, NULL), 'automatische Nachzeichnung'
FROM member_history h
JOIN season s ON s.year = SUBSTR(h.date, 1, 4);
PRAGMA foreign_keys = OFF;
DROP TABLE member_history;
ALTER TABLE member_history_new RENAME TO member_history;
PRAGMA foreign_keys = ON;
CREATE TRIGGER t_member_history_i_member
AFTER INSERT ON member_history FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS') = 1
BEGIN
UPDATE member SET shares = shares - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 1;
UPDATE member SET shares_red = shares_red - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 2;
UPDATE member SET shares_white = shares_white - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 9;
UPDATE member SET shares = shares + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 1;
UPDATE member SET shares_red = shares_red + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 2;
UPDATE member SET shares_white = shares_white + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 9;
END;
CREATE TRIGGER t_member_history_u_member
AFTER UPDATE OF from_mgnr, from_type, to_mgnr, to_type, shares ON member_history FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS') = 1
BEGIN
UPDATE member SET shares = shares + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 1;
UPDATE member SET shares_red = shares_red + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 2;
UPDATE member SET shares_white = shares_white + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 9;
UPDATE member SET shares = shares - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 1;
UPDATE member SET shares_red = shares_red - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 2;
UPDATE member SET shares_white = shares_white - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 9;
UPDATE member SET shares = shares - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 1;
UPDATE member SET shares_red = shares_red - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 2;
UPDATE member SET shares_white = shares_white - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant - NEW.shares WHERE mgnr = NEW.from_mgnr AND NEW.from_type = 9;
UPDATE member SET shares = shares + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 1;
UPDATE member SET shares_red = shares_red + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 2;
UPDATE member SET shares_white = shares_white + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant + NEW.shares WHERE mgnr = NEW.to_mgnr AND NEW.to_type = 9;
END;
CREATE TRIGGER t_member_history_d_member
AFTER DELETE ON member_history FOR EACH ROW
WHEN (SELECT value FROM client_parameter WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS') = 1
BEGIN
UPDATE member SET shares = shares + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 1;
UPDATE member SET shares_red = shares_red + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 2;
UPDATE member SET shares_white = shares_white + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 3;
UPDATE member SET shares_dormant = shares_dormant + OLD.shares WHERE mgnr = OLD.from_mgnr AND OLD.from_type = 9;
UPDATE member SET shares = shares - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 1;
UPDATE member SET shares_red = shares_red - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 2;
UPDATE member SET shares_white = shares_white - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 3;
UPDATE member SET shares_dormant = shares_dormant - OLD.shares WHERE mgnr = OLD.to_mgnr AND OLD.to_type = 9;
END;
INSERT INTO client_parameter (param, value) VALUES ('ENABLE_MEMBER_HISTORY_TRIGGERS', '1');
CREATE TABLE season_new (
year INTEGER NOT NULL CHECK (year >= 1000 AND year <= 9999),
currency TEXT NOT NULL,
precision INTEGER NOT NULL DEFAULT 4,
max_kg_per_ha INTEGER NOT NULL DEFAULT 10000,
vat_normal REAL NOT NULL DEFAULT 0.10,
vat_flatrate REAL NOT NULL DEFAULT 0.13,
min_kg_per_share INTEGER DEFAULT NULL,
max_kg_per_share INTEGER DEFAULT NULL,
min_kg_per_share_red INTEGER DEFAULT NULL,
max_kg_per_share_red INTEGER DEFAULT NULL,
min_kg_per_share_white INTEGER DEFAULT NULL,
max_kg_per_share_white INTEGER DEFAULT NULL,
penalty_per_kg INTEGER DEFAULT NULL,
penalty_amount INTEGER DEFAULT NULL,
penalty_none INTEGER DEFAULT NULL,
penalty_per_share_amount INTEGER DEFAULT NULL,
penalty_per_share_none INTEGER DEFAULT NULL,
share_value INTEGER,
start_date TEXT CHECK (start_date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$'),
end_date TEXT CHECK (end_date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$'),
calc_mode INTEGER NOT NULL DEFAULT 0,
CONSTRAINT pk_season PRIMARY KEY (year),
CONSTRAINT fk_season_currency FOREIGN KEY (currency) REFERENCES currency (code)
ON UPDATE CASCADE
ON DELETE RESTRICT
) STRICT;
INSERT INTO season_new (year, currency, precision, max_kg_per_ha, vat_normal, vat_flatrate, min_kg_per_share, max_kg_per_share, penalty_per_kg, penalty_amount, penalty_none, penalty_per_share_amount, penalty_per_share_none, share_value, start_date, end_date, calc_mode)
SELECT year, currency, precision, max_kg_per_ha, vat_normal, vat_flatrate, min_kg_per_bs, max_kg_per_bs, penalty_per_kg, penalty_amount, penalty_none, penalty_per_bs_amount, penalty_per_bs_none, bs_value, start_date, end_date, calc_mode
FROM season;
PRAGMA foreign_keys = OFF;
DROP TABLE season;
ALTER TABLE season_new RENAME TO season;
PRAGMA foreign_keys = ON;
DROP VIEW v_delivery;
CREATE VIEW v_delivery AS
SELECT p.year, p.did, p.dpnr,
d.date, d.time, d.zwstid, d.lnr, d.lsnr,
m.mgnr, m.name, m.given_name,
v.sortid, v.type, a.attrid, p.cultid,
p.weight, p.kmw, ROUND(p.kmw * (4.54 + 0.022 * p.kmw), 0) AS oe, p.qualid,
p.hkid, p.kgnr, p.rdnr,
p.net_weight, p.gebunden,
p.qualid IN (SELECT l.qualid FROM wine_quality_level l WHERE NOT l.predicate AND (p.kmw >= l.min_kmw OR l.min_kmw IS NULL) ORDER BY l.min_kmw DESC LIMIT 1,100) AS abgewertet,
p.qualid NOT IN ('WEI', 'RSW', 'LDW') AS min_quw,
IIF(a.strict, COALESCE(a.fill_lower, 0), 0) AS attribute_prio,
GROUP_CONCAT(o.modid) AS modifiers,
d.comment, p.comment AS part_comment
FROM delivery_part p
JOIN delivery d ON (d.year, d.did) = (p.year, p.did)
JOIN member m ON m.mgnr = d.mgnr
LEFT JOIN wine_variety v ON v.sortid = p.sortid
LEFT JOIN wine_attribute a ON a.attrid = p.attrid
LEFT JOIN delivery_part_modifier o ON (o.year, o.did, o.dpnr) = (p.year, p.did, p.dpnr)
GROUP BY p.year, p.did, p.dpnr
ORDER BY p.year, p.did, p.dpnr, o.modid;
DROP VIEW v_stat_season;
CREATE VIEW v_stat_season AS
SELECT year,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year
ORDER BY year;
DROP VIEW v_stat_sort;
CREATE VIEW v_stat_variety AS
SELECT year, sortid,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year, sortid
ORDER BY year, sortid;
DROP VIEW v_stat_attr;
CREATE VIEW v_stat_attr AS
SELECT year, attrid,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year, attrid
ORDER BY year, attrid;
DROP VIEW v_stat_sort_attr;
CREATE VIEW v_stat_variety_attr AS
SELECT year, sortid, attrid,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen,
COUNT(DISTINCT mgnr) AS mitglieder
FROM v_delivery
GROUP BY year, sortid, attrid
ORDER BY year, sortid, attrid;
DROP VIEW v_stat_member;
CREATE VIEW v_stat_member AS
SELECT year, mgnr,
SUM(weight) AS weight_total,
SUM(IIF(type = 'R', weight, 0)) AS weight_red,
SUM(IIF(type = 'W', weight, 0)) AS weight_white,
ROUND(SUM(kmw * weight) / SUM(weight), 2) AS kmw,
ROUND(SUM(oe * weight) / SUM(weight), 1) AS oe,
COUNT(DISTINCT did) AS lieferungen
FROM v_delivery
GROUP BY year, mgnr
ORDER BY year, mgnr;
CREATE VIEW v_member_history AS
SELECT m.mgnr, s.year,
m.shares + SUM(IIF(h1.from_type = 1, h1.shares, 0)) - SUM(IIF(h2.to_type = 1, h2.shares, 0)) AS shares,
m.shares_red + SUM(IIF(h1.from_type = 2, h1.shares, 0)) - SUM(IIF(h2.to_type = 2, h2.shares, 0)) AS shares_red,
m.shares_white + SUM(IIF(h1.from_type = 3, h1.shares, 0)) - SUM(IIF(h2.to_type = 3, h2.shares, 0)) AS shares_white,
m.shares_dormant + SUM(IIF(h1.from_type = 9, h1.shares, 0)) - SUM(IIF(h2.to_type = 9, h2.shares, 0)) AS shares_dormant
FROM member m, v_virtual_season s
LEFT JOIN member_history h1 ON (SUBSTR(h1.date_notice, 1, 4) + 0) > s.year AND h1.from_mgnr = m.mgnr
LEFT JOIN member_history h2 ON (SUBSTR(h2.date_notice, 1, 4) + 0) > s.year AND h2.to_mgnr = m.mgnr
GROUP BY m.mgnr, s.year
ORDER BY m.mgnr, s.year;
DROP VIEW v_total_under_delivery;
CREATE VIEW v_total_under_delivery AS
SELECT s.year, m.mgnr,
h.shares,
h.shares * COALESCE(s.min_kg_per_share, 0) AS min_kg,
h.shares * COALESCE(s.max_kg_per_share, 0) AS max_kg,
COALESCE(d.weight_total, 0) AS weight_total,
IIF(COALESCE(d.weight_total, 0) < h.shares * COALESCE(s.min_kg_per_share, 0),
COALESCE(d.weight_total, 0) - h.shares * COALESCE(s.min_kg_per_share, 0),
IIF(COALESCE(d.weight_total, 0) > h.shares * COALESCE(s.max_kg_per_share, 0),
COALESCE(d.weight_total, 0) - h.shares * COALESCE(s.max_kg_per_share, 0),
0)) AS diff,
h.shares_red,
h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0) AS min_kg_red,
h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0) AS max_kg_red,
COALESCE(d.weight_red, 0) AS weight_red,
IIF(COALESCE(d.weight_red, 0) < h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0),
COALESCE(d.weight_red, 0) - h.shares_red * COALESCE(s.min_kg_per_share_red, s.min_kg_per_share, 0),
IIF(COALESCE(d.weight_red, 0) > h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0),
COALESCE(d.weight_red, 0) - h.shares_red * COALESCE(s.max_kg_per_share_red, s.max_kg_per_share, 0),
0)) AS diff_red,
h.shares_white,
h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0) AS min_kg_white,
h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0) AS max_kg_white,
COALESCE(d.weight_white, 0) AS weight_white,
IIF(COALESCE(d.weight_white, 0) < h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0),
COALESCE(d.weight_white, 0) - h.shares_white * COALESCE(s.min_kg_per_share_white, s.min_kg_per_share, 0),
IIF(COALESCE(d.weight_white, 0) > h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0),
COALESCE(d.weight_white, 0) - h.shares_white * COALESCE(s.max_kg_per_share_white, s.max_kg_per_share, 0),
0)) AS diff_white
FROM member m, season s
LEFT JOIN v_member_history h ON (h.year, h.mgnr) = (s.year, m.mgnr)
LEFT JOIN v_stat_member d ON (d.year, d.mgnr) = (s.year, m.mgnr)
ORDER BY s.year, m.mgnr;
DROP VIEW v_penalty_business_shares;
CREATE VIEW v_penalty_business_shares AS
SELECT u.year, u.mgnr,
SUM(IIF(u.weight_total = 0, COALESCE(-s.penalty_none, 0) + COALESCE(-(u.shares + u.shares_red + u.shares_white) * s.penalty_per_share_none, 0), 0) +
IIF(u.diff < 0 OR u.diff_red < 0 OR u.diff_white < 0, COALESCE(-s.penalty_amount, 0), 0) +
COALESCE((u.diff + u.diff_red + u.diff_white) * s.penalty_per_kg, 0) +
COALESCE(CEIL(CAST(u.diff AS REAL) / s.min_kg_per_share) * s.penalty_per_share_amount, 0) +
COALESCE(CEIL(CAST(u.diff_red AS REAL) / s.min_kg_per_share_red) * s.penalty_per_share_amount, 0) +
COALESCE(CEIL(CAST(u.diff_white AS REAL) / s.min_kg_per_share_white) * s.penalty_per_share_amount, 0)
) AS total_penalty
FROM v_total_under_delivery u
JOIN season s ON u.year = s.year
JOIN member m ON m.mgnr = u.mgnr
WHERE m.active
GROUP BY u.year, u.mgnr
HAVING total_penalty < 0
ORDER BY u.year, u.mgnr;
DROP VIEW v_auto_business_shares;
CREATE VIEW v_auto_business_shares AS
SELECT (SUBSTR(h.date_notice, 1, 4) + 0) AS year,
h.to_mgnr AS mgnr,
SUM(h.shares) AS shares,
SUM(h.shares * COALESCE(h.value_per_share, 0)) AS total_amount
FROM member_history h
WHERE h.reason = 'prescription' AND h.source = 'elwig'
GROUP BY year, h.to_mgnr
ORDER BY year, h.to_mgnr;
PRAGMA writable_schema = OFF;
+37
View File
@@ -0,0 +1,37 @@
-- schema version 40 to 41
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = REPLACE(sql, 'scale_id ', 'scale_terminal')
WHERE type = 'table' AND name = 'delivery_part';
DROP VIEW v_bki_delivery;
DROP VIEW v_bki_member;
CREATE VIEW v_bki_member AS
SELECT s.year, m.mgnr, m.lfbis_nr, m.name,
(COALESCE(m.prefix || ' ', '') || m.given_name || COALESCE(' ' || m.middle_names, '') || COALESCE(' ' || m.suffix, '')) AS other_names,
a.name AS billing_name, COALESCE(a.address, m.address) AS address,
COALESCE(a.country, m.country) AS country, COALESCE(a.postal_dest, m.postal_dest) AS postal_dest,
m.volllieferant AS vollablieferer,
SUM(COALESCE(IIF((c.year_from IS NULL OR c.year_from <= s.year) AND (c.year_to IS NULL OR c.year_to >= s.year), c.area, NULL), 0)) AS area
FROM season s, member m
LEFT JOIN member_billing_address a ON a.mgnr = m.mgnr
LEFT JOIN area_commitment c ON c.mgnr = m.mgnr
GROUP BY s.year, m.mgnr;
CREATE VIEW v_bki_delivery AS
SELECT m.lfbis_nr, m.name, m.other_names, m.billing_name,
m.address, plz.plz, IIF(INSTR(o.name, ',') = 0, o.name, SUBSTR(o.name, 1, INSTR(o.name, ',') - 1)) AS ort,
d.date, d.weight, v.type, v.sortid, d.qualid, d.year, d.hkid, d.kmw, d.oe,
m.vollablieferer, m.area
FROM v_delivery d
JOIN v_bki_member m ON (m.year, m.mgnr) = (d.year, d.mgnr)
JOIN postal_dest pd ON (pd.country, pd.id) = (m.country, m.postal_dest)
LEFT JOIN AT_plz_dest ap ON (ap.country, ap.id) = (pd.country, pd.id)
LEFT JOIN AT_plz plz ON plz.plz = ap.plz
LEFT JOIN AT_ort o ON o.okz = ap.okz
JOIN wine_variety v ON v.sortid = d.sortid
ORDER BY d.date, d.time;
PRAGMA writable_schema = OFF;
+65
View File
@@ -0,0 +1,65 @@
-- schema version 41 to 42
ALTER TABLE member ADD COLUMN ooc_id TEXT CHECK (ooc_id REGEXP '^[0-9]{3}-[0-9]{7}$') DEFAULT NULL;
ALTER TABLE member ADD COLUMN org_auth_code TEXT CHECK (org_auth_code REGEXP '^[A-Z]{2}-[A-ZÖØ]{3}-[0-9]{2,3}(-[A-Z]{2})?$') DEFAULT NULL;
ALTER TABLE wine_cultivation ADD COLUMN organic INTEGER NOT NULL CHECK (organic IN (TRUE, FALSE)) DEFAULT FALSE;
ALTER TABLE member_history RENAME COLUMN signed TO signed_member;
ALTER TABLE member_history ADD COLUMN signed_company INTEGER NOT NULL CHECK (signed_company IN (TRUE, FALSE)) DEFAULT FALSE;
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = REPLACE(REPLACE(REPLACE(sql, 'funktionär', 'officer '), 'buchführend', 'accounting '), 'volllieferant', 'full_delivery')
WHERE type = 'table' AND name = 'member';
UPDATE sqlite_schema SET sql = REPLACE(sql, 'gebunden', 'contracted')
WHERE type = 'table' AND name = 'delivery_part';
UPDATE sqlite_schema SET sql = REPLACE(sql, 'gebunden', 'contracted')
WHERE type = 'view' AND name = 'v_delivery';
UPDATE sqlite_schema SET sql = REPLACE(sql, 'volllieferant', 'full_delivery')
WHERE type = 'view' AND name = 'v_bki_member';
UPDATE sqlite_schema SET sql = REPLACE(sql, ' geb', ' contracted')
WHERE type = 'view' AND name = 'v_stat_total';
UPDATE sqlite_schema SET sql = REPLACE(sql, 'gebunden', 'contracted')
WHERE type = 'view' AND (name = 'v_payment_bucket_strict' OR name = 'v_payment_bucket' OR name = 'v_under_delivery_bucket');
PRAGMA schema_version = 4101;
PRAGMA writable_schema = OFF;
UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS';
UPDATE member SET org_auth_code = UPPER(SUBSTR(a.name, -11, 10))
FROM member m JOIN member_billing_address a ON a.mgnr = m.mgnr
WHERE m.mgnr = member.mgnr AND a.name LIKE '%AT-BIO-___)';
UPDATE member SET org_auth_code = UPPER(suffix) WHERE suffix LIKE 'AT-BIO-___';
UPDATE wine_cultivation SET organic = TRUE WHERE name LIKE '%bio%' OR name LIKE '%org%' OR description LIKE '%AT-BIO-%';
UPDATE wine_cultivation SET description = NULL WHERE description LIKE '%AT-BIO-%';
UPDATE wine_cultivation SET description = 'Biologische Produktion' WHERE organic;
UPDATE delivery_schedule SET cultid = (SELECT cultid FROM wine_cultivation WHERE organic LIMIT 1) WHERE description LIKE '%Bio%';
UPDATE client_parameter SET param = 'CLIENT_ORGANIC_AUTHORITYCODE' WHERE param = 'CLIENT_ORGANIC_AUTHORITY';
UPDATE member_telephone_number SET comment = NULL WHERE comment = '';
UPDATE member_email_address SET comment = NULL WHERE comment = '';
UPDATE member SET address = CONCAT('Untere Weißgerberstraße ', SUBSTR(address, 21)) WHERE address LIKE 'U. Weißgasse Straße %';
UPDATE member SET address = CONCAT('Edgar-Penzig-Franz-Straße ', SUBSTR(address, 28)) WHERE address LIKE 'Edgar-Penzing-Franz-Straße %';
UPDATE member
SET address = NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member.postal_dest))
WHERE address != NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member.postal_dest));
UPDATE member_billing_address
SET address = NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member_billing_address.postal_dest))
WHERE address != NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member_billing_address.postal_dest));
UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS';
@@ -6,12 +6,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows;
namespace Elwig.Services { namespace Elwig.Services {
public static class AreaComService { public static class AreaCmtService {
public static async Task InitInputs(this AreaComAdminViewModel vm) { public static async Task InitInputs(this AreaCmtAdminViewModel vm) {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
vm.FbNr = await ctx.NextFbNr(); vm.FbNr = await ctx.NextFbNr();
vm.MgNr = vm.FilterMember.MgNr; vm.MgNr = vm.FilterMember.MgNr;
@@ -19,11 +18,11 @@ namespace Elwig.Services {
vm.WineCult = null; vm.WineCult = null;
} }
public static void ClearInputs(this AreaComAdminViewModel vm) { public static void ClearInputs(this AreaCmtAdminViewModel vm) {
vm.Period = null; vm.Period = null;
} }
public static void FillInputs(this AreaComAdminViewModel vm, AreaComContract c) { public static void FillInputs(this AreaCmtAdminViewModel vm, AreaCmtContract c) {
vm.FbNr = c.FbNr; vm.FbNr = c.FbNr;
vm.Period = c.YearTo == null ? $"ab {c.YearFrom}" : $"{c.YearFrom}\u2013{c.YearTo}"; vm.Period = c.YearTo == null ? $"ab {c.YearFrom}" : $"{c.YearFrom}\u2013{c.YearTo}";
vm.Comment = c.Comment; vm.Comment = c.Comment;
@@ -31,21 +30,23 @@ namespace Elwig.Services {
vm.Rd = ControlUtils.GetItemFromSourceWithPk(vm.RdSource, c.KgNr, c.RdNr) as WbRd; vm.Rd = ControlUtils.GetItemFromSourceWithPk(vm.RdSource, c.KgNr, c.RdNr) as WbRd;
} }
public static void FillInputs(this AreaComAdminViewModel vm, AreaCom a) { public static void FillInputs(this AreaCmtAdminViewModel vm, AreaCmt a) {
vm.MgNr = a.MgNr; vm.MgNr = a.MgNr;
vm.YearFrom = a.YearFrom; vm.YearFrom = a.YearFrom;
vm.YearTo = a.YearTo; vm.YearTo = a.YearTo;
vm.AreaComType = ControlUtils.GetItemFromSourceWithPk(vm.AreaComTypeSource, a.VtrgId) as AreaComType; vm.AreaCmtType = ControlUtils.GetItemFromSourceWithPk(vm.AreaCmtTypeSource, a.VtrgId) as AreaCmtType;
vm.WineCult = ControlUtils.GetItemFromSourceWithPk(vm.WineCultSource, a.CultId) as WineCult; vm.WineCult = ControlUtils.GetItemFromSourceWithPk(vm.WineCultSource, a.CultId) as WineCult;
vm.GstNr = a.GstNr; vm.GstNr = a.GstNr;
vm.Area = a.Area; vm.Area = a.Area;
} }
public static async Task<(List<string>, IQueryable<AreaComContract>, IQueryable<AreaCom>, List<string>)> GetFilters(this AreaComAdminViewModel vm, AppDbContext ctx) { public static async Task<(List<string>, IQueryable<AreaCmtContract>, IQueryable<AreaCmt>, List<string>)> GetFilters(this AreaCmtAdminViewModel vm, AppDbContext ctx) {
List<string> filterNames = []; List<string> filterNames = [];
IQueryable<AreaCom> areaComQuery = ctx.AreaCommitments.Where(a => a.MgNr == vm.FilterMember.MgNr).OrderBy(a => a.FbNr); IQueryable<AreaCmt> areaCmtQuery = ctx.AreaCommitments.Where(a => a.MgNr == vm.FilterMember.MgNr).OrderBy(a => a.FbNr);
filterNames.Add(vm.FilterMember.AdministrativeName);
if (vm.FilterSeason is int season) { if (vm.FilterSeason is int season) {
areaComQuery = Utils.ActiveAreaCommitments(areaComQuery, season); areaCmtQuery = Utils.ActiveAreaCommitments(areaCmtQuery, season);
filterNames.Add($"laufend {season}"); filterNames.Add($"laufend {season}");
} }
@@ -95,37 +96,37 @@ namespace Elwig.Services {
} }
} }
if (filterVar.Count > 0) areaComQuery = areaComQuery.Where(a => filterVar.Contains(a.AreaComType.WineVar.SortId)); if (filterVar.Count > 0) areaCmtQuery = areaCmtQuery.Where(a => filterVar.Contains(a.AreaCmtType.WineVar.SortId));
if (filterNotVar.Count > 0) areaComQuery = areaComQuery.Where(a => !filterNotVar.Contains(a.AreaComType.WineVar.SortId)); if (filterNotVar.Count > 0) areaCmtQuery = areaCmtQuery.Where(a => !filterNotVar.Contains(a.AreaCmtType.WineVar.SortId));
if (filterAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId != null && filterAttr.Contains(a.AreaComType.WineAttr.AttrId)); if (filterAttr.Count > 0) areaCmtQuery = areaCmtQuery.Where(a => a.AreaCmtType.WineAttr!.AttrId != null && filterAttr.Contains(a.AreaCmtType.WineAttr.AttrId));
if (filterNotAttr.Count > 0) areaComQuery = areaComQuery.Where(a => a.AreaComType.WineAttr!.AttrId == null || !filterNotAttr.Contains(a.AreaComType.WineAttr.AttrId)); if (filterNotAttr.Count > 0) areaCmtQuery = areaCmtQuery.Where(a => a.AreaCmtType.WineAttr!.AttrId == null || !filterNotAttr.Contains(a.AreaCmtType.WineAttr.AttrId));
} }
IQueryable<AreaComContract> contracts = areaComQuery IQueryable<AreaCmtContract> contracts = areaCmtQuery
.Select(c => c.Contract).Distinct() .Select(c => c.Contract).Distinct()
.OrderBy(c => c.FbNr); .OrderBy(c => c.FbNr);
return (filterNames, contracts, areaComQuery, filter); return (filterNames, contracts, areaCmtQuery, filter);
} }
public static async Task<(int FbNr, int RevNr)> UpdateAreaCommitment(this AreaComAdminViewModel vm, int? oldFbNr, int? revNr) { public static async Task<(int FbNr, int RevNr)> UpdateAreaCommitment(this AreaCmtAdminViewModel vm, int? oldFbNr, int? revNr) {
int newFbNr = vm.FbNr!.Value; int newFbNr = vm.FbNr!.Value;
return await Task.Run(async () => { return await Task.Run(async () => {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var c = new AreaComContract { var c = new AreaCmtContract {
FbNr = oldFbNr ?? newFbNr, FbNr = oldFbNr ?? newFbNr,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment, Comment = string.IsNullOrWhiteSpace(vm.Comment) ? null : vm.Comment,
KgNr = vm.Kg!.KgNr, KgNr = vm.Kg!.KgNr,
RdNr = vm.Rd?.RdNr, RdNr = vm.Rd?.RdNr,
}; };
var a = new AreaCom { var a = new AreaCmt {
FbNr = oldFbNr ?? newFbNr, FbNr = oldFbNr ?? newFbNr,
RevNr = revNr ?? await ctx.NextRevNr(oldFbNr ?? newFbNr), RevNr = revNr ?? await ctx.NextRevNr(oldFbNr ?? newFbNr),
MgNr = vm.MgNr!.Value, MgNr = vm.MgNr!.Value,
YearFrom = vm.YearFrom, YearFrom = vm.YearFrom,
YearTo = vm.YearTo, YearTo = vm.YearTo,
VtrgId = vm.AreaComType!.VtrgId, VtrgId = vm.AreaCmtType!.VtrgId,
CultId = vm.WineCult?.CultId, CultId = vm.WineCult?.CultId,
GstNr = vm.GstNr?.Trim() ?? "-", GstNr = vm.GstNr?.Trim() ?? "-",
Area = vm.Area!.Value, Area = vm.Area!.Value,
@@ -158,74 +159,62 @@ namespace Elwig.Services {
}); });
} }
private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
var tb = new TextBlock() {
Text = text,
TextAlignment = alignRight ? TextAlignment.Right : alignCenter ? TextAlignment.Center : TextAlignment.Left,
Margin = new(0, 12 * row, 0, 0),
FontWeight = bold ? FontWeights.Bold : FontWeights.Normal,
};
tb.SetValue(Grid.ColumnProperty, col);
tb.SetValue(Grid.ColumnSpanProperty, colSpan);
grid.Children.Add(tb);
}
private static void AddToolTipRow(Grid grid, int row, string? h1, string? h2, int area, int? min, int? max) { private static void AddToolTipRow(Grid grid, int row, string? h1, string? h2, int area, int? min, int? max) {
var bold = h2 == null; var bold = h2 == null;
if (h1 != null) AddToolTipCell(grid, h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold); if (h1 != null) grid.AddToolTipCell(h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold);
if (h2 != null) AddToolTipCell(grid, h2 + ":", row, 1, 1, bold); if (h2 != null) grid.AddToolTipCell(h2 + ":", row, 1, bold: bold);
AddToolTipCell(grid, $"{area:N0} m²", row, 2, 1, bold, true); grid.AddToolTipCell($"{area:N0} m²", row, 2, bold: bold, alignRight: true);
AddToolTipCell(grid, min == null ? "" : $"{min:N0} kg", row, 3, 1, bold, true); grid.AddToolTipCell(min == null ? "" : $"{min:N0} kg", row, 3, bold: bold, alignRight: true);
AddToolTipCell(grid, max == null ? "" : $"{max:N0} kg", row, 4, 1, bold, true); grid.AddToolTipCell(max == null ? "" : $"{max:N0} kg", row, 4, bold: bold, alignRight: true);
} }
public static async Task<(string, (string?, string?, int, int?, int?)[])> GenerateToolTipData(IQueryable<AreaCom> areaComs, int maxKgPerHa) { public static async Task<(string, (string?, string?, int, int?, int?)[])> GenerateToolTipData(IQueryable<AreaCmt> areaCmts, int maxKgPerHa) {
var grid = new List<(string?, string?, int, int?, int?)>(); var grid = new List<(string?, string?, int, int?, int?)>();
var text = "-"; var text = "-";
var area = await areaComs.SumAsync(p => p.Area); var area = await areaCmts.SumAsync(p => p.Area);
text = $"{area:N0} m²"; text = $"{area:N0} m²";
grid.Add(("Geb. Fläche", null, area, null, null)); grid.Add(("Geb. Fläche", null, area, null, null));
if (await areaComs.AnyAsync()) { if (await areaCmts.AnyAsync()) {
var attrGroups = await areaComs var attrGroups = await areaCmts
.Where(c => c.AreaComType.WineAttr != null) .Where(c => c.AreaCmtType.WineAttr != null)
.GroupBy(c => c.AreaComType.WineAttr!.Name) .GroupBy(c => c.AreaCmtType.WineAttr!.Name)
.Select(g => new { .Select(g => new {
Attr = g.Key, Attr = g.Key,
Area = g.Sum(c => c.Area), Area = g.Sum(c => c.Area),
Min = g.Sum(c => c.Area * (c.AreaComType.MinKgPerHa ?? 0) / 10_000), Min = g.Sum(c => c.Area * (c.AreaCmtType.MinKgPerHa ?? 0) / 10_000),
Max = g.Sum(c => c.Area * (c.AreaComType.WineAttr!.MaxKgPerHa ?? maxKgPerHa) / 10_000), Max = g.Sum(c => c.Area * (c.AreaCmtType.WineAttr!.MaxKgPerHa ?? maxKgPerHa) / 10_000),
}) })
.OrderByDescending(g => g.Area) .OrderByDescending(g => g.Area)
.ThenBy(g => g.Attr) .ThenBy(g => g.Attr)
.ToListAsync(); .ToListAsync();
var groups = await areaComs var groups = await areaCmts
.Where(c => c.AreaComType.WineAttr != null) .Where(c => c.AreaCmtType.WineAttr != null)
.GroupBy(c => new { .GroupBy(c => new {
Attr = c.AreaComType.WineAttr!.Name, Attr = c.AreaCmtType.WineAttr!.Name,
c.AreaComType.SortId, c.AreaCmtType.SortId,
}) })
.Select(g => new { .Select(g => new {
g.Key.Attr, g.Key.Attr,
g.Key.SortId, g.Key.SortId,
Area = g.Sum(c => c.Area), Area = g.Sum(c => c.Area),
Min = g.Sum(c => c.Area * (c.AreaComType.MinKgPerHa ?? 0) / 10_000), Min = g.Sum(c => c.Area * (c.AreaCmtType.MinKgPerHa ?? 0) / 10_000),
Max = g.Sum(c => c.Area * (c.AreaComType.WineAttr!.MaxKgPerHa ?? maxKgPerHa) / 10_000), Max = g.Sum(c => c.Area * (c.AreaCmtType.WineAttr!.MaxKgPerHa ?? maxKgPerHa) / 10_000),
}) })
.OrderByDescending(g => g.Area) .OrderByDescending(g => g.Area)
.ThenBy(g => g.Attr) .ThenBy(g => g.Attr)
.ThenBy(g => g.SortId) .ThenBy(g => g.SortId)
.ToListAsync(); .ToListAsync();
var noAttr = await areaComs var noAttr = await areaCmts
.Where(c => c.AreaComType.WineAttr == null || !c.AreaComType.WineAttr.IsStrict) .Where(c => c.AreaCmtType.WineAttr == null || !c.AreaCmtType.WineAttr.IsStrict)
.GroupBy(c => c.AreaComType.SortId) .GroupBy(c => c.AreaCmtType.SortId)
.Select(g => new { .Select(g => new {
SortId = g.Key, SortId = g.Key,
Area = g.Sum(c => c.Area), Area = g.Sum(c => c.Area),
Min = g.Sum(c => c.Area * (c.AreaComType.MinKgPerHa ?? 0) / 10_000), Min = g.Sum(c => c.Area * (c.AreaCmtType.MinKgPerHa ?? 0) / 10_000),
Max = g.Sum(c => c.Area * (c.AreaComType.WineAttr!.MaxKgPerHa ?? maxKgPerHa) / 10_000), Max = g.Sum(c => c.Area * (c.AreaCmtType.WineAttr!.MaxKgPerHa ?? maxKgPerHa) / 10_000),
}) })
.OrderByDescending(g => g.Area) .OrderByDescending(g => g.Area)
.ThenBy(g => g.SortId) .ThenBy(g => g.SortId)
@@ -255,8 +244,8 @@ namespace Elwig.Services {
grid.ColumnDefinitions.Add(new() { Width = new(80) }); grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(80) }); grid.ColumnDefinitions.Add(new() { Width = new(80) });
grid.ColumnDefinitions.Add(new() { Width = new(80) }); grid.ColumnDefinitions.Add(new() { Width = new(80) });
AddToolTipCell(grid, "Lieferpflicht", 0, 3, 1, false, false, true); grid.AddToolTipCell("Lieferpflicht", 0, 3, alignCenter: true);
AddToolTipCell(grid, "Lieferrecht", 0, 4, 1, false, false, true); grid.AddToolTipCell("Lieferrecht", 0, 4, 1, alignCenter: true);
int rowNum = 1; int rowNum = 1;
foreach (var row in data) { foreach (var row in data) {
if (rowNum == 2 || (rowNum != 1 && row.Item1 != null)) rowNum++; if (rowNum == 2 || (rowNum != 1 && row.Item1 != null)) rowNum++;
@@ -265,7 +254,7 @@ namespace Elwig.Services {
return grid; return grid;
} }
public static async Task DeleteAreaCom(int fbnr) { public static async Task DeleteAreaCmt(int fbnr) {
await Task.Run(async () => { await Task.Run(async () => {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var l = (await ctx.AreaCommitmentContracts.FindAsync(fbnr))!; var l = (await ctx.AreaCommitmentContracts.FindAsync(fbnr))!;
@@ -274,7 +263,7 @@ namespace Elwig.Services {
}); });
} }
public static async Task DeleteAreaComRevision(int fbnr, int revnr) { public static async Task DeleteAreaCmtRevision(int fbnr, int revnr) {
await Task.Run(async () => { await Task.Run(async () => {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
var l = (await ctx.AreaCommitments.FindAsync(fbnr, revnr))!; var l = (await ctx.AreaCommitments.FindAsync(fbnr, revnr))!;
+133
View File
@@ -0,0 +1,133 @@
using Elwig.Helpers;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Elwig.Services {
public static class BackupService {
public static bool IsAnyBackupUpToDate {
get {
var lastDbWrite = File.GetLastWriteTime(App.Config.DatabaseFile);
lastDbWrite += new TimeSpan(0, 0, -1);
return Directory.GetFiles(App.Config.BackupPath, "????-??-??_??-??-??.*.zip")
.Any(n => DateTime.ParseExact(Path.GetFileName(n)[..19], "yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.None) >= lastDbWrite);
}
}
public static async Task TryDatabaseBackup() {
if (IsAnyBackupUpToDate) return;
await BackupDatabase();
PruneBackups();
}
public static async Task BackupDatabase() {
await Database.ExportSql(Path.Combine(App.Config.BackupPath, $"{File.GetLastWriteTime(App.Config.DatabaseFile):yyyy-MM-dd_HH-mm-ss}.sql.zip"), true);
}
public static void PruneBackups() {
var retain = new HashSet<string>();
var backups = Directory.GetFiles(App.Config.BackupPath, "????-??-??_??-??-??.*.zip")
.Select(n => (FileName: n, Timestamp: DateTime.ParseExact(Path.GetFileName(n)[..19], "yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture, DateTimeStyles.None)))
.OrderByDescending(t => t.Timestamp).ToArray();
DateTime now;
// retain hourly backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainHours; i++, now = now.AddHours(-1)) {
foreach (var b in backups) {
if (b.Timestamp.Date == now.Date && b.Timestamp.Hour == now.Hour) {
retain.Add(b.FileName);
break;
}
}
}
// retain daily backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainDays; i++, now = now.AddDays(-1)) {
foreach (var b in backups) {
if (b.Timestamp.Date == now.Date) {
retain.Add(b.FileName);
break;
}
}
}
// retain weekly backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainWeeks; i++, now = now.AddDays(-7)) {
foreach (var b in backups) {
if (b.Timestamp.GetYearAndWeek() == now.GetYearAndWeek()) {
retain.Add(b.FileName);
break;
}
}
}
// retain monthly backups
now = DateTime.Now;
for (int i = 0; i < App.Config.BackupRetainMonths; i++, now = now.AddMonths(-1)) {
foreach (var b in backups) {
if (b.Timestamp.Year == now.Year && b.Timestamp.Month == now.Month) {
retain.Add(b.FileName);
break;
}
}
}
// retain tertial backups
now = new DateTime(DateTime.Now.Year + 1, 3, 1);
for (int i = 0; i < 600; i++, now = now.AddMonths(-4)) {
foreach (var b in backups) {
if (b.Timestamp.GetYearAndTertial() == now.GetYearAndTertial()) {
retain.Add(b.FileName);
break;
}
}
}
// delete non-retained files
foreach (var b in backups) {
if (retain.Contains(b.FileName)) continue;
try {
File.Delete(b.FileName);
} catch { }
}
// delete temp files
foreach (var f in Directory.GetFiles(App.Config.BackupPath, "*.tmp")) {
try {
File.Delete(f);
} catch { }
}
}
public static async Task RestoreDatabase() {
try {
var d = new OpenFileDialog() {
Title = "Datenbank wiederherstellen - Elwig",
DefaultDirectory = App.Config.BackupPath,
InitialDirectory = App.Config.BackupPath,
DefaultExt = "sql.zip",
Filter = "SQLite-Datenbank (*.sqlite3, *.sqlite3.zip, *.sql, *.sql.zip)|*.sqlite3;*.sqlite3.zip;*.sql;*.sql.zip",
};
if (d.ShowDialog() == true) {
if (!InteractionService.AskContinue("Datenbank wiederherstellen", "Soll die Datenbank wirklich unwiederruflich durch die wiederhergestellte Version ersetzt werden?"))
return;
Mouse.OverrideCursor = Cursors.Wait;
await App.ReplaceDatabase(d.FileName);
}
} catch (Exception exc) {
InteractionService.ShowException(exc);
}
Mouse.OverrideCursor = null;
}
}
}
+12 -43
View File
@@ -8,9 +8,6 @@ using Microsoft.EntityFrameworkCore;
using Elwig.Documents; using Elwig.Documents;
using Elwig.Helpers.Export; using Elwig.Helpers.Export;
using Elwig.Models.Dtos; using Elwig.Models.Dtos;
using Microsoft.Win32;
using System.Windows.Input;
using System.Windows;
using System; using System;
using LinqKit; using LinqKit;
using System.Windows.Controls; using System.Windows.Controls;
@@ -48,7 +45,8 @@ namespace Elwig.Services {
deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == s.Year && a.DsNr == s.DsNr); deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == s.Year && a.DsNr == s.DsNr);
filterNames.Add($"{s.Date:dd.MM.yyyy} – {s.Branch.Name} – {s.Description}"); filterNames.Add($"{s.Date:dd.MM.yyyy} – {s.Branch.Name} – {s.Description}");
} else { } else {
deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == vm.FilterSeason && (!vm.FilterOnlyUpcoming || a.Schedule.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0)); var today = DateTime.Today;
deliveryAncmtQuery = deliveryAncmtQuery.Where(a => a.Year == vm.FilterSeason && (!vm.FilterOnlyUpcoming || a.Schedule.DateString.CompareTo(today.ToString("yyyy-MM-dd")) >= 0));
filterNames.Add($"{vm.FilterSeason}"); filterNames.Add($"{vm.FilterSeason}");
} }
@@ -261,61 +259,32 @@ namespace Elwig.Services {
.ThenBy(a => a.Member.MgNr); .ThenBy(a => a.Member.MgNr);
if (mode == ExportMode.SaveList) { if (mode == ExportMode.SaveList) {
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile(DeliveryAncmtList.Name, DeliveryAncmtList.Name, "ods");
FileName = $"{DeliveryAncmtList.Name}.ods", if (filename != null) {
DefaultExt = "ods", await Utils.RunForeground(async () => {
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{DeliveryAncmtList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryAncmtListData.FromQuery(query, filterNames); var data = await DeliveryAncmtListData.FromQuery(query, filterNames);
using var ods = new OdsFile(d.FileName); using var ods = new OdsFile(filename);
await ods.AddTable(data); await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} else { } else {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
var data = await DeliveryAncmtListData.FromQuery(query, filterNames); var data = await DeliveryAncmtListData.FromQuery(query, filterNames);
using var doc = new DeliveryAncmtList(string.Join(" / ", filterNames), data); using var doc = new DeliveryAncmtList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode); await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} }
private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
var tb = new TextBlock() {
Text = text,
TextAlignment = alignRight ? TextAlignment.Right : alignCenter ? TextAlignment.Center : TextAlignment.Left,
Margin = new(0, 12 * row, 0, 0),
FontWeight = bold ? FontWeights.Bold : FontWeights.Normal,
};
tb.SetValue(Grid.ColumnProperty, col);
tb.SetValue(Grid.ColumnSpanProperty, colSpan);
grid.Children.Add(tb);
}
private static void AddToolTipRow(Grid grid, int row, string? h1, string? h2, int weight, int? total1, int total2) { private static void AddToolTipRow(Grid grid, int row, string? h1, string? h2, int weight, int? total1, int total2) {
var bold = h2 == null; var bold = h2 == null;
if (h1 != null) AddToolTipCell(grid, h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold); if (h1 != null) grid.AddToolTipCell(h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold);
if (h2 != null) AddToolTipCell(grid, h2 + ":", row, 1, 1, bold); if (h2 != null) grid.AddToolTipCell(h2 + ":", row, 1, 1, bold);
AddToolTipCell(grid, $"{weight:N0} kg", row, 2, 1, bold, true); grid.AddToolTipCell($"{weight:N0} kg", row, 2, bold: bold, alignRight: true);
if (total1 != null && total1 != 0) if (total1 != null && total1 != 0)
AddToolTipCell(grid, $"{weight * 100.0 / total1:N1} %", row, 3, 1, bold, true); grid.AddToolTipCell($"{weight * 100.0 / total1:N1} %", row, 3, bold: bold, alignRight: true);
if (total2 != 0) if (total2 != 0)
AddToolTipCell(grid, $"{weight * 100.0 / total2:N1} %", row, 4, 1, bold, true); grid.AddToolTipCell($"{weight * 100.0 / total2:N1} %", row, 4, bold: bold, alignRight: true);
} }
public static async Task<(string Text, (string?, string?, int, int?, int)[] Grid)> GenerateToolTipData(IQueryable<DeliveryAncmt> deliveryAncmts) { public static async Task<(string Text, (string?, string?, int, int?, int)[] Grid)> GenerateToolTipData(IQueryable<DeliveryAncmt> deliveryAncmts) {
+3 -2
View File
@@ -49,8 +49,9 @@ namespace Elwig.Services {
filterNames.Add($"{vm.FilterSeason}"); filterNames.Add($"{vm.FilterSeason}");
} }
if (vm.FilterOnlyUpcoming) { if (vm.FilterOnlyUpcoming) {
deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.DateString.CompareTo(Utils.Today.ToString("yyyy-MM-dd")) >= 0); var today = DateTime.Today;
filterNames.Add($"ab {Utils.Today:dd.MM.yyyy}"); deliveryScheduleQuery = deliveryScheduleQuery.Where(s => s.DateString.CompareTo(today.ToString("yyyy-MM-dd")) >= 0);
filterNames.Add($"ab {today:dd.MM.yyyy}");
} }
var filterVar = new List<string>(); var filterVar = new List<string>();
+212 -229
View File
@@ -1,22 +1,21 @@
using Elwig.Documents; using Elwig.Documents;
using Elwig.Helpers.Export;
using Elwig.Helpers; using Elwig.Helpers;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos; using Elwig.Models.Dtos;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows;
using System;
using Elwig.ViewModels; using Elwig.ViewModels;
using iText.Commons.Internal.Runtime;
using LinqKit; using LinqKit;
using System.Globalization;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media;
namespace Elwig.Services { namespace Elwig.Services {
public static class DeliveryService { public static class DeliveryService {
@@ -25,15 +24,6 @@ namespace Elwig.Services {
FromFilters, FromToday, FromSeason, FromSeasonAndBranch, Selected, FromFilters, FromToday, FromSeason, FromSeasonAndBranch, Selected,
}; };
public static async Task<Member?> GetMemberAsync(int mgnr) {
using var ctx = new AppDbContext();
return await ctx.FetchMembers(mgnr).SingleOrDefaultAsync();
}
public static Member? GetMember(int mgnr) {
return GetMemberAsync(mgnr).GetAwaiter().GetResult();
}
public static void ClearInputs(this DeliveryAdminViewModel vm) { public static void ClearInputs(this DeliveryAdminViewModel vm) {
} }
@@ -47,8 +37,13 @@ namespace Elwig.Services {
vm.SortId = ""; vm.SortId = "";
vm.GradationKmwString = ""; vm.GradationKmwString = "";
vm.WeightString = "";
vm.Weight = null;
vm.ActualTare = null;
vm.TargetTare = null;
vm.Tare = null;
vm.IsManualWeighing = false; vm.IsManualWeighing = false;
vm.PartComment = ""; vm.PartComment = "";
vm.TemperatureString = ""; vm.TemperatureString = "";
vm.AcidString = ""; vm.AcidString = "";
@@ -63,7 +58,11 @@ namespace Elwig.Services {
vm.WineKg = ControlUtils.GetItemFromSourceWithPk(vm.WineKgSource, p.KgNr) as AT_Kg; vm.WineKg = ControlUtils.GetItemFromSourceWithPk(vm.WineKgSource, p.KgNr) as AT_Kg;
vm.WineRd = ControlUtils.GetItemFromSourceWithPk(vm.WineRdSource, p.KgNr, p.RdNr) as WbRd; vm.WineRd = ControlUtils.GetItemFromSourceWithPk(vm.WineRdSource, p.KgNr, p.RdNr) as WbRd;
vm.WineOrigin = ControlUtils.GetItemFromSourceWithPk(vm.WineOriginSource, p.HkId) as WineOrigin; vm.WineOrigin = ControlUtils.GetItemFromSourceWithPk(vm.WineOriginSource, p.HkId) as WineOrigin;
vm.WeightString = $"{p.Weight:N0}";
vm.Weight = p.Weight;
vm.ActualTare = p.WeighingInfo.Tare;
vm.TargetTare = vm.ActualTare + p.WeighingInfo.TareCorrection;
vm.Tare = vm.TargetTare ?? (vm.ActualTare is int t && t != 0 ? t : null);
vm.IsManualWeighing = p.IsManualWeighing; vm.IsManualWeighing = p.IsManualWeighing;
vm.IsNetWeight = p.IsNetWeight; vm.IsNetWeight = p.IsNetWeight;
@@ -76,14 +75,53 @@ namespace Elwig.Services {
vm.TemperatureString = (p.Temperature != null) ? $"{p.Temperature:N1}" : ""; vm.TemperatureString = (p.Temperature != null) ? $"{p.Temperature:N1}" : "";
vm.AcidString = (p.Acid != null) ? $"{p.Acid:N1}" : ""; vm.AcidString = (p.Acid != null) ? $"{p.Acid:N1}" : "";
vm.IsHandPicked = p.IsHandPicked; vm.IsHandPicked = p.IsHandPicked;
vm.IsGebunden = p.IsGebunden; vm.IsContracted = p.IsContracted;
vm.Unloading = p.Unloading; vm.Unloading = p.Unloading;
vm.ScaleId = p.ScaleId; vm.ScaleTerminalId = p.ScaleTerminalId;
vm.WeighingData = p.WeighingData; vm.WeighingData = p.WeighingData;
vm.ManualWeighingReason = p.WeighingReason; vm.ManualWeighingReason = p.WeighingReason;
} }
public static (DateTime From, DateTime To)? GetFilterToday(DateTime today, IEnumerable<DateTime> timestamps) {
var filtered = timestamps.Where(t => t >= today.AddDays(-1) && t < today.AddDays(1)).Order().ToArray();
if (filtered.Length == 0) return null;
var latest = filtered.Last();
var since = filtered.Cast<DateTime>().Reverse().Aggregate(latest, (s, c) => s.Subtract(c).TotalHours <= 6 ? c : s);
latest += new TimeSpan(0, 1, -latest.Minute, -latest.Second, -latest.Millisecond, -latest.Microsecond);
since += new TimeSpan(0, 0, -since.Minute, -since.Second, -since.Millisecond, -since.Microsecond);
if (since.Subtract(today) <= new TimeSpan(-6, 0, 0) && latest.Subtract(today) < new TimeSpan(0, 0, 0))
return null;
return (since, latest);
}
private static async Task<(Expression<Func<Delivery, bool>>, string)> GetFilterToday(AppDbContext ctx) {
var today = DateTime.Today;
var timestamps = (await ctx.Deliveries
.Where(d => d.DateString.CompareTo(today.AddDays(-1).ToString("yyyy-MM-dd")) >= 0)
.OrderBy(d => d.DateString).ThenBy(d => d.TimeString)
.Select(d => d.DateString + " " + d.TimeString)
.ToListAsync())
.Select(s => DateTime.TryParseExact(s, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? (DateTime?)dt : null)
.Where(t => t.HasValue)
.Cast<DateTime>()
.ToArray();
var res = GetFilterToday(today, timestamps);
if (!res.HasValue) {
return (_ => false, today.ToString("dd.MM.yyyy"));
} else {
var from = res.Value.From;
var to = res.Value.To;
return (d => (d.DateString == from.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo(from.ToString("HH:mm:ss")) >= 0)) ||
(d.DateString.CompareTo(from.ToString("yyyy-MM-dd")) > 0),
from.Date == to.Date ? from.ToString("dd.MM.yyyy") : $"{from:dd.MM.yyyy \\a\\b HH:mm} / {to:dd.MM.yyyy \\b\\i\\s HH:mm}");
}
}
public static async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, Predicate<DeliveryPart>, List<string>)> GetFilters(this DeliveryAdminViewModel vm, AppDbContext ctx) { public static async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, Predicate<DeliveryPart>, List<string>)> GetFilters(this DeliveryAdminViewModel vm, AppDbContext ctx) {
List<string> filterNames = []; List<string> filterNames = [];
IQueryable<Delivery> deliveryQuery = ctx.Deliveries; IQueryable<Delivery> deliveryQuery = ctx.Deliveries;
@@ -96,10 +134,9 @@ namespace Elwig.Services {
filterNames.Add(vm.FilterMember.AdministrativeName); filterNames.Add(vm.FilterMember.AdministrativeName);
} }
if (vm.FilterTodayOnly) { if (vm.FilterTodayOnly) {
deliveryQuery = deliveryQuery var (pred, name) = await GetFilterToday(ctx);
.Where(d => (d.DateString == Utils.Today.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") > 0)) || deliveryQuery = deliveryQuery.Where(pred);
(d.DateString == Utils.Today.AddDays(1).ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") <= 0))); filterNames.Add(name);
filterNames.Add(Utils.Today.ToString("dd.MM.yyyy"));
} else if (!vm.FilterAllSeasons) { } else if (!vm.FilterAllSeasons) {
deliveryQuery = deliveryQuery.Where(d => d.Year == vm.FilterSeason); deliveryQuery = deliveryQuery.Where(d => d.Year == vm.FilterSeason);
filterNames.Add($"{vm.FilterSeason}"); filterNames.Add($"{vm.FilterSeason}");
@@ -144,19 +181,19 @@ namespace Elwig.Services {
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Weißweinsorten"); filterNames.Add("Weißweinsorten");
} else if (e.Length >= 3 && e.Length <= 8 && "gebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 3 && e.Length <= 8 && "gebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.IsGebunden == true); prd = prd.And(p => p.IsContracted == true);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("gebunden"); filterNames.Add("gebunden");
} else if (e.Length >= 4 && e.Length <= 9 && "!gebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 4 && e.Length <= 9 && "!gebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.IsGebunden != true); prd = prd.And(p => p.IsContracted != true);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("nicht gebunden"); filterNames.Add("nicht gebunden");
} else if (e.Length >= 5 && e.Length <= 10 && "ungebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 5 && e.Length <= 10 && "ungebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.IsGebunden == false); prd = prd.And(p => p.IsContracted == false);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("ungebunden"); filterNames.Add("ungebunden");
} else if (e.Length >= 6 && e.Length <= 11 && "!ungebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 6 && e.Length <= 11 && "!ungebunden".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
prd = prd.And(p => p.IsGebunden != false); prd = prd.And(p => p.IsContracted != false);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("nicht ungebunden"); filterNames.Add("nicht ungebunden");
} else if (e.Length >= 5 && e.Length <= 8 && "handlese".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 5 && e.Length <= 8 && "handlese".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
@@ -488,7 +525,7 @@ namespace Elwig.Services {
if (partNew && timeIsDefault) { if (partNew && timeIsDefault) {
newTimeString = DateTime.Now.ToString("HH:mm:ss"); newTimeString = DateTime.Now.ToString("HH:mm:ss");
} else if (partNew || timeHasChanged) { } else if (partNew || timeHasChanged) {
newTimeString = string.IsNullOrEmpty(vm.Time) ? null : vm.Time + ":00"; newTimeString = string.IsNullOrWhiteSpace(vm.Time) ? null : vm.Time + ":00";
} }
var d = new Delivery { var d = new Delivery {
@@ -500,7 +537,7 @@ namespace Elwig.Services {
ZwstId = vm.Branch!.ZwstId, ZwstId = vm.Branch!.ZwstId,
LsNr = newLsNr ?? vm.LsNr!, LsNr = newLsNr ?? vm.LsNr!,
MgNr = vm.MgNr!.Value, MgNr = vm.MgNr!.Value,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment, Comment = string.IsNullOrWhiteSpace(vm.Comment) ? null : vm.Comment,
}; };
p = new DeliveryPart { p = new DeliveryPart {
@@ -519,15 +556,15 @@ namespace Elwig.Services {
IsNetWeight = vm.IsNetWeight, IsNetWeight = vm.IsNetWeight,
IsHandPicked = vm.IsHandPicked, IsHandPicked = vm.IsHandPicked,
IsGebunden = vm.IsGebunden, IsContracted = vm.IsContracted,
Unloading = vm.Unloading, Unloading = vm.Unloading,
Temperature = vm.Temperature, Temperature = vm.Temperature,
Acid = vm.Acid, Acid = vm.Acid,
Comment = string.IsNullOrEmpty(vm.PartComment) ? null : vm.PartComment, Comment = string.IsNullOrWhiteSpace(vm.PartComment) ? null : vm.PartComment,
Weight = vm.Weight!.Value, Weight = vm.Weight!.Value,
IsManualWeighing = vm.IsManualWeighing, IsManualWeighing = vm.IsManualWeighing,
ScaleId = vm.ScaleId, ScaleTerminalId = vm.ScaleTerminalId,
WeighingData = vm.WeighingData, WeighingData = vm.WeighingData,
WeighingReason = vm.ManualWeighingReason, WeighingReason = vm.ManualWeighingReason,
}; };
@@ -727,16 +764,10 @@ namespace Elwig.Services {
} }
public static async Task GenerateDeliveryNote(int year, int did, ExportMode mode) { public static async Task GenerateDeliveryNote(int year, int did, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
using var doc = await DeliveryNote.Initialize(year, did); using var doc = await DeliveryNote.Initialize(year, did);
await Utils.ExportDocument(doc, mode, doc.Delivery.LsNr, (doc.Member, $"{DeliveryNote.Name} Nr. {doc.Delivery.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {doc.Delivery.LsNr}")); await Utils.ExportDocument(doc, mode, doc.Delivery.LsNr, (doc.Member, $"{DeliveryNote.Name} Nr. {doc.Delivery.LsNr}", $"Im Anhang finden Sie den {DeliveryNote.Name} Nr. {doc.Delivery.LsNr}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
public static async Task GenerateDeliveryJournal(this DeliveryAdminViewModel vm, ExportSubject subject, ExportMode mode) { public static async Task GenerateDeliveryJournal(this DeliveryAdminViewModel vm, ExportSubject subject, ExportMode mode) {
@@ -748,10 +779,9 @@ namespace Elwig.Services {
query = q; query = q;
filterNames.AddRange(f); filterNames.AddRange(f);
} else if (subject == ExportSubject.FromToday) { } else if (subject == ExportSubject.FromToday) {
var date = $"{Utils.Today:yyyy-MM-dd}"; var (pred, name) = await GetFilterToday(ctx);
query = ctx.DeliveryParts query = ctx.Deliveries.Where(pred).SelectMany(d => d.Parts);
.Where(p => p.Delivery.DateString == date); filterNames.Add(name);
filterNames.Add($"{Utils.Today:dd.MM.yyyy}");
} else if (subject == ExportSubject.FromSeasonAndBranch) { } else if (subject == ExportSubject.FromSeasonAndBranch) {
query = ctx.DeliveryParts query = ctx.DeliveryParts
.Where(p => p.Year == Utils.CurrentLastSeason && p.Delivery.ZwstId == App.ZwstId); .Where(p => p.Year == Utils.CurrentLastSeason && p.Delivery.ZwstId == App.ZwstId);
@@ -773,38 +803,19 @@ namespace Elwig.Services {
.ThenBy(p => p.DPNr); .ThenBy(p => p.DPNr);
if (mode == ExportMode.SaveList) { if (mode == ExportMode.SaveList) {
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile(DeliveryJournal.Name, DeliveryJournal.Name, "ods");
FileName = $"{DeliveryJournal.Name}.ods", if (filename != null) {
DefaultExt = "ods", await Utils.RunForeground(async () => {
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames); var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var ods = new OdsFile(d.FileName); using var ods = new OdsFile(filename);
await ods.AddTable(data); await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} else if (mode == ExportMode.Export) { } else if (mode == ExportMode.Export) {
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile(DeliveryJournal.Name, subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}", "elwig.zip");
FileName = subject == ExportSubject.Selected ? $"Lieferung_{vm.SelectedDelivery?.LsNr}.elwig.zip" : $"Lieferungen_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip", if (filename != null) {
DefaultExt = "elwig.zip", if (!filename.EndsWith(".elwig.zip")) filename += ".elwig.zip";
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip", await Utils.RunForeground(async () => {
Title = $"{DeliveryJournal.Name} speichern unter - Elwig",
AddExtension = false,
};
if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var list = await query var list = await query
.Select(p => p.Delivery) .Select(p => p.Delivery)
.Distinct() .Distinct()
@@ -817,31 +828,19 @@ namespace Elwig.Services {
.DistinctBy(k => k.KgNr) .DistinctBy(k => k.KgNr)
.OrderBy(k => k.KgNr) .OrderBy(k => k.KgNr)
.ToList(); .ToList();
await ElwigData.Export(d.FileName, list, wbKgs, filterNames); await ElwigData.Export(filename, list, wbKgs, filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) { } else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames); await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
}); });
Mouse.OverrideCursor = null;
} else { } else {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames); var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryJournal(string.Join(" / ", filterNames), data); using var doc = new DeliveryJournal(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode); await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} }
@@ -854,25 +853,18 @@ namespace Elwig.Services {
query = q; query = q;
filterNames.AddRange(f); filterNames.AddRange(f);
} else if (subject == ExportSubject.FromToday) { } else if (subject == ExportSubject.FromToday) {
var date = $"{Utils.Today:yyyy-MM-dd}"; var (pred, name) = await GetFilterToday(ctx);
query = ctx.DeliveryParts query = ctx.Deliveries.Where(pred).SelectMany(d => d.Parts);
.Where(p => p.Delivery.DateString == date); filterNames.Add(name);
filterNames.Add($"{Utils.Today:dd.MM.yyyy}");
} else { } else {
throw new ArgumentException("Invalid value for ExportSubject"); throw new ArgumentException("Invalid value for ExportSubject");
} }
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
var data = await WineQualityStatisticsData.FromQuery(query, App.Client.OrderingMemberList); var data = await WineQualityStatisticsData.FromQuery(query, App.Client.OrderingMemberList);
using var doc = new WineQualityStatistics(string.Join(" / ", filterNames), data); using var doc = new WineQualityStatistics(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode); await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
public static async Task GenerateLocalityStatistics(this DeliveryAdminViewModel vm, ExportSubject subject) { public static async Task GenerateLocalityStatistics(this DeliveryAdminViewModel vm, ExportSubject subject) {
@@ -888,24 +880,13 @@ namespace Elwig.Services {
throw new ArgumentException("Invalid value for ExportSubject"); throw new ArgumentException("Invalid value for ExportSubject");
} }
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile("Lieferstatistik pro Ort", $"Lieferstatistik-{vm.FilterSeason ?? Utils.CurrentLastSeason}", "ods");
FileName = $"Lieferstatistik-{vm.FilterSeason ?? Utils.CurrentLastSeason}.ods", if (filename != null) {
DefaultExt = "ods", await Utils.RunForeground(async () => {
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", using var ods = new OdsFile(filename);
Title = $"Lieferstatistik pro Ort speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ods = new OdsFile(d.FileName);
var tbl = await WineLocalityStatisticsData.FromQuery(query, filterNames); var tbl = await WineLocalityStatisticsData.FromQuery(query, filterNames);
await ods.AddTable(tbl); await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} }
@@ -936,17 +917,10 @@ namespace Elwig.Services {
filterNames.Remove("abgewertet"); filterNames.Remove("abgewertet");
if (mode == ExportMode.SaveList) { if (mode == ExportMode.SaveList) {
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile(DeliveryDepreciationList.Name, $"{DeliveryDepreciationList.Name}-{vm.FilterSeason ?? Utils.CurrentLastSeason}", "ods");
FileName = $"{DeliveryDepreciationList.Name}-{vm.FilterSeason ?? Utils.CurrentLastSeason}.ods", if (filename != null) {
DefaultExt = "ods", await Utils.RunForeground(async () => {
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", using var ods = new OdsFile(filename);
Title = $"{DeliveryDepreciationList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ods = new OdsFile(d.FileName);
var tblTotal = await DeliveryJournalData.FromQuery(query, filterNames); var tblTotal = await DeliveryJournalData.FromQuery(query, filterNames);
tblTotal.FullName = DeliveryDepreciationList.Name; tblTotal.FullName = DeliveryDepreciationList.Name;
tblTotal.Name = "Gesamt"; tblTotal.Name = "Gesamt";
@@ -957,24 +931,14 @@ namespace Elwig.Services {
tbl.Name = branch.Name; tbl.Name = branch.Name;
await ods.AddTable(tbl); await ods.AddTable(tbl);
} }
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} else { } else {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
var data = await DeliveryJournalData.FromQuery(query, filterNames); var data = await DeliveryJournalData.FromQuery(query, filterNames);
using var doc = new DeliveryDepreciationList(string.Join(" / ", filterNames), data); using var doc = new DeliveryDepreciationList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode); await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} }
@@ -1001,65 +965,52 @@ namespace Elwig.Services {
.ThenBy(p => p.AttrId) .ThenBy(p => p.AttrId)
.ThenBy(p => p.CultId); .ThenBy(p => p.CultId);
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile("Liefermengen", "Liefermengen", "ods");
FileName = $"Liefermengen.ods", if (filename != null) {
DefaultExt = "ods", await Utils.RunForeground(async () => {
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods", using var ods = new OdsFile(filename);
Title = $"Liefermengen speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
using var ods = new OdsFile(d.FileName);
var tblTotal = await MemberDeliveryData.FromQuery(query, filterNames); var tblTotal = await MemberDeliveryData.FromQuery(query, filterNames);
var tbl = await MemberDeliveryPerVarietyData.FromQuery(query, filterNames); var tbl = await MemberDeliveryPerVarietyData.FromQuery(query, filterNames);
await ods.AddTable(tblTotal); await ods.AddTable(tblTotal);
await ods.AddTable(tbl); await ods.AddTable(tbl);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} }
private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) { private static void AddWeightToolTipRow(Grid grid, int row, string? h1, string? h2, string? h3, int weight, int? total1, int? total2, int total3, string? type) {
var tb = new TextBlock() { var bold = h3 == null;
Text = text, var color = type == "R" ? Brushes.Firebrick : type == "W" ? Brushes.ForestGreen : null;
TextAlignment = alignRight ? TextAlignment.Right : alignCenter ? TextAlignment.Center : TextAlignment.Left, if (h1 != null) grid.AddToolTipCell(h1 + ":", row, 0, (h2 == null) ? (h3 == null) ? 3 : 2 : 1, bold: bold, color: color);
Margin = new(0, 12 * row, 0, 0), if (h2 != null) grid.AddToolTipCell(h2 + ":", row, 1, (h3 == null) ? 2 : 1, bold: bold, color: color);
FontWeight = bold ? FontWeights.Bold : FontWeights.Normal, if (h3 != null) grid.AddToolTipCell(h3 + ":", row, 2, 1, bold: bold, color: color);
}; grid.AddToolTipCell($"{weight:N0} kg", row, 3, bold: bold, alignRight: true, color: color);
tb.SetValue(Grid.ColumnProperty, col);
tb.SetValue(Grid.ColumnSpanProperty, colSpan);
grid.Children.Add(tb);
}
private static void AddWeightToolTipRow(Grid grid, int row, string? h1, string? h2, int weight, int? total1, int total2) {
var bold = h2 == null;
if (h1 != null) AddToolTipCell(grid, h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold);
if (h2 != null) AddToolTipCell(grid, h2 + ":", row, 1, 1, bold);
AddToolTipCell(grid, $"{weight:N0} kg", row, 2, 1, bold, true);
if (total1 != null && total1 != 0) if (total1 != null && total1 != 0)
AddToolTipCell(grid, $"{weight * 100.0 / total1:N1} %", row, 3, 1, bold, true); grid.AddToolTipCell($"{weight * 100.0 / total1:N1} %", row, 4, bold: bold, alignRight: true, color: color);
if (total2 != 0) if (total2 != null && total2 != 0)
AddToolTipCell(grid, $"{weight * 100.0 / total2:N1} %", row, 4, 1, bold, true); grid.AddToolTipCell($"{weight * 100.0 / total2:N1} %", row, 5, bold: bold, alignRight: true, color: color);
if (total3 != 0)
grid.AddToolTipCell($"{weight * 100.0 / total3:N1} %", row, 6, bold: bold, alignRight: true, color: color);
} }
private static void AddGradationToolTipRow(Grid grid, int row, string? h1, string? h2, double min, double avg, double max) { private static void AddGradationToolTipRow(Grid grid, int row, string? h1, string? h2, string? h3, double min, double avg, double max, string? type) {
var bold = h2 == null; var bold = h3 == null;
if (h1 != null) AddToolTipCell(grid, h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold); var color = type == "R" ? Brushes.Firebrick : type == "W" ? Brushes.ForestGreen : null;
if (h2 != null) AddToolTipCell(grid, h2 + ":", row, 1, 1, bold); if (h1 != null) grid.AddToolTipCell(h1 + ":", row, 0, (h2 == null) ? (h3 == null) ? 3 : 2 : 1, bold: bold, color: color);
AddToolTipCell(grid, $"{min:N1}°", row, 2, 1, bold, true); if (h2 != null) grid.AddToolTipCell(h2 + ":", row, 1, (h3 == null) ? 2 : 1, bold: bold, color: color);
AddToolTipCell(grid, $"{avg:N1}°", row, 3, 1, bold, true); if (h3 != null) grid.AddToolTipCell(h3 + ":", row, 2, bold: bold, color: color);
AddToolTipCell(grid, $"{max:N1}°", row, 4, 1, bold, true); grid.AddToolTipCell($"{min:N1}°", row, 3, bold: bold, alignRight: true, color: color);
grid.AddToolTipCell($"{avg:N1}°", row, 4, bold: bold, alignRight: true, color: color);
grid.AddToolTipCell($"{max:N1}°", row, 5, bold: bold, alignRight: true, color: color);
} }
public static async Task<(string WeightText, (string?, string?, int, int?, int)[] WeightGrid, string GradationText, (string?, string?, double, double, double)[] GradationGrid)> GenerateToolTipData(IQueryable<DeliveryPart> deliveryParts) { private static string? GetGroupName(params string?[] parts) {
var wGrid = new List<(string?, string?, int, int?, int)>(); return parts.All(p => p == null) ? null : string.Join(" / ", parts.Where(p => p != null));
}
public static async Task<(string WeightText, (string?, string?, string?, int, int?, int?, int, string?)[] WeightGrid, string GradationText, (string?, string?, string?, double, double, double, string?)[] GradationGrid)> GenerateToolTipData(IQueryable<DeliveryPart> deliveryParts) {
var wGrid = new List<(string?, string?, string?, int, int?, int?, int, string?)>();
var wText = "-"; var wText = "-";
var gGrid = new List<(string?, string?, double, double, double)>(); var gGrid = new List<(string?, string?, string?, double, double, double, string?)>();
var gText = "-"; var gText = "-";
var stat = (await deliveryParts.GroupBy(p => 0) var stat = (await deliveryParts.GroupBy(p => 0)
@@ -1074,11 +1025,11 @@ namespace Elwig.Services {
.Single(); .Single();
wText = $"{stat.Weight:N0} kg"; wText = $"{stat.Weight:N0} kg";
wGrid.Add(("Menge", null, stat.Weight, null, stat.Weight)); wGrid.Add(("Menge", null, null, stat.Weight, null, null, stat.Weight, null));
if (stat.Min != null && stat.Max != null) { if (stat.Min != null && stat.Max != null) {
gText = $"{stat.Min:N1}° / {stat.Avg:N1}° / {stat.Max:N1}°"; gText = $"{stat.Min:N1}° / {stat.Avg:N1}° / {stat.Max:N1}°";
gGrid.Add(("Gradation", null, stat.Min.Value, stat.Avg, stat.Max.Value)); gGrid.Add(("Gradation", null, null, stat.Min.Value, stat.Avg, stat.Max.Value, null));
var attrGroups = await deliveryParts var attrGroups = await deliveryParts
.GroupBy(p => new { Attr = p.Attribute!.Name, Cult = p.Cultivation!.Name }) .GroupBy(p => new { Attr = p.Attribute!.Name, Cult = p.Cultivation!.Name })
@@ -1095,9 +1046,10 @@ namespace Elwig.Services {
.ThenBy(g => g.Cult) .ThenBy(g => g.Cult)
.ToListAsync(); .ToListAsync();
var sortGroups = await deliveryParts var sortGroups = await deliveryParts
.GroupBy(p => p.SortId) .GroupBy(p => new { p.SortId, p.Variety.Type })
.Select(g => new { .Select(g => new {
SortId = g.Key, g.Key.SortId,
g.Key.Type,
Weight = g.Sum(p => p.Weight), Weight = g.Sum(p => p.Weight),
Min = g.Min(p => p.Kmw), Min = g.Min(p => p.Kmw),
Avg = g.Sum(p => p.Kmw * p.Weight) / g.Sum(p => p.Weight), Avg = g.Sum(p => p.Kmw * p.Weight) / g.Sum(p => p.Weight),
@@ -1110,11 +1062,13 @@ namespace Elwig.Services {
.GroupBy(p => new { .GroupBy(p => new {
Attr = p.Attribute!.Name, Attr = p.Attribute!.Name,
Cult = p.Cultivation!.Name, Cult = p.Cultivation!.Name,
p.Variety.Type,
p.SortId, p.SortId,
}) })
.Select(g => new { .Select(g => new {
g.Key.Attr, g.Key.Attr,
g.Key.Cult, g.Key.Cult,
g.Key.Type,
g.Key.SortId, g.Key.SortId,
Weight = g.Sum(p => p.Weight), Weight = g.Sum(p => p.Weight),
Min = g.Min(p => p.Kmw), Min = g.Min(p => p.Kmw),
@@ -1124,27 +1078,62 @@ namespace Elwig.Services {
.OrderByDescending(g => g.Weight) .OrderByDescending(g => g.Weight)
.ThenBy(g => g.Attr) .ThenBy(g => g.Attr)
.ThenBy(g => g.Cult) .ThenBy(g => g.Cult)
.ThenBy(g => g.Type)
.ThenBy(g => g.SortId) .ThenBy(g => g.SortId)
.ToListAsync(); .ToListAsync();
foreach (var attrG in attrGroups) { foreach (var attrG in attrGroups) {
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult; var name = GetGroupName(attrG.Attr, attrG.Cult);
wGrid.Add((name, null, attrG.Weight, attrG.Weight, stat.Weight)); var typeGroups = groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult)
.GroupBy(g => g.Type)
.Select(g => new { Type = g.Key, Weight = g.Sum(p => p.Weight) })
.OrderByDescending(g => g.Weight).ThenBy(g => g.Type)
.ToList();
wGrid.Add((name, null, null, attrG.Weight, null, attrG.Weight, stat.Weight, typeGroups.Count == 1 ? typeGroups.First().Type : null));
if (typeGroups.Count == 1) {
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) { foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
wGrid.Add((null, g.SortId, g.Weight, attrG.Weight, stat.Weight)); wGrid.Add((null, null, g.SortId, g.Weight, null, attrG.Weight, stat.Weight, null));
}
} else {
foreach (var t in typeGroups) {
wGrid.Add((null, t.Type == "R" ? "Rot" : "Weiß", null, t.Weight, t.Weight, attrG.Weight, stat.Weight, t.Type));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult && g.Type == t.Type).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
wGrid.Add((null, null, g.SortId, g.Weight, t.Weight, attrG.Weight, stat.Weight, null));
}
}
} }
} }
foreach (var attrG in attrGroups) { foreach (var attrG in attrGroups) {
var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult; var name = GetGroupName(attrG.Attr, attrG.Cult);
gGrid.Add((name, null, attrG.Min, attrG.Avg, attrG.Max)); var typeGroups = groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult)
.GroupBy(g => g.Type)
.Select(g => new {
Type = g.Key,
Weight = g.Sum(p => p.Weight),
Min = g.Min(p => p.Min),
Avg = g.Sum(p => p.Avg * p.Weight) / g.Sum(p => p.Weight),
Max = g.Max(p => p.Max),
})
.OrderByDescending(g => g.Weight).ThenBy(g => g.Type)
.ToList();
gGrid.Add((name, null, null, attrG.Min, attrG.Avg, attrG.Max, typeGroups.Count == 1 ? typeGroups.First().Type : null));
if (typeGroups.Count == 1) {
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Avg).ThenBy(g => g.SortId)) { foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Avg).ThenBy(g => g.SortId)) {
gGrid.Add((null, g.SortId, g.Min, g.Avg, g.Max)); gGrid.Add((null, null, g.SortId, g.Min, g.Avg, g.Max, null));
}
} else {
foreach (var t in typeGroups) {
gGrid.Add((null, t.Type == "R" ? "Rot" : "Weiß", null, t.Min, t.Avg, t.Max, t.Type));
foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult && g.Type == t.Type).OrderByDescending(g => g.Avg).ThenBy(g => g.SortId)) {
gGrid.Add((null, null, g.SortId, g.Min, g.Avg, g.Max, null));
}
}
} }
} }
if (attrGroups.Count == 1) { if (attrGroups.Count == 1) {
var g = attrGroups.First(); var g = attrGroups.First();
var name = g.Attr == null && g.Cult == null ? null : g.Attr + (g.Attr != null && g.Cult != null ? " / " : "") + g.Cult; var name = GetGroupName(g.Attr, g.Cult);
if (name != null) { if (name != null) {
wText += $" [{name}]"; wText += $" [{name}]";
gText += $" [{name}]"; gText += $" [{name}]";
@@ -1155,40 +1144,43 @@ namespace Elwig.Services {
} }
} else if (attrGroups.Count <= 4) { } else if (attrGroups.Count <= 4) {
wText += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / stat.Weight:0%})" + (g.Attr == null && g.Cult == null ? "" : $" [{g.Attr}{(g.Attr != null && g.Cult != null ? " / " : "")}{g.Cult}]")))}"; wText += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / stat.Weight:0%})" + (GetGroupName(g.Attr, g.Cult) is string s ? $" [{s}]" : "")))}";
gText += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Min:N1}/{g.Avg:N1}/{g.Max:N1}" + (g.Attr == null && g.Cult == null ? "" : $" [{g.Attr}{(g.Attr != null && g.Cult != null ? " / " : "")}{g.Cult}]")))}"; gText += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Min:N1}/{g.Avg:N1}/{g.Max:N1}" + (GetGroupName(g.Attr, g.Cult) is string s ? $" [{s}]" : "")))}";
} }
} }
return (wText, wGrid.ToArray(), gText, gGrid.ToArray()); return (wText, wGrid.ToArray(), gText, gGrid.ToArray());
} }
public static (Grid WeightGrid, Grid GradationGrid) GenerateToolTip((string?, string?, int, int?, int)[] weightData, (string?, string?, double, double, double)[] gradationData) { public static (Grid WeightGrid, Grid GradationGrid) GenerateToolTip((string?, string?, string?, int, int?, int?, int, string?)[] weightData, (string?, string?, string?, double, double, double, string?)[] gradationData) {
var wGrid = new Grid(); var wGrid = new Grid();
wGrid.ColumnDefinitions.Add(new() { Width = new(10) }); wGrid.ColumnDefinitions.Add(new() { Width = new(10) });
wGrid.ColumnDefinitions.Add(new() { Width = new(60) }); wGrid.ColumnDefinitions.Add(new() { Width = new(10) });
wGrid.ColumnDefinitions.Add(new() { Width = new(80) }); wGrid.ColumnDefinitions.Add(new() { Width = new(80) });
wGrid.ColumnDefinitions.Add(new() { Width = new(80) });
wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
wGrid.ColumnDefinitions.Add(new() { Width = new(50) }); wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
wGrid.ColumnDefinitions.Add(new() { Width = new(50) }); wGrid.ColumnDefinitions.Add(new() { Width = new(50) });
int rowNum = 0; int rowNum = 0;
foreach (var row in weightData) { foreach (var row in weightData) {
if (rowNum != 0 && row.Item2 == null) rowNum++; if (rowNum != 0 && row.Item2 == null && row.Item3 == null) rowNum++;
AddWeightToolTipRow(wGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5); AddWeightToolTipRow(wGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5, row.Item6, row.Item7, row.Item8);
} }
var gGrid = new Grid(); var gGrid = new Grid();
gGrid.ColumnDefinitions.Add(new() { Width = new(10) }); gGrid.ColumnDefinitions.Add(new() { Width = new(10) });
gGrid.ColumnDefinitions.Add(new() { Width = new(60) }); gGrid.ColumnDefinitions.Add(new() { Width = new(10) });
gGrid.ColumnDefinitions.Add(new() { Width = new(80) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) }); gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) }); gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
gGrid.ColumnDefinitions.Add(new() { Width = new(35) }); gGrid.ColumnDefinitions.Add(new() { Width = new(35) });
AddToolTipCell(gGrid, "Min.", 0, 2, 1, false, false, true); gGrid.AddToolTipCell("Min.", 0, 3, alignCenter: true);
AddToolTipCell(gGrid, "⌀", 0, 3, 1, false, false, true); gGrid.AddToolTipCell("⌀", 0, 4, alignCenter: true);
AddToolTipCell(gGrid, "Max.", 0, 4, 1, false, false, true); gGrid.AddToolTipCell("Max.", 0, 5, alignCenter: true);
rowNum = 1; rowNum = 1;
foreach (var row in gradationData) { foreach (var row in gradationData) {
if (rowNum != 1 && row.Item2 == null) rowNum++; if (rowNum != 1 && row.Item2 == null && row.Item3 == null) rowNum++;
AddGradationToolTipRow(gGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5); AddGradationToolTipRow(gGrid, rowNum++, row.Item1, row.Item2, row.Item3, row.Item4, row.Item5, row.Item6, row.Item7);
} }
return (wGrid, gGrid); return (wGrid, gGrid);
@@ -1219,12 +1211,11 @@ namespace Elwig.Services {
attrid = $"'{attr.AttrId}'"; attrid = $"'{attr.AttrId}'";
} }
var dids = await vm.GetDidsFromFilters(); var dids = await vm.GetDidsFromFilters();
var res = MessageBox.Show($"Soll wirklich für {dids.Length:N0} Teillieferung(en) das Attribut\n'{attributeName}' gesetz werden?", if (!InteractionService.AskContinue("Massenaktion: Attribut setzen",
"Massenaktion: Attribut setzen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); $"Soll wirklich für {dids.Length:N0} Teillieferung(en) das Attribut\n'{attributeName}' gesetz werden?"))
if (res != MessageBoxResult.OK) return; return;
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
using (var cnx = await AppDbContext.ConnectAsync()) { using (var cnx = await AppDbContext.ConnectAsync()) {
await cnx.ExecuteBatch($""" await cnx.ExecuteBatch($"""
UPDATE delivery_part SET attrid = {attrid} UPDATE delivery_part SET attrid = {attrid}
@@ -1234,9 +1225,7 @@ namespace Elwig.Services {
App.HintContextChange(); App.HintContextChange();
}); });
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException(exc);
} finally {
Mouse.OverrideCursor = null;
} }
} }
@@ -1249,12 +1238,11 @@ namespace Elwig.Services {
} }
var dids = await vm.GetDidsFromFilters(); var dids = await vm.GetDidsFromFilters();
var res = MessageBox.Show($"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' hinzugefügt werden?", if (!InteractionService.AskContinue("Massenaktion: Zu-/Abschlag hinzufügen",
"Massenaktion: Zu-/Abschlag hinzufügen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); $"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' hinzugefügt werden?"))
if (res != MessageBoxResult.OK) return; return;
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
using (var cnx = await AppDbContext.ConnectAsync()) { using (var cnx = await AppDbContext.ConnectAsync()) {
await cnx.ExecuteBatch($""" await cnx.ExecuteBatch($"""
INSERT INTO delivery_part_modifier (year, did, dpnr, modid) INSERT INTO delivery_part_modifier (year, did, dpnr, modid)
@@ -1265,9 +1253,7 @@ namespace Elwig.Services {
App.HintContextChange(); App.HintContextChange();
}); });
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException(exc);
} finally {
Mouse.OverrideCursor = null;
} }
} }
@@ -1280,12 +1266,11 @@ namespace Elwig.Services {
} }
var dids = await vm.GetDidsFromFilters(); var dids = await vm.GetDidsFromFilters();
var res = MessageBox.Show($"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' entfernt werden?", if (!InteractionService.AskContinue("Massenaktion: Zu-/Abschlag entfernen",
"Massenaktion: Zu-/Abschlag entfernen", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); $"Soll wirklich für {dids.Length:N0} Teillieferung(en) der Zu-/Abschlag\n'{modifierName}' entfernt werden?"))
if (res != MessageBoxResult.OK) return; return;
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
using (var cnx = await AppDbContext.ConnectAsync()) { using (var cnx = await AppDbContext.ConnectAsync()) {
await cnx.ExecuteBatch($""" await cnx.ExecuteBatch($"""
DELETE FROM delivery_part_modifier DELETE FROM delivery_part_modifier
@@ -1295,9 +1280,7 @@ namespace Elwig.Services {
App.HintContextChange(); App.HintContextChange();
}); });
} catch (Exception exc) { } catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); InteractionService.ShowException(exc);
} finally {
Mouse.OverrideCursor = null;
} }
} }
} }
+191
View File
@@ -0,0 +1,191 @@
using Microsoft.Data.Sqlite;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Windows;
namespace Elwig.Services {
public static class InteractionService {
public static Func<string, string, string, string?>? Override;
public static Action<string, string>? NextInformation;
public static Action<string, string>? NextWarning;
public static Action<string, string>? NextError;
public static Func<string, string, bool>? NextContinue;
public static Func<string, string, bool>? NextConfirmation;
public static Func<string, string, bool>? NextQuestion;
public static Func<string, string, string?>? NextSave;
public static readonly Dictionary<string, string> ExtensionFilters = new() {
["pdf"] = "PDF-Datei (*.pdf)|*.pdf",
["ods"] = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
["vcf"] = "vCard-Datei (*.vcf)|*.vcf",
["xml"] = "EBICS-Datei (*.xml)|*.xml",
["csv"] = "CSV-Datei (*.csv)|*.csv",
["sql.zip"] = "Komprimierte SQL-Datei (*.sql.zip)|*.sql.zip",
["elwig.zip"] = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip",
};
public static void ShowInformation(string title, string text) {
if (NextInformation != null) {
NextInformation(title, text);
NextInformation = null;
} else if (Override != null) {
Override("information", title, text);
} else {
MessageBox.Show(text, title, MessageBoxButton.OK, MessageBoxImage.Information);
}
}
public static bool AskContinue(string title, string text) {
if (NextContinue != null) {
var r = NextContinue(title, text);
NextContinue = null;
return r;
} else if (Override != null) {
return Override("continue", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel) == MessageBoxResult.OK;
}
}
public static bool? AskContinueYesNo(string title, string text) {
if (NextContinue != null) {
var r = NextContinue(title, text);
NextContinue = null;
return r;
} else if (Override != null) {
return Override("continue", title, text) != null;
} else {
var r = MessageBox.Show(text, title, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
return r == MessageBoxResult.Yes ? true : r == MessageBoxResult.No ? false : null;
}
}
public static bool AskConfirmation(string title, string text) {
if (NextConfirmation != null) {
var r = NextConfirmation(title, text);
NextConfirmation = null;
return r;
} else if (Override != null) {
return Override("confirm", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No) == MessageBoxResult.Yes;
}
}
public static bool AskQuestion(string title, string text, bool defaultResult) {
if (NextQuestion != null) {
var r = NextQuestion(title, text);
NextQuestion = null;
return r;
} else if (Override != null) {
return Override("question", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.YesNo, MessageBoxImage.Question, defaultResult ? MessageBoxResult.Yes : MessageBoxResult.No) == MessageBoxResult.Yes;
}
}
public static void ShowWarning(string title, string text) {
if (NextWarning != null) {
NextWarning(title, text);
NextWarning = null;
} else if (Override != null) {
Override("warning", title, text);
} else {
MessageBox.Show(text, title, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
public static void ShowError(string title, string text) {
if (NextError != null) {
NextError(title, text);
NextError = null;
} else if (Override != null) {
Override("error", title, text);
} else {
MessageBox.Show(text, title, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public static void ShowException(Exception exc, bool showExcType = false, bool isError = true) {
ShowException("Fehler", exc, showExcType, isError);
}
public static void ShowException(string title, Exception exc, bool showExcType = false, bool isError = true) {
ShowException(title, null, exc, showExcType, isError);
}
public static void ShowException(string title, string? text, Exception exc, bool showExcType = false, bool isError = true) {
text = text == null ? "" : text + (text.EndsWith('.') || text.EndsWith('!') || text.EndsWith('?') ? "" : ":") + "\n\n";
if ((exc.InnerException ?? exc) is SqliteException sql) {
if (sql.SqliteErrorCode == 5 || sql.SqliteErrorCode == 6) {
// SQLITE_BUSY / SQLITE_LOCKED
text += "Die Datenbank wurde gerade von einem anderen Gerät/Prozess verwendet. Bitte noch einmal versuchen!\n\n" + sql.Message;
} else if (sql.SqliteErrorCode == 19) {
// SQLITE_CONSTRAINT
switch (sql.SqliteExtendedErrorCode) {
case 1555: // SQLITE_CONSTRAINT_PRIMARYKEY - indicating that a PRIMARY KEY constraint failed
case 2067: // SQLITE_CONSTRAINT_UNIQUE - indicating that a UNIQUE constraint failed
case 2579: // SQLITE_CONSTRAINT_ROWID - indicating that a rowid is not unique
text += "Dieser Datensatz existiert bereits!\n\n";
break;
case 787: // SQLITE_CONSTRAINT_FOREIGNKEY - indicating that a foreign key constraint failed
case 1299: // SQLITE_CONSTRAINT_NOTNULL - indicating that a NOT NULL constraint failed
case 275: // SQLITE_CONSTRAINT_CHECK - indicating that a CHECK constraint failed
case 1811: // SQLITE_CONSTRAINT_TRIGGER - indicating that a RAISE function within a trigger fired, causing the SQL statement to abort
case 531: // SQLITE_CONSTRAINT_COMMITHOOK - indicating that a commit hook callback returned non-zero that thus caused the SQL statement to be rolled back
text += "Der Datensatz ist inkonsistent!\n\n";
break;
}
text += sql.Message;
} else {
text += exc.Message + (showExcType ? $" ({exc.GetType().Name})" : "");
if (exc.InnerException != null) text += "\n\n" + exc.InnerException.Message;
}
} else {
text += exc.Message + (showExcType ? $" ({exc.GetType().Name})" : "");
if (exc.InnerException != null) text += "\n\n" + exc.InnerException.Message;
}
if (isError) {
ShowError(title, text);
} else {
ShowWarning(title, text);
}
}
public static void ShowDbException(string title, Exception exc) {
ShowException(title, "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!", exc);
}
public static bool AskException(string title, string? text, Exception exc) {
text = text == null ? "" : text + (text.EndsWith('.') || text.EndsWith('!') || text.EndsWith('?') ? "" : ":") + "\n\n";
text += exc.Message;
if (exc.InnerException != null) text += "\n\n" + exc.InnerException.Message;
if (Override != null) {
return Override("error", title, text) != null;
} else {
return MessageBox.Show(text, title, MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No) == MessageBoxResult.Yes;
}
}
public static string? SaveFile(string title, string defaultFileName, string extension) {
if (NextSave != null) {
var r = NextSave(title, $"{defaultFileName}.{extension}");
NextSave = null;
return r;
} else if (Override != null) {
return Override("save", title, $"{defaultFileName}.{extension}");
} else {
var d = new SaveFileDialog() {
FileName = $"{defaultFileName}.{extension}",
DefaultExt = extension,
Filter = ExtensionFilters.GetValueOrDefault(extension, ""),
Title = $"{title} speichern unter - Elwig",
AddExtension = !extension.Contains('.'),
};
return d.ShowDialog() == true ? d.FileName : null;
}
}
}
}
@@ -0,0 +1,121 @@
using Elwig.Helpers;
using Elwig.Helpers.Export;
using Elwig.Models;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Elwig.Services {
public static class MemberBusinessSharesService {
public enum ExportSubject {
Pending,
};
public static async Task InitInputs(this MemberBusinessSharesViewModel vm) {
using var ctx = new AppDbContext();
vm.DateNoticeString = $"{DateTime.Today:dd.MM.yyyy}";
vm.ValuePerShare = (await ctx.Seasons.OrderBy(s => s.Year).LastOrDefaultAsync())?.BusinessShareValue;
}
public static void ClearInputs(this MemberBusinessSharesViewModel vm) {
}
public static void FillInputs(this MemberBusinessSharesViewModel vm, MemberHistory h) {
var mgnr = vm.FilterMember.MgNr;
vm.DateNotice = h.DateNotice;
vm.DateEffective = h.DateEffective;
vm.Reason = h.Reason + (h.Reason == MemberHistory.REASON_TRANSFER ? $"_{(h.ToMgNr == mgnr ? "from" : "to")}" : "");
vm.Shares = h.Shares;
vm.CompanyHasSigned = h.CompanyHasSigned;
vm.MemberHasSigned = h.MemberHasSigned;
vm.ValuePerShare = h.ValuePerShare;
vm.OtherMgNr = h.FromMgNr != null && h.FromMgNr != mgnr ? h.FromMgNr : h.ToMgNr != null && h.ToMgNr != mgnr ? h.ToMgNr : null;
vm.DeductYear = h.DeductYear ?? h.DateNotice.Year;
vm.Deduct = h.DeductYear != null;
vm.Comment = h.Comment;
}
public static async Task<int> UpdateMemberHistory(this MemberBusinessSharesViewModel vm, int? oldHistNr, bool enableTriggers) {
return await Task.Run(async () => {
using var ctx = new AppDbContext();
var tx = await ctx.Database.BeginTransactionAsync();
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = {(enableTriggers ? "1" : "0")} WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
var h = new MemberHistory {
HistNr = oldHistNr ?? await ctx.NextHistNr(),
FromMgNr =
vm.Reason == $"{MemberHistory.REASON_TRANSFER}_from" ? vm.OtherMgNr :
vm.Reason == MemberHistory.REASON_PURCHASE || vm.Reason == MemberHistory.REASON_PRESCRIPTION ? null :
vm.FilterMember.MgNr,
FromType =
vm.Reason == MemberHistory.REASON_PAYOUT ? BusinessShareType.Dormant :
vm.Reason == MemberHistory.REASON_PURCHASE || vm.Reason == MemberHistory.REASON_PRESCRIPTION ? null :
BusinessShareType.Normal,
ToMgNr =
vm.Reason == MemberHistory.REASON_PAYOUT || vm.Reason == MemberHistory.REASON_EXCLUSION ? null :
vm.Reason == $"{MemberHistory.REASON_TRANSFER}_to" ? vm.OtherMgNr :
vm.FilterMember.MgNr,
ToType =
vm.Reason == MemberHistory.REASON_PAYOUT || vm.Reason == MemberHistory.REASON_EXCLUSION ? null :
vm.Reason == MemberHistory.REASON_RETIREMENT || vm.Reason == MemberHistory.REASON_DECEASED ? BusinessShareType.Dormant :
BusinessShareType.Normal,
DateNoticeString = $"{vm.DateNotice:yyyy-MM-dd}",
DateEffectiveString = $"{vm.DateEffective:yyyy-MM-dd}",
Reason = vm.Reason?.Split("_")[0] ?? MemberHistory.REASON_CONVERT,
Source = "manual",
Shares = vm.Shares ?? 0,
CompanyHasSigned = vm.CompanyHasSigned,
MemberHasSigned = vm.MemberHasSigned,
ValuePerShare = vm.ValuePerShare,
CurrencyCode = vm.ValuePerShare != null ? "EUR" : null,
DeductYear = vm.Deduct ? vm.DeductYear : null,
Comment = string.IsNullOrWhiteSpace(vm.Comment) ? null : vm.Comment.Trim(),
};
if (oldHistNr != null) {
ctx.Update(h);
} else {
ctx.Add(h);
}
await ctx.SaveChangesAsync();
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
await tx.CommitAsync();
return h.HistNr;
});
}
public static async Task DeleteMemberHistory(int histnr, bool enableTriggers) {
await Task.Run(async () => {
using var ctx = new AppDbContext();
var tx = await ctx.Database.BeginTransactionAsync();
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = {(enableTriggers ? "1" : "0")} WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
await ctx.MemberHistory.Where(h => h.HistNr == histnr).ExecuteDeleteAsync();
await ctx.Database.ExecuteSqlAsync($"UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_MEMBER_HISTORY_TRIGGERS'");
await tx.CommitAsync();
});
}
public static async Task GenerateMemberMovements(ExportSubject subject, ExportMode mode) {
var today = DateTime.Today;
var filename = InteractionService.SaveFile($"Ausstehende Mitgl.-Bewegungen {today:dd.MM.yyyy}", $"MitglBewegungen_{today:yyyy-MM-dd}", "ods");
if (filename == null)
return;
await Utils.RunForeground(async () => {
using var ctx = new AppDbContext();
var hist = await ctx.MemberHistory.Where(h => !h.CompanyHasSigned).ToListAsync();
var data = MemberMovementData.FromMemberHistory($"Ausstehend ({today:dd.MM.yyyy})", hist, await ctx.GetMemberHistory());
using var ods = new OdsFile(filename);
await ods.AddTable(data);
});
}
}
}
+180 -140
View File
@@ -6,13 +6,11 @@ using Elwig.Models.Dtos;
using Elwig.Models.Entities; using Elwig.Models.Entities;
using Elwig.ViewModels; using Elwig.ViewModels;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows.Media;
using System.Windows.Input;
namespace Elwig.Services { namespace Elwig.Services {
public static class MemberService { public static class MemberService {
@@ -22,6 +20,7 @@ namespace Elwig.Services {
}; };
public static async Task InitInputs(this MemberAdminViewModel vm) { public static async Task InitInputs(this MemberAdminViewModel vm) {
vm.IsMemberSelectedOrCreating = true;
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
vm.MgNr = await ctx.NextMgNr(); vm.MgNr = await ctx.NextMgNr();
vm.EntryDate = DateTime.Now.ToString("dd.MM.yyyy"); vm.EntryDate = DateTime.Now.ToString("dd.MM.yyyy");
@@ -34,6 +33,7 @@ namespace Elwig.Services {
public static void ClearInputs(this MemberAdminViewModel vm) { public static void ClearInputs(this MemberAdminViewModel vm) {
vm.IsMemberSelected = false; vm.IsMemberSelected = false;
vm.IsMemberSelectedOrCreating = false;
vm.MemberHasEmail = false; vm.MemberHasEmail = false;
vm.MemberCanSendEmail = false; vm.MemberCanSendEmail = false;
vm.EnableMemberReferenceButton = false; vm.EnableMemberReferenceButton = false;
@@ -52,10 +52,15 @@ namespace Elwig.Services {
vm.ModifiedAtShort = "-"; vm.ModifiedAtShort = "-";
vm.ExportedAt = "-"; vm.ExportedAt = "-";
vm.ImportedAt = "-"; vm.ImportedAt = "-";
vm.OrganicState = null;
vm.OrganicColor = Brushes.Black;
vm.OrganicDetails = "-";
vm.OrganicToolTip = null;
} }
public static void FillInputs(this MemberAdminViewModel vm, Member m) { public static void FillInputs(this MemberAdminViewModel vm, Member m) {
vm.IsMemberSelected = true; vm.IsMemberSelected = true;
vm.IsMemberSelectedOrCreating = true;
vm.MgNr = m.MgNr; vm.MgNr = m.MgNr;
vm.PredecessorMgNr = m.PredecessorMgNr; vm.PredecessorMgNr = m.PredecessorMgNr;
vm.IsJuridicalPerson = m.IsJuridicalPerson; vm.IsJuridicalPerson = m.IsJuridicalPerson;
@@ -109,7 +114,9 @@ namespace Elwig.Services {
vm.UstIdNr = m.UstIdNr; vm.UstIdNr = m.UstIdNr;
vm.LfbisNr = m.LfbisNr; vm.LfbisNr = m.LfbisNr;
vm.IsBuchführend = m.IsBuchführend; vm.OrganicOperatorId = m.OrganicOperatorId;
vm.OrganicAuthorityCode = m.OrganicAuthorityCode;
vm.IsAccounting = m.IsAccounting;
vm.IsOrganic = m.IsOrganic; vm.IsOrganic = m.IsOrganic;
var billingAddr = m.BillingAddress; var billingAddr = m.BillingAddress;
@@ -129,14 +136,22 @@ namespace Elwig.Services {
vm.EntryDate = (m.EntryDateString != null) ? string.Join(".", m.EntryDateString.Split("-").Reverse()) : null; vm.EntryDate = (m.EntryDateString != null) ? string.Join(".", m.EntryDateString.Split("-").Reverse()) : null;
vm.ExitDate = (m.ExitDateString != null) ? string.Join(".", m.ExitDateString.Split("-").Reverse()) : null; vm.ExitDate = (m.ExitDateString != null) ? string.Join(".", m.ExitDateString.Split("-").Reverse()) : null;
vm.BusinessShares = m.BusinessShares; if (App.Client.HasRedWhite) {
vm.BusinessShares1 = m.SharesRed;
vm.BusinessShares2 = m.SharesWhite;
vm.BusinessShares3 = m.SharesDormant + m.Shares;
} else {
vm.BusinessShares1 = m.Shares;
vm.BusinessShares2 = m.SharesDormant + m.SharesRed + m.SharesWhite;
vm.BusinessShares3 = 0;
}
vm.AccountingNr = m.AccountingNr; vm.AccountingNr = m.AccountingNr;
vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, m.ZwstId); vm.Branch = (Branch?)ControlUtils.GetItemFromSourceWithPk(vm.BranchSource, m.ZwstId);
vm.DefaultKg = (AT_Kg?)ControlUtils.GetItemFromSourceWithPk(vm.DefaultKgSource, m.DefaultKgNr); vm.DefaultKg = (AT_Kg?)ControlUtils.GetItemFromSourceWithPk(vm.DefaultKgSource, m.DefaultKgNr);
vm.Comment = m.Comment; vm.Comment = m.Comment;
vm.IsActive = m.IsActive; vm.IsActive = m.IsActive;
vm.IsVollLieferant = m.IsVollLieferant; vm.IsFullDelivery = m.IsFullDelivery;
vm.IsFunktionär = m.IsFunktionär; vm.IsOfficer = m.IsOfficer;
vm.ContactViaPost = m.ContactViaPost; vm.ContactViaPost = m.ContactViaPost;
vm.ContactViaEmail = m.ContactViaEmail; vm.ContactViaEmail = m.ContactViaEmail;
@@ -175,20 +190,19 @@ namespace Elwig.Services {
var s = await ctx.FetchSeasons().FirstOrDefaultAsync(); var s = await ctx.FetchSeasons().FirstOrDefaultAsync();
if (s != null) maxKgPerHa = s.MaxKgPerHa; if (s != null) maxKgPerHa = s.MaxKgPerHa;
} catch { } } catch { }
var (text, gridData) = await AreaComService.GenerateToolTipData(c, maxKgPerHa); var (text, gridData) = await AreaCmtService.GenerateToolTipData(c, maxKgPerHa);
var deliveries = await ctx.Deliveries var deliveries = await ctx.Deliveries
.Where(d => d.MgNr == m.MgNr) .Where(d => d.MgNr == m.MgNr)
.GroupBy(d => d.Year) .GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Any()); .ToDictionaryAsync(g => g.Key, g => g.Any());
await App.MainDispatcher.BeginInvoke(() => {
if (m.MgNr != vm.MgNr) if (m.MgNr != vm.MgNr)
return; return;
await App.MainDispatcher.BeginInvoke(() => {
var (d1Grid, _) = DeliveryService.GenerateToolTip(d1GridData, []); var (d1Grid, _) = DeliveryService.GenerateToolTip(d1GridData, []);
var (d2Grid, _) = DeliveryService.GenerateToolTip(d2GridData, []); var (d2Grid, _) = DeliveryService.GenerateToolTip(d2GridData, []);
var grid = AreaComService.GenerateToolTip(gridData); var grid = AreaCmtService.GenerateToolTip(gridData);
vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}"; vm.StatusDeliveriesLastSeasonInfo = $"{Utils.CurrentLastSeason - 1}";
vm.StatusDeliveriesLastSeason = textLast; vm.StatusDeliveriesLastSeason = textLast;
vm.StatusDeliveriesLastSeasonToolTip = d1Grid; vm.StatusDeliveriesLastSeasonToolTip = d1Grid;
@@ -198,13 +212,76 @@ namespace Elwig.Services {
vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}"; vm.StatusAreaCommitmentInfo = $"{Utils.CurrentLastSeason}";
vm.StatusAreaCommitment = text; vm.StatusAreaCommitment = text;
vm.StatusAreaCommitmentToolTip = grid; vm.StatusAreaCommitmentToolTip = grid;
vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => deliveries.GetValueOrDefault(i, false)).ToList(); vm.MemberHasDeliveries = [.. Enumerable.Range(0, 9999).Select(i => deliveries.GetValueOrDefault(i, false))];
}); });
}); });
vm.OrganicState = null;
vm.OrganicColor = Brushes.Black;
vm.OrganicToolTip = null;
if (!vm.IsOrganic) {
vm.OrganicDetails = "-";
} else {
vm.OrganicDetails = "...";
Utils.RunBackground("Bio-Zertifikatsdaten laden", async () => {
if (App.MainDispatcher == null) return;
var certs = await OrganicService.GetTracesCertificates(m);
await App.MainDispatcher.BeginInvoke(() => {
if (m.MgNr != vm.MgNr) return;
vm.FillOrganicInputs(certs.FirstOrDefault(c => c.IsValidForWineProduction));
});
});
}
vm.MemberHasEmail = m.EmailAddresses.Count > 0; vm.MemberHasEmail = m.EmailAddresses.Count > 0;
vm.MemberCanSendEmail = App.Config.Smtp != null && m.EmailAddresses.Count > 0; vm.MemberCanSendEmail = App.Config.Smtp != null && m.EmailAddresses.Count > 0;
vm.MemberHasDeliveries = Enumerable.Range(0, 9999).Select(i => false).ToList(); vm.MemberHasDeliveries = [.. Enumerable.Range(0, 9999).Select(i => false)];
}
public static void FillOrganicInputs(this MemberAdminViewModel vm, OrganicService.TracesCertificate? cert) {
vm.OrganicDetails = null;
if (cert == null) {
vm.OrganicState = "Nicht gefunden";
vm.OrganicColor = Brushes.DarkRed;
vm.OrganicToolTip = null;
return;
}
vm.OrganicToolTip = $"""
Zertifikat-Nr.: {cert.Id}
Status: {cert.Status}
Bio-KSt.: {cert.AuthorityCode}
EU Operator-ID: {cert.OperatorId}
Operator: {cert.OperatorName}
Ausgestellt am: {cert.IssuedOn:dd.MM.yyyy}
Aktualisiert am: {cert.LastStatusUpdateOn:dd.MM.yyyy}
Erlischt am: {cert.ExpiresOn:dd.MM.yyyy}
""";
if (cert.IsValidForWineProduction) {
vm.OrganicState = "Gültig";
vm.OrganicDetails = $" ({cert.ExpiresOn:MMM. yyyy})";
vm.OrganicColor = Brushes.DarkGreen;
} else if (cert.IsValid) {
vm.OrganicState = "Gültig";
vm.OrganicDetails = $" ({cert.ExpiresOn:MMM. yyyy})";
vm.OrganicColor = Brushes.OrangeRed;
} else {
vm.OrganicColor = Brushes.DarkRed;
switch (cert.Status) {
case OrganicService.TracesStatus.ISSUED:
vm.OrganicState = "Ausgestellt";
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
case OrganicService.TracesStatus.SUSPENDED:
vm.OrganicState = "Gesperrt";
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
case OrganicService.TracesStatus.EXPIRED:
vm.OrganicState = "Ausgelaufen";
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
case OrganicService.TracesStatus.WITHDRAWN:
vm.OrganicState = "Widerrufen";
vm.OrganicDetails = $" ({cert.LastStatusUpdateOn:MMM. yyyy})"; break;
}
}
} }
public static async Task<(List<string>, IQueryable<Member>, List<string>)> GetFilters(this MemberAdminViewModel vm, AppDbContext ctx) { public static async Task<(List<string>, IQueryable<Member>, List<string>)> GetFilters(this MemberAdminViewModel vm, AppDbContext ctx) {
@@ -220,15 +297,15 @@ namespace Elwig.Services {
var filterKgNr = new List<int>(); var filterKgNr = new List<int>();
var filterLfbisNr = new List<string>(); var filterLfbisNr = new List<string>();
var filterUstIdNr = new List<string>(); var filterUstIdNr = new List<string>();
var filterAreaCom = new List<string>(); var filterAreaCmt = new List<string>();
var filterNotAreaCom = new List<string>(); var filterNotAreaCmt = new List<string>();
var filter = vm.TextFilter; var filter = vm.TextFilter;
if (filter.Count > 0) { if (filter.Count > 0) {
var branches = await ctx.FetchBranches().ToListAsync(); var branches = await ctx.FetchBranches().ToListAsync();
var mgnr = await ctx.FetchMembers(includeNotActive: true).ToDictionaryAsync(m => m.MgNr.ToString(), m => m); var mgnr = await ctx.FetchMembers(includeNotActive: true).ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
var kgs = await ctx.WbKgs.ToDictionaryAsync(k => k.AtKg.Name.ToLower(), k => k.AtKg); var kgs = await ctx.WbKgs.ToDictionaryAsync(k => k.AtKg.Name.ToLower(), k => k.AtKg);
var areaComs = await ctx.AreaCommitmentTypes.ToDictionaryAsync(t => $"{t.SortId}{t.AttrId}", t => t); var areaCmts = await ctx.AreaCommitmentTypes.ToDictionaryAsync(t => $"{t.SortId}{t.AttrId}", t => t);
for (int i = 0; i < filter.Count; i++) { for (int i = 0; i < filter.Count; i++) {
var e = filter[i]; var e = filter[i];
@@ -242,11 +319,11 @@ namespace Elwig.Services {
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("inaktive Mitglieder"); filterNames.Add("inaktive Mitglieder");
} else if (e.Length >= 5 && e.Length <= 10 && "funktionär".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 5 && e.Length <= 10 && "funktionär".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsFunktionär); memberQuery = memberQuery.Where(m => m.IsOfficer);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Funktionäre"); filterNames.Add("Funktionäre");
} else if (e.Length >= 6 && e.Length <= 11 && e[0] == '!' && "funktionär".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 6 && e.Length <= 11 && e[0] == '!' && "funktionär".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsFunktionär); memberQuery = memberQuery.Where(m => !m.IsOfficer);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Nicht-Funktionäre"); filterNames.Add("Nicht-Funktionäre");
} else if (e.Equals("bio", StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Equals("bio", StringComparison.CurrentCultureIgnoreCase)) {
@@ -257,28 +334,36 @@ namespace Elwig.Services {
memberQuery = memberQuery.Where(m => !m.IsOrganic); memberQuery = memberQuery.Where(m => !m.IsOrganic);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Nicht-Bio-Betriebe"); filterNames.Add("Nicht-Bio-Betriebe");
} else if (e.Length >= 4 && e.Length <= 14 && "vollablieferer".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsFullDelivery);
filter.RemoveAt(i--);
filterNames.Add("Vollablieferer");
} else if (e.Length >= 4 && e.Length <= 13 && "volllieferant".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 4 && e.Length <= 13 && "volllieferant".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsVollLieferant); memberQuery = memberQuery.Where(m => m.IsFullDelivery);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Volllieferanten"); filterNames.Add("Vollablieferer");
} else if (e.Length >= 5 && e.Length <= 15 && e[0] == '!' && "vollablieferer".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsFullDelivery);
filter.RemoveAt(i--);
filterNames.Add("Nicht-Vollablieferer");
} else if (e.Length >= 5 && e.Length <= 14 && e[0] == '!' && "volllieferant".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 5 && e.Length <= 14 && e[0] == '!' && "volllieferant".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsVollLieferant); memberQuery = memberQuery.Where(m => !m.IsFullDelivery);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("Nicht-Vollieferanten"); filterNames.Add("Nicht-Vollablieferer");
} else if (e.Length >= 5 && e.Length <= 11 && "buchführend".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 5 && e.Length <= 11 && "buchführend".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsBuchführend); memberQuery = memberQuery.Where(m => m.IsAccounting);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("buchführend"); filterNames.Add("buchführend");
} else if (e.Length >= 6 && e.Length <= 12 && e[0] == '!' && "buchführend".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 6 && e.Length <= 12 && e[0] == '!' && "buchführend".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsBuchführend); memberQuery = memberQuery.Where(m => !m.IsAccounting);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("pauschaliert"); filterNames.Add("pauschaliert");
} else if (e.Length >= 8 && e.Length <= 12 && "pauschaliert".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 8 && e.Length <= 12 && "pauschaliert".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => !m.IsBuchführend); memberQuery = memberQuery.Where(m => !m.IsAccounting);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("pauschaliert"); filterNames.Add("pauschaliert");
} else if (e.Length >= 9 && e.Length <= 13 && e[0] == '!' && "pauschaliert".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 9 && e.Length <= 13 && e[0] == '!' && "pauschaliert".StartsWith(e[1..], StringComparison.CurrentCultureIgnoreCase)) {
memberQuery = memberQuery.Where(m => m.IsBuchführend); memberQuery = memberQuery.Where(m => m.IsAccounting);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add("buchführend"); filterNames.Add("buchführend");
} else if (e.Equals("email", StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Equals("email", StringComparison.CurrentCultureIgnoreCase)) {
@@ -349,19 +434,19 @@ namespace Elwig.Services {
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"Tel.-Nr. {e}"); filterNames.Add($"Tel.-Nr. {e}");
} else if (e.Length >= 5 && e.Length <= 14 && "flächenbindung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 5 && e.Length <= 14 && "flächenbindung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
filterAreaCom.AddRange(areaComs.Keys); filterAreaCmt.AddRange(areaCmts.Keys);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"Flächenbindung"); filterNames.Add($"Flächenbindung");
} else if (e.Length >= 6 && e.Length <= 15 && "!flächenbindung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) { } else if (e.Length >= 6 && e.Length <= 15 && "!flächenbindung".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
filterNotAreaCom.AddRange(areaComs.Keys); filterNotAreaCmt.AddRange(areaCmts.Keys);
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"keine Flächenbindung"); filterNames.Add($"keine Flächenbindung");
} else if (areaComs.ContainsKey(e.ToUpper())) { } else if (areaCmts.ContainsKey(e.ToUpper())) {
filterAreaCom.Add(e.ToUpper()); filterAreaCmt.Add(e.ToUpper());
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"Flächenbindung {e.ToUpper()}"); filterNames.Add($"Flächenbindung {e.ToUpper()}");
} else if (e.Length >= 3 && e[0] == '!' && areaComs.ContainsKey(e[1..].ToUpper())) { } else if (e.Length >= 3 && e[0] == '!' && areaCmts.ContainsKey(e[1..].ToUpper())) {
filterNotAreaCom.Add(e[1..].ToUpper()); filterNotAreaCmt.Add(e[1..].ToUpper());
filter.RemoveAt(i--); filter.RemoveAt(i--);
filterNames.Add($"ohne Flächenbindung {e[1..].ToUpper()}"); filterNames.Add($"ohne Flächenbindung {e[1..].ToUpper()}");
} else if (Validator.CheckLfbisNr(e)) { } else if (Validator.CheckLfbisNr(e)) {
@@ -382,8 +467,8 @@ namespace Elwig.Services {
if (filterMgNr.Count > 0) memberQuery = memberQuery.Where(m => filterMgNr.Contains(m.MgNr)); if (filterMgNr.Count > 0) memberQuery = memberQuery.Where(m => filterMgNr.Contains(m.MgNr));
if (filterKgNr.Count > 0) memberQuery = memberQuery.Where(m => m.DefaultKgNr != null && filterKgNr.Contains((int)m.DefaultKgNr)); if (filterKgNr.Count > 0) memberQuery = memberQuery.Where(m => m.DefaultKgNr != null && filterKgNr.Contains((int)m.DefaultKgNr));
if (filterZwst.Count > 0) memberQuery = memberQuery.Where(m => m.ZwstId != null && filterZwst.Contains(m.ZwstId)); if (filterZwst.Count > 0) memberQuery = memberQuery.Where(m => m.ZwstId != null && filterZwst.Contains(m.ZwstId));
if (filterAreaCom.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments(Utils.CurrentLastSeason)).Any(c => filterAreaCom.Contains(c.VtrgId))); if (filterAreaCmt.Count > 0) memberQuery = memberQuery.Where(m => m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments(Utils.CurrentLastSeason)).Any(c => filterAreaCmt.Contains(c.VtrgId)));
if (filterNotAreaCom.Count > 0) memberQuery = memberQuery.Where(m => !m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments(Utils.CurrentLastSeason)).All(c => filterNotAreaCom.Contains(c.VtrgId))); if (filterNotAreaCmt.Count > 0) memberQuery = memberQuery.Where(m => !m.AreaCommitments.AsQueryable().Where(Utils.ActiveAreaCommitments(Utils.CurrentLastSeason)).All(c => filterNotAreaCmt.Contains(c.VtrgId)));
if (filterLfbisNr.Count > 0) memberQuery = memberQuery.Where(m => m.LfbisNr != null && filterLfbisNr.Contains(m.LfbisNr)); if (filterLfbisNr.Count > 0) memberQuery = memberQuery.Where(m => m.LfbisNr != null && filterLfbisNr.Contains(m.LfbisNr));
if (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr)); if (filterUstIdNr.Count > 0) memberQuery = memberQuery.Where(m => m.UstIdNr != null && filterUstIdNr.Contains(m.UstIdNr));
} }
@@ -392,22 +477,14 @@ namespace Elwig.Services {
} }
public static async Task GenerateMemberDataSheet(Member m, ExportMode mode) { public static async Task GenerateMemberDataSheet(Member m, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
using var doc = new MemberDataSheet(m); using var doc = new MemberDataSheet(m);
await Utils.ExportDocument(doc, mode, emailData: (m, MemberDataSheet.Name, "Im Anhang finden Sie das aktuelle Stammdatenblatt")); await Utils.ExportDocument(doc, mode, emailData: (m, MemberDataSheet.Name, "Im Anhang finden Sie das aktuelle Stammdatenblatt"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
public static async Task GenerateDeliveryConfirmation(Member m, int year, ExportMode mode) { public static async Task GenerateDeliveryConfirmation(Member m, int year, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
var b = await Billing.Create(year); var b = await Billing.Create(year);
await b.FinishSeason(); await b.FinishSeason();
await b.CalculateBuckets(); await b.CalculateBuckets();
@@ -415,24 +492,14 @@ namespace Elwig.Services {
using var doc = new DeliveryConfirmation(year, m, null); using var doc = new DeliveryConfirmation(year, m, null);
await Utils.ExportDocument(doc, mode, emailData: (m, $"{DeliveryConfirmation.Name} {year}", $"Im Anhang finden Sie die Anlieferungsbestätigung {year}")); await Utils.ExportDocument(doc, mode, emailData: (m, $"{DeliveryConfirmation.Name} {year}", $"Im Anhang finden Sie die Anlieferungsbestätigung {year}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
public static async Task GenerateCreditNote(Member m, int year, int avnr, ExportMode mode) { public static async Task GenerateCreditNote(Member m, int year, int avnr, ExportMode mode) {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
using var doc = await CreditNote.Initialize(year, avnr, m.MgNr, null); using var doc = await CreditNote.Initialize(year, avnr, m.MgNr, null);
await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {doc.Payment.Variant.Name}", $"Im Anhang finden Sie die Traubengutschrift {doc.Payment.Variant.Name}")); await Utils.ExportDocument(doc, mode, emailData: (m, $"{CreditNote.Name} {doc.Payment.Variant.Name}", $"Im Anhang finden Sie die Traubengutschrift {doc.Payment.Variant.Name}"));
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
public static async Task GenerateMemberList(this MemberAdminViewModel vm, ExportSubject subject, ExportMode mode) { public static async Task GenerateMemberList(this MemberAdminViewModel vm, ExportSubject subject, ExportMode mode) {
@@ -477,68 +544,48 @@ namespace Elwig.Services {
} }
if (mode == ExportMode.SaveList) { if (mode == ExportMode.SaveList) {
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile(MemberList.Name, MemberList.Name, "ods");
FileName = $"{MemberList.Name}.ods", if (filename != null) {
DefaultExt = "ods", await Utils.RunForeground(async () => {
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"{MemberList.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1])); var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var ods = new OdsFile(d.FileName); using var ods = new OdsFile(filename);
await ods.AddTable(data); await ods.AddTable(data);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} else if (mode == ExportMode.Vcf) { } else if (mode == ExportMode.Vcf) {
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile("Kontakte", "Mitglieder", "vcf");
FileName = "Mitglieder.vcf", if (filename != null) {
DefaultExt = "vcf", await Utils.RunForeground(async () => {
Filter = "vCard-Datei (*.vcf)|*.vcf",
Title = "Kontakte speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var members = await query var members = await query
.OrderBy(m => m.MgNr) .OrderBy(m => m.MgNr)
.Include(m => m.TelephoneNumbers) .Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses) .Include(m => m.EmailAddresses)
.ToListAsync(); .ToListAsync();
using var exporter = new VCard(d.FileName); using var exporter = new VCard(filename);
await exporter.ExportAsync(members); await exporter.ExportAsync(members);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} else if (mode == ExportMode.Export) { } else if (mode == ExportMode.Export) {
var d = new SaveFileDialog() { var filename = InteractionService.SaveFile(MemberList.Name, subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}", "elwig.zip");
FileName = subject == ExportSubject.Selected ? $"Mitglied_{vm.SelectedMember?.MgNr}.elwig.zip" : $"Mitglieder_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.elwig.zip", if (filename != null) {
DefaultExt = "elwig.zip", if (!filename.EndsWith(".elwig.zip")) filename += ".elwig.zip";
Filter = "Elwig-Export-Datei (*.elwig.zip)|*.elwig.zip", await Utils.RunForeground(async () => {
Title = $"{MemberList.Name} speichern unter - Elwig",
AddExtension = false,
};
if (d.ShowDialog() == true) {
if (!d.FileName.EndsWith(".elwig.zip")) d.FileName += ".elwig.zip";
Mouse.OverrideCursor = Cursors.Wait;
await Task.Run(async () => {
try {
var members = await query var members = await query
.OrderBy(m => m.MgNr) .OrderBy(m => m.MgNr)
.Include(m => m.TelephoneNumbers) .Include(m => m.TelephoneNumbers)
.Include(m => m.EmailAddresses) .Include(m => m.EmailAddresses)
.ToListAsync(); .ToListAsync();
var areaComs = await query var history1 = await query.IgnoreAutoIncludes()
.SelectMany(m => m.HistoryFrom)
.ToListAsync();
var history2 = await query.IgnoreAutoIncludes()
.SelectMany(m => m.HistoryTo)
.ToListAsync();
var history = history1.Union(history2)
.DistinctBy(h => h.HistNr)
.OrderBy(h => h.DateNotice).ThenBy(h => h.HistNr)
.ToList();
var areaCmts = await query
.SelectMany(m => m.AreaCommitments) .SelectMany(m => m.AreaCommitments)
.Select(c => c.Contract).Distinct() .Select(c => c.Contract).Distinct()
.Include(c => c.Revisions) .Include(c => c.Revisions)
@@ -546,35 +593,23 @@ namespace Elwig.Services {
var wbKgs = members var wbKgs = members
.Where(m => m.DefaultWbKg != null) .Where(m => m.DefaultWbKg != null)
.Select(m => m.DefaultWbKg!) .Select(m => m.DefaultWbKg!)
.Union(areaComs.Select(c => c.Kg)) .Union(areaCmts.Select(c => c.Kg))
.Distinct() .Distinct()
.OrderBy(k => k.KgNr) .OrderBy(k => k.KgNr)
.ToList(); .ToList();
await ElwigData.Export(d.FileName, members, areaComs, wbKgs, filterNames); await ElwigData.Export(filename, members, history, areaCmts, wbKgs, filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) { } else if (mode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames); await SyncService.Upload(App.Config.SyncUrl, App.Config.SyncUrl, App.Config.SyncPassword, query, filterNames);
}); });
Mouse.OverrideCursor = null;
} else { } else {
Mouse.OverrideCursor = Cursors.Wait; await Utils.RunForeground(async () => {
await Task.Run(async () => {
try {
var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1])); var data = await MemberListData.FromQuery(query, filterNames, filterNames.Where(f => f.StartsWith("Flächenbindung")).Select(f => f.Split(' ')[^1]));
using var doc = new MemberList(string.Join(" / ", filterNames), data); using var doc = new MemberList(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, mode); await Utils.ExportDocument(doc, mode);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}); });
Mouse.OverrideCursor = null;
} }
} }
@@ -589,30 +624,35 @@ namespace Elwig.Services {
Name = vm.Name!, Name = vm.Name!,
Suffix = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.Suffix) ? null : vm.Suffix, Suffix = vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.Suffix) ? null : vm.Suffix,
ForTheAttentionOf = !vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.ForTheAttentionOf) ? null : vm.ForTheAttentionOf, ForTheAttentionOf = !vm.IsJuridicalPerson || string.IsNullOrWhiteSpace(vm.ForTheAttentionOf) ? null : vm.ForTheAttentionOf,
Birthday = string.IsNullOrEmpty(vm.Birthday) ? null : string.Join("-", vm.Birthday!.Split(".").Reverse()), Birthday = string.IsNullOrWhiteSpace(vm.Birthday) ? null : string.Join("-", vm.Birthday!.Split(".").Reverse()),
IsDeceased = vm.IsDeceased, IsDeceased = vm.IsDeceased,
CountryNum = 40, // Austria AT AUT CountryNum = 40, // Austria AT AUT
PostalDestId = vm.Ort!.Id, PostalDestId = vm.Ort!.Id,
Address = vm.Address!, Address = vm.Address!,
Iban = string.IsNullOrEmpty(vm.Iban) ? null : vm.Iban?.Replace(" ", ""), Iban = string.IsNullOrWhiteSpace(vm.Iban) ? null : vm.Iban?.Replace(" ", ""),
Bic = string.IsNullOrEmpty(vm.Bic) ? null : vm.Bic, Bic = string.IsNullOrWhiteSpace(vm.Bic) ? null : vm.Bic,
UstIdNr = string.IsNullOrEmpty(vm.UstIdNr) ? null : vm.UstIdNr, UstIdNr = string.IsNullOrWhiteSpace(vm.UstIdNr) ? null : vm.UstIdNr,
LfbisNr = string.IsNullOrEmpty(vm.LfbisNr) ? null : vm.LfbisNr, LfbisNr = string.IsNullOrWhiteSpace(vm.LfbisNr) ? null : vm.LfbisNr,
IsBuchführend = vm.IsBuchführend, OrganicOperatorId = string.IsNullOrWhiteSpace(vm.OrganicOperatorId) ? null : vm.OrganicOperatorId,
OrganicAuthorityCode = string.IsNullOrWhiteSpace(vm.OrganicAuthorityCode) ? null : vm.OrganicAuthorityCode,
IsAccounting = vm.IsAccounting,
IsOrganic = vm.IsOrganic, IsOrganic = vm.IsOrganic,
EntryDateString = string.IsNullOrEmpty(vm.EntryDate) ? null : string.Join("-", vm.EntryDate.Split(".").Reverse()), EntryDateString = string.IsNullOrWhiteSpace(vm.EntryDate) ? null : string.Join("-", vm.EntryDate.Split(".").Reverse()),
ExitDateString = string.IsNullOrEmpty(vm.ExitDate) ? null : string.Join("-", vm.ExitDate.Split(".").Reverse()), ExitDateString = string.IsNullOrWhiteSpace(vm.ExitDate) ? null : string.Join("-", vm.ExitDate.Split(".").Reverse()),
BusinessShares = (int)vm.BusinessShares!, Shares = App.Client.HasRedWhite ? 0 : vm.BusinessShares1 ?? 0,
AccountingNr = string.IsNullOrEmpty(vm.AccountingNr) ? null : vm.AccountingNr, SharesRed = App.Client.HasRedWhite ? vm.BusinessShares1 ?? 0 : 0,
SharesWhite = App.Client.HasRedWhite ? vm.BusinessShares2 ?? 0 : 0,
SharesDormant = App.Client.HasRedWhite ? vm.BusinessShares3 ?? 0 : vm.BusinessShares2 ?? 0,
AccountingNr = string.IsNullOrWhiteSpace(vm.AccountingNr) ? null : vm.AccountingNr,
IsActive = vm.IsActive, IsActive = vm.IsActive,
IsVollLieferant = vm.IsVollLieferant, IsFullDelivery = vm.IsFullDelivery,
IsFunktionär = vm.IsFunktionär, IsOfficer = vm.IsOfficer,
ZwstId = vm.Branch?.ZwstId, ZwstId = vm.Branch?.ZwstId,
DefaultKgNr = vm.DefaultKg?.KgNr, DefaultKgNr = vm.DefaultKg?.KgNr,
Comment = string.IsNullOrEmpty(vm.Comment) ? null : vm.Comment, Comment = string.IsNullOrWhiteSpace(vm.Comment) ? null : vm.Comment,
ContactViaPost = vm.ContactViaPost, ContactViaPost = vm.ContactViaPost,
ContactViaEmail = vm.ContactViaEmail, ContactViaEmail = vm.ContactViaEmail,
}; };
@@ -660,12 +700,12 @@ namespace Elwig.Services {
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
if (vm.TransferPredecessorAreaComs is int year && m.PredecessorMgNr is int predecessor) { if (vm.TransferPredecessorAreaCmts is int year && m.PredecessorMgNr is int predecessor) {
var areaComs = await ctx.AreaCommitments var areaCmts = await ctx.AreaCommitments
.Where(c => c.MgNr == predecessor && (c.YearTo == null || c.YearTo >= year)) .Where(c => c.MgNr == predecessor && (c.YearTo == null || c.YearTo >= year))
.ToListAsync(); .ToListAsync();
ctx.AddRange(await Task.WhenAll(areaComs.Select(async (c, i) => new AreaCom { ctx.AddRange(await Task.WhenAll(areaCmts.Select(async (c, i) => new AreaCmt {
FbNr = c.FbNr, FbNr = c.FbNr,
RevNr = await ctx.NextRevNr(c.FbNr), RevNr = await ctx.NextRevNr(c.FbNr),
MgNr = m.MgNr, MgNr = m.MgNr,
@@ -677,24 +717,24 @@ namespace Elwig.Services {
YearTo = c.YearTo, YearTo = c.YearTo,
}))); })));
foreach (var ac in areaComs) foreach (var ac in areaCmts)
ac.YearTo = year - 1; ac.YearTo = year - 1;
ctx.UpdateRange(areaComs); ctx.UpdateRange(areaCmts);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
vm.TransferPredecessorAreaComs = null; vm.TransferPredecessorAreaCmts = null;
if (vm.CancelAreaComs is int yearTo) { if (vm.CancelAreaCmts is int yearTo) {
var areaComs = await ctx.AreaCommitments var areaCmts = await ctx.AreaCommitments
.Where(c => c.MgNr == m.MgNr && (c.YearTo == null || c.YearTo > yearTo)) .Where(c => c.MgNr == m.MgNr && (c.YearTo == null || c.YearTo > yearTo))
.ToListAsync(); .ToListAsync();
foreach (var ac in areaComs) foreach (var ac in areaCmts)
ac.YearTo = yearTo; ac.YearTo = yearTo;
ctx.UpdateRange(areaComs); ctx.UpdateRange(areaCmts);
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
vm.CancelAreaComs = null; vm.CancelAreaCmts = null;
if (newMgNr != m.MgNr) { if (newMgNr != m.MgNr) {
await ctx.Database.ExecuteSqlAsync($"UPDATE member SET mgnr = {newMgNr} WHERE mgnr = {oldMgNr}"); await ctx.Database.ExecuteSqlAsync($"UPDATE member SET mgnr = {newMgNr} WHERE mgnr = {oldMgNr}");
@@ -704,7 +744,7 @@ namespace Elwig.Services {
}); });
} }
public static async Task DeleteMember(int mgnr, bool deletePaymentData, bool deleteDeliveries, bool deleteAreaComs) { public static async Task DeleteMember(int mgnr, bool deletePaymentData, bool deleteDeliveries, bool deleteAreaCmts) {
await Task.Run(async () => { await Task.Run(async () => {
using var ctx = new AppDbContext(); using var ctx = new AppDbContext();
using var tx = await ctx.Database.BeginTransactionAsync(); using var tx = await ctx.Database.BeginTransactionAsync();
@@ -715,7 +755,7 @@ namespace Elwig.Services {
if (deleteDeliveries) { if (deleteDeliveries) {
await ctx.Deliveries.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync(); await ctx.Deliveries.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync();
} }
if (deleteAreaComs) { if (deleteAreaCmts) {
await ctx.AreaCommitments.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync(); await ctx.AreaCommitments.Where(c => c.MgNr == mgnr).ExecuteDeleteAsync();
} }
ctx.Remove(l); ctx.Remove(l);
+163
View File
@@ -0,0 +1,163 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
namespace Elwig.Services {
public static class OrganicService {
public const string TRACES_API_URL = "https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/for/query";
public const string TRACES_PDF_URL_PREFIX = "https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/digitally-signed/";
public enum TracesStatus {
ISSUED, SUSPENDED, WITHDRAWN, EXPIRED
}
public enum TracesActivity {
PREPARATION, EXPORT, IMPORT, STORING, PRODUCTION, DISTRIBUTION, DISTRIBUTION_PLACING_ON_THE_MARKET
}
public enum TracesCategoryOfProduct {
UNPROCESSED_PLANT_PRODUCTS_INCLUDING_SEEDS, LIVESTOCK_AND_UNPROCESSED_LIVESTOCK_PRODUCTS, ALGAE_AND_UNPROCESSED_AQUACULTURE_PRODUCTS,
PROCESSED_AGRICULTURAL_PRODUCTS_INCLUDING_AQUACULTURE_FOR_USE_AS_FOOD, FEED, WINE, OTHER_PRODUCTS_NOT_COVERED_BY_PREVIOUS_CATEGORIES
}
public record TracesCertificate {
public required string Id { get; init; }
public required string AuthorityCode { get; init; }
public required string OperatorId { get; init; }
public required string OperatorName { get; init; }
public required TracesActivity[] Activities { get; init; }
public required TracesCategoryOfProduct[] CategoriesOfProduct { get; init; }
public required DateOnly IssuedOn { get; init; }
public required DateOnly ExpiresOn { get; init; }
public required DateOnly LastStatusUpdateOn { get; init; }
public required TracesStatus Status { get; init; }
public string PdfUrl => TRACES_PDF_URL_PREFIX + Id + ".pdf";
public bool IsValid => Status == TracesStatus.ISSUED && IssuedOn.ToDateTime(new()) <= DateTime.Today && ExpiresOn.ToDateTime(new()) >= DateTime.Today;
public bool IsValidForWineProduction => IsValid && Activities.Contains(TracesActivity.PRODUCTION) &&
(CategoriesOfProduct.Contains(TracesCategoryOfProduct.UNPROCESSED_PLANT_PRODUCTS_INCLUDING_SEEDS) || CategoriesOfProduct.Contains(TracesCategoryOfProduct.WINE));
}
private static readonly ConcurrentDictionary<int, (DateTime Timestamp, TracesCertificate[] Certificates)> _memberCerts = [];
public async static Task<TracesCertificate[]> GetTracesCertificates(Member m, bool? tryNameAndAddress = null) {
if (_memberCerts.TryGetValue(m.MgNr, out var entry) && entry.Timestamp >= DateTime.Now.AddDays(-1))
return entry.Certificates;
var certs = await FetchTracesCertificates(m, tryNameAndAddress);
_memberCerts[m.MgNr] = (DateTime.Now, certs);
return certs;
}
private async static Task<JsonArray> TryFetchTracesCertificates(Dictionary<string, string> query) {
var q = HttpUtility.ParseQueryString("");
q.Add("sort", "-issuedOn");
q.Add("countryCode", "AT");
foreach (var (k, v) in query) {
q.Add(k, v);
}
using var client = Utils.GetHttpClient();
using var res = await client.GetAsync($"{TRACES_API_URL}?{q}");
res.EnsureSuccessStatusCode();
var resJson = JsonNode.Parse(await res.Content.ReadAsStringAsync());
return resJson?.AsArray() ?? throw new JsonException();
}
public static Task<TracesCertificate[]> FetchTracesCertificates(Member m, bool? tryNameAndAddress = null) {
return FetchTracesCertificates(m.OrganicOperatorId, m.LfbisNr,
(tryNameAndAddress ?? m.IsOrganic) ? m.BillingAddress is BillingAddr a ?
(a.FullName, a.Address, $"{a.PostalDest.AtPlz?.Plz}") :
(m.FullName, m.Address, $"{m.PostalDest.AtPlz?.Plz}") :
null);
}
public async static Task<TracesCertificate[]> FetchTracesCertificates(string? oocId = null, string? lfbisNr = null, (string Name, string Address, string PostalCode)? address = null) {
bool searchedByName = false;
JsonArray jsonCerts = [];
if (jsonCerts.Count == 0 && oocId != null) {
jsonCerts = await TryFetchTracesCertificates(new Dictionary<string, string> {
{ "operatorIdentifierType", "ooc_identifier" },
{ "operatorIdentifierSearchOperator", "STRICT" },
{ "operatorIdentifier", oocId },
});
}
if (jsonCerts.Count == 0 && lfbisNr != null) {
jsonCerts = await TryFetchTracesCertificates(new Dictionary<string, string> {
{ "operatorIdentifierType", "comp_reg" },
{ "operatorIdentifierSearchOperator", "STRICT" },
{ "operatorIdentifier", lfbisNr.TrimStart('0') },
});
}
if (jsonCerts.Count == 0 && address != null) {
var query = $"{Regex.Replace(address.Value.Name, @"\(.*?\)", "")} {address.Value.Address}";
jsonCerts = await TryFetchTracesCertificates(new Dictionary<string, string> {
{ "operatorPostalCode", address.Value.PostalCode },
{ "query", query.Length > 40 ? query[..query.LastIndexOf(' ', 40)] : query },
});
searchedByName = true;
}
if (jsonCerts.Count == 0)
return [];
TracesCertificate[] certs = [.. jsonCerts.Select(j => new TracesCertificate {
Id = j!["reference"]?.GetValue<string>() ?? throw new Exception(),
AuthorityCode = j["issuingBody"]?["code"]?.GetValue<string>() ?? throw new Exception(),
OperatorId = j["operatorIdentifier"]?.GetValue<string>() ?? throw new Exception(),
OperatorName = j["operator"]?["name"]?.GetValue<string>() ?? throw new Exception(),
Activities = [.. j["activities"]?.AsArray().Select(a => Enum.Parse<TracesActivity>(a?["id"]?.GetValue<string>().ToUpper().Replace("_IMPORT", "IMPORT") ?? throw new Exception())) ?? []],
CategoriesOfProduct = [.. j["categoriesOfProduct"]?.AsArray().Select(c => Enum.Parse<TracesCategoryOfProduct>(c?["id"]?.GetValue<string>() ?? throw new Exception())) ?? []],
IssuedOn = DateOnly.FromDateTime(DateTime.ParseExact(j["issuedOn"]?.GetValue<string>() ?? throw new Exception(), "yyyy-MM-ddTHH:mm:ss.fffK", CultureInfo.InvariantCulture, DateTimeStyles.None)),
ExpiresOn = DateOnly.ParseExact(j["expiresOn"]?.GetValue<string>() ?? throw new Exception(), "yyyy-MM-dd"),
LastStatusUpdateOn = DateOnly.FromDateTime(DateTime.ParseExact(j["lastStatusUpdateDateTime"]?.GetValue<string>() ?? throw new Exception(), "yyyy-MM-ddTHH:mm:ss.fffK", CultureInfo.InvariantCulture, DateTimeStyles.None)),
Status = Enum.Parse<TracesStatus>(j["status"]?["id"]?.GetValue<string>() ?? throw new Exception()),
})];
return (!searchedByName || certs.Where(c => c.Status == TracesStatus.ISSUED).Select(c => c.OperatorId).ToHashSet().Count == 1) ? certs : [];
}
public static string? GetTracesSearchUrl(Member m, bool? tryNameAndAddress = null) {
return GetTracesSearchUrl(m.OrganicOperatorId, m.LfbisNr,
(tryNameAndAddress ?? m.IsOrganic) ? m.BillingAddress is BillingAddr a ?
(a.FullName, a.Address, $"{a.PostalDest.AtPlz?.Plz}") :
(m.FullName, m.Address, $"{m.PostalDest.AtPlz?.Plz}") :
null);
}
public static string? GetTracesSearchUrl(string? oocId = null, string? lfbisNr = null, (string Name, string Address, string PostalCode)? address = null) {
var baseUrl = "https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/index";
if (oocId != null) {
return $"{baseUrl}#!?countryCode=AT&operatorIdentifierType=ooc_identifier&operatorIdentifierSearchOperator=STRICT&operatorIdentifier={oocId}&states=ISSUED";
} else if (lfbisNr != null) {
return $"{baseUrl}#!?countryCode=AT&operatorIdentifierType=comp_reg&operatorIdentifierSearchOperator=STRICT&operatorIdentifier={lfbisNr.TrimStart('0')}&states=ISSUED";
} else if (address != null) {
return $"{baseUrl}#!?countryCode=AT&operatorPostalCode={address?.PostalCode}&query={address?.Name.Replace(' ', '+')}+{address?.Address.Replace(' ', '+')}&states=ISSUED";
} else {
return null;
}
}
public static string? GetEasyCertSearchUrl(string lfbisNr) {
var baseUrl = "https://www.easy-cert.com/htm/suchergebnis.htm";
return $"{baseUrl}?CountryCode=AT&CustomerNumber={lfbisNr.TrimStart('0')}";
}
public static string? GetBiocSearchUrl(string lfbisNr) {
var baseUrl = "https://www.bioc.info/search/producersearchresult";
return $"{baseUrl}?producerSearch[country]=a1e51f85-27b5-a1e3-65f7-561569884d38&producerSearch[operatorId]={lfbisNr.TrimStart('0')}";
}
}
}

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