Compare commits
	
		
			129 Commits
		
	
	
		
			b52c09a176
			...
			v0.6.8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6e26bd8922 | |||
| ae7fdef2ea | |||
| c0ff852f5e | |||
| 10b78dfb72 | |||
| d289a5d4bf | |||
| 9172222307 | |||
| 05a75a52cc | |||
| 8732141e6b | |||
| 99ca12b276 | |||
| 7ff069d068 | |||
| 583d5b4e3e | |||
| 3f2b5b684c | |||
| 5db14c09ad | |||
| 791eaddf58 | |||
| 5cb29aa75f | |||
| 3c0fea30f5 | |||
| f2df121435 | |||
| 7f4cfdc1b5 | |||
| f4eb6456be | |||
| f13fb3aaf0 | |||
| 9a39879804 | |||
| 11be424c38 | |||
| 1b9064a97c | |||
| 805f782c83 | |||
| 912206f52d | |||
| 825bd6f304 | |||
| 9ecad6aa79 | |||
| 68f1a2c091 | |||
| 59cd69ddaf | |||
| 7c23f9bdae | |||
| 6d53e35399 | |||
| 42eb68d431 | |||
| 0591d91f49 | |||
| befe6a753b | |||
| 4daa6deb26 | |||
| c07a6b450c | |||
| 6fdd72e28b | |||
| 6af33c591f | |||
| f850fd08ff | |||
| b063b201e3 | |||
| 60b624b009 | |||
| 71a234ca60 | |||
| 38abfb0edd | |||
| 05a037db70 | |||
| b9287f8260 | |||
| 50ac757067 | |||
| c6cd9d7c73 | |||
| 1b28752f4c | |||
| e0bdbee2ae | |||
| ff3bd5cea5 | |||
| 116d88d3d6 | |||
| 6bcb2fb406 | |||
| 8665c93702 | |||
| 62496a0770 | |||
| 8678a02318 | |||
| 9de7fad139 | |||
| 85c8783f7e | |||
| 75e02751f0 | |||
| ef1c3b25cf | |||
| 255953a658 | |||
| 9470b26aec | |||
| 3a2bf81bd9 | |||
| d3aca196dd | |||
| 519e903d1c | |||
| dd568b81e8 | |||
| 31b0ae245d | |||
| 46498ce337 | |||
| 0fff698a5d | |||
| a71c6685f0 | |||
| ab41702f6c | |||
| 2bbf4dd1fd | |||
| 8909b4a3a8 | |||
| f8d776c028 | |||
| 2154e253ad | |||
| df83430c35 | |||
| d59a713a8c | |||
| 5e48d8e8d1 | |||
| 4f95d3fe16 | |||
| ce3185842a | |||
| e1d19fd9e5 | |||
| 3931a4084c | |||
| 1a492e4eff | |||
| 58a13eb3cc | |||
| d5124829de | |||
| 37658869e4 | |||
| 24a43ff37d | |||
| 16cf055834 | |||
| ef0b913063 | |||
| 05909919e2 | |||
| 3642c5ac07 | |||
| 6cee604448 | |||
| 89d20f4c42 | |||
| 182b367811 | |||
| a2bb09cfbd | |||
| b981b5f895 | |||
| 9dc2e8a59a | |||
| 1dc05e47cf | |||
| 21cc20ee63 | |||
| 491c41b239 | |||
| 47658a72ae | |||
| 8b0a4d7979 | |||
| 9ee7f6baf1 | |||
| ecbc9c2d82 | |||
| bf90543ad8 | |||
| 6a5676f916 | |||
| 75e9d756d2 | |||
| ee161b149b | |||
| 0cb7b4bfc8 | |||
| 4a49a17b6a | |||
| 741ccaacae | |||
| 19f4300440 | |||
| 954c7a8bdb | |||
| 626724fe87 | |||
| 42bf01656e | |||
| 51293baaae | |||
| 1d1398a9cd | |||
| 7d199282d0 | |||
| b56a5ed5c6 | |||
| 201b63c2f1 | |||
| b2bd0c9a21 | |||
| 8502afdc9a | |||
| cb541cb6e6 | |||
| 403e7723d2 | |||
| 8fbce03031 | |||
| b32a935150 | |||
| 337bfa89d9 | |||
| f886888ccc | |||
| 4dd036babd | |||
| b6fd62f8ca | 
							
								
								
									
										55
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					name: Deploy
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    tags: ["v[0-9]+.[0-9]+.[0-9]+"]
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  deploy:
 | 
				
			||||||
 | 
					    name: Build and Deploy
 | 
				
			||||||
 | 
					    runs-on: windows-latest
 | 
				
			||||||
 | 
					    permissions:
 | 
				
			||||||
 | 
					      contents: write
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Set APP_VERSION variable from tag
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          $APP_VERSION = $env:GITHUB_REF -replace '^refs/tags/v', ''
 | 
				
			||||||
 | 
					          Add-Content -Path $env:GITHUB_ENV -Value "APP_VERSION=$APP_VERSION"
 | 
				
			||||||
 | 
					      - name: Checkout repository
 | 
				
			||||||
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					      - name: Check version in project
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          Select-String Elwig/Elwig.csproj -Pattern "<Version>"
 | 
				
			||||||
 | 
					          $res = Select-String Elwig/Elwig.csproj -Pattern "<Version>${{ env.APP_VERSION }}</Version>"
 | 
				
			||||||
 | 
					          if ($res -eq $null) {
 | 
				
			||||||
 | 
					            exit 1
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					      - name: Setup MSBuild
 | 
				
			||||||
 | 
					        uses: microsoft/setup-msbuild@v1.1
 | 
				
			||||||
 | 
					      - name: Setup NuGet
 | 
				
			||||||
 | 
					        uses: nuget/setup-nuget@v1
 | 
				
			||||||
 | 
					      - name: Restore NuGet packages
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: $(& nuget restore Elwig.sln; $a=$lastexitcode) | findstr x*; exit $a
 | 
				
			||||||
 | 
					      - name: Build Setup
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: $(& msbuild -verbosity:quiet Setup/Setup.wixproj -property:Configuration=Release -property:Platform=x64; $a=$lastexitcode) | findstr x*; exit $a
 | 
				
			||||||
 | 
					      - name: Rename artifact
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: Move-Item Setup/bin/x64/Release/Elwig.exe Setup/bin/x64/Release/Elwig-${{ env.APP_VERSION }}.exe
 | 
				
			||||||
 | 
					      - name: Create release
 | 
				
			||||||
 | 
					        uses: akkuman/gitea-release-action@v1
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          name: Elwig ${{ env.APP_VERSION }}
 | 
				
			||||||
 | 
					          files: |-
 | 
				
			||||||
 | 
					            Setup/bin/x64/Release/Elwig-${{ env.APP_VERSION }}.exe
 | 
				
			||||||
 | 
					      - name: Upload to website
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          $content = [System.IO.File]::ReadAllBytes("Setup/bin/x64/Release/Elwig-${{ env.APP_VERSION }}.exe")
 | 
				
			||||||
 | 
					          Invoke-WebRequest `
 | 
				
			||||||
 | 
					            -Uri "https://www.necronda.net/elwig/files/Elwig-${{ env.APP_VERSION }}.exe" `
 | 
				
			||||||
 | 
					            -Method PUT `
 | 
				
			||||||
 | 
					            -Body $content `
 | 
				
			||||||
 | 
					            -Headers @{ Authorization = "${{ secrets.API_AUTHORIZATION }}" } `
 | 
				
			||||||
 | 
					            -ContentType "application/octet-stream"
 | 
				
			||||||
							
								
								
									
										29
									
								
								.gitea/workflows/test.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.gitea/workflows/test.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					name: Test
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches: ["**"]
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  test:
 | 
				
			||||||
 | 
					    name: Run tests
 | 
				
			||||||
 | 
					    runs-on: windows-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Checkout repository
 | 
				
			||||||
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					      - name: Setup MSBuild
 | 
				
			||||||
 | 
					        uses: microsoft/setup-msbuild@v1.1
 | 
				
			||||||
 | 
					      - name: Setup NuGet
 | 
				
			||||||
 | 
					        uses: nuget/setup-nuget@v1
 | 
				
			||||||
 | 
					      - name: Restore NuGet packages
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: $(& nuget restore Elwig.sln; $a=$lastexitcode) | findstr x*; exit $a
 | 
				
			||||||
 | 
					      - name: Build Elwig
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: $(& msbuild -verbosity:quiet Elwig/Elwig.csproj -property:Configuration=Debug; $a=$lastexitcode) | findstr x*; exit $a
 | 
				
			||||||
 | 
					      - name: Build Tests
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: $(& dotnet build Tests; $a=$lastexitcode) | findstr x*; exit $a
 | 
				
			||||||
 | 
					      - name: Run Tests
 | 
				
			||||||
 | 
					        shell: powershell
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          $env:PATH += ";$(pwd)\Installer\Files"
 | 
				
			||||||
 | 
					          $(& dotnet test Tests; $a=$lastexitcode) | findstr x*; exit $a
 | 
				
			||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,4 +3,6 @@ bin/
 | 
				
			|||||||
*.user
 | 
					*.user
 | 
				
			||||||
.vs
 | 
					.vs
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
Tests/Resources/Create.sql
 | 
					Tests/Resources/Sql/Create.sql
 | 
				
			||||||
 | 
					*.exe
 | 
				
			||||||
 | 
					!WinziPrint.exe
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,7 +53,9 @@ namespace Elwig {
 | 
				
			|||||||
        public static string? BranchFaxNr { get; private set; }
 | 
					        public static string? BranchFaxNr { get; private set; }
 | 
				
			||||||
        public static string? BranchMobileNr { get; private set; }
 | 
					        public static string? BranchMobileNr { get; private set; }
 | 
				
			||||||
        public static IList<IScale> Scales { get; private set; }
 | 
					        public static IList<IScale> Scales { get; private set; }
 | 
				
			||||||
        public static ClientParameters Client { get; private set; }
 | 
					        public static IList<ICommandScale> CommandScales => Scales.Where(s => s is ICommandScale).Cast<ICommandScale>().ToList();
 | 
				
			||||||
 | 
					        public static IList<IEventScale> EventScales => Scales.Where(s => s is IEventScale).Cast<IEventScale>().ToList();
 | 
				
			||||||
 | 
					        public static ClientParameters Client { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static bool IsPrintingReady => Html.IsReady && Pdf.IsReady;
 | 
					        public static bool IsPrintingReady => Html.IsReady && Pdf.IsReady;
 | 
				
			||||||
        public static Dispatcher MainDispatcher { get; private set; }
 | 
					        public static Dispatcher MainDispatcher { get; private set; }
 | 
				
			||||||
@@ -63,24 +65,29 @@ namespace Elwig {
 | 
				
			|||||||
            Directory.CreateDirectory(TempPath);
 | 
					            Directory.CreateDirectory(TempPath);
 | 
				
			||||||
            Directory.CreateDirectory(DataPath);
 | 
					            Directory.CreateDirectory(DataPath);
 | 
				
			||||||
            MainDispatcher = Dispatcher;
 | 
					            MainDispatcher = Dispatcher;
 | 
				
			||||||
            Scales = Array.Empty<IScale>();
 | 
					            Scales = [];
 | 
				
			||||||
            CurrentApp = this;
 | 
					            CurrentApp = this;
 | 
				
			||||||
 | 
					            OverrideCulture();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override async void OnStartup(StartupEventArgs evt) {
 | 
					        private static void OverrideCulture() {
 | 
				
			||||||
            var locale = new CultureInfo("de-AT");
 | 
					            var locale = new CultureInfo("de-AT", false);
 | 
				
			||||||
            locale.NumberFormat.CurrencyGroupSeparator = "\u202f";
 | 
					            locale.NumberFormat.CurrencyGroupSeparator = Utils.GroupSeparator;
 | 
				
			||||||
            locale.NumberFormat.NumberGroupSeparator = "\u202f";
 | 
					            locale.NumberFormat.NumberGroupSeparator = Utils.GroupSeparator;
 | 
				
			||||||
            locale.NumberFormat.PercentGroupSeparator = "\u202f";
 | 
					            locale.NumberFormat.PercentGroupSeparator = Utils.GroupSeparator;
 | 
				
			||||||
 | 
					            CultureInfo.CurrentCulture = locale;
 | 
				
			||||||
 | 
					            CultureInfo.CurrentUICulture = locale;
 | 
				
			||||||
            Thread.CurrentThread.CurrentCulture = locale;
 | 
					            Thread.CurrentThread.CurrentCulture = locale;
 | 
				
			||||||
            Thread.CurrentThread.CurrentUICulture = locale;
 | 
					            Thread.CurrentThread.CurrentUICulture = locale;
 | 
				
			||||||
            CultureInfo.DefaultThreadCurrentCulture = locale;
 | 
					            CultureInfo.DefaultThreadCurrentCulture = locale;
 | 
				
			||||||
            CultureInfo.DefaultThreadCurrentUICulture = locale;
 | 
					            CultureInfo.DefaultThreadCurrentUICulture = locale;
 | 
				
			||||||
            FrameworkElement.LanguageProperty.OverrideMetadata(
 | 
					            FrameworkElement.LanguageProperty.OverrideMetadata(
 | 
				
			||||||
                typeof(FrameworkElement),
 | 
					                typeof(FrameworkElement),
 | 
				
			||||||
                new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag))
 | 
					                new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name))
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected override async void OnStartup(StartupEventArgs evt) {
 | 
				
			||||||
            Version = typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split("+")[0] ?? "0.0.0";
 | 
					            Version = typeof(App).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Split("+")[0] ?? "0.0.0";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
@@ -91,7 +98,7 @@ namespace Elwig {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = new();
 | 
					            Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = [];
 | 
				
			||||||
            using (var ctx = new AppDbContext()) {
 | 
					            using (var ctx = new AppDbContext()) {
 | 
				
			||||||
                branches = ctx.Branches.ToDictionary(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
 | 
					                branches = ctx.Branches.ToDictionary(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
@@ -110,23 +117,11 @@ namespace Elwig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var list = new List<IScale>();
 | 
					            var list = new List<IScale>();
 | 
				
			||||||
            foreach (var s in Config.Scales) {
 | 
					            foreach (var s in Config.Scales) {
 | 
				
			||||||
                var id = s[0];
 | 
					 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    var type = s[1]?.ToLower();
 | 
					                    list.Add(Scale.FromConfig(s));
 | 
				
			||||||
                    var model = s[2];
 | 
					 | 
				
			||||||
                    var cnx = s[3];
 | 
					 | 
				
			||||||
                    var empty = s[4];
 | 
					 | 
				
			||||||
                    var filling = s[5];
 | 
					 | 
				
			||||||
                    int? limit = s[6] == null ? null : int.Parse(s[6]);
 | 
					 | 
				
			||||||
                    var log = s[7];
 | 
					 | 
				
			||||||
                    if (type == "systec") {
 | 
					 | 
				
			||||||
                        list.Add(new SystecScale(id, model, cnx, empty, filling, limit, log));
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        throw new ArgumentException($"Invalid scale type: \"{type}\"");
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } catch (Exception e) {
 | 
					                } catch (Exception e) {
 | 
				
			||||||
                    list.Add(new InvalidScale(id));
 | 
					                    list.Add(new InvalidScale(s.Id));
 | 
				
			||||||
                    MessageBox.Show($"Unable to create scale {s[0]}:\n\n{e.Message}", "Scale Error", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					                    MessageBox.Show($"Unable to create scale {s.Id}:\n\n{e.Message}", "Scale Error", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Scales = list;
 | 
					            Scales = list;
 | 
				
			||||||
@@ -136,26 +131,10 @@ namespace Elwig {
 | 
				
			|||||||
                    MessageBox.Show("Invalid branch name in config!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					                    MessageBox.Show("Invalid branch name in config!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
                    Shutdown();
 | 
					                    Shutdown();
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    var entry = branches[Config.Branch.ToLower()];
 | 
					                    SetBranch(branches[Config.Branch.ToLower()]);
 | 
				
			||||||
                    ZwstId = entry.Item1;
 | 
					 | 
				
			||||||
                    BranchName = entry.Item2;
 | 
					 | 
				
			||||||
                    BranchPlz = entry.Item3;
 | 
					 | 
				
			||||||
                    BranchLocation = entry.Item4?.Split(" im ")[0].Split(" an ")[0].Split(" bei ")[0];  // FIXME
 | 
					 | 
				
			||||||
                    BranchAddress = entry.Item5;
 | 
					 | 
				
			||||||
                    BranchPhoneNr = entry.Item6;
 | 
					 | 
				
			||||||
                    BranchFaxNr = entry.Item7;
 | 
					 | 
				
			||||||
                    BranchMobileNr = entry.Item8;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else if (branches.Count == 1) {
 | 
					            } else if (branches.Count == 1) {
 | 
				
			||||||
                var entry = branches.First().Value;
 | 
					                SetBranch(branches.First().Value);
 | 
				
			||||||
                ZwstId = entry.Item1;
 | 
					 | 
				
			||||||
                BranchName = entry.Item2;
 | 
					 | 
				
			||||||
                BranchPlz = entry.Item3;
 | 
					 | 
				
			||||||
                BranchLocation = entry.Item4?.Split(" im ")[0].Split(" an ")[0].Split(" bei ")[0];  // FIXME
 | 
					 | 
				
			||||||
                BranchAddress = entry.Item5;
 | 
					 | 
				
			||||||
                BranchPhoneNr = entry.Item6;
 | 
					 | 
				
			||||||
                BranchFaxNr = entry.Item7;
 | 
					 | 
				
			||||||
                BranchMobileNr = entry.Item8;
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                MessageBox.Show("Unable to determine local branch!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					                MessageBox.Show("Unable to determine local branch!", "Invalid Branch Config", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
                Shutdown();
 | 
					                Shutdown();
 | 
				
			||||||
@@ -164,6 +143,21 @@ namespace Elwig {
 | 
				
			|||||||
            base.OnStartup(evt);
 | 
					            base.OnStartup(evt);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static void SetBranch(Branch b) {
 | 
				
			||||||
 | 
					            SetBranch((b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static void SetBranch((string, string, int?, string?, string?, string?, string?, string?) entry) {
 | 
				
			||||||
 | 
					            ZwstId = entry.Item1;
 | 
				
			||||||
 | 
					            BranchName = entry.Item2;
 | 
				
			||||||
 | 
					            BranchPlz = entry.Item3;
 | 
				
			||||||
 | 
					            BranchLocation = entry.Item4?.Split(" im ")[0].Split(" an ")[0].Split(" bei ")[0];  // FIXME
 | 
				
			||||||
 | 
					            BranchAddress = entry.Item5;
 | 
				
			||||||
 | 
					            BranchPhoneNr = entry.Item6;
 | 
				
			||||||
 | 
					            BranchFaxNr = entry.Item7;
 | 
				
			||||||
 | 
					            BranchMobileNr = entry.Item8;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void PrintingReadyChanged() {
 | 
					        private void PrintingReadyChanged() {
 | 
				
			||||||
            Dispatcher.BeginInvoke(OnPrintingReadyChanged, new EventArgs());
 | 
					            Dispatcher.BeginInvoke(OnPrintingReadyChanged, new EventArgs());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ namespace Elwig.Dialogs {
 | 
				
			|||||||
            InitializeComponent();
 | 
					            InitializeComponent();
 | 
				
			||||||
            TextLsNr.Text = lsnr;
 | 
					            TextLsNr.Text = lsnr;
 | 
				
			||||||
            TextMember.Text = name;
 | 
					            TextMember.Text = name;
 | 
				
			||||||
            TextWeight.Text = $"{weight:N0}\u202fkg";
 | 
					            TextWeight.Text = $"{weight:N0}{Utils.UnitSeparator}kg";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					 | 
				
			||||||
using System.Windows;
 | 
					using System.Windows;
 | 
				
			||||||
using System.Windows.Controls;
 | 
					using System.Windows.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,7 +13,7 @@ namespace Elwig.Dialogs {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private void ConfirmButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            DialogResult = true;
 | 
					            DialogResult = true;
 | 
				
			||||||
            Price = double.Parse(PriceInput.Text.Replace("\u202f", ""));
 | 
					            Price = double.Parse(PriceInput.Text.Replace(Utils.GroupSeparator, ""));
 | 
				
			||||||
            Close();
 | 
					            Close();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,6 +119,11 @@ main h1 {
 | 
				
			|||||||
    font-size: 10pt;
 | 
					    font-size: 10pt;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main-wrapper p.custom {
 | 
				
			||||||
 | 
					    white-space: pre-wrap;
 | 
				
			||||||
 | 
					    break-inside: avoid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.main-wrapper .hidden {
 | 
					.main-wrapper .hidden {
 | 
				
			||||||
    break-before: avoid;
 | 
					    break-before: avoid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ namespace Elwig.Documents {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public PaymentMember? Payment;
 | 
					        public PaymentMember? Payment;
 | 
				
			||||||
        public Credit? Credit;
 | 
					        public Credit? Credit;
 | 
				
			||||||
        public CreditNoteData Data;
 | 
					        public CreditNoteDeliveryData Data;
 | 
				
			||||||
        public string? Text;
 | 
					        public string? Text;
 | 
				
			||||||
        public string CurrencySymbol;
 | 
					        public string CurrencySymbol;
 | 
				
			||||||
        public int Precision;
 | 
					        public int Precision;
 | 
				
			||||||
@@ -20,7 +20,15 @@ namespace Elwig.Documents {
 | 
				
			|||||||
        public decimal MemberTotalUnderDelivery;
 | 
					        public decimal MemberTotalUnderDelivery;
 | 
				
			||||||
        public decimal MemberAutoBusinessShares;
 | 
					        public decimal MemberAutoBusinessShares;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data, Dictionary<string, UnderDelivery>? underDeliveries = null) :
 | 
					        public CreditNote(
 | 
				
			||||||
 | 
					            AppDbContext ctx,
 | 
				
			||||||
 | 
					            PaymentMember p,
 | 
				
			||||||
 | 
					            CreditNoteDeliveryData data,
 | 
				
			||||||
 | 
					            bool considerContractPenalties,
 | 
				
			||||||
 | 
					            bool considerTotalPenalty,
 | 
				
			||||||
 | 
					            bool considerAutoBusinessShares,
 | 
				
			||||||
 | 
					            Dictionary<string, UnderDelivery>? underDeliveries = null
 | 
				
			||||||
 | 
					        ) :
 | 
				
			||||||
            base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.Name)} – {p.Variant.Name}", p.Member) {
 | 
					            base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.Name)} – {p.Variant.Name}", p.Member) {
 | 
				
			||||||
            UseBillingAddress = true;
 | 
					            UseBillingAddress = true;
 | 
				
			||||||
            ShowDateAndLocation = true;
 | 
					            ShowDateAndLocation = true;
 | 
				
			||||||
@@ -34,34 +42,40 @@ namespace Elwig.Documents {
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                MemberModifier = "Sonstige Zu-/Abschläge";
 | 
					                MemberModifier = "Sonstige Zu-/Abschläge";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            var total = data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value);
 | 
					 | 
				
			||||||
            var totalUnderDelivery = total - p.Member.BusinessShares * season.MinKgPerBusinessShare;
 | 
					 | 
				
			||||||
            MemberTotalUnderDelivery = totalUnderDelivery < 0 ? totalUnderDelivery * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) : 0;
 | 
					 | 
				
			||||||
            var fromDate = $"{season.Year}-06-01";
 | 
					 | 
				
			||||||
            var toDate = $"{season.Year + 1}-06-01";
 | 
					 | 
				
			||||||
            MemberAutoBusinessShares = ctx.MemberHistory
 | 
					 | 
				
			||||||
                .Where(h => h.MgNr == p.Member.MgNr && h.Type == "auto")
 | 
					 | 
				
			||||||
                .Where(h => h.DateString.CompareTo(fromDate) >= 0 && h.DateString.CompareTo(toDate) < 0)
 | 
					 | 
				
			||||||
                .Sum(h => h.BusinessShares) * (-season.BusinessShareValue ?? 0);
 | 
					 | 
				
			||||||
            if (total == 0) MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0);
 | 
					 | 
				
			||||||
            Aside = Aside.Replace("</table>", "") +
 | 
					            Aside = Aside.Replace("</table>", "") +
 | 
				
			||||||
                $"<thead><tr><th colspan='2'>Gutschrift</th></tr></thead><tbody>" +
 | 
					                $"<thead><tr><th colspan='2'>Gutschrift</th></tr></thead><tbody>" +
 | 
				
			||||||
                $"<tr><th>TG-Nr.</th><td>{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : "-")}</td></tr>" +
 | 
					                $"<tr><th>TG-Nr.</th><td>{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : "-")}</td></tr>" +
 | 
				
			||||||
                $"<tr><th>Überw. am</th><td>{p.Variant.TransferDate:dd.MM.yyyy}</td></tr>" +
 | 
					                $"<tr><th>Überw. am</th><td>{p.Variant.TransferDate:dd.MM.yyyy}</td></tr>" +
 | 
				
			||||||
                $"<tr><th>Datum/Zeit</th><td>{p.Credit?.ModifiedTimestamp:dd.MM.yyyy} / {p.Credit?.ModifiedTimestamp:HH:mm}</td></tr>" +
 | 
					                $"<tr><th>Datum/Zeit</th><td>{p.Credit?.ModifiedTimestamp:dd.MM.yyyy} / {p.Credit?.ModifiedTimestamp:HH:mm}</td></tr>" +
 | 
				
			||||||
                $"</tbody></table>";
 | 
					                $"</tbody></table>";
 | 
				
			||||||
            Text = App.Client.TextDeliveryNote;
 | 
					            Text = App.Client.TextCreditNote;
 | 
				
			||||||
            DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr);
 | 
					            DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr);
 | 
				
			||||||
            CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code;
 | 
					            CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code;
 | 
				
			||||||
            Precision = season.Precision;
 | 
					            Precision = season.Precision;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var variants = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
 | 
					            if (considerTotalPenalty) {
 | 
				
			||||||
 | 
					                var total = data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value);
 | 
				
			||||||
 | 
					                var totalUnderDelivery = total - p.Member.BusinessShares * season.MinKgPerBusinessShare;
 | 
				
			||||||
 | 
					                MemberTotalUnderDelivery = totalUnderDelivery < 0 ? totalUnderDelivery * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) : 0;
 | 
				
			||||||
 | 
					                if (total == 0)
 | 
				
			||||||
 | 
					                    MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (considerAutoBusinessShares) {
 | 
				
			||||||
 | 
					                var fromDate = $"{season.Year}-01-01";
 | 
				
			||||||
 | 
					                var toDate = $"{season.Year}-12-31";
 | 
				
			||||||
 | 
					                MemberAutoBusinessShares = ctx.MemberHistory
 | 
				
			||||||
 | 
					                    .Where(h => h.MgNr == p.Member.MgNr && h.Type == "auto")
 | 
				
			||||||
 | 
					                    .Where(h => h.DateString.CompareTo(fromDate) >= 0 && h.DateString.CompareTo(toDate) <= 0)
 | 
				
			||||||
 | 
					                    .Sum(h => h.BusinessShares) * (-season.BusinessShareValue ?? 0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (considerContractPenalties) {
 | 
				
			||||||
 | 
					                var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
 | 
				
			||||||
                var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
 | 
					                var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
 | 
				
			||||||
                var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t);
 | 
					                var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t);
 | 
				
			||||||
                MemberUnderDeliveries = underDeliveries?
 | 
					                MemberUnderDeliveries = underDeliveries?
 | 
				
			||||||
                    .OrderBy(u => u.Key)
 | 
					                    .OrderBy(u => u.Key)
 | 
				
			||||||
                    .Select(u => (
 | 
					                    .Select(u => (
 | 
				
			||||||
                    variants[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""),
 | 
					                        varieties[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""),
 | 
				
			||||||
                        u.Value.Diff,
 | 
					                        u.Value.Diff,
 | 
				
			||||||
                        u.Value.Diff * (comTypes[u.Key].PenaltyPerKg ?? 0)
 | 
					                        u.Value.Diff * (comTypes[u.Key].PenaltyPerKg ?? 0)
 | 
				
			||||||
                          - (comTypes[u.Key].PenaltyAmount ?? 0)
 | 
					                          - (comTypes[u.Key].PenaltyAmount ?? 0)
 | 
				
			||||||
@@ -69,4 +83,5 @@ namespace Elwig.Documents {
 | 
				
			|||||||
                    .Where(u => u.Item3 != 0)
 | 
					                    .Where(u => u.Item3 != 0)
 | 
				
			||||||
                    .ToList();
 | 
					                    .ToList();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }}
 | 
					    }}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@
 | 
				
			|||||||
                        @if (i == 0) {
 | 
					                        @if (i == 0) {
 | 
				
			||||||
                            <td rowspan="@rows">@p.LsNr</td>
 | 
					                            <td rowspan="@rows">@p.LsNr</td>
 | 
				
			||||||
                            <td rowspan="@rows">@p.DPNr</td>
 | 
					                            <td rowspan="@rows">@p.DPNr</td>
 | 
				
			||||||
                            <td class="small">@p.Variant</td>
 | 
					                            <td class="small">@p.Variety</td>
 | 
				
			||||||
                            <td class="small">@p.Attribute</td>
 | 
					                            <td class="small">@p.Attribute</td>
 | 
				
			||||||
                            <td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
 | 
					                            <td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
 | 
				
			||||||
                            <td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
 | 
					                            <td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
 | 
				
			||||||
@@ -81,15 +81,20 @@
 | 
				
			|||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
    <div class="hint">
 | 
					    <div class="hint">
 | 
				
			||||||
        Hinweis:<br/>
 | 
					        Hinweis:<br/>
 | 
				
			||||||
        Die Summe der Lieferungen und die Summe der anfallenden Pönalen werden mit
 | 
					        Die Summe der Lieferungen und die Summe der anfal­lenden Pönalen werden mit
 | 
				
			||||||
        @Model.Payment?.Variant.Season.Precision Nachkommastellen berechnent,
 | 
					        @Model.Payment?.Variant.Season.Precision Nach­komma­stellen berechnent,
 | 
				
			||||||
        erst das Ergebnis wird kaufmännisch auf 2 Nachkommastellen gerundet.
 | 
					        erst das Ergebnis wird kauf­männisch auf 2 Nach­komma­stellen gerundet.
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <table class="credit-sum">
 | 
					    <table class="credit-sum">
 | 
				
			||||||
 | 
					        <colgroup>
 | 
				
			||||||
 | 
					            <col style="width: auto;"/>
 | 
				
			||||||
 | 
					            <col style="width:  5mm;"/>
 | 
				
			||||||
 | 
					            <col style="width: 30mm;"/>
 | 
				
			||||||
 | 
					        </colgroup>
 | 
				
			||||||
        @{
 | 
					        @{
 | 
				
			||||||
            string FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) {
 | 
					            string FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) {
 | 
				
			||||||
                return $"<tr class=\"{(!add && !noTopBorder ? "sum" : !add ? "large" : "")} {(bold ? "large bold" : "")}\">"
 | 
					                return $"<tr class=\"{(!add && !noTopBorder ? "sum" : !add ? "large" : "")} {(bold ? "large bold" : "")}\">"
 | 
				
			||||||
                     + $"<td class=\"{(subCat ? "small" : "")}\" style=\"overflow: visible;\">{name}:</td>"
 | 
					                     + $"<td class=\"{(subCat ? "small" : "")}\">{name}:</td>"
 | 
				
			||||||
                     + $"<td class=\"number {(subCat ? "small" : "large")}\">{(value < 0 ? "–" : (add ? "+" : ""))}</td>"
 | 
					                     + $"<td class=\"number {(subCat ? "small" : "large")}\">{(value < 0 ? "–" : (add ? "+" : ""))}</td>"
 | 
				
			||||||
                     + $"<td class=\"number {(subCat ? "small" : "large")}\">"
 | 
					                     + $"<td class=\"number {(subCat ? "small" : "large")}\">"
 | 
				
			||||||
                     +   $"<span class=\"fleft\">{Model.CurrencySymbol}</span>{Math.Abs(value ?? 0):N2}</td>"
 | 
					                     +   $"<span class=\"fleft\">{Model.CurrencySymbol}</span>{Math.Abs(value ?? 0):N2}</td>"
 | 
				
			||||||
@@ -148,8 +153,9 @@
 | 
				
			|||||||
            @if (Model.Credit == null) {
 | 
					            @if (Model.Credit == null) {
 | 
				
			||||||
                @Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true))
 | 
					                @Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true))
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                if (Model.Credit.Modifiers - penalty != 0) {
 | 
					                var diff = Model.Credit.Modifiers - penalty;
 | 
				
			||||||
                    @Raw(FormatRow("Weitere Abzüge", Model.Credit.Modifiers - penalty, add: true))
 | 
					                if (diff != 0) {
 | 
				
			||||||
 | 
					                    @Raw(FormatRow(diff < 0 ? "Weitere Abzüge" : "Weitere Zuschläge", diff, add: true))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (Model.Credit.PrevModifiers != null && Model.Credit.PrevModifiers != 0) {
 | 
					                if (Model.Credit.PrevModifiers != null && Model.Credit.PrevModifiers != 0) {
 | 
				
			||||||
                    @Raw(FormatRow("Bereits berücksichtigte Abzüge", -Model.Credit.PrevModifiers, add: true))
 | 
					                    @Raw(FormatRow("Bereits berücksichtigte Abzüge", -Model.Credit.PrevModifiers, add: true))
 | 
				
			||||||
@@ -158,4 +164,10 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
 | 
					    <p>Überweisung erfolgt auf Konto @(Elwig.Helpers.Utils.FormatIban(Model.Member.Iban ?? "-")).</p>
 | 
				
			||||||
 | 
					    <div style="margin-top: 1em;">
 | 
				
			||||||
 | 
					        @if (Model.Text != null) {
 | 
				
			||||||
 | 
					            <p class="custom">@Model.Text</p>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
</main>
 | 
					</main>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,8 @@ table.credit tr.last td {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
table.credit-sum {
 | 
					table.credit-sum {
 | 
				
			||||||
    width: 50%;
 | 
					    width: 60%;
 | 
				
			||||||
    margin-left: 50%;
 | 
					    margin-left: 40%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
table.credit-sum tr.sum,
 | 
					table.credit-sum tr.sum,
 | 
				
			||||||
@@ -41,7 +41,7 @@ table.credit-sum td.sum {
 | 
				
			|||||||
.hint {
 | 
					.hint {
 | 
				
			||||||
    font-style: italic;
 | 
					    font-style: italic;
 | 
				
			||||||
    font-size: 8pt;
 | 
					    font-size: 8pt;
 | 
				
			||||||
    width: 74mm;
 | 
					    width: 56mm;
 | 
				
			||||||
    position: absolute;
 | 
					    position: absolute;
 | 
				
			||||||
    left: 0;
 | 
					    left: 0;
 | 
				
			||||||
    margin: 2mm 4mm;
 | 
					    margin: 2mm 4mm;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,11 +10,11 @@ namespace Elwig.Documents {
 | 
				
			|||||||
        public new static string Name => "Anlieferungsbestätigung";
 | 
					        public new static string Name => "Anlieferungsbestätigung";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Season Season;
 | 
					        public Season Season;
 | 
				
			||||||
        public DeliveryConfirmationData 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationData data) :
 | 
					        public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationDeliveryData data) :
 | 
				
			||||||
            base($"{Name} {year}", m) {
 | 
					            base($"{Name} {year}", m) {
 | 
				
			||||||
            Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season");
 | 
					            Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season");
 | 
				
			||||||
            ShowDateAndLocation = true;
 | 
					            ShowDateAndLocation = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,17 +42,17 @@
 | 
				
			|||||||
        </thead>
 | 
					        </thead>
 | 
				
			||||||
        <tbody>
 | 
					        <tbody>
 | 
				
			||||||
            @{
 | 
					            @{
 | 
				
			||||||
                var lastVariant = "";
 | 
					                var lastVariety = "";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            @foreach (var p in Model.Data.Rows) {
 | 
					            @foreach (var p in Model.Data.Rows) {
 | 
				
			||||||
                var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1);
 | 
					                var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1);
 | 
				
			||||||
                var first = true;
 | 
					                var first = true;
 | 
				
			||||||
                @for (int i = 0; i < rows; i++) {
 | 
					                @for (int i = 0; i < rows; i++) {
 | 
				
			||||||
                    <tr class="@(first ? "first" : "") @(p.Variant != lastVariant && lastVariant != "" ? "new": "") @(rows > i + 1 ? "last" : "")">
 | 
					                    <tr class="@(first ? "first" : "") @(p.Variety != lastVariety && lastVariety != "" ? "new": "") @(rows > i + 1 ? "last" : "")">
 | 
				
			||||||
                        @if (first) {
 | 
					                        @if (first) {
 | 
				
			||||||
                            <td rowspan="@rows">@p.LsNr</td>
 | 
					                            <td rowspan="@rows">@p.LsNr</td>
 | 
				
			||||||
                            <td rowspan="@rows">@p.DPNr</td>
 | 
					                            <td rowspan="@rows">@p.DPNr</td>
 | 
				
			||||||
                            <td class="small">@p.Variant</td>
 | 
					                            <td class="small">@p.Variety</td>
 | 
				
			||||||
                            <td class="small">@p.Attribute</td>
 | 
					                            <td class="small">@p.Attribute</td>
 | 
				
			||||||
                            <td class="small">@p.QualityLevel</td>
 | 
					                            <td class="small">@p.QualityLevel</td>
 | 
				
			||||||
                            <td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
 | 
					                            <td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
 | 
				
			||||||
@@ -80,7 +80,7 @@
 | 
				
			|||||||
                            first = false;
 | 
					                            first = false;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    </tr>
 | 
					                    </tr>
 | 
				
			||||||
                    lastVariant = p.Variant;
 | 
					                    lastVariety = p.Variety;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            <tr class="sum bold">
 | 
					            <tr class="sum bold">
 | 
				
			||||||
@@ -92,9 +92,9 @@
 | 
				
			|||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
    @Raw(BusinessDocument.PrintSortenaufteilung(Model.MemberBuckets))
 | 
					    @Raw(BusinessDocument.PrintSortenaufteilung(Model.MemberBuckets))
 | 
				
			||||||
    @Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includePayment: true))
 | 
					    @Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includePayment: true))
 | 
				
			||||||
    <div class="text" style="margin-top: 2em;">
 | 
					    <div style="margin-top: 2em;">
 | 
				
			||||||
        @if (Model.Text != null) {
 | 
					        @if (Model.Text != null) {
 | 
				
			||||||
            <p class="comment" style="white-space: pre-wrap; break-inside: avoid;">@Model.Text</p>
 | 
					            <p class="custom comment">@Model.Text</p>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</main>
 | 
					</main>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ namespace Elwig.Documents {
 | 
				
			|||||||
        public DeliveryJournal(string filter, IQueryable<DeliveryPart> deliveries) :
 | 
					        public DeliveryJournal(string filter, IQueryable<DeliveryPart> deliveries) :
 | 
				
			||||||
            this(filter, deliveries
 | 
					            this(filter, deliveries
 | 
				
			||||||
                .Include(p => p.Delivery).ThenInclude(d => d.Member)
 | 
					                .Include(p => p.Delivery).ThenInclude(d => d.Member)
 | 
				
			||||||
                .Include(p => p.Variant)
 | 
					                .Include(p => p.Variety)
 | 
				
			||||||
                .ToList()) { }
 | 
					                .ToList()) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public DeliveryJournal(AppDbContext ctx, DateOnly date) :
 | 
					        public DeliveryJournal(AppDbContext ctx, DateOnly date) :
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,7 +45,7 @@
 | 
				
			|||||||
                    <td class="small">@($"{p.Delivery.Time:HH:mm}")</td>
 | 
					                    <td class="small">@($"{p.Delivery.Time:HH:mm}")</td>
 | 
				
			||||||
                    <td class="number">@p.Delivery.Member.MgNr</td>
 | 
					                    <td class="number">@p.Delivery.Member.MgNr</td>
 | 
				
			||||||
                    <td class="small">@p.Delivery.Member.AdministrativeName</td>
 | 
					                    <td class="small">@p.Delivery.Member.AdministrativeName</td>
 | 
				
			||||||
                    <td class="small">@p.Variant.Name</td>
 | 
					                    <td class="small">@p.Variety.Name</td>
 | 
				
			||||||
                    <td class="center">@($"{p.Oe:N0}")</td>
 | 
					                    <td class="center">@($"{p.Oe:N0}")</td>
 | 
				
			||||||
                    <td class="center">@($"{p.Kmw:N1}")</td>
 | 
					                    <td class="center">@($"{p.Kmw:N1}")</td>
 | 
				
			||||||
                    <td class="number">@($"{p.Weight:N0}")</td>
 | 
					                    <td class="number">@($"{p.Weight:N0}")</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ namespace Elwig.Documents {
 | 
				
			|||||||
        // 3 - full
 | 
					        // 3 - full
 | 
				
			||||||
        public int DisplayStats = App.Client.ModeDeliveryNoteStats;
 | 
					        public int DisplayStats = App.Client.ModeDeliveryNoteStats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public DeliveryNote(Delivery d, AppDbContext ctx) : base($"Traubenübernahmeschein Nr. {d.LsNr}", d.Member) {
 | 
					        public DeliveryNote(Delivery d, AppDbContext? ctx = null) : base($"Traubenübernahmeschein Nr. {d.LsNr}", d.Member) {
 | 
				
			||||||
            UseBillingAddress = true;
 | 
					            UseBillingAddress = true;
 | 
				
			||||||
            ShowDateAndLocation = true;
 | 
					            ShowDateAndLocation = true;
 | 
				
			||||||
            Delivery = d;
 | 
					            Delivery = d;
 | 
				
			||||||
@@ -27,7 +27,7 @@ namespace Elwig.Documents {
 | 
				
			|||||||
                $"</tbody></table>";
 | 
					                $"</tbody></table>";
 | 
				
			||||||
            Text = App.Client.TextDeliveryNote;
 | 
					            Text = App.Client.TextDeliveryNote;
 | 
				
			||||||
            DocumentId = d.LsNr;
 | 
					            DocumentId = d.LsNr;
 | 
				
			||||||
            MemberBuckets = ctx.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult();
 | 
					            MemberBuckets = ctx?.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult() ?? [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@
 | 
				
			|||||||
        @foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) {
 | 
					        @foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) {
 | 
				
			||||||
            <tr class="main">
 | 
					            <tr class="main">
 | 
				
			||||||
                <td class="center">@part.DPNr</td>
 | 
					                <td class="center">@part.DPNr</td>
 | 
				
			||||||
                <td colspan="2">@part.Variant.Name</td>
 | 
					                <td colspan="2">@part.Variety.Name</td>
 | 
				
			||||||
                <td colspan="2">@part.Attribute?.Name</td>
 | 
					                <td colspan="2">@part.Attribute?.Name</td>
 | 
				
			||||||
                <td>@part.Quality.Name</td>
 | 
					                <td>@part.Quality.Name</td>
 | 
				
			||||||
                <td class="center">@($"{part.Oe:N0}")</td>
 | 
					                <td class="center">@($"{part.Oe:N0}")</td>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ namespace Elwig.Documents {
 | 
				
			|||||||
        public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) {
 | 
					        public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) {
 | 
				
			||||||
            DocumentId = $"{Name} {m.MgNr}";
 | 
					            DocumentId = $"{Name} {m.MgNr}";
 | 
				
			||||||
            Season = ctx.Seasons.ToList().MaxBy(s => s.Year) ?? throw new ArgumentException("invalid season");
 | 
					            Season = ctx.Seasons.ToList().MaxBy(s => s.Year) ?? throw new ArgumentException("invalid season");
 | 
				
			||||||
            MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult();
 | 
					            MemberBuckets = ctx.GetMemberBuckets(Utils.CurrentYear, m.MgNr).GetAwaiter().GetResult();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,8 +63,8 @@
 | 
				
			|||||||
                <td colspan="5">
 | 
					                <td colspan="5">
 | 
				
			||||||
                    @if (Model.Member.BillingAddress != null) {
 | 
					                    @if (Model.Member.BillingAddress != null) {
 | 
				
			||||||
                        @Model.Member.BillingAddress.PostalDest.AtPlz?.Plz
 | 
					                        @Model.Member.BillingAddress.PostalDest.AtPlz?.Plz
 | 
				
			||||||
                        @Model.Member.BillingAddress.PostalDest.AtPlz?.Dest
 | 
					                        @(" ")@Model.Member.BillingAddress.PostalDest.AtPlz?.Dest
 | 
				
			||||||
                        @("(")@Model.Member.BillingAddress.PostalDest.AtPlz?.Ort.Name@(")")
 | 
					                        @(" (")@Model.Member.BillingAddress.PostalDest.AtPlz?.Ort.Name@(")")
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
    <UseWPF>true</UseWPF>
 | 
					    <UseWPF>true</UseWPF>
 | 
				
			||||||
    <PreserveCompilationContext>true</PreserveCompilationContext>
 | 
					    <PreserveCompilationContext>true</PreserveCompilationContext>
 | 
				
			||||||
    <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
 | 
					    <ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
 | 
				
			||||||
    <Version>0.5.1</Version>
 | 
					    <Version>0.6.8</Version>
 | 
				
			||||||
    <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
 | 
					    <SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
 | 
				
			||||||
  </PropertyGroup>
 | 
					  </PropertyGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,14 +25,14 @@
 | 
				
			|||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" />
 | 
					    <PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" />
 | 
				
			||||||
    <PackageReference Include="LinqKit" Version="1.2.5" />
 | 
					    <PackageReference Include="LinqKit" Version="1.2.5" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.25" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.26" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.1" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
 | 
					    <PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2151.40" />
 | 
					    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2210.55" />
 | 
				
			||||||
    <PackageReference Include="NJsonSchema" Version="11.0.0" />
 | 
					    <PackageReference Include="NJsonSchema" Version="11.0.0" />
 | 
				
			||||||
    <PackageReference Include="RazorLight" Version="2.3.1" />
 | 
					    <PackageReference Include="RazorLight" Version="2.3.1" />
 | 
				
			||||||
    <PackageReference Include="ScottPlot.WPF" Version="4.1.68" />
 | 
					    <PackageReference Include="ScottPlot.WPF" Version="5.0.19" />
 | 
				
			||||||
    <PackageReference Include="System.IO.Ports" Version="8.0.0" />
 | 
					    <PackageReference Include="System.IO.Ports" Version="8.0.0" />
 | 
				
			||||||
    <PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
 | 
					    <PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,6 +58,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
 | 
					        public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
 | 
				
			||||||
        public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
 | 
					        public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
 | 
				
			||||||
        public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; }
 | 
					        public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; }
 | 
				
			||||||
 | 
					        public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
 | 
				
			||||||
        public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
 | 
					        public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly StreamWriter? LogFile = null;
 | 
					        private readonly StreamWriter? LogFile = null;
 | 
				
			||||||
@@ -159,16 +160,16 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task<int> NextMgNr() {
 | 
					        public async Task<int> NextMgNr() {
 | 
				
			||||||
            int c = await Members.Select(m => m.MgNr).MinAsync();
 | 
					            int c = 0;
 | 
				
			||||||
            (await Members.OrderBy(m => m.MgNr).Select(m => m.MgNr).ToListAsync())
 | 
					            (await Members.OrderBy(m => m.MgNr).Select(m => m.MgNr).ToListAsync())
 | 
				
			||||||
                .ForEach(a => { if (a <= c + 1000) c = a; });
 | 
					                .ForEach(a => { if (a <= c + 1000) c = a; });
 | 
				
			||||||
            return c + 1;
 | 
					            return c + 1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task<int> NextFbNr() {
 | 
					        public async Task<int> NextFbNr() {
 | 
				
			||||||
            int c = await AreaCommitments.Select(ac => ac.FbNr).MinAsync();
 | 
					            int c = 0;
 | 
				
			||||||
            (await AreaCommitments.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync())
 | 
					            (await AreaCommitments.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync())
 | 
				
			||||||
                .ForEach(a => { if (a <= c + 1000) c = a; });
 | 
					                .ForEach(a => { if (a <= c + 10000) c = a; });
 | 
				
			||||||
            return c + 1;
 | 
					            return c + 1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ 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 = 13;
 | 
					        public static readonly int RequiredSchemaVersion = 17;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static int VersionOffset = 0;
 | 
					        private static int VersionOffset = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
 | 
					using Microsoft.Data.Sqlite;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
@@ -8,6 +10,7 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected readonly int Year;
 | 
					        protected readonly int Year;
 | 
				
			||||||
        protected readonly AppDbContext Context;
 | 
					        protected readonly AppDbContext Context;
 | 
				
			||||||
 | 
					        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?)> AreaComTypes;
 | 
				
			||||||
@@ -15,6 +18,7 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
        public Billing(int year) {
 | 
					        public Billing(int year) {
 | 
				
			||||||
            Year = year;
 | 
					            Year = year;
 | 
				
			||||||
            Context = new AppDbContext();
 | 
					            Context = new AppDbContext();
 | 
				
			||||||
 | 
					            Season = Context.Seasons.Find(Year)!;
 | 
				
			||||||
            Attributes = Context.WineAttributes.ToDictionary(a => a.AttrId, a => a.Name);
 | 
					            Attributes = Context.WineAttributes.ToDictionary(a => a.AttrId, a => a.Name);
 | 
				
			||||||
            Modifiers = Context.Modifiers.Where(m => m.Year == Year).ToDictionary(m => m.ModId, m => (m.Abs, m.Rel));
 | 
					            Modifiers = Context.Modifiers.Where(m => m.Year == Year).ToDictionary(m => m.ModId, m => (m.Abs, m.Rel));
 | 
				
			||||||
            AreaComTypes = Context.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount));
 | 
					            AreaComTypes = Context.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount));
 | 
				
			||||||
@@ -26,8 +30,6 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                UPDATE season
 | 
					                UPDATE season
 | 
				
			||||||
                SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year})
 | 
					                SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year})
 | 
				
			||||||
                WHERE year = {Year};
 | 
					                WHERE year = {Year};
 | 
				
			||||||
 | 
					 | 
				
			||||||
                DELETE FROM delivery_part_bucket WHERE year = {Year};
 | 
					 | 
				
			||||||
                """);
 | 
					                """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,10 +45,19 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                """);
 | 
					                """);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task CalculateBuckets(bool allowAttrsIntoLower, bool avoidUnderDeliveries, bool honorGebunden) {
 | 
					        public async Task CalculateBuckets(
 | 
				
			||||||
 | 
					            bool? honorGebundenField = null,
 | 
				
			||||||
 | 
					            bool? allowAttributesIntoLower = null,
 | 
				
			||||||
 | 
					            bool? avoidUnderDeliveries = null,
 | 
				
			||||||
 | 
					            SqliteConnection? cnx = null
 | 
				
			||||||
 | 
					         ) {
 | 
				
			||||||
 | 
					            var honorGebunden = honorGebundenField ?? Season.Billing_HonorGebunden;
 | 
				
			||||||
 | 
					            var allowAttrsIntoLower = allowAttributesIntoLower ?? Season.Billing_AllowAttrsIntoLower;
 | 
				
			||||||
 | 
					            var avoidUnderDlvrs = avoidUnderDeliveries ?? Season.Billing_AvoidUnderDeliveries;
 | 
				
			||||||
            var attrVals = Context.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower));
 | 
					            var attrVals = Context.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower));
 | 
				
			||||||
            var attrForced = attrVals.Where(a => a.Value.IsStrict && a.Value.FillLower == 0).Select(a => a.Key).ToArray();
 | 
					            var attrForced = attrVals.Where(a => a.Value.IsStrict && a.Value.FillLower == 0).Select(a => a.Key).ToArray();
 | 
				
			||||||
            using var cnx = await AppDbContext.ConnectAsync();
 | 
					            var ownCnx = cnx == null;
 | 
				
			||||||
 | 
					            cnx ??= await AppDbContext.ConnectAsync();
 | 
				
			||||||
            await Context.GetMemberAreaCommitmentBuckets(Year, 0, cnx);
 | 
					            await Context.GetMemberAreaCommitmentBuckets(Year, 0, cnx);
 | 
				
			||||||
            var inserts = new List<(int, int, int, string, int)>();
 | 
					            var inserts = new List<(int, int, int, string, int)>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,7 +76,7 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                        reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetString(3), reader.GetInt32(4),
 | 
					                        reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetString(3), reader.GetInt32(4),
 | 
				
			||||||
                        reader.GetDouble(5), reader.GetString(6),
 | 
					                        reader.GetDouble(5), reader.GetString(6),
 | 
				
			||||||
                        reader.IsDBNull(7) ? null : reader.GetString(7),
 | 
					                        reader.IsDBNull(7) ? null : reader.GetString(7),
 | 
				
			||||||
                        reader.IsDBNull(8) ? Array.Empty<string>() : reader.GetString(8).Split(",").Order().ToArray(),
 | 
					                        reader.IsDBNull(8) ? [] : reader.GetString(8).Split(",").Order().ToArray(),
 | 
				
			||||||
                        reader.IsDBNull(9) ? null : reader.GetBoolean(9)
 | 
					                        reader.IsDBNull(9) ? null : reader.GetBoolean(9)
 | 
				
			||||||
                    ));
 | 
					                    ));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -73,11 +84,11 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            int lastMgNr = 0;
 | 
					            int lastMgNr = 0;
 | 
				
			||||||
            Dictionary<string, AreaComBucket>? rightsAndObligations = null;
 | 
					            Dictionary<string, AreaComBucket>? rightsAndObligations = null;
 | 
				
			||||||
            Dictionary<string, int> used = new();
 | 
					            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, gebunden) in deliveries) {
 | 
				
			||||||
                if (lastMgNr != mgnr) {
 | 
					                if (lastMgNr != mgnr) {
 | 
				
			||||||
                    rightsAndObligations = await Context.GetMemberAreaCommitmentBuckets(Year, mgnr);
 | 
					                    rightsAndObligations = await Context.GetMemberAreaCommitmentBuckets(Year, mgnr);
 | 
				
			||||||
                    used = new();
 | 
					                    used = [];
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if ((honorGebunden && gebunden == false) ||
 | 
					                if ((honorGebunden && gebunden == false) ||
 | 
				
			||||||
                    rightsAndObligations == null || rightsAndObligations.Count == 0 ||
 | 
					                    rightsAndObligations == null || rightsAndObligations.Count == 0 ||
 | 
				
			||||||
@@ -92,16 +103,16 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                int w = weight;
 | 
					                int w = weight;
 | 
				
			||||||
                var attributes = attrid == null ? Array.Empty<string>() : new string[] { attrid };
 | 
					                var attributes = attrid == null ? [] : new string[] { attrid };
 | 
				
			||||||
                var isStrict = attrid != null && attrVals[attrid].IsStrict;
 | 
					                var isStrict = attrid != null && attrVals[attrid].IsStrict;
 | 
				
			||||||
                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.ContainsKey(key)) {
 | 
					                    if (rightsAndObligations.TryGetValue(key, out AreaComBucket 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(rightsAndObligations[key].Right - u, w));
 | 
					                        var vr = Math.Max(0, Math.Min(value.Right - u, w));
 | 
				
			||||||
                        var vo = Math.Max(0, Math.Min(rightsAndObligations[key].Obligation - u, w));
 | 
					                        var vo = Math.Max(0, Math.Min(value.Obligation - u, w));
 | 
				
			||||||
                        var v = (attributes.Length == c || attributes.Select(a => !attrVals[a].IsStrict ? 2 : attrVals[a].FillLower).Min() == 2) ? vr : vo;
 | 
					                        var v = (attributes.Length == c || attributes.Select(a => !attrVals[a].IsStrict ? 2 : attrVals[a].FillLower).Min() == 2) ? vr : vo;
 | 
				
			||||||
                        used[key] = u + v;
 | 
					                        used[key] = u + v;
 | 
				
			||||||
                        if (key.Length > 2 && !isStrict) used[key[..2]] = used.GetValueOrDefault(key[..2], 0) + v;
 | 
					                        if (key.Length > 2 && !isStrict) used[key[..2]] = used.GetValueOrDefault(key[..2], 0) + v;
 | 
				
			||||||
@@ -115,14 +126,17 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await AppDbContext.ExecuteBatch(cnx, $"""
 | 
					            await AppDbContext.ExecuteBatch(cnx, $"""
 | 
				
			||||||
 | 
					                UPDATE delivery_part_bucket SET value = 0 WHERE year = {Year};
 | 
				
			||||||
                INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
 | 
					                INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value)
 | 
				
			||||||
                VALUES {string.Join(",\n       ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
 | 
					                VALUES {string.Join(",\n       ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))}
 | 
				
			||||||
                ON CONFLICT DO UPDATE
 | 
					                ON CONFLICT DO UPDATE
 | 
				
			||||||
                SET discr = excluded.discr, value = value + excluded.value;
 | 
					                SET discr = excluded.discr, value = value + excluded.value;
 | 
				
			||||||
                """);
 | 
					                """);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!avoidUnderDeliveries)
 | 
					            if (!avoidUnderDlvrs) {
 | 
				
			||||||
 | 
					                if (ownCnx) await cnx.DisposeAsync();
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // FIXME avoidUnderDelivery-calculations not always right!
 | 
					            // FIXME avoidUnderDelivery-calculations not always right!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -200,6 +214,8 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                    ON CONFLICT DO UPDATE
 | 
					                    ON CONFLICT DO UPDATE
 | 
				
			||||||
                    SET value = excluded.value;
 | 
					                    SET value = excluded.value;
 | 
				
			||||||
                    """);
 | 
					                    """);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (ownCnx) await cnx.DisposeAsync();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,6 @@ 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>? Gebunden);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static JsonSchema? Schema { get; private set; }
 | 
					        public static JsonSchema? Schema { get; private set; }
 | 
				
			||||||
@@ -24,11 +23,7 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public readonly JsonObject Data;
 | 
					        public readonly JsonObject Data;
 | 
				
			||||||
 | 
					        public readonly CalculationMode Mode;
 | 
				
			||||||
        private readonly CalculationMode Mode;
 | 
					 | 
				
			||||||
        private readonly Dictionary<int, Curve> Curves;
 | 
					 | 
				
			||||||
        private readonly Dictionary<string, Curve> PaymentData;
 | 
					 | 
				
			||||||
        private readonly Dictionary<string, Curve> QualityData;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public bool ConsiderDelieryModifiers {
 | 
					        public bool ConsiderDelieryModifiers {
 | 
				
			||||||
            get => GetConsider("consider_delivery_modifiers");
 | 
					            get => GetConsider("consider_delivery_modifiers");
 | 
				
			||||||
@@ -61,18 +56,13 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public BillingData(JsonObject data, IEnumerable<string> attributeVariants) {
 | 
					        public BillingData(JsonObject data) {
 | 
				
			||||||
            if (attributeVariants.Any(e => e.Any(c => c < 'A' || c > 'Z')))
 | 
					 | 
				
			||||||
                throw new ArgumentException("Invalid attributeVariants");
 | 
					 | 
				
			||||||
            Data = data;
 | 
					            Data = data;
 | 
				
			||||||
            var mode = Data["mode"]?.GetValue<string>();
 | 
					            var mode = Data["mode"]?.GetValue<string>();
 | 
				
			||||||
            Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster;
 | 
					            Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster;
 | 
				
			||||||
            Curves = GetCurves(Data, Mode);
 | 
					 | 
				
			||||||
            PaymentData = GetPaymentData(attributeVariants);
 | 
					 | 
				
			||||||
            QualityData = GetQualityData(attributeVariants);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public 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 {
 | 
				
			||||||
                var errors = Schema.Validate(json);
 | 
					                var errors = Schema.Validate(json);
 | 
				
			||||||
@@ -84,11 +74,19 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static BillingData FromJson(string json) {
 | 
					        public static BillingData FromJson(string json) {
 | 
				
			||||||
            return FromJson(json, []);
 | 
					            return new(ParseJson(json));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static BillingData FromJson(string json, IEnumerable<string> attributeVariants) {
 | 
					        protected JsonArray GetCurvesEntry() {
 | 
				
			||||||
            return new(ParseJson(json), attributeVariants);
 | 
					            return Data[Mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected JsonNode GetPaymentEntry() {
 | 
				
			||||||
 | 
					            return Data[Mode == CalculationMode.Elwig ? "payment" : "AuszahlungSorten"] ?? throw new InvalidOperationException();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected JsonObject? GetQualityEntry() {
 | 
				
			||||||
 | 
					            return Data[Mode == CalculationMode.Elwig ? "quality" : "AuszahlungSortenQualitätsstufe"]?.AsObject();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static Dictionary<double, decimal> GetCurveData(JsonObject data, CurveMode mode) {
 | 
					        private static Dictionary<double, decimal> GetCurveData(JsonObject data, CurveMode mode) {
 | 
				
			||||||
@@ -119,13 +117,14 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
            return dict;
 | 
					            return dict;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static Dictionary<int, Curve> GetCurves(JsonObject data, CalculationMode mode) {
 | 
					        protected Dictionary<int, Curve> GetCurves() {
 | 
				
			||||||
            var dict = new Dictionary<int, Curve>();
 | 
					            var dict = new Dictionary<int, Curve>();
 | 
				
			||||||
            var curves = data[mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException();
 | 
					            var curves = GetCurvesEntry();
 | 
				
			||||||
            foreach (var c in curves) {
 | 
					            foreach (var c in curves) {
 | 
				
			||||||
                var obj = c?.AsObject() ?? throw new InvalidOperationException();
 | 
					                var obj = c?.AsObject() ?? throw new InvalidOperationException();
 | 
				
			||||||
                var id = obj["id"]?.GetValue<int>() ?? throw new InvalidOperationException();
 | 
					                var id = obj["id"]?.GetValue<int>() ?? throw new InvalidOperationException();
 | 
				
			||||||
                var cMode = (obj["mode"]?.GetValue<string>() == "kmw") ? CurveMode.Kmw : CurveMode.Oe;
 | 
					                var cMode = (obj["mode"]?.GetValue<string>() == "kmw") ? CurveMode.Kmw : CurveMode.Oe;
 | 
				
			||||||
 | 
					                double quw = cMode == CurveMode.Oe ? 73 : 15;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Dictionary<double, decimal> c1;
 | 
					                Dictionary<double, decimal> c1;
 | 
				
			||||||
                Dictionary<double, decimal>? c2 = null;
 | 
					                Dictionary<double, decimal>? c2 = null;
 | 
				
			||||||
@@ -133,7 +132,7 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                if (norm is JsonObject) {
 | 
					                if (norm is JsonObject) {
 | 
				
			||||||
                    c1 = GetCurveData(norm.AsObject(), cMode);
 | 
					                    c1 = GetCurveData(norm.AsObject(), cMode);
 | 
				
			||||||
                } else if (norm?.AsValue().TryGetValue(out decimal v) == true) {
 | 
					                } else if (norm?.AsValue().TryGetValue(out decimal v) == true) {
 | 
				
			||||||
                    c1 = new() { { cMode == CurveMode.Oe ? 73 : 15, v } };
 | 
					                    c1 = new() { { quw, v } };
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    throw new InvalidOperationException();
 | 
					                    throw new InvalidOperationException();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -141,114 +140,248 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                if (geb is JsonObject) {
 | 
					                if (geb is JsonObject) {
 | 
				
			||||||
                    c2 = GetCurveData(geb.AsObject(), cMode);
 | 
					                    c2 = GetCurveData(geb.AsObject(), cMode);
 | 
				
			||||||
                } else if (geb?.AsValue().TryGetValue(out decimal v) == true) {
 | 
					                } else if (geb?.AsValue().TryGetValue(out decimal v) == true) {
 | 
				
			||||||
                    c2 = c1.ToDictionary(e => e.Key, e => e.Value + v);
 | 
					                    var splitVal = GetCurveValueAt(c1, quw);
 | 
				
			||||||
 | 
					                    c2 = c1.ToDictionary(e => e.Key, e => e.Value + (e.Key >= quw ? v : 0));
 | 
				
			||||||
 | 
					                    c2[quw] = splitVal + v;
 | 
				
			||||||
 | 
					                    c2[Math.BitDecrement(quw)] = splitVal;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                dict.Add(id, new(cMode, c1, c2));
 | 
					                dict.Add(id, new(cMode, c1, c2));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return dict;
 | 
					            return dict;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Dictionary<string, Curve> GetData(JsonObject data, IEnumerable<string> attributeVariants) {
 | 
					        protected static Dictionary<string, JsonValue> GetSelection(JsonNode value, IEnumerable<string> vaributes) {
 | 
				
			||||||
            Dictionary<string, Curve> dict;
 | 
					            if (value is JsonValue flatRate) {
 | 
				
			||||||
 | 
					                return vaributes.ToDictionary(e => e, _ => flatRate);
 | 
				
			||||||
 | 
					            } if (value is not JsonObject data) {
 | 
				
			||||||
 | 
					                throw new InvalidOperationException();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Dictionary<string, JsonValue> dict;
 | 
				
			||||||
            if (data["default"] is JsonValue def) {
 | 
					            if (data["default"] is JsonValue def) {
 | 
				
			||||||
                var c = LookupCurve(def);
 | 
					                dict = vaributes.ToDictionary(e => e, _ => def);
 | 
				
			||||||
                dict = attributeVariants.ToDictionary(e => e, _ => c);
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                dict = [];
 | 
					                dict = [];
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var variants = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length == 2);
 | 
					            var varieties = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length == 2);
 | 
				
			||||||
            var attributes = data.Where(p => p.Key.StartsWith('/'));
 | 
					            var attributes = data.Where(p => p.Key.StartsWith('/'));
 | 
				
			||||||
            var others = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length > 2);
 | 
					            var others = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length > 2 && p.Key != "default");
 | 
				
			||||||
            foreach (var (idx, v) in variants) {
 | 
					            foreach (var (idx, v) in varieties) {
 | 
				
			||||||
                var curve = LookupCurve(v?.AsValue() ?? throw new InvalidOperationException());
 | 
					                var curve = v?.AsValue() ?? throw new InvalidOperationException();
 | 
				
			||||||
                foreach (var i in attributeVariants.Where(e => e.StartsWith(idx[..^1]))) {
 | 
					                foreach (var i in vaributes.Where(e => e.StartsWith(idx[..^1]))) {
 | 
				
			||||||
                    dict[i] = curve;
 | 
					                    dict[i] = curve;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            foreach (var (idx, v) in attributes) {
 | 
					            foreach (var (idx, v) in attributes) {
 | 
				
			||||||
                var curve = LookupCurve(v?.AsValue() ?? throw new InvalidOperationException());
 | 
					                var curve = v?.AsValue() ?? throw new InvalidOperationException();
 | 
				
			||||||
                foreach (var i in attributeVariants.Where(e => e[2..] == idx[1..])) {
 | 
					                foreach (var i in vaributes.Where(e => e[2..] == idx[1..])) {
 | 
				
			||||||
                    dict[i] = curve;
 | 
					                    dict[i] = curve;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            foreach (var (idx, v) in others) {
 | 
					            foreach (var (idx, v) in others) {
 | 
				
			||||||
                var curve = LookupCurve(v?.AsValue() ?? throw new InvalidOperationException());
 | 
					                var curve = v?.AsValue() ?? throw new InvalidOperationException();
 | 
				
			||||||
                dict[idx.Replace("/", "")] = curve;
 | 
					                dict[idx.Replace("/", "")] = curve;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return dict;
 | 
					            return dict;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Dictionary<string, Curve> GetPaymentData(IEnumerable<string> attributeVariants) {
 | 
					        public static decimal GetCurveValueAt(Dictionary<double, decimal> curve, double key) {
 | 
				
			||||||
            var p = Data[Mode == CalculationMode.Elwig ? "payment" : "AuszahlungSorten"];
 | 
					            if (curve.Count == 1) return curve.First().Value;
 | 
				
			||||||
            if (p is JsonValue val) {
 | 
					 | 
				
			||||||
                var c = LookupCurve(val);
 | 
					 | 
				
			||||||
                return attributeVariants.ToDictionary(e => e, _ => c);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return GetData(p?.AsObject() ?? throw new InvalidOperationException(), attributeVariants);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Dictionary<string, Curve> GetQualityData(IEnumerable<string> attributeVariants) {
 | 
					            var lt = curve.Keys.Where(v => v <= key);
 | 
				
			||||||
            var q = Data[Mode == CalculationMode.Elwig ? "quality" : "AuszahlungSortenQualitätsstufe"]?.AsObject();
 | 
					            var gt = curve.Keys.Where(v => v >= key);
 | 
				
			||||||
            Dictionary<string, Curve> dict = [];
 | 
					 | 
				
			||||||
            if (q == null) return dict;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var (qualid, data) in q) {
 | 
					 | 
				
			||||||
                Dictionary<string, Curve> qualDict;
 | 
					 | 
				
			||||||
                if (data is JsonValue val) {
 | 
					 | 
				
			||||||
                    var c = LookupCurve(val);
 | 
					 | 
				
			||||||
                    qualDict = attributeVariants.ToDictionary(e => e, _ => c);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    qualDict = GetData(data?.AsObject() ?? throw new InvalidOperationException(), attributeVariants);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                foreach (var (idx, d) in qualDict) {
 | 
					 | 
				
			||||||
                    dict[$"{qualid}/{idx}"] = d;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return dict;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public decimal CalculatePrice(string sortid, string? attrid, string qualid, bool gebunden, double oe, double kmw) {
 | 
					 | 
				
			||||||
            var curve = GetQualityCurve(qualid, sortid, attrid) ?? GetCurve(sortid, attrid);
 | 
					 | 
				
			||||||
            var d = (gebunden ? curve.Gebunden : null) ?? curve.Normal;
 | 
					 | 
				
			||||||
            if (d.Count == 1) return d.First().Value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var r = curve.Mode == CurveMode.Oe ? oe : kmw;
 | 
					 | 
				
			||||||
            var lt = d.Keys.Where(v => v <= r);
 | 
					 | 
				
			||||||
            var gt = d.Keys.Where(v => v >= r);
 | 
					 | 
				
			||||||
            if (!lt.Any()) {
 | 
					            if (!lt.Any()) {
 | 
				
			||||||
                return d[gt.Min()];
 | 
					                return curve[gt.Min()];
 | 
				
			||||||
            } else if (!gt.Any()) {
 | 
					            } else if (!gt.Any()) {
 | 
				
			||||||
                return d[lt.Max()];
 | 
					                return curve[lt.Max()];
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var max = lt.Max();
 | 
					            var max = lt.Max();
 | 
				
			||||||
            var min = gt.Min();
 | 
					            var min = gt.Min();
 | 
				
			||||||
            if (max == min) return d[r];
 | 
					            if (max == min) return curve[key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var p1 = ((decimal)r - (decimal)min) / ((decimal)max - (decimal)min);
 | 
					            var p1 = ((decimal)key - (decimal)min) / ((decimal)max - (decimal)min);
 | 
				
			||||||
            var p2 = 1 - p1;
 | 
					            var p2 = 1 - p1;
 | 
				
			||||||
            return d[min] * p2 + d[max] * p1;
 | 
					            return curve[min] * p2 + curve[max] * p1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Curve LookupCurve(JsonValue val) {
 | 
					        protected static JsonObject GraphToJson(Graph graph, string mode) {
 | 
				
			||||||
            if (val.TryGetValue(out string? curve)) {
 | 
					            var x = graph.DataX;
 | 
				
			||||||
                var curveId = int.Parse(curve.Split(":")[1]);
 | 
					            var y = graph.DataY;
 | 
				
			||||||
                return Curves[curveId];
 | 
					            var prec = graph.Precision;
 | 
				
			||||||
            } else if (val.TryGetValue(out decimal value)) {
 | 
					
 | 
				
			||||||
                return new(CurveMode.Oe, new() { { 73, value } }, null);
 | 
					            try {
 | 
				
			||||||
 | 
					                return new JsonObject() {
 | 
				
			||||||
 | 
					                    ["15kmw"] = Math.Round(y.Distinct().Single(), prec)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            } catch { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var data = new JsonObject();
 | 
				
			||||||
 | 
					            if (y[0] != y[1]) {
 | 
				
			||||||
 | 
					                data[$"{x[0]}{mode}"] = Math.Round(y[0], prec);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            throw new InvalidOperationException();
 | 
					            for (int i = 1; i < x.Length - 1; i++) {
 | 
				
			||||||
 | 
					                var d1 = Math.Round(y[i] - y[i - 1], prec);
 | 
				
			||||||
 | 
					                var d2 = Math.Round(y[i + 1] - y[i], prec);
 | 
				
			||||||
 | 
					                if (d1 != d2) {
 | 
				
			||||||
 | 
					                    data[$"{x[i]}{mode}"] = Math.Round(y[i], prec);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (y[^1] != y[^2]) {
 | 
				
			||||||
 | 
					                data[$"{x[^1]}{mode}"] = Math.Round(y[^1], prec);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return data;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Curve GetCurve(string sortid, string? attrid) {
 | 
					        protected static JsonNode GraphEntryToJson(GraphEntry entry) {
 | 
				
			||||||
            return PaymentData[$"{sortid}{attrid ?? ""}"];
 | 
					            try {
 | 
				
			||||||
 | 
					                if (entry.GebundenFlatBonus == null) {
 | 
				
			||||||
 | 
					                    return JsonValue.Create((decimal)entry.DataGraph.DataY.Distinct().Single());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var curve = new JsonObject {
 | 
				
			||||||
 | 
					                ["id"] = entry.Id,
 | 
				
			||||||
 | 
					                ["mode"] = entry.Mode.ToString().ToLower(),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (entry.GebundenFlatBonus != null) {
 | 
				
			||||||
 | 
					                curve["geb"] = (decimal)entry.GebundenFlatBonus;
 | 
				
			||||||
 | 
					            } else if (entry.GebundenGraph != null) {
 | 
				
			||||||
 | 
					                curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Curve? GetQualityCurve(string qualid, string sortid, string? attrid) {
 | 
					            return curve;
 | 
				
			||||||
            return QualityData.TryGetValue($"{qualid}/{sortid}{attrid ?? ""}", out var curve) ? curve : null;
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected static void CollapsePaymentData(JsonObject data, IEnumerable<string> vaributes, bool useDefault = true) {
 | 
				
			||||||
 | 
					            Dictionary<string, List<string>> rev1 = [];
 | 
				
			||||||
 | 
					            Dictionary<decimal, List<string>> rev2 = [];
 | 
				
			||||||
 | 
					            foreach (var (k, v) in data) {
 | 
				
			||||||
 | 
					                if (k == "default" || k.StartsWith('/') || !k.Contains('/') || v is not JsonValue val) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                } else if (val.TryGetValue<decimal>(out var dec)) {
 | 
				
			||||||
 | 
					                    rev2[dec] = rev2.GetValueOrDefault(dec) ?? [];
 | 
				
			||||||
 | 
					                    rev2[dec].Add(k);
 | 
				
			||||||
 | 
					                } else if (val.TryGetValue<string>(out var cur)) {
 | 
				
			||||||
 | 
					                    rev1[cur] = rev1.GetValueOrDefault(cur) ?? [];
 | 
				
			||||||
 | 
					                    rev1[cur].Add(k);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!data.ContainsKey("default")) {
 | 
				
			||||||
 | 
					                foreach (var (v, ks) in rev1) {
 | 
				
			||||||
 | 
					                    if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
 | 
				
			||||||
 | 
					                        foreach (var k in ks) data.Remove(k);
 | 
				
			||||||
 | 
					                        data["default"] = v;
 | 
				
			||||||
 | 
					                        CollapsePaymentData(data, vaributes, useDefault);
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                foreach (var (v, ks) in rev2) {
 | 
				
			||||||
 | 
					                    if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) {
 | 
				
			||||||
 | 
					                        foreach (var k in ks) data.Remove(k);
 | 
				
			||||||
 | 
					                        data["default"] = v;
 | 
				
			||||||
 | 
					                        CollapsePaymentData(data, vaributes, useDefault);
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            var attributes = data
 | 
				
			||||||
 | 
					                .Select(e => e.Key)
 | 
				
			||||||
 | 
					                .Where(k => k.Length > 3 && k.Contains('/'))
 | 
				
			||||||
 | 
					                .Select(k => "/" + k.Split('/')[1])
 | 
				
			||||||
 | 
					                .Distinct()
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					            foreach (var idx in attributes) {
 | 
				
			||||||
 | 
					                var len = vaributes.Count(e => e.EndsWith(idx));
 | 
				
			||||||
 | 
					                foreach (var (v, ks) in rev1) {
 | 
				
			||||||
 | 
					                    var myKs = ks.Where(k => k.EndsWith(idx)).ToList();
 | 
				
			||||||
 | 
					                    if (myKs.Count > 1 && ((myKs.Count >= len * 0.5 && useDefault) || myKs.Count == len)) {
 | 
				
			||||||
 | 
					                        foreach (var k in myKs) data.Remove(k);
 | 
				
			||||||
 | 
					                        data[idx] = v;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                foreach (var (v, ks) in rev2) {
 | 
				
			||||||
 | 
					                    var myKs = ks.Where(k => k.EndsWith(idx)).ToList();
 | 
				
			||||||
 | 
					                    if (myKs.Count > 1 && ((myKs.Count >= len * 0.5 && useDefault) || myKs.Count == len)) {
 | 
				
			||||||
 | 
					                        foreach (var k in myKs) data.Remove(k);
 | 
				
			||||||
 | 
					                        data[idx] = v;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static JsonObject FromGraphEntries(
 | 
				
			||||||
 | 
					            IEnumerable<GraphEntry> graphEntries,
 | 
				
			||||||
 | 
					            BillingData? origData = null,
 | 
				
			||||||
 | 
					            IEnumerable<string>? vaributes = null,
 | 
				
			||||||
 | 
					            bool useDefaultPayment = true,
 | 
				
			||||||
 | 
					            bool useDefaultQuality = true
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            var payment = new JsonObject();
 | 
				
			||||||
 | 
					            var qualityWei = new JsonObject();
 | 
				
			||||||
 | 
					            var curves = new JsonArray();
 | 
				
			||||||
 | 
					            int curveId = 0;
 | 
				
			||||||
 | 
					            foreach (var entry in graphEntries) {
 | 
				
			||||||
 | 
					                var curve = GraphEntryToJson(entry);
 | 
				
			||||||
 | 
					                JsonValue node;
 | 
				
			||||||
 | 
					                if (curve is JsonObject obj) {
 | 
				
			||||||
 | 
					                    obj["id"] = ++curveId;
 | 
				
			||||||
 | 
					                    node = JsonValue.Create($"curve:{curveId}");
 | 
				
			||||||
 | 
					                    curves.Add(obj);
 | 
				
			||||||
 | 
					                } else if (curve is JsonValue val && val.TryGetValue<decimal>(out var flat)) {
 | 
				
			||||||
 | 
					                    node = JsonValue.Create(flat);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                foreach (var c in entry.Vaributes) {
 | 
				
			||||||
 | 
					                    if (entry.Abgewertet) {
 | 
				
			||||||
 | 
					                        qualityWei[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        payment[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            CollapsePaymentData(payment, vaributes ?? payment.Select(e => e.Key).ToList(), useDefaultPayment);
 | 
				
			||||||
 | 
					            CollapsePaymentData(qualityWei, vaributes ?? qualityWei.Select(e => e.Key).ToList(), useDefaultQuality);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var data = new JsonObject {
 | 
				
			||||||
 | 
					                ["mode"] = "elwig",
 | 
				
			||||||
 | 
					                ["version"] = 1,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (origData?.ConsiderDelieryModifiers == true)
 | 
				
			||||||
 | 
					                data["consider_delivery_modifiers"] = true;
 | 
				
			||||||
 | 
					            if (origData?.ConsiderContractPenalties == true)
 | 
				
			||||||
 | 
					                data["consider_contract_penalties"] = true;
 | 
				
			||||||
 | 
					            if (origData?.ConsiderTotalPenalty == true)
 | 
				
			||||||
 | 
					                data["consider_total_penalty"] = true;
 | 
				
			||||||
 | 
					            if (origData?.ConsiderAutoBusinessShares == true)
 | 
				
			||||||
 | 
					                data["consider_auto_business_shares"] = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (payment.Count == 0) {
 | 
				
			||||||
 | 
					                data["payment"] = 0;
 | 
				
			||||||
 | 
					            } else if (payment.Count == 1 && payment.First().Key == "default") {
 | 
				
			||||||
 | 
					                data["payment"] = payment.Single().Value?.DeepClone();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                data["payment"] = payment;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (qualityWei.Count == 1 && qualityWei.First().Key == "default") {
 | 
				
			||||||
 | 
					                data["quality"] = new JsonObject() {
 | 
				
			||||||
 | 
					                    ["WEI"] = qualityWei.Single().Value?.DeepClone()
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            } else if (qualityWei.Count >= 1) {
 | 
				
			||||||
 | 
					                data["quality"] = new JsonObject() {
 | 
				
			||||||
 | 
					                    ["WEI"] = qualityWei
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            data["curves"] = curves;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return data;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,30 +10,25 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected readonly int AvNr;
 | 
					        protected readonly int AvNr;
 | 
				
			||||||
        protected readonly PaymentVar PaymentVariant;
 | 
					        protected readonly PaymentVar PaymentVariant;
 | 
				
			||||||
        protected readonly BillingData Data;
 | 
					        protected readonly PaymentBillingData Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public BillingVariant(int year, int avnr) : base(year) {
 | 
					        public BillingVariant(int year, int avnr) : base(year) {
 | 
				
			||||||
            AvNr = avnr;
 | 
					            AvNr = avnr;
 | 
				
			||||||
            PaymentVariant = Context.PaymentVariants.Find(Year, AvNr) ?? throw new ArgumentException("PaymentVar not found");
 | 
					            PaymentVariant = Context.PaymentVariants.Find(Year, AvNr) ?? throw new ArgumentException("PaymentVar not found");
 | 
				
			||||||
            var attrVariants = Context.DeliveryParts
 | 
					            Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(Context, Year, onlyDelivered: false));
 | 
				
			||||||
                .Where(d => d.Year == Year)
 | 
					 | 
				
			||||||
                .Select(d => $"{d.SortId}{d.AttrId}")
 | 
					 | 
				
			||||||
                .Distinct()
 | 
					 | 
				
			||||||
                .ToList()
 | 
					 | 
				
			||||||
                .Union(Context.WineVarieties.Select(v => v.SortId))
 | 
					 | 
				
			||||||
                .ToList();
 | 
					 | 
				
			||||||
            Data = BillingData.FromJson(PaymentVariant.Data, attrVariants);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task Calculate() {
 | 
					        public async Task Calculate(bool? honorGebunden = null, bool ? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
 | 
				
			||||||
            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 DeleteInDb(cnx);
 | 
					            await DeleteInDb(cnx);
 | 
				
			||||||
            await SetCalcTime(cnx);
 | 
					            await SetCalcTime(cnx);
 | 
				
			||||||
            await CalculatePrices(cnx);
 | 
					            await CalculatePrices(cnx);
 | 
				
			||||||
            if (Data.ConsiderDelieryModifiers)
 | 
					            if (Data.ConsiderDelieryModifiers) {
 | 
				
			||||||
                await CalculateDeliveryModifiers(cnx);
 | 
					                await CalculateDeliveryModifiers(cnx);
 | 
				
			||||||
                await CalculateMemberModifiers(cnx);
 | 
					                await CalculateMemberModifiers(cnx);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            await tx.CommitAsync();
 | 
					            await tx.CommitAsync();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,11 +44,10 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                           ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
 | 
					                           ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
 | 
				
			||||||
                           ROUND(lp.amount / POW(10, s.precision - 2)) AS prev_amount,
 | 
					                           ROUND(lp.amount / POW(10, s.precision - 2)) AS prev_amount,
 | 
				
			||||||
                           IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
 | 
					                           IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
 | 
				
			||||||
                           ROUND(
 | 
					                           ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
 | 
				
			||||||
                               IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0) / POW(10, 4 - 2), 0) +
 | 
					                           ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
 | 
				
			||||||
                               IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) +
 | 
					                           ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2))
 | 
				
			||||||
                               IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.business_shares * s.bs_value, 0), 0) / POW(10, s.precision - 2)
 | 
					                           AS modifiers,
 | 
				
			||||||
                           ) AS modifiers,
 | 
					 | 
				
			||||||
                           lc.modifiers AS prev_modifiers
 | 
					                           lc.modifiers AS prev_modifiers
 | 
				
			||||||
                    FROM season s
 | 
					                    FROM season s
 | 
				
			||||||
                        JOIN payment_variant v ON v.year = s.year
 | 
					                        JOIN payment_variant v ON v.year = s.year
 | 
				
			||||||
@@ -69,26 +63,9 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                        LEFT JOIN payment_member lp ON (lp.year, lp.avnr, lp.mgnr) = (l.year, l.avnr, m.mgnr)
 | 
					                        LEFT JOIN payment_member lp ON (lp.year, lp.avnr, lp.mgnr) = (l.year, l.avnr, m.mgnr)
 | 
				
			||||||
                        LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (v.year, v.avnr, m.mgnr)
 | 
					                        LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (v.year, v.avnr, m.mgnr)
 | 
				
			||||||
                        LEFT JOIN credit lc ON (lc.year, lc.avnr, lc.mgnr) = (l.year, l.avnr, m.mgnr)
 | 
					                        LEFT JOIN credit lc ON (lc.year, lc.avnr, lc.mgnr) = (l.year, l.avnr, m.mgnr)
 | 
				
			||||||
                        LEFT JOIN (SELECT year, mgnr,
 | 
					                        LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
 | 
				
			||||||
                                          SUM(COALESCE(IIF(u.weight = 0, -t.penalty_none, 0), 0) +
 | 
					                        LEFT JOIN v_penalty_business_shares b ON (b.year, b.mgnr) = (s.year, m.mgnr)
 | 
				
			||||||
                                              COALESCE(IIF(u.diff < 0, -t.penalty_amount, 0), 0) +
 | 
					                        LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr)
 | 
				
			||||||
                                              COALESCE(u.diff * t.penalty_per_kg, 0)) AS total_penalty
 | 
					 | 
				
			||||||
                                   FROM v_under_delivery u
 | 
					 | 
				
			||||||
                                       JOIN area_commitment_type t ON t.vtrgid = u.bucket
 | 
					 | 
				
			||||||
                                   GROUP BY year, mgnr) u ON (u.year, u.mgnr) = (s.year, m.mgnr)
 | 
					 | 
				
			||||||
                        LEFT JOIN (SELECT s.year, u.mgnr,
 | 
					 | 
				
			||||||
                                          (COALESCE(IIF(u.weight = 0, -s.penalty_none, 0), 0) +
 | 
					 | 
				
			||||||
                                           COALESCE(IIF(u.diff < 0, -s.penalty_amount, 0), 0) +
 | 
					 | 
				
			||||||
                                           COALESCE(u.diff * s.penalty_per_kg, 0)
 | 
					 | 
				
			||||||
                                           ) / POW(10, s.precision - 2) AS total_penalty
 | 
					 | 
				
			||||||
                                   FROM v_total_under_delivery u
 | 
					 | 
				
			||||||
                                       JOIN season s ON s.year = u.year
 | 
					 | 
				
			||||||
                                   WHERE u.diff < 0) b ON (b.year, b.mgnr) = (s.year, m.mgnr)
 | 
					 | 
				
			||||||
                        LEFT JOIN (SELECT h.mgnr, h.business_shares
 | 
					 | 
				
			||||||
                                   FROM member_history h
 | 
					 | 
				
			||||||
                                   WHERE type = 'auto' AND
 | 
					 | 
				
			||||||
                                         date >= '{Year}-06-01' AND
 | 
					 | 
				
			||||||
                                         date < '{Year + 1}-06-01') a ON a.mgnr = m.mgnr
 | 
					 | 
				
			||||||
                    WHERE s.year = {Year} AND v.avnr = {AvNr};
 | 
					                    WHERE s.year = {Year} AND v.avnr = {AvNr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
 | 
					                    UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
 | 
				
			||||||
@@ -146,10 +123,10 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task CalculatePrices(SqliteConnection cnx) {
 | 
					        protected async Task CalculatePrices(SqliteConnection cnx) {
 | 
				
			||||||
            var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, double Oe, double Kmw, string QualId)>();
 | 
					            var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string Discr, int Value, double Oe, double Kmw, string QualId)>();
 | 
				
			||||||
            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, b.discr, b.value, d.oe, d.kmw, d.qualid
 | 
					                    SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, d.attrid, b.discr, b.value, d.oe, d.kmw, d.qualid
 | 
				
			||||||
                    FROM delivery_part_bucket b
 | 
					                    FROM delivery_part_bucket b
 | 
				
			||||||
                        JOIN v_delivery d ON (d.year, d.did, d.dpnr) = (b.year, b.did, b.dpnr)
 | 
					                        JOIN v_delivery d ON (d.year, d.did, d.dpnr) = (b.year, b.did, b.dpnr)
 | 
				
			||||||
                    WHERE b.year = {Year}
 | 
					                    WHERE b.year = {Year}
 | 
				
			||||||
@@ -158,16 +135,19 @@ namespace Elwig.Helpers.Billing {
 | 
				
			|||||||
                while (await reader.ReadAsync()) {
 | 
					                while (await reader.ReadAsync()) {
 | 
				
			||||||
                    parts.Add((
 | 
					                    parts.Add((
 | 
				
			||||||
                        reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3),
 | 
					                        reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3),
 | 
				
			||||||
                        reader.GetString(4), reader.GetString(5), reader.GetInt32(6),
 | 
					                        reader.GetString(4), reader.IsDBNull(5) ? null : reader.GetString(5), reader.GetString(6),
 | 
				
			||||||
                        reader.GetDouble(7), reader.GetDouble(8), reader.GetString(9)
 | 
					                        reader.GetInt32(7), reader.GetDouble(8), reader.GetDouble(9), reader.GetString(10)
 | 
				
			||||||
                    ));
 | 
					                    ));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>();
 | 
					            var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>();
 | 
				
			||||||
            foreach (var part in parts) {
 | 
					            foreach (var part in parts) {
 | 
				
			||||||
                var attrId = (part.Discr == "_" || part.Discr == "") ? null : part.Discr;
 | 
					                var ungeb = part.Discr == "_";
 | 
				
			||||||
                var price = Data.CalculatePrice(part.SortId, attrId, part.QualId, part.Discr != "_", part.Oe, part.Kmw);
 | 
					                var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
 | 
				
			||||||
 | 
					                var attrId = part.AttrId == "B" ? "B" : payAttrId;  // FIXME
 | 
				
			||||||
 | 
					                var geb = !ungeb;  // FIXME && payAttrId == part.AttrId;
 | 
				
			||||||
 | 
					                var price = Data.CalculatePrice(part.SortId, attrId, part.QualId, geb, part.Oe, part.Kmw);
 | 
				
			||||||
                var priceL = PaymentVariant.Season.DecToDb(price);
 | 
					                var priceL = PaymentVariant.Season.DecToDb(price);
 | 
				
			||||||
                inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
 | 
					                inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										106
									
								
								Elwig/Helpers/Billing/EditBillingData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								Elwig/Helpers/Billing/EditBillingData.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Billing {
 | 
				
			||||||
 | 
					    public class EditBillingData : BillingData {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected readonly IEnumerable<string> Vaributes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public EditBillingData(JsonObject data, IEnumerable<string> vaributes) :
 | 
				
			||||||
 | 
					            base(data) {
 | 
				
			||||||
 | 
					            Vaributes = vaributes;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static EditBillingData FromJson(string json, IEnumerable<string> vaributes) {
 | 
				
			||||||
 | 
					            return new(ParseJson(json), vaributes);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private (Dictionary<int, Curve>, Dictionary<int, List<string>>) GetGraphEntries(JsonNode root) {
 | 
				
			||||||
 | 
					            Dictionary<int, List<string>> dict1 = [];
 | 
				
			||||||
 | 
					            Dictionary<decimal, List<string>> dict2 = [];
 | 
				
			||||||
 | 
					            if (root is JsonObject paymentObj) {
 | 
				
			||||||
 | 
					                foreach (var (selector, node) in paymentObj) {
 | 
				
			||||||
 | 
					                    var val = node?.AsValue();
 | 
				
			||||||
 | 
					                    if (val == null) {
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    } else if (val.TryGetValue<decimal>(out var price)) {
 | 
				
			||||||
 | 
					                        if (!dict2.ContainsKey(price)) dict2[price] = [];
 | 
				
			||||||
 | 
					                        dict2[price].Add(selector);
 | 
				
			||||||
 | 
					                    } else if (val.TryGetValue<string>(out var curve)) {
 | 
				
			||||||
 | 
					                        var idx = int.Parse(curve.Split(":")[1] ?? "0");
 | 
				
			||||||
 | 
					                        if (!dict1.ContainsKey(idx)) dict1[idx] = [];
 | 
				
			||||||
 | 
					                        dict1[idx].Add(selector);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if (root is JsonValue paymentVal) {
 | 
				
			||||||
 | 
					                if (paymentVal.TryGetValue<decimal>(out var price)) {
 | 
				
			||||||
 | 
					                    if (!dict2.ContainsKey(price)) dict2[price] = [];
 | 
				
			||||||
 | 
					                    dict2[price].Add("default");
 | 
				
			||||||
 | 
					                } else if (paymentVal.TryGetValue<string>(out var curve)) {
 | 
				
			||||||
 | 
					                    var idx = int.Parse(curve.Split(":")[1] ?? "0");
 | 
				
			||||||
 | 
					                    if (!dict1.ContainsKey(idx)) dict1[idx] = [];
 | 
				
			||||||
 | 
					                    dict1[idx].Add("default");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var virtOffset = dict1.Count > 0 ? dict1.Max(e => e.Key) + 1 : 1;
 | 
				
			||||||
 | 
					            Dictionary<int, Curve> curves = GetCurves();
 | 
				
			||||||
 | 
					            decimal[] virtCurves = [.. dict2.Keys.Order()];
 | 
				
			||||||
 | 
					            for (int i = 0; i < virtCurves.Length; i++) {
 | 
				
			||||||
 | 
					                var idx = virtCurves[i];
 | 
				
			||||||
 | 
					                dict1[i + virtOffset] = dict2[idx];
 | 
				
			||||||
 | 
					                curves[i + virtOffset] = new Curve(CurveMode.Oe, new() { { 73, idx } }, null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Dictionary<int, List<string>> dict3 = curves.ToDictionary(c => c.Key, _ => new List<string>());
 | 
				
			||||||
 | 
					            foreach (var (selector, value) in GetSelection(root, Vaributes)) {
 | 
				
			||||||
 | 
					                int? idx = null;
 | 
				
			||||||
 | 
					                if (value.TryGetValue<decimal>(out var val)) {
 | 
				
			||||||
 | 
					                    idx = Array.IndexOf(virtCurves, val) + virtOffset;
 | 
				
			||||||
 | 
					                } else if (value.TryGetValue<string>(out var str)) {
 | 
				
			||||||
 | 
					                    idx = int.Parse(str.Split(":")[1]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (idx != null)
 | 
				
			||||||
 | 
					                    dict3[(int)idx].Add(selector);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return (curves, dict3);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static List<GraphEntry> CreateGraphEntries(
 | 
				
			||||||
 | 
					            AppDbContext ctx, int precision,
 | 
				
			||||||
 | 
					            Dictionary<int, Curve> curves,
 | 
				
			||||||
 | 
					            Dictionary<int, List<string>> entries
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            var vars = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
 | 
				
			||||||
 | 
					            var attrs = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
 | 
				
			||||||
 | 
					            return entries
 | 
				
			||||||
 | 
					                .Select(e => new GraphEntry(e.Key, precision, curves[e.Key], e.Value
 | 
				
			||||||
 | 
					                    .Select(s => new Varibute(vars[s[..2]], s.Length > 2 ? attrs[s[2..]] : null))
 | 
				
			||||||
 | 
					                    .ToList()))
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public IEnumerable<GraphEntry> GetPaymentGraphEntries(AppDbContext ctx, Season season) {
 | 
				
			||||||
 | 
					            var root = GetPaymentEntry();
 | 
				
			||||||
 | 
					            var (curves, entries) = GetGraphEntries(root);
 | 
				
			||||||
 | 
					            return CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public IEnumerable<GraphEntry> GetQualityGraphEntries(AppDbContext ctx, Season season, int idOffset = 0) {
 | 
				
			||||||
 | 
					            var root = GetQualityEntry();
 | 
				
			||||||
 | 
					            if (root == null || root["WEI"] is not JsonNode qualityWei)
 | 
				
			||||||
 | 
					                return [];
 | 
				
			||||||
 | 
					            var (curves, entries) = GetGraphEntries(qualityWei);
 | 
				
			||||||
 | 
					            var list = CreateGraphEntries(ctx, season.Precision, curves, entries).Where(e => e.Vaributes.Count > 0);
 | 
				
			||||||
 | 
					            foreach (var e in list) {
 | 
				
			||||||
 | 
					                e.Id += idOffset;
 | 
				
			||||||
 | 
					                e.Abgewertet = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return list;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,119 +1,107 @@
 | 
				
			|||||||
using ScottPlot;
 | 
					 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Text.Json.Nodes;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Billing {
 | 
					namespace Elwig.Helpers.Billing {
 | 
				
			||||||
    public class Graph : ICloneable {
 | 
					    public class Graph : ICloneable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string Type { get; set; }
 | 
					        public readonly int Precision;
 | 
				
			||||||
        public int Num { get; set; }
 | 
					 | 
				
			||||||
        private int MinX { get; set; }
 | 
					 | 
				
			||||||
        private int MaxX { get; set; }
 | 
					 | 
				
			||||||
        public string Contracts { get; set; }
 | 
					 | 
				
			||||||
        public double[] DataX { get; set; }
 | 
					        public double[] DataX { get; set; }
 | 
				
			||||||
        public double[] DataY { get; set; }
 | 
					        public double[] DataY { get; set; }
 | 
				
			||||||
 | 
					        public int MinX { get; set; }
 | 
				
			||||||
 | 
					        public int MaxX { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Graph(int num, int minX, int maxX) {
 | 
					        public Graph(int precision, int minX, int maxX) {
 | 
				
			||||||
            Type = "oe";
 | 
					            Precision = precision;
 | 
				
			||||||
            Num = num;
 | 
					 | 
				
			||||||
            Contracts = "";
 | 
					 | 
				
			||||||
            MinX = minX;
 | 
					            MinX = minX;
 | 
				
			||||||
            MaxX = maxX;
 | 
					            MaxX = maxX;
 | 
				
			||||||
 | 
					            DataX = Enumerable.Range(minX, maxX - minX + 1).Select(n => (double)n).ToArray();
 | 
				
			||||||
            DataX = DataGen.Range(MinX, MaxX + 1);
 | 
					            DataY = new double[DataX.Length];
 | 
				
			||||||
            DataY = DataGen.Zeros(MaxX - MinX + 1);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Graph(string type, int num, JsonObject graphData, string contracts, int minX, int maxX) {
 | 
					        public Graph(Dictionary<double, decimal> data, int precision, int minX, int maxX) {
 | 
				
			||||||
            Type = type;
 | 
					            Precision = precision;
 | 
				
			||||||
            Num = num;
 | 
					 | 
				
			||||||
            Contracts =  contracts;
 | 
					 | 
				
			||||||
            MinX = minX;
 | 
					            MinX = minX;
 | 
				
			||||||
            MaxX = maxX;
 | 
					            MaxX = maxX;
 | 
				
			||||||
 | 
					            DataX = Enumerable.Range(minX, maxX - minX + 1).Select(n => (double)n).ToArray();
 | 
				
			||||||
            DataX = DataGen.Range(MinX, MaxX + 1);
 | 
					            DataY = DataX.Select(i => (double)BillingData.GetCurveValueAt(data, i)).ToArray();
 | 
				
			||||||
            DataY = DataGen.Zeros(MaxX - MinX + 1);
 | 
					 | 
				
			||||||
            ParseGraphData(graphData);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Graph(string type, int num, int minX, int maxX, string contracts, double[] dataX, double[] dataY) {
 | 
					        public Graph(double[] values, int precision, int minX, int maxX) {
 | 
				
			||||||
            Type = type;
 | 
					            Precision = precision;
 | 
				
			||||||
            Num = num;
 | 
					            MinX = minX;
 | 
				
			||||||
 | 
					            MaxX = maxX;
 | 
				
			||||||
 | 
					            DataX = Enumerable.Range(MinX, MaxX - MinX + 1).Select(i => (double)i).ToArray();
 | 
				
			||||||
 | 
					            DataY = values;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Graph(double[] dataX, double[] dataY, int precision, int minX, int maxX) {
 | 
				
			||||||
 | 
					            Precision = precision;
 | 
				
			||||||
            MinX = minX;
 | 
					            MinX = minX;
 | 
				
			||||||
            MaxX = maxX;
 | 
					            MaxX = maxX;
 | 
				
			||||||
            Contracts = contracts;
 | 
					 | 
				
			||||||
            DataX = dataX;
 | 
					            DataX = dataX;
 | 
				
			||||||
            DataY = dataY;
 | 
					            DataY = dataY;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void ParseGraphData(JsonObject graphData) {
 | 
					        public double GetOechsleAt(int index) {
 | 
				
			||||||
            var GraphPoints = graphData.ToDictionary(p => int.Parse(p.Key[..^2]), p => (double)p.Value?.AsValue());
 | 
					            return DataX[index];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (GraphPoints.Keys.Count < 1) {
 | 
					        public void SetOechsleAt(int index, double oechsle) {
 | 
				
			||||||
 | 
					            DataX[index] = oechsle;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void SetPriceAt(int index, double price) {
 | 
				
			||||||
 | 
					            DataY[index] = price;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public double GetPriceAt(int index) {
 | 
				
			||||||
 | 
					            return DataY[index];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public double GetPriceAtOe(double oe) {
 | 
				
			||||||
 | 
					            return DataY[Array.IndexOf(DataX, oe)];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void FlattenGraph(int begin, int end, double value) {
 | 
				
			||||||
 | 
					            for (int i = begin; i <= end; i++) {
 | 
				
			||||||
 | 
					                DataY[i] = value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void FlattenGraphLeft(int pointIndex) {
 | 
				
			||||||
 | 
					            FlattenGraph(0, pointIndex, DataY[pointIndex]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void FlattenGraphRight(int pointIndex) {
 | 
				
			||||||
 | 
					            FlattenGraph(pointIndex, DataY.Length - 1, DataY[pointIndex]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void LinearIncreaseGraph(int begin, int end, double inc) {
 | 
				
			||||||
 | 
					            for (int i = begin; i < end; i++) {
 | 
				
			||||||
 | 
					                DataY[i + 1] = Math.Round(DataY[i] + inc, Precision);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void LinearIncreaseGraphToEnd(int begin, double inc) {
 | 
				
			||||||
 | 
					            LinearIncreaseGraph(begin, DataY.Length - 1, inc);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void InterpolateGraph(int firstPoint, int secondPoint) {
 | 
				
			||||||
 | 
					            int steps = Math.Abs(firstPoint - secondPoint);
 | 
				
			||||||
 | 
					            if (firstPoint == -1 || secondPoint == -1 || steps < 2) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            var (lowIndex, highIndex) = firstPoint < secondPoint ? (firstPoint, secondPoint) : (secondPoint, firstPoint);
 | 
				
			||||||
 | 
					            double step = (DataY[highIndex] - DataY[lowIndex]) / steps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var minKey = GraphPoints.Keys.Order().First();
 | 
					            for (int i = lowIndex; i < highIndex - 1; i++) {
 | 
				
			||||||
            var maxKey = GraphPoints.Keys.OrderDescending().First();
 | 
					                DataY[i + 1] = Math.Round(DataY[i] + step, Precision);
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!GraphPoints.ContainsKey(MinX)) {
 | 
					 | 
				
			||||||
                GraphPoints.Add(MinX, GraphPoints.GetValueOrDefault(minKey));
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!GraphPoints.ContainsKey(MaxX)) {
 | 
					 | 
				
			||||||
                GraphPoints.Add(MaxX, GraphPoints.GetValueOrDefault(maxKey));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var keys = GraphPoints.Keys.Order().ToArray();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (int i = 0; i < keys.Length; i++) {
 | 
					 | 
				
			||||||
                double point1Value = GraphPoints[keys[i]];
 | 
					 | 
				
			||||||
                if (i + 1 < keys.Length) {
 | 
					 | 
				
			||||||
                    double point2Value = GraphPoints[keys[i + 1]];
 | 
					 | 
				
			||||||
                    if (point1Value == point2Value) {
 | 
					 | 
				
			||||||
                        for (int j = keys[i] - MinX; j < keys[i + 1] - MinX; j++) {
 | 
					 | 
				
			||||||
                            DataY[j] = point1Value;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        int steps = Math.Abs(keys[i + 1] - keys[i]);
 | 
					 | 
				
			||||||
                        double step = (point2Value - point1Value) / steps;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        DataY[keys[i] - MinX] = point1Value;
 | 
					 | 
				
			||||||
                        DataY[keys[i + 1] - MinX] = point2Value;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        for (int j = keys[i] - MinX; j < keys[i + 1] - MinX - 1; j++) {
 | 
					 | 
				
			||||||
                            DataY[j + 1] = Math.Round(DataY[j] + step, 4); // TODO richtig runden
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else {
 | 
					 | 
				
			||||||
                    for (int j = keys[i] - MinX; j < DataX.Length; j++) {
 | 
					 | 
				
			||||||
                        DataY[j] = point1Value;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public JsonObject ToJson() {
 | 
					 | 
				
			||||||
            JsonObject graph = new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (DataY[0] != DataY[1]) {
 | 
					 | 
				
			||||||
                graph.Add(new KeyValuePair<string, JsonNode?>(DataX[0] + Type.ToLower(), Math.Round(DataY[0], 4)));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            for (int i = 1; i < DataX.Length - 1; i++) {
 | 
					 | 
				
			||||||
                if (Math.Round(DataY[i] - DataY[i - 1], 4) != Math.Round(DataY[i + 1] - DataY[i], 4)) {
 | 
					 | 
				
			||||||
                    graph.Add(new KeyValuePair<string, JsonNode?>(DataX[i] + Type.ToLower(), Math.Round(DataY[i], 4)));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (DataY[^1] != DataY[^2]) {
 | 
					 | 
				
			||||||
                graph.Add(new KeyValuePair<string, JsonNode?>(DataX[^1] + Type.ToLower(), Math.Round(DataY[^1], 4)));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return graph;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public object Clone() {
 | 
					        public object Clone() {
 | 
				
			||||||
            return new Graph(Type, Num, MinX, MaxX, Contracts, (double[])DataX.Clone(), (double[])DataY.Clone());
 | 
					            return new Graph((double[])DataX.Clone(), (double[])DataY.Clone(), Precision, MinX, MaxX);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										88
									
								
								Elwig/Helpers/Billing/GraphEntry.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Elwig/Helpers/Billing/GraphEntry.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Billing {
 | 
				
			||||||
 | 
					    public class GraphEntry {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public const int MinX = 50;
 | 
				
			||||||
 | 
					        public const int MinXGeb = 73;
 | 
				
			||||||
 | 
					        public const int MaxX = 120;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public int Id { get; set; }
 | 
				
			||||||
 | 
					        public BillingData.CurveMode Mode { get; set; }
 | 
				
			||||||
 | 
					        public bool Abgewertet { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Graph DataGraph { get; set; }
 | 
				
			||||||
 | 
					        public Graph? GebundenGraph { get; set; }
 | 
				
			||||||
 | 
					        public double? GebundenFlatBonus {
 | 
				
			||||||
 | 
					            get {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    var val = GebundenGraph?.DataX.Zip(GebundenGraph.DataY)
 | 
				
			||||||
 | 
					                        .Select(e => Math.Round(e.Second - DataGraph.GetPriceAtOe(e.First), Precision))
 | 
				
			||||||
 | 
					                        .Distinct()
 | 
				
			||||||
 | 
					                        .Single();
 | 
				
			||||||
 | 
					                    return (val == 0) ? null : val;
 | 
				
			||||||
 | 
					                } catch {
 | 
				
			||||||
 | 
					                    return null;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            set {
 | 
				
			||||||
 | 
					                if (value is not double v) return;
 | 
				
			||||||
 | 
					                var values = Enumerable.Range(MinXGeb, MaxX - MinXGeb + 1)
 | 
				
			||||||
 | 
					                    .Select(i => Math.Round(DataGraph.GetPriceAtOe(i) + v, Precision))
 | 
				
			||||||
 | 
					                    .ToArray();
 | 
				
			||||||
 | 
					                GebundenGraph = new Graph(values, Precision, MinXGeb, MaxX);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public List<Varibute> Vaributes { get; set; }
 | 
				
			||||||
 | 
					        public string VaributeStringSimple => (Abgewertet ? "Abgew.: " : "") + (Vaributes.Count != 0 ? (Vaributes.Count >= 25 ? "Restliche Sorten" : string.Join(", ", Vaributes.Select(c => c.Listing))) : "-");
 | 
				
			||||||
 | 
					        public string VaributeString => Vaributes.Count != 0 ? string.Join("\n", Vaributes.Select(c => c.FullName)) : "-";
 | 
				
			||||||
 | 
					        public string VaributeStringChange => (Abgewertet ? "A." : "") + string.Join(",", Vaributes.Select(c => c.Listing));
 | 
				
			||||||
 | 
					        private readonly int Precision;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public GraphEntry(int id, int precision, BillingData.CurveMode mode) {
 | 
				
			||||||
 | 
					            Id = id;
 | 
				
			||||||
 | 
					            Precision = precision;
 | 
				
			||||||
 | 
					            Mode = mode;
 | 
				
			||||||
 | 
					            DataGraph = new Graph(precision, MinX, MaxX); ;
 | 
				
			||||||
 | 
					            Vaributes = [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public GraphEntry(int id, int precision, BillingData.CurveMode mode, Dictionary<double, decimal> data, Dictionary<double, decimal>? gebunden) :
 | 
				
			||||||
 | 
					            this(id, precision, mode) {
 | 
				
			||||||
 | 
					            DataGraph = new Graph(data, precision, MinX, MaxX);
 | 
				
			||||||
 | 
					            if (gebunden != null) GebundenGraph = new Graph(gebunden, precision, MinXGeb, MaxX);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public GraphEntry(int id, int precision, BillingData.Curve curve, List<Varibute> vaributes) :
 | 
				
			||||||
 | 
					            this(id, precision, curve.Mode) {
 | 
				
			||||||
 | 
					            DataGraph = new Graph(curve.Normal, precision, MinX, MaxX);
 | 
				
			||||||
 | 
					            if (curve.Gebunden != null)
 | 
				
			||||||
 | 
					                GebundenGraph = new Graph(curve.Gebunden, precision, MinXGeb, MaxX);
 | 
				
			||||||
 | 
					            Vaributes = vaributes;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private GraphEntry(int id, int precision, BillingData.CurveMode mode, Graph dataGraph, Graph? gebundenGraph, List<Varibute> vaributes) {
 | 
				
			||||||
 | 
					            Id = id;
 | 
				
			||||||
 | 
					            Precision = precision;
 | 
				
			||||||
 | 
					            Mode = mode;
 | 
				
			||||||
 | 
					            DataGraph = dataGraph;
 | 
				
			||||||
 | 
					            GebundenGraph = gebundenGraph;
 | 
				
			||||||
 | 
					            Vaributes = vaributes;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void AddGebundenGraph() {
 | 
				
			||||||
 | 
					            GebundenGraph ??= new Graph(Precision, MinXGeb, MaxX);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void RemoveGebundenGraph() {
 | 
				
			||||||
 | 
					            GebundenGraph = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public GraphEntry Copy(int id) {
 | 
				
			||||||
 | 
					            return new GraphEntry(id, Precision, Mode, (Graph)DataGraph.Clone(), (Graph?)GebundenGraph?.Clone(), []);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										73
									
								
								Elwig/Helpers/Billing/PaymentBillingData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								Elwig/Helpers/Billing/PaymentBillingData.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text.Json.Nodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Billing {
 | 
				
			||||||
 | 
					    public class PaymentBillingData : BillingData {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected readonly Dictionary<int, Curve> Curves;
 | 
				
			||||||
 | 
					        protected readonly Dictionary<string, Curve> PaymentData;
 | 
				
			||||||
 | 
					        protected readonly Dictionary<string, Curve> QualityData;
 | 
				
			||||||
 | 
					        protected readonly IEnumerable<string> Vaributes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public PaymentBillingData(JsonObject data, IEnumerable<string> vaributes) :
 | 
				
			||||||
 | 
					            base(data) {
 | 
				
			||||||
 | 
					            if (vaributes.Any(e => e.Any(c => c < 'A' || c > 'Z')))
 | 
				
			||||||
 | 
					                throw new ArgumentException("Invalid vaributes");
 | 
				
			||||||
 | 
					            Vaributes = vaributes;
 | 
				
			||||||
 | 
					            Curves = GetCurves();
 | 
				
			||||||
 | 
					            PaymentData = GetPaymentData();
 | 
				
			||||||
 | 
					            QualityData = GetQualityData();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static PaymentBillingData FromJson(string json, IEnumerable<string> vaributes) {
 | 
				
			||||||
 | 
					            return new(ParseJson(json), vaributes);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Dictionary<string, Curve> GetData(JsonNode data) {
 | 
				
			||||||
 | 
					            return GetSelection(data, Vaributes).ToDictionary(e => e.Key, e => LookupCurve(e.Value));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Dictionary<string, Curve> GetPaymentData() {
 | 
				
			||||||
 | 
					            return GetData(GetPaymentEntry());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Dictionary<string, Curve> GetQualityData() {
 | 
				
			||||||
 | 
					            Dictionary<string, Curve> dict = [];
 | 
				
			||||||
 | 
					            var q = GetQualityEntry();
 | 
				
			||||||
 | 
					            if (q == null) return dict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var (qualid, data) in q) {
 | 
				
			||||||
 | 
					                foreach (var (idx, d) in GetData(data ?? throw new InvalidOperationException())) {
 | 
				
			||||||
 | 
					                    dict[$"{qualid}/{idx}"] = d;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return dict;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public decimal CalculatePrice(string sortid, string? attrid, string qualid, bool gebunden, double oe, double kmw) {
 | 
				
			||||||
 | 
					            var curve = GetQualityCurve(qualid, sortid, attrid) ?? GetCurve(sortid, attrid);
 | 
				
			||||||
 | 
					            return GetCurveValueAt((gebunden ? curve.Gebunden : null) ?? curve.Normal, curve.Mode == CurveMode.Oe ? oe : kmw);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Curve LookupCurve(JsonValue val) {
 | 
				
			||||||
 | 
					            if (val.TryGetValue(out string? curve)) {
 | 
				
			||||||
 | 
					                var curveId = int.Parse(curve.Split(":")[1]);
 | 
				
			||||||
 | 
					                return Curves[curveId];
 | 
				
			||||||
 | 
					            } else if (val.TryGetValue(out decimal value)) {
 | 
				
			||||||
 | 
					                return new(CurveMode.Oe, new() { { 73, value } }, null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            throw new InvalidOperationException();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Curve GetCurve(string sortid, string? attrid) {
 | 
				
			||||||
 | 
					            return PaymentData[$"{sortid}{attrid}"];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Curve? GetQualityCurve(string qualid, string sortid, string? attrid) {
 | 
				
			||||||
 | 
					            return QualityData.TryGetValue($"{qualid}/{sortid}{attrid}", out var curve) ? curve : null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								Elwig/Helpers/Billing/Varibute.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Elwig/Helpers/Billing/Varibute.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Billing {
 | 
				
			||||||
 | 
					    public class Varibute : IComparable<Varibute> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public WineVar? Variety { get; }
 | 
				
			||||||
 | 
					        public WineAttr? Attribute { get; }
 | 
				
			||||||
 | 
					        public int? AssignedGraphId { get; set; }
 | 
				
			||||||
 | 
					        public int? AssignedAbgewGraphId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string Listing => $"{Variety?.SortId}{Attribute?.AttrId}";
 | 
				
			||||||
 | 
					        public string FullName => $"{Variety?.Name}" + (Variety != null && Attribute != null ? " " : "") + $"{Attribute?.Name}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Varibute(WineVar? var, WineAttr? attr) {
 | 
				
			||||||
 | 
					            Variety = var;
 | 
				
			||||||
 | 
					            Attribute = attr;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public override string ToString() {
 | 
				
			||||||
 | 
					            return Listing;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public int CompareTo(Varibute? other) {
 | 
				
			||||||
 | 
					            return Listing.CompareTo(other?.Listing);
 | 
				
			||||||
 | 
					        } 
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,20 +7,20 @@ using System.Threading.Tasks;
 | 
				
			|||||||
namespace Elwig.Helpers {
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
    public class ClientParameters {
 | 
					    public class ClientParameters {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public enum Type { Matzen, Winzerkeller };
 | 
					        public enum Type { Matzen, Winzerkeller, Weinland, Baden };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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 IsWolkersdorf => Client == Type.Winzerkeller && App.ZwstId == "W";
 | 
					        public bool IsWeinland => Client == Type.Weinland;
 | 
				
			||||||
        public bool IsHaugsdorf => Client == Type.Winzerkeller && App.ZwstId == "H";
 | 
					        public bool IsBaden => Client == Type.Baden;
 | 
				
			||||||
        public bool IsSitzendorf => Client == Type.Winzerkeller && App.ZwstId == "S";
 | 
					        public bool IsWolkersdorf => IsWinzerkeller && App.ZwstId == "W";
 | 
				
			||||||
 | 
					        public bool IsHaugsdorf => IsWinzerkeller && App.ZwstId == "H";
 | 
				
			||||||
 | 
					        public bool IsSitzendorf => IsWinzerkeller && App.ZwstId == "S";
 | 
				
			||||||
 | 
					        public bool IsGrInzersdorf => IsWeinland;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public bool HasRebler(string? zwstId) => IsMatzen || (IsWinzerkeller && zwstId == "W");
 | 
					        public bool HasNetWeighing(string? zwstId) => IsMatzen || (IsWinzerkeller && zwstId == "W");
 | 
				
			||||||
        public bool HasRebler(Branch? b) => HasRebler(b?.ZwstId);
 | 
					        public bool HasNetWeighing(Branch? b) => HasNetWeighing(b?.ZwstId);
 | 
				
			||||||
        public bool HasRebler() => HasRebler(App.ZwstId);
 | 
					        public bool HasNetWeighing() => HasNetWeighing(App.ZwstId);
 | 
				
			||||||
        public bool HasKisten(string? zwstId) => IsWinzerkeller && (zwstId == "H" || zwstId == "S");
 | 
					 | 
				
			||||||
        public bool HasKisten(Branch? b) => HasKisten(b?.ZwstId);
 | 
					 | 
				
			||||||
        public bool HasKisten() => HasKisten(App.ZwstId);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string NameToken;
 | 
					        public string NameToken;
 | 
				
			||||||
        public string NameShort;
 | 
					        public string NameShort;
 | 
				
			||||||
@@ -36,8 +36,8 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public PostalDest PostalDest {
 | 
					        public PostalDest PostalDest {
 | 
				
			||||||
            set {
 | 
					            set {
 | 
				
			||||||
                Plz = value.AtPlz.Plz;
 | 
					                Plz = value.AtPlz!.Plz;
 | 
				
			||||||
                Ort = value.AtPlz.Ort.Name;
 | 
					                Ort = value.AtPlz!.Ort.Name;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        public int Plz;
 | 
					        public int Plz;
 | 
				
			||||||
@@ -60,6 +60,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public string? TextDeliveryNote;
 | 
					        public string? TextDeliveryNote;
 | 
				
			||||||
        public string? TextDeliveryConfirmation;
 | 
					        public string? TextDeliveryConfirmation;
 | 
				
			||||||
 | 
					        public string? TextCreditNote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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)) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -71,8 +72,14 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
                NameSuffix = parameters.GetValueOrDefault("CLIENT_NAME_SUFFIX");
 | 
					                NameSuffix = parameters.GetValueOrDefault("CLIENT_NAME_SUFFIX");
 | 
				
			||||||
                NameType = parameters["CLIENT_NAME_TYPE"] ?? throw new KeyNotFoundException();
 | 
					                NameType = parameters["CLIENT_NAME_TYPE"] ?? throw new KeyNotFoundException();
 | 
				
			||||||
                switch (Name) {
 | 
					                switch (Name) {
 | 
				
			||||||
                    case "Winzergenossenschaft für Matzen und Umgebung": Client = Type.Matzen; break;
 | 
					                    case "Winzergenossenschaft für Matzen und Umgebung":
 | 
				
			||||||
                    case "Winzerkeller im Weinviertel": Client = Type.Winzerkeller; break;
 | 
					                        Client = Type.Matzen; break;
 | 
				
			||||||
 | 
					                    case "Winzerkeller im Weinviertel":
 | 
				
			||||||
 | 
					                        Client = Type.Winzerkeller; break;
 | 
				
			||||||
 | 
					                    case "Winzergenossenschaft Weinland":
 | 
				
			||||||
 | 
					                        Client = Type.Weinland; break;
 | 
				
			||||||
 | 
					                    case "Winzergenossenschaft Baden - Bad Vöslau":
 | 
				
			||||||
 | 
					                        Client = Type.Baden; break;
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Plz = int.Parse(parameters["CLIENT_PLZ"] ?? "");
 | 
					                Plz = int.Parse(parameters["CLIENT_PLZ"] ?? "");
 | 
				
			||||||
@@ -99,6 +106,8 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
                if (TextDeliveryNote == "") TextDeliveryNote = null;
 | 
					                if (TextDeliveryNote == "") TextDeliveryNote = null;
 | 
				
			||||||
                TextDeliveryConfirmation = parameters.GetValueOrDefault("TEXT_DELIVERYCONFIRMATION");
 | 
					                TextDeliveryConfirmation = parameters.GetValueOrDefault("TEXT_DELIVERYCONFIRMATION");
 | 
				
			||||||
                if (TextDeliveryConfirmation == "") TextDeliveryConfirmation = null;
 | 
					                if (TextDeliveryConfirmation == "") TextDeliveryConfirmation = null;
 | 
				
			||||||
 | 
					                TextCreditNote = parameters.GetValueOrDefault("TEXT_CREDITNOTE");
 | 
				
			||||||
 | 
					                if (TextCreditNote == "") TextCreditNote = null;
 | 
				
			||||||
            } catch {
 | 
					            } catch {
 | 
				
			||||||
                throw new KeyNotFoundException();
 | 
					                throw new KeyNotFoundException();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -112,7 +121,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
                case 2: deliveryNoteStats = "SHORT"; break;
 | 
					                case 2: deliveryNoteStats = "SHORT"; break;
 | 
				
			||||||
                case 3: deliveryNoteStats = "FULL"; break;
 | 
					                case 3: deliveryNoteStats = "FULL"; break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return new (string, string?)[] {
 | 
					            return [
 | 
				
			||||||
                ("CLIENT_NAME_TOKEN", NameToken),
 | 
					                ("CLIENT_NAME_TOKEN", NameToken),
 | 
				
			||||||
                ("CLIENT_NAME_SHORT", NameShort),
 | 
					                ("CLIENT_NAME_SHORT", NameShort),
 | 
				
			||||||
                ("CLIENT_NAME", Name),
 | 
					                ("CLIENT_NAME", Name),
 | 
				
			||||||
@@ -133,7 +142,8 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
                ("DOCUMENT_SENDER", Sender2),
 | 
					                ("DOCUMENT_SENDER", Sender2),
 | 
				
			||||||
                ("TEXT_DELIVERYNOTE", TextDeliveryNote),
 | 
					                ("TEXT_DELIVERYNOTE", TextDeliveryNote),
 | 
				
			||||||
                ("TEXT_DELIVERYCONFIRMATION", TextDeliveryConfirmation),
 | 
					                ("TEXT_DELIVERYCONFIRMATION", TextDeliveryConfirmation),
 | 
				
			||||||
            };
 | 
					                ("TEXT_CREDITNOTE", TextCreditNote),
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task UpdateValues() {
 | 
					        public async Task UpdateValues() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,31 @@ using System.Linq;
 | 
				
			|||||||
using Microsoft.Extensions.Configuration;
 | 
					using Microsoft.Extensions.Configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers {
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public record struct ScaleConfig {
 | 
				
			||||||
 | 
					        public string Id;
 | 
				
			||||||
 | 
					        public string? Type;
 | 
				
			||||||
 | 
					        public string? Model;
 | 
				
			||||||
 | 
					        public string? Connection;
 | 
				
			||||||
 | 
					        public string? Empty;
 | 
				
			||||||
 | 
					        public string? Filling;
 | 
				
			||||||
 | 
					        public string? Limit;
 | 
				
			||||||
 | 
					        public string? Log;
 | 
				
			||||||
 | 
					        public string? _Log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, string? log) {
 | 
				
			||||||
 | 
					            Id = id;
 | 
				
			||||||
 | 
					            Type = type;
 | 
				
			||||||
 | 
					            Model = model;
 | 
				
			||||||
 | 
					            Connection = cnx;
 | 
				
			||||||
 | 
					            Empty = empty;
 | 
				
			||||||
 | 
					            Filling = filling;
 | 
				
			||||||
 | 
					            Limit = limit;
 | 
				
			||||||
 | 
					            _Log = log;
 | 
				
			||||||
 | 
					            Log = log != null ? Path.Combine(App.DataPath, log) : null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public class Config {
 | 
					    public class Config {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly string FileName;
 | 
					        private readonly string FileName;
 | 
				
			||||||
@@ -11,8 +36,8 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public string DatabaseFile = App.DataPath + "database.sqlite3";
 | 
					        public string DatabaseFile = App.DataPath + "database.sqlite3";
 | 
				
			||||||
        public string? DatabaseLog = null;
 | 
					        public string? DatabaseLog = null;
 | 
				
			||||||
        public string? Branch = null;
 | 
					        public string? Branch = null;
 | 
				
			||||||
        public IList<string?[]> Scales;
 | 
					        public IList<ScaleConfig> Scales;
 | 
				
			||||||
        private readonly List<string?[]> ScaleList = [];
 | 
					        private readonly List<ScaleConfig> ScaleList = [];
 | 
				
			||||||
        private static readonly string[] trueValues = ["1", "true", "yes", "on"];
 | 
					        private static readonly string[] trueValues = ["1", "true", "yes", "on"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Config(string filename) {
 | 
					        public Config(string filename) {
 | 
				
			||||||
@@ -24,9 +49,9 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        public void Read() {
 | 
					        public void Read() {
 | 
				
			||||||
            var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
 | 
					            var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            DatabaseFile = Utils.GetAbsolutePath(config["database:file"] ?? "database.sqlite3", App.DataPath);
 | 
					            DatabaseFile = Path.Combine(App.DataPath, config["database:file"] ?? "database.sqlite3");
 | 
				
			||||||
            var log = config["database:log"];
 | 
					            var log = config["database:log"];
 | 
				
			||||||
            DatabaseLog = log != null ? Utils.GetAbsolutePath(log, App.DataPath) : null;
 | 
					            DatabaseLog = log != null ? Path.Combine(App.DataPath, log) : null;
 | 
				
			||||||
            Branch = config["general:branch"];
 | 
					            Branch = config["general:branch"];
 | 
				
			||||||
            Debug = trueValues.Contains(config["general:debug"]?.ToLower());
 | 
					            Debug = trueValues.Contains(config["general:debug"]?.ToLower());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,12 +59,10 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
            ScaleList.Clear();
 | 
					            ScaleList.Clear();
 | 
				
			||||||
            Scales = ScaleList;
 | 
					            Scales = ScaleList;
 | 
				
			||||||
            foreach (var s in scales) {
 | 
					            foreach (var s in scales) {
 | 
				
			||||||
                string? scaleLog = config[$"scale.{s}:log"];
 | 
					                ScaleList.Add(new(
 | 
				
			||||||
                if (scaleLog != null) scaleLog = Utils.GetAbsolutePath(scaleLog, App.DataPath);
 | 
					 | 
				
			||||||
                ScaleList.Add([
 | 
					 | 
				
			||||||
                    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"], scaleLog
 | 
					                    config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], config[$"scale.{s}:log"]
 | 
				
			||||||
                ]);
 | 
					                ));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,11 +74,11 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
            file.Write($"\r\n[database]\r\nfile = {DatabaseFile}\r\n");
 | 
					            file.Write($"\r\n[database]\r\nfile = {DatabaseFile}\r\n");
 | 
				
			||||||
            if (DatabaseLog != null) file.Write($"log = {DatabaseLog}\r\n");
 | 
					            if (DatabaseLog != null) file.Write($"log = {DatabaseLog}\r\n");
 | 
				
			||||||
            foreach (var s in ScaleList) {
 | 
					            foreach (var s in ScaleList) {
 | 
				
			||||||
                file.Write($"\r\n[scale.{s[0]}]\r\ntype = {s[1]}\r\nmodel = {s[2]}\r\nconnection = {s[3]}\r\n");
 | 
					                file.Write($"\r\n[scale.{s.Id}]\r\ntype = {s.Type}\r\nmodel = {s.Model}\r\nconnection = {s.Connection}\r\n");
 | 
				
			||||||
                if (s[4] != null) file.Write($"empty = {s[4]}\r\n");
 | 
					                if (s.Empty != null) file.Write($"empty = {s.Empty}\r\n");
 | 
				
			||||||
                if (s[5] != null) file.Write($"filling = {s[5]}\r\n");
 | 
					                if (s.Filling != null) file.Write($"filling = {s.Filling}\r\n");
 | 
				
			||||||
                if (s[6] != null) file.Write($"limit = {s[6]}\r\n");
 | 
					                if (s.Limit != null) file.Write($"limit = {s.Limit}\r\n");
 | 
				
			||||||
                if (s[7] != null) file.Write($"log = {s[7]}\r\n");
 | 
					                if (s._Log != null) file.Write($"log = {s._Log}\r\n");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,12 +2,14 @@ using Elwig.Models.Dtos;
 | 
				
			|||||||
using Elwig.Models.Entities;
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					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) : IBankingExporter {
 | 
					    public class Ebics(PaymentVar variant, string filename, int version) : IBankingExporter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string FileExtension => "xml";
 | 
					        public static string FileExtension => "xml";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,6 +18,7 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
        private readonly int Year = variant.Year;
 | 
					        private readonly int Year = variant.Year;
 | 
				
			||||||
        private readonly string Name = variant.Name;
 | 
					        private readonly string Name = variant.Name;
 | 
				
			||||||
        private readonly int AvNr = variant.AvNr;
 | 
					        private readonly int AvNr = variant.AvNr;
 | 
				
			||||||
 | 
					        private readonly int Version = version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Dispose() {
 | 
					        public void Dispose() {
 | 
				
			||||||
            GC.SuppressFinalize(this);
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
@@ -32,6 +35,8 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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))
 | 
				
			||||||
 | 
					                throw new ArgumentException("Tranaction amount may not be negative");
 | 
				
			||||||
            progress?.Report(0.0);
 | 
					            progress?.Report(0.0);
 | 
				
			||||||
            var nbOfTxs = transactions.Count();
 | 
					            var nbOfTxs = transactions.Count();
 | 
				
			||||||
            int count = nbOfTxs + 2, i = 0;
 | 
					            int count = nbOfTxs + 2, i = 0;
 | 
				
			||||||
@@ -41,26 +46,24 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            await Writer.WriteLineAsync($"""
 | 
					            await Writer.WriteLineAsync($"""
 | 
				
			||||||
                <?xml version="1.0" encoding="UTF-8"?>
 | 
					                <?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
                <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09"
 | 
					                <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.{Version:00}">
 | 
				
			||||||
                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
					 | 
				
			||||||
                          xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd">
 | 
					 | 
				
			||||||
                 <CstmrCdtTrfInitn>
 | 
					                 <CstmrCdtTrfInitn>
 | 
				
			||||||
                  <GrpHdr>
 | 
					                  <GrpHdr>
 | 
				
			||||||
                   <MsgId>{msgId}</MsgId>
 | 
					                   <MsgId>{msgId}</MsgId>
 | 
				
			||||||
                   <CreDtTm>{DateTime.UtcNow:o}</CreDtTm>
 | 
					                   <CreDtTm>{DateTime.UtcNow:o}</CreDtTm>
 | 
				
			||||||
                   <NbOfTxs>{nbOfTxs}</NbOfTxs>
 | 
					                   <NbOfTxs>{nbOfTxs}</NbOfTxs>
 | 
				
			||||||
                   <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
 | 
					                   <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
 | 
				
			||||||
                   <InitgPty><Nm>{App.Client.NameFull}</Nm></InitgPty>
 | 
					                   <InitgPty><Nm>{SecurityElement.Escape(App.Client.NameFull)}</Nm></InitgPty>
 | 
				
			||||||
                  </GrpHdr>
 | 
					                  </GrpHdr>
 | 
				
			||||||
                  <PmtInf>
 | 
					                  <PmtInf>
 | 
				
			||||||
                   <PmtInfId>{pmtInfId}</PmtInfId>
 | 
					                   <PmtInfId>{pmtInfId}</PmtInfId>
 | 
				
			||||||
                   <PmtMtd>TRF</PmtMtd>
 | 
					                   <PmtMtd>TRF</PmtMtd>
 | 
				
			||||||
                   <NbOfTxs>{nbOfTxs}</NbOfTxs>
 | 
					                   <NbOfTxs>{nbOfTxs}</NbOfTxs>
 | 
				
			||||||
                   <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
 | 
					                   <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum>
 | 
				
			||||||
                   <ReqdExctnDt><Dt>{Date:yyyy-MM-dd}</Dt></ReqdExctnDt>
 | 
					                   <ReqdExctnDt>{(Version >= 8 ? "<Dt>" : "")}{Date:yyyy-MM-dd}{(Version >= 8 ? "</Dt>" : "")}</ReqdExctnDt>
 | 
				
			||||||
                   <Dbtr><Nm>{App.Client.NameFull}</Nm></Dbtr>
 | 
					                   <Dbtr><Nm>{SecurityElement.Escape(App.Client.NameFull)}</Nm></Dbtr>
 | 
				
			||||||
                   <DbtrAcct><Id><IBAN>{App.Client.Iban?.Replace(" ", "")}</IBAN></Id></DbtrAcct>
 | 
					                   <DbtrAcct><Id><IBAN>{App.Client.Iban!.Replace(" ", "")}</IBAN></Id></DbtrAcct>
 | 
				
			||||||
                   <DbtrAgt><FinInstnId><BICFI>{App.Client.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></DbtrAgt>
 | 
					                   <DbtrAgt><FinInstnId>{(Version >= 4 ? "<BICFI>" : "<BIC>")}{App.Client.Bic ?? "NOTPROVIDED"}{(Version >= 4 ? "</BICFI>" : "</BIC>")}</FinInstnId></DbtrAgt>
 | 
				
			||||||
                """);
 | 
					                """);
 | 
				
			||||||
            progress?.Report(100.0 * ++i / count);
 | 
					            progress?.Report(100.0 * ++i / count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,16 +77,15 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
                        <PmtId><EndToEndId>{id}</EndToEndId></PmtId>
 | 
					                        <PmtId><EndToEndId>{id}</EndToEndId></PmtId>
 | 
				
			||||||
                        <Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmount(tx.Amount)}</InstdAmt></Amt>
 | 
					                        <Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmount(tx.Amount)}</InstdAmt></Amt>
 | 
				
			||||||
                        <Cdtr>
 | 
					                        <Cdtr>
 | 
				
			||||||
                         <Nm>{a.Name}</Nm>
 | 
					                         <Nm>{SecurityElement.Escape(a.Name[..Math.Min(140, a.Name.Length)])}</Nm>
 | 
				
			||||||
                         <PstlAdr>
 | 
					                         <PstlAdr>
 | 
				
			||||||
                          <StrtNm>{a1}</StrtNm><BldgNb>{a2}</BldgNb>
 | 
					                          <StrtNm>{a1?[..Math.Min(70, a1.Length)]}</StrtNm><BldgNb>{SecurityElement.Escape(a2?[..Math.Min(16, a2.Length)])}</BldgNb>
 | 
				
			||||||
                          <PstCd>{a.PostalDest.AtPlz?.Plz}</PstCd><TwnNm>{a.PostalDest.AtPlz?.Ort.Name}</TwnNm>
 | 
					                          <PstCd>{a.PostalDest.AtPlz?.Plz}</PstCd><TwnNm>{SecurityElement.Escape(a.PostalDest.AtPlz?.Ort.Name)}</TwnNm>
 | 
				
			||||||
                          <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>
 | 
					                          <Ctry>{a.PostalDest.Country.Alpha2}</Ctry>
 | 
				
			||||||
                         </PstlAdr>
 | 
					                         </PstlAdr>
 | 
				
			||||||
                        </Cdtr>
 | 
					                        </Cdtr>
 | 
				
			||||||
                        <CdtrAcct><Id><IBAN>{tx.Member.Iban}</IBAN></Id></CdtrAcct>
 | 
					                        <CdtrAcct><Id><IBAN>{tx.Member.Iban!}</IBAN></Id></CdtrAcct>
 | 
				
			||||||
                        <CdtrAgt><FinInstnId><BICFI>{tx.Member.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></CdtrAgt>
 | 
					                        <RmtInf><Ustrd>{SecurityElement.Escape(info)}</Ustrd></RmtInf>
 | 
				
			||||||
                        <RmtInf><Ustrd>{info}</Ustrd></RmtInf>
 | 
					 | 
				
			||||||
                       </CdtTrfTxInf>
 | 
					                       </CdtTrfTxInf>
 | 
				
			||||||
                    """);
 | 
					                    """);
 | 
				
			||||||
                progress?.Report(100.0 * ++i / count);
 | 
					                progress?.Report(100.0 * ++i / count);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -108,19 +108,19 @@ 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>
 | 
				
			||||||
                  <number:number-style style:name="NN0"><number:number number:decimal-places="0" number:min-decimal-places="0" number:min-integer-digits="1"/></number:number-style>
 | 
					                  <number:number-style style:name="NN0"><number:number number:decimal-places="0" number:min-decimal-places="0" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
 | 
				
			||||||
                  <style:style style:name="N0" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN0"/>
 | 
					                  <style:style style:name="N0" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN0"/>
 | 
				
			||||||
                  <number:number-style style:name="NN1"><number:number number:decimal-places="1" number:min-decimal-places="1" number:min-integer-digits="1"/></number:number-style>
 | 
					                  <number:number-style style:name="NN1"><number:number number:decimal-places="1" number:min-decimal-places="1" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
 | 
				
			||||||
                  <style:style style:name="N1" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN1"/>
 | 
					                  <style:style style:name="N1" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN1"/>
 | 
				
			||||||
                  <number:number-style style:name="NN2"><number:number number:decimal-places="2" number:min-decimal-places="2" number:min-integer-digits="1"/></number:number-style>
 | 
					                  <number:number-style style:name="NN2"><number:number number:decimal-places="2" number:min-decimal-places="2" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
 | 
				
			||||||
                  <style:style style:name="N2" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN2"/>
 | 
					                  <style:style style:name="N2" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN2"/>
 | 
				
			||||||
                  <number:number-style style:name="NN3"><number:number number:decimal-places="3" number:min-decimal-places="3" number:min-integer-digits="1"/></number:number-style>
 | 
					                  <number:number-style style:name="NN3"><number:number number:decimal-places="3" number:min-decimal-places="3" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
 | 
				
			||||||
                  <style:style style:name="N3" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN3"/>
 | 
					                  <style:style style:name="N3" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN3"/>
 | 
				
			||||||
                  <number:number-style style:name="NN4"><number:number number:decimal-places="4" number:min-decimal-places="4" number:min-integer-digits="1"/></number:number-style>
 | 
					                  <number:number-style style:name="NN4"><number:number number:decimal-places="4" number:min-decimal-places="4" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
 | 
				
			||||||
                  <style:style style:name="N4" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN4"/>
 | 
					                  <style:style style:name="N4" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN4"/>
 | 
				
			||||||
                  <number:number-style style:name="NN5"><number:number number:decimal-places="5" number:min-decimal-places="5" number:min-integer-digits="1"/></number:number-style>
 | 
					                  <number:number-style style:name="NN5"><number:number number:decimal-places="5" number:min-decimal-places="5" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
 | 
				
			||||||
                  <style:style style:name="N5" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN5"/>
 | 
					                  <style:style style:name="N5" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN5"/>
 | 
				
			||||||
                  <number:number-style style:name="NN6"><number:number number:decimal-places="6" number:min-decimal-places="6" number:min-integer-digits="1"/></number:number-style>
 | 
					                  <number:number-style style:name="NN6"><number:number number:decimal-places="6" number:min-decimal-places="6" number:min-integer-digits="1" number:grouping="true"/></number:number-style>
 | 
				
			||||||
                  <style:style style:name="N6" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN6"/>
 | 
					                  <style:style style:name="N6" style:family="table-cell" style:parent-style-name="default" style:data-style-name="NN6"/>
 | 
				
			||||||
                 </office:automatic-styles>
 | 
					                 </office:automatic-styles>
 | 
				
			||||||
                 <office:body>
 | 
					                 <office:body>
 | 
				
			||||||
@@ -262,13 +262,14 @@ namespace Elwig.Helpers.Export {
 | 
				
			|||||||
            string c;
 | 
					            string c;
 | 
				
			||||||
            if (data == null) {
 | 
					            if (data == null) {
 | 
				
			||||||
                c = $"<{ct}{add}/>";
 | 
					                c = $"<{ct}{add}/>";
 | 
				
			||||||
            } else if (data is float || data is double || data is byte || data is char ||
 | 
					            } else if (data is decimal || data is float || data is double || data is byte || data is char ||
 | 
				
			||||||
                       data is short || data is ushort || data is int || data is uint || data is long || data is ulong) {
 | 
					                       data is short || data is ushort || data is int || data is uint || data is long || data is ulong) {
 | 
				
			||||||
                double v = double.Parse(data?.ToString() ?? "0");  // use default culture for ToString and Parse()!
 | 
					                double v = double.Parse(data?.ToString() ?? "0");  // use default culture for ToString and Parse()!
 | 
				
			||||||
                if (units != null && units.Length > 0) {
 | 
					                if (units != null && units.Length > 0) {
 | 
				
			||||||
                    int n = -1;
 | 
					                    int n = -1;
 | 
				
			||||||
                    switch (units[0]) {
 | 
					                    switch (units[0]) {
 | 
				
			||||||
                        case "%": n = 1; data = $"{v:N1}"; break;
 | 
					                        case "%": n = 1; data = $"{v:N1}"; break;
 | 
				
			||||||
 | 
					                        case "€": n = 2; data = $"{v:N2}"; break;
 | 
				
			||||||
                        case "°KMW": n = 1; data = $"{v:N1}"; break;
 | 
					                        case "°KMW": n = 1; data = $"{v:N1}"; break;
 | 
				
			||||||
                        case "°Oe": n = 0; data = $"{v:N0}"; break;
 | 
					                        case "°Oe": n = 0; data = $"{v:N0}"; break;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ namespace Elwig.Helpers.Printing {
 | 
				
			|||||||
        private static RazorLightEngine? Engine = null;
 | 
					        private static RazorLightEngine? Engine = null;
 | 
				
			||||||
        public static bool IsReady => Engine != null;
 | 
					        public static bool IsReady => Engine != null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task Init(Action evtHandler) {
 | 
					        public static async Task Init(Action? evtHandler = null) {
 | 
				
			||||||
            var e = new RazorLightEngineBuilder()
 | 
					            var e = new RazorLightEngineBuilder()
 | 
				
			||||||
                .UseFileSystemProject(App.DataPath + "resources")
 | 
					                .UseFileSystemProject(App.DataPath + "resources")
 | 
				
			||||||
                .UseMemoryCachingProvider()
 | 
					                .UseMemoryCachingProvider()
 | 
				
			||||||
@@ -24,7 +24,7 @@ namespace Elwig.Helpers.Printing {
 | 
				
			|||||||
            await e.CompileTemplateAsync("DeliveryConfirmation");
 | 
					            await e.CompileTemplateAsync("DeliveryConfirmation");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Engine = e;
 | 
					            Engine = e;
 | 
				
			||||||
            evtHandler();
 | 
					            evtHandler?.Invoke();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<string> CompileRenderAsync(string key, object model) {
 | 
					        public static async Task<string> CompileRenderAsync(string key, object model) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,13 +11,20 @@ using System.Linq;
 | 
				
			|||||||
namespace Elwig.Helpers.Printing {
 | 
					namespace Elwig.Helpers.Printing {
 | 
				
			||||||
    public static class Pdf {
 | 
					    public static class Pdf {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly string PdfToPrinter = App.ExePath + "PDFtoPrinter.exe";
 | 
					        private static readonly string PdfToPrinter = new string[] { App.ExePath }
 | 
				
			||||||
        private static readonly string WinziPrint = App.ExePath + "WinziPrint.exe";
 | 
					            .Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
 | 
				
			||||||
 | 
					            .Select(x => Path.Combine(x, "PDFtoPrinter.exe"))
 | 
				
			||||||
 | 
					            .Where(x => File.Exists(x))
 | 
				
			||||||
 | 
					            .FirstOrDefault() ?? throw new FileNotFoundException("PDFtoPrinter executable not found");
 | 
				
			||||||
 | 
					        private static readonly string WinziPrint = new string[] { App.ExePath }
 | 
				
			||||||
 | 
					            .Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
 | 
				
			||||||
 | 
					            .Select(x => Path.Combine(x, "WinziPrint.exe"))
 | 
				
			||||||
 | 
					            .Where(x => File.Exists(x))
 | 
				
			||||||
 | 
					            .FirstOrDefault() ?? throw new FileNotFoundException("WiniPrint executable not found");
 | 
				
			||||||
        private static Process? WinziPrintProc;
 | 
					        private static Process? WinziPrintProc;
 | 
				
			||||||
        public static bool IsReady => WinziPrintProc != null;
 | 
					        public static bool IsReady => WinziPrintProc != null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static async Task Init(Action? evtHandler = null) {
 | 
				
			||||||
        public static async Task Init(Action evtHandler) {
 | 
					 | 
				
			||||||
            var p = new Process() { StartInfo = new() {
 | 
					            var p = new Process() { StartInfo = new() {
 | 
				
			||||||
                FileName = WinziPrint,
 | 
					                FileName = WinziPrint,
 | 
				
			||||||
                CreateNoWindow = true,
 | 
					                CreateNoWindow = true,
 | 
				
			||||||
@@ -33,7 +40,7 @@ namespace Elwig.Helpers.Printing {
 | 
				
			|||||||
            p.StartInfo.ArgumentList.Add("-");
 | 
					            p.StartInfo.ArgumentList.Add("-");
 | 
				
			||||||
            p.Start();
 | 
					            p.Start();
 | 
				
			||||||
            WinziPrintProc = p;
 | 
					            WinziPrintProc = p;
 | 
				
			||||||
            evtHandler();
 | 
					            evtHandler?.Invoke();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<IEnumerable<int>> Convert(string htmlPath, string pdfPath, bool doubleSided = false, IProgress<double>? progress = null) {
 | 
					        public static async Task<IEnumerable<int>> Convert(string htmlPath, string pdfPath, bool doubleSided = false, IProgress<double>? progress = null) {
 | 
				
			||||||
@@ -74,12 +81,16 @@ namespace Elwig.Helpers.Printing {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task Print(string path, int copies = 1) {
 | 
					        public static async Task Print(string path, int copies = 1) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
                var p = new Process() { StartInfo = new() { FileName = PdfToPrinter } };
 | 
					                var p = new Process() { StartInfo = new() { FileName = PdfToPrinter } };
 | 
				
			||||||
                p.StartInfo.ArgumentList.Add(path);
 | 
					                p.StartInfo.ArgumentList.Add(path);
 | 
				
			||||||
                p.StartInfo.ArgumentList.Add("/s");
 | 
					                p.StartInfo.ArgumentList.Add("/s");
 | 
				
			||||||
                p.StartInfo.ArgumentList.Add($"copies={copies}");
 | 
					                p.StartInfo.ArgumentList.Add($"copies={copies}");
 | 
				
			||||||
                p.Start();
 | 
					                p.Start();
 | 
				
			||||||
                await p.WaitForExitAsync();
 | 
					                await p.WaitForExitAsync();
 | 
				
			||||||
 | 
					            } catch (Exception e) {
 | 
				
			||||||
 | 
					                MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,8 @@ using System.Text;
 | 
				
			|||||||
using System.Numerics;
 | 
					using System.Numerics;
 | 
				
			||||||
using Elwig.Models.Entities;
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using ScottPlot.TickGenerators.TimeUnits;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers {
 | 
					namespace Elwig.Helpers {
 | 
				
			||||||
    public static partial class Utils {
 | 
					    public static partial class Utils {
 | 
				
			||||||
@@ -48,6 +50,9 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        [GeneratedRegex(@"^(.*?) +([0-9].*)$", RegexOptions.Compiled)]
 | 
					        [GeneratedRegex(@"^(.*?) +([0-9].*)$", RegexOptions.Compiled)]
 | 
				
			||||||
        private static partial Regex GeneratedAddressRegex();
 | 
					        private static partial Regex GeneratedAddressRegex();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static readonly string GroupSeparator = "\u202F";
 | 
				
			||||||
 | 
					        public static readonly string UnitSeparator = "\u00A0";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static readonly KeyValuePair<string, string>[] PhoneNrTypes = [
 | 
					        public static readonly KeyValuePair<string, string>[] PhoneNrTypes = [
 | 
				
			||||||
            new("landline", "Tel.-Nr. (Festnetz)"),
 | 
					            new("landline", "Tel.-Nr. (Festnetz)"),
 | 
				
			||||||
            new("mobile", "Tel.-Nr. (mobil)"),
 | 
					            new("mobile", "Tel.-Nr. (mobil)"),
 | 
				
			||||||
@@ -158,7 +163,7 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string FormatIban(string iban) {
 | 
					        public static string FormatIban(string iban) {
 | 
				
			||||||
            return Regex.Replace(iban, ".{4}", "$0 ");
 | 
					            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> a) {
 | 
				
			||||||
@@ -357,8 +362,22 @@ namespace Elwig.Helpers {
 | 
				
			|||||||
            return output.OrderByDescending(l => l.Count());
 | 
					            return output.OrderByDescending(l => l.Count());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string GetAbsolutePath(string path, string basePath) {
 | 
					        public static List<string> GetVaributes(AppDbContext ctx, int year, bool withSlash = false, bool onlyDelivered = true) {
 | 
				
			||||||
            return (path.Length > 1 && (path[1] == ':' || path[0] == '/' || path[0] == '\\')) ? Path.Combine(basePath, path) : path;
 | 
					            var varieties = ctx.WineVarieties.Select(v => v.SortId).ToList();
 | 
				
			||||||
 | 
					            var delivered = ctx.DeliveryParts
 | 
				
			||||||
 | 
					               .Where(d => d.Year == year)
 | 
				
			||||||
 | 
					               .Select(d => $"{d.SortId}{(withSlash ? "/" : "")}{d.AttrId}")
 | 
				
			||||||
 | 
					               .Distinct()
 | 
				
			||||||
 | 
					               .ToList();
 | 
				
			||||||
 | 
					            return [.. (onlyDelivered ? delivered : delivered.Union(varieties)).Order()];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static List<Varibute> GetVaributeList(AppDbContext ctx, int year, bool onlyDelivered = true) {
 | 
				
			||||||
 | 
					            var varieties = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v);
 | 
				
			||||||
 | 
					            var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a);
 | 
				
			||||||
 | 
					            return GetVaributes(ctx, year, false, onlyDelivered)
 | 
				
			||||||
 | 
					                .Select(s => new Varibute(varieties[s[..2]], s.Length > 2 ? attributes[s[2..]] : null))
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,64 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using System.IO.Ports;
 | 
					 | 
				
			||||||
using System.Text;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Elwig.Helpers.Weighing {
 | 
					 | 
				
			||||||
    public class GassnerScale : IScale {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected SerialPort Serial = null;
 | 
					 | 
				
			||||||
        protected StreamReader Reader;
 | 
					 | 
				
			||||||
        protected StreamWriter Writer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public string Manufacturer => "Gassner";
 | 
					 | 
				
			||||||
        public int InternalScaleNr => 1;
 | 
					 | 
				
			||||||
        public string Model { get; private set; }
 | 
					 | 
				
			||||||
        public string ScaleId { get; private set; }
 | 
					 | 
				
			||||||
        public bool IsReady { get; private set; }
 | 
					 | 
				
			||||||
        public bool HasFillingClearance { get; private set; }
 | 
					 | 
				
			||||||
        public int? WeightLimit { get; private set; }
 | 
					 | 
				
			||||||
        public string? LogPath { get; private set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public GassnerScale(string id, string model, string connection) {
 | 
					 | 
				
			||||||
            ScaleId = id;
 | 
					 | 
				
			||||||
            Model = model;
 | 
					 | 
				
			||||||
            IsReady = true;
 | 
					 | 
				
			||||||
            HasFillingClearance = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!connection.StartsWith("serial:"))
 | 
					 | 
				
			||||||
                throw new ArgumentException("Unsupported scheme");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Serial = Utils.OpenSerialConnection(connection);
 | 
					 | 
				
			||||||
            Writer = new(Serial.BaseStream, Encoding.ASCII, -1, true);
 | 
					 | 
				
			||||||
            Reader = new(Serial.BaseStream, Encoding.ASCII, false, -1, true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Dispose() {
 | 
					 | 
				
			||||||
            Writer.Close();
 | 
					 | 
				
			||||||
            Reader.Close();
 | 
					 | 
				
			||||||
            Serial.Close();
 | 
					 | 
				
			||||||
            GC.SuppressFinalize(this);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task<WeighingResult> Weigh(bool incIdentNr) {
 | 
					 | 
				
			||||||
            await Writer.WriteAsync(incIdentNr ? "\x05" : "?");
 | 
					 | 
				
			||||||
            // TODO receive response
 | 
					 | 
				
			||||||
            return new();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task<WeighingResult> GetCurrentWeight() {
 | 
					 | 
				
			||||||
            return await Weigh(false);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task<WeighingResult> Weigh() {
 | 
					 | 
				
			||||||
            return await Weigh(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task Empty() { }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task GrantFillingClearance() { }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public async Task RevokeFillingClearance() { }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										35
									
								
								Elwig/Helpers/Weighing/ICommandScale.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Elwig/Helpers/Weighing/ICommandScale.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Interface for controlling a a scale which responds to commands sent to it
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public interface ICommandScale : IScale {
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get the current weight on the scale without performing a weighing process
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>Result of the weighing process (probably without a weighing id)</returns>
 | 
				
			||||||
 | 
					        Task<WeighingResult> GetCurrentWeight();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Perform a weighing process
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns>Result of the weighing process (including a weighing id)</returns>
 | 
				
			||||||
 | 
					        Task<WeighingResult> Weigh();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Empty the scale container or grant clearance to do so
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        Task Empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Grant clearance to fill the scale container
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        Task GrantFillingClearance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Revoke clearance to fill the scale container
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        Task RevokeFillingClearance();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								Elwig/Helpers/Weighing/IEventScale.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Elwig/Helpers/Weighing/IEventScale.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// Interface for controlling a a scale which automatically sends weighing updates
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public interface IEventScale : IScale {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public event EventHandler<WeighingEventArgs> WeighingEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        delegate void EventHandler<WeighingEventArgs>(object sender, WeighingEventArgs args);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,9 +1,8 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Weighing {
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// Interface for controlling a industrial scale
 | 
					    /// Interface for controlling a industrial scale (industrial terminal, "IT")
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public interface IScale : IDisposable {
 | 
					    public interface IScale : IDisposable {
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
@@ -45,32 +44,5 @@ namespace Elwig.Helpers.Weighing {
 | 
				
			|||||||
        /// Where to log the requests and responses from the scale to
 | 
					        /// Where to log the requests and responses from the scale to
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        string? LogPath { get; }
 | 
					        string? LogPath { get; }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Get the current weight on the scale without performing a weighing process
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>Result of the weighing process (probably without a weighing id)</returns>
 | 
					 | 
				
			||||||
        Task<WeighingResult> GetCurrentWeight();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Perform a weighing process
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <returns>Result of the weighing process (including a weighing id)</returns>
 | 
					 | 
				
			||||||
        Task<WeighingResult> Weigh();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Empty the scale container or grant clearance to do so
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        Task Empty();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Grant clearance to fill the scale container
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        Task GrantFillingClearance();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Revoke clearance to fill the scale container
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        Task RevokeFillingClearance();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,44 +1,19 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Weighing {
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
    public class InvalidScale : IScale {
 | 
					    public class InvalidScale(string id) : IScale {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string Manufacturer => "NONE";
 | 
					        public string Manufacturer => "NONE";
 | 
				
			||||||
        public string Model => "NONE";
 | 
					        public string Model => "NONE";
 | 
				
			||||||
        public string ScaleId { get; private set; }
 | 
					        public string ScaleId => id;
 | 
				
			||||||
        public int InternalScaleNr => 0;
 | 
					        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;
 | 
				
			||||||
        public string? LogPath => null;
 | 
					        public string? LogPath => null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public InvalidScale(string id) {
 | 
					 | 
				
			||||||
            ScaleId = id;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Dispose() {
 | 
					        public void Dispose() {
 | 
				
			||||||
            GC.SuppressFinalize(this);
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<WeighingResult> Weigh() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<WeighingResult> GetCurrentWeight() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task Empty() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task GrantFillingClearance() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RevokeFillingClearance() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										95
									
								
								Elwig/Helpers/Weighing/Scale.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								Elwig/Helpers/Weighing/Scale.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					using System.IO.Ports;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using System.Net.Sockets;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
 | 
					    public abstract class Scale : IDisposable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected enum Output { RTS, DTR, OUT1, OUT2 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected SerialPort? Serial = null;
 | 
				
			||||||
 | 
					        protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
 | 
				
			||||||
 | 
					        protected TcpClient? Tcp = null;
 | 
				
			||||||
 | 
					        protected Stream Stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected readonly Output? EmptyMode = null;
 | 
				
			||||||
 | 
					        protected readonly Output? FillingClearanceMode = null;
 | 
				
			||||||
 | 
					        protected readonly int EmptyDelay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public int? WeightLimit { get; private set; }
 | 
				
			||||||
 | 
					        public string? LogPath { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static IScale FromConfig(ScaleConfig config) {
 | 
				
			||||||
 | 
					            int? limit = config.Limit != null ? int.Parse(config.Limit) : null;
 | 
				
			||||||
 | 
					            if (config.Type == "SysTec-IT") {
 | 
				
			||||||
 | 
					                return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
 | 
				
			||||||
 | 
					            } else if (config.Type == "Schember-Async") {
 | 
				
			||||||
 | 
					                return new SchemberEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) {
 | 
				
			||||||
 | 
					            if (cnx.StartsWith("serial:")) {
 | 
				
			||||||
 | 
					                Serial = Utils.OpenSerialConnection(cnx);
 | 
				
			||||||
 | 
					                Stream = Serial.BaseStream;
 | 
				
			||||||
 | 
					            } else if (cnx.StartsWith("tcp:")) {
 | 
				
			||||||
 | 
					                Tcp = Utils.OpenTcpConnection(cnx);
 | 
				
			||||||
 | 
					                Stream = Tcp.GetStream();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                throw new ArgumentException($"Unsupported scheme: \"{cnx.Split(':')[0]}\"");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            LogPath = log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (empty != null) {
 | 
				
			||||||
 | 
					                var parts = empty.Split(':');
 | 
				
			||||||
 | 
					                if (parts.Length == 3) {
 | 
				
			||||||
 | 
					                    if (parts[0] != Serial?.PortName)
 | 
				
			||||||
 | 
					                        ControlSerialEmpty = Utils.OpenSerialConnection($"serial://{parts[0]}:9600");
 | 
				
			||||||
 | 
					                } else if (parts.Length != 2) {
 | 
				
			||||||
 | 
					                    throw new ArgumentException("Invalid value for 'empty'");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                EmptyMode = ConvertOutput(parts[^2]);
 | 
				
			||||||
 | 
					                EmptyDelay = int.Parse(parts[^1]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            WeightLimit = limit;
 | 
				
			||||||
 | 
					            if (filling != null) {
 | 
				
			||||||
 | 
					                var parts = filling.Split(':');
 | 
				
			||||||
 | 
					                if (parts.Length == 2) {
 | 
				
			||||||
 | 
					                    if (parts[0] != Serial?.PortName)
 | 
				
			||||||
 | 
					                        ControlSerialFilling = parts[0] != ControlSerialEmpty?.PortName ? Utils.OpenSerialConnection($"serial://{parts[0]}:9600") : ControlSerialEmpty;
 | 
				
			||||||
 | 
					                } else if (parts.Length != 1) {
 | 
				
			||||||
 | 
					                    throw new ArgumentException("Invalid value for 'filling'");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                FillingClearanceMode = ConvertOutput(parts[^1]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (FillingClearanceMode != null && WeightLimit == null)
 | 
				
			||||||
 | 
					                throw new ArgumentException("Weight limit has to be set, if filling clearance supervision is enabled");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Dispose() {
 | 
				
			||||||
 | 
					            Stream.Close();
 | 
				
			||||||
 | 
					            Serial?.Close();
 | 
				
			||||||
 | 
					            ControlSerialEmpty?.Close();
 | 
				
			||||||
 | 
					            ControlSerialFilling?.Close();
 | 
				
			||||||
 | 
					            Tcp?.Close();
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected static Output? ConvertOutput(string? value) {
 | 
				
			||||||
 | 
					            return value switch {
 | 
				
			||||||
 | 
					                null => null,
 | 
				
			||||||
 | 
					                "RTS" => Output.RTS,
 | 
				
			||||||
 | 
					                "DTR" => Output.DTR,
 | 
				
			||||||
 | 
					                "OUT1" => Output.OUT1,
 | 
				
			||||||
 | 
					                "OUT2" => Output.OUT2,
 | 
				
			||||||
 | 
					                _ => throw new ArgumentException($"Invalid value for argument: '{value}'"),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								Elwig/Helpers/Weighing/SchemberEventScale.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								Elwig/Helpers/Weighing/SchemberEventScale.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.IO;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using System.Threading;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using System.Windows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
 | 
					    public class SchemberEventScale : Scale, IEventScale, IDisposable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string Manufacturer => "Schember";
 | 
				
			||||||
 | 
					        public int InternalScaleNr => 1;
 | 
				
			||||||
 | 
					        public string Model { get; private set; }
 | 
				
			||||||
 | 
					        public string ScaleId { get; private set; }
 | 
				
			||||||
 | 
					        public bool IsReady { get; private set; }
 | 
				
			||||||
 | 
					        public bool HasFillingClearance { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public event IEventScale.EventHandler<WeighingEventArgs> WeighingEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private bool IsRunning = true;
 | 
				
			||||||
 | 
					        private readonly Thread BackgroundThread;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public SchemberEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
 | 
				
			||||||
 | 
					            base(cnx, empty, filling, limit, log) {
 | 
				
			||||||
 | 
					            ScaleId = id;
 | 
				
			||||||
 | 
					            Model = model;
 | 
				
			||||||
 | 
					            IsReady = true;
 | 
				
			||||||
 | 
					            HasFillingClearance = false;
 | 
				
			||||||
 | 
					            BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
 | 
				
			||||||
 | 
					            BackgroundThread.Start();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected virtual void RaiseWeighingEvent(WeighingEventArgs evt) {
 | 
				
			||||||
 | 
					            WeighingEvent?.Invoke(this, evt);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public new void Dispose() {
 | 
				
			||||||
 | 
					            IsRunning = false;
 | 
				
			||||||
 | 
					            BackgroundThread.Interrupt();
 | 
				
			||||||
 | 
					            BackgroundThread.Join();
 | 
				
			||||||
 | 
					            base.Dispose();
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async void BackgroundLoop(object? parameters) {
 | 
				
			||||||
 | 
					            while (IsRunning) {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    var data = await Receive();
 | 
				
			||||||
 | 
					                    RaiseWeighingEvent(new WeighingEventArgs(data));
 | 
				
			||||||
 | 
					                } catch (Exception ex) {
 | 
				
			||||||
 | 
					                    MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "Waagenfehler",
 | 
				
			||||||
 | 
					                        MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        protected async Task<WeighingResult> Receive() {
 | 
				
			||||||
 | 
					            string? line = null;
 | 
				
			||||||
 | 
					            using (var reader = new StreamReader(Stream, Encoding.ASCII, false, -1, true)) {
 | 
				
			||||||
 | 
					                line = await reader.ReadLineAsync();
 | 
				
			||||||
 | 
					                if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (line == null || line.Length != 32 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ') {
 | 
				
			||||||
 | 
					                throw new IOException($"Invalid event from scale: '{line}'");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var date    = line[ 1.. 9];
 | 
				
			||||||
 | 
					            var time    = line[10..15];
 | 
				
			||||||
 | 
					            var identNr = line[16..20].Trim();
 | 
				
			||||||
 | 
					            var netto   = line[21..30].Trim();
 | 
				
			||||||
 | 
					            var unit    = line[30..32];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (unit != "kg") {
 | 
				
			||||||
 | 
					                throw new IOException($"Unsupported unit in weighing event: '{unit}'");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
 | 
				
			||||||
 | 
					            var parsedDate = DateOnly.Parse(date);
 | 
				
			||||||
 | 
					            return new() {
 | 
				
			||||||
 | 
					                Weight = int.Parse(netto),
 | 
				
			||||||
 | 
					                WeighingId = identNr,
 | 
				
			||||||
 | 
					                FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null,
 | 
				
			||||||
 | 
					                Date = parsedDate,
 | 
				
			||||||
 | 
					                Time = TimeOnly.Parse(time),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,41 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Elwig.Helpers.Weighing {
 | 
					 | 
				
			||||||
    // TODO implement SchemberScale
 | 
					 | 
				
			||||||
    public class SchemberScale : IScale {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public string Manufacturer => "Schember";
 | 
					 | 
				
			||||||
        public string Model => throw new NotImplementedException();
 | 
					 | 
				
			||||||
        public string ScaleId => throw new NotImplementedException();
 | 
					 | 
				
			||||||
        public int InternalScaleNr => throw new NotImplementedException();
 | 
					 | 
				
			||||||
        public bool IsReady => throw new NotImplementedException();
 | 
					 | 
				
			||||||
        public bool HasFillingClearance => throw new NotImplementedException();
 | 
					 | 
				
			||||||
        public int? WeightLimit => throw new NotImplementedException();
 | 
					 | 
				
			||||||
        public string? LogPath => throw new NotImplementedException();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Dispose() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task Empty() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<WeighingResult> GetCurrentWeight() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task GrantFillingClearance() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task RevokeFillingClearance() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Task<WeighingResult> Weigh() {
 | 
					 | 
				
			||||||
            throw new NotImplementedException();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,23 +1,11 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.IO.Ports;
 | 
					using System.IO.Ports;
 | 
				
			||||||
using System.Net.Sockets;
 | 
					 | 
				
			||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Helpers.Weighing {
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
    public class SystecScale : IScale {
 | 
					    public class SysTecITScale : Scale, ICommandScale {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected enum Output { RTS, DTR, OUT1, OUT2 };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected SerialPort? Serial = null;
 | 
					 | 
				
			||||||
        protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
 | 
					 | 
				
			||||||
        protected TcpClient? Tcp = null;
 | 
					 | 
				
			||||||
        protected Stream Stream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected readonly Output? EmptyMode = null;
 | 
					 | 
				
			||||||
        protected readonly Output? FillingClearanceMode = null;
 | 
					 | 
				
			||||||
        protected readonly int EmptyDelay;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string Manufacturer => "SysTec";
 | 
					        public string Manufacturer => "SysTec";
 | 
				
			||||||
        public int InternalScaleNr => 1;
 | 
					        public int InternalScaleNr => 1;
 | 
				
			||||||
@@ -25,72 +13,15 @@ namespace Elwig.Helpers.Weighing {
 | 
				
			|||||||
        public string ScaleId { get; private set; }
 | 
					        public string ScaleId { 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 int? WeightLimit { get; private set; }
 | 
					 | 
				
			||||||
        public string? LogPath { get; private set; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public SystecScale(string id, string model, string connection, 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) {
 | 
				
			||||||
            ScaleId = id;
 | 
					            ScaleId = id;
 | 
				
			||||||
            Model = model;
 | 
					            Model = model;
 | 
				
			||||||
            IsReady = true;
 | 
					            IsReady = true;
 | 
				
			||||||
            HasFillingClearance = false;
 | 
					            HasFillingClearance = false;
 | 
				
			||||||
            LogPath = log;
 | 
					            Stream.WriteTimeout = 250;
 | 
				
			||||||
 | 
					            Stream.ReadTimeout = 11000;
 | 
				
			||||||
            if (connection.StartsWith("serial:")) {
 | 
					 | 
				
			||||||
                Serial = Utils.OpenSerialConnection(connection);
 | 
					 | 
				
			||||||
                Stream = Serial.BaseStream;
 | 
					 | 
				
			||||||
            } else if (connection.StartsWith("tcp:")) {
 | 
					 | 
				
			||||||
                Tcp = Utils.OpenTcpConnection(connection);
 | 
					 | 
				
			||||||
                Stream = Tcp.GetStream();
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                throw new ArgumentException("Unsupported scheme");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (empty != null) {
 | 
					 | 
				
			||||||
                var parts = empty.Split(':');
 | 
					 | 
				
			||||||
                if (parts.Length == 3) {
 | 
					 | 
				
			||||||
                    if (parts[0] != Serial?.PortName)
 | 
					 | 
				
			||||||
                        ControlSerialEmpty = Utils.OpenSerialConnection($"serial://{parts[0]}:9600");
 | 
					 | 
				
			||||||
                } else if (parts.Length != 2) {
 | 
					 | 
				
			||||||
                    throw new ArgumentException("Invalid value for 'empty'");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                EmptyMode = ConvertOutput(parts[^2]);
 | 
					 | 
				
			||||||
                EmptyDelay = int.Parse(parts[^1]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            WeightLimit = limit;
 | 
					 | 
				
			||||||
            if (filling != null) {
 | 
					 | 
				
			||||||
                var parts = filling.Split(':');
 | 
					 | 
				
			||||||
                if (parts.Length == 2) {
 | 
					 | 
				
			||||||
                    if (parts[0] != Serial?.PortName)
 | 
					 | 
				
			||||||
                        ControlSerialFilling = parts[0] != ControlSerialEmpty?.PortName ? Utils.OpenSerialConnection($"serial://{parts[0]}:9600") : ControlSerialEmpty;
 | 
					 | 
				
			||||||
                } else if (parts.Length != 1) {
 | 
					 | 
				
			||||||
                    throw new ArgumentException("Invalid value for 'filling'");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                FillingClearanceMode = ConvertOutput(parts[^1]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (FillingClearanceMode != null && WeightLimit == null)
 | 
					 | 
				
			||||||
                throw new ArgumentException("Weight limit has to be set, if filling clearance supervision is enabled");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Dispose() {
 | 
					 | 
				
			||||||
            Stream.Close();
 | 
					 | 
				
			||||||
            Serial?.Close();
 | 
					 | 
				
			||||||
            ControlSerialEmpty?.Close();
 | 
					 | 
				
			||||||
            ControlSerialFilling?.Close();
 | 
					 | 
				
			||||||
            Tcp?.Close();
 | 
					 | 
				
			||||||
            GC.SuppressFinalize(this);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected static Output? ConvertOutput(string? value) {
 | 
					 | 
				
			||||||
            return value switch {
 | 
					 | 
				
			||||||
                null => null,
 | 
					 | 
				
			||||||
                "RTS" => Output.RTS,
 | 
					 | 
				
			||||||
                "DTR" => Output.DTR,
 | 
					 | 
				
			||||||
                "OUT1" => Output.OUT1,
 | 
					 | 
				
			||||||
                "OUT2" => Output.OUT2,
 | 
					 | 
				
			||||||
                _ => throw new ArgumentException($"Invalid value for argument: '{value}'"),
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected async Task SendCommand(string command) {
 | 
					        protected async Task SendCommand(string command) {
 | 
				
			||||||
@@ -105,7 +36,7 @@ namespace Elwig.Helpers.Weighing {
 | 
				
			|||||||
                line = await reader.ReadLineAsync();
 | 
					                line = await reader.ReadLineAsync();
 | 
				
			||||||
                if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
 | 
					                if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (line == null || line.Length < 4 || !line.StartsWith("<") || !line.EndsWith(">")) {
 | 
					            if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith('>')) {
 | 
				
			||||||
                throw new IOException("Invalid response from scale");
 | 
					                throw new IOException("Invalid response from scale");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -162,7 +93,7 @@ namespace Elwig.Helpers.Weighing {
 | 
				
			|||||||
            var crc16      = line[52..60].Trim();
 | 
					            var crc16      = line[52..60].Trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (Utils.CalcCrc16Modbus(record[..54]) != ushort.Parse(crc16)) {
 | 
					            if (Utils.CalcCrc16Modbus(record[..54]) != ushort.Parse(crc16)) {
 | 
				
			||||||
                throw new IOException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54]).ToString()})");
 | 
					                throw new IOException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})");
 | 
				
			||||||
            } else if (unit != "kg") {
 | 
					            } else if (unit != "kg") {
 | 
				
			||||||
                throw new IOException($"Unsupported unit in weighing response: '{unit}'");
 | 
					                throw new IOException($"Unsupported unit in weighing response: '{unit}'");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
							
								
								
									
										12
									
								
								Elwig/Helpers/Weighing/WeighingEventArgs.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Elwig/Helpers/Weighing/WeighingEventArgs.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Helpers.Weighing {
 | 
				
			||||||
 | 
					    public class WeighingEventArgs : EventArgs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public WeighingResult Result { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public WeighingEventArgs(WeighingResult result) {
 | 
				
			||||||
 | 
					            Result = result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,36 +4,38 @@ namespace Elwig.Helpers.Weighing {
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// Result of a weighing process on an industrial scale
 | 
					    /// Result of a weighing process on an industrial scale
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public class WeighingResult {
 | 
					    public struct WeighingResult {
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Measured net weight in kg
 | 
					        /// Measured net weight in kg
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public int? Weight = null;
 | 
					        public int? Weight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Weighing id (or IdentNr) provided by the scale
 | 
					        /// Weighing id (or IdentNr) provided by the scale
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public string? WeighingId = null;
 | 
					        public string? WeighingId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Wheighing id (or IdentNr) provided by the scale optionally combined with the current date
 | 
					        /// Wheighing id (or IdentNr) provided by the scale optionally combined with the current date
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public string? FullWeighingId = null;
 | 
					        public string? FullWeighingId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Date string provided by the scale
 | 
					        /// Date string provided by the scale
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public DateOnly? Date = null;
 | 
					        public DateOnly? Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Time string provided by the scale
 | 
					        /// Time string provided by the scale
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public TimeOnly? Time = null;
 | 
					        public TimeOnly? Time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <returns><Weight/WeighingId/Date/Time></returns>
 | 
					        /// <returns><Weight/WeighingId/Date/Time></returns>
 | 
				
			||||||
        override public string ToString() {
 | 
					        public override readonly string ToString() {
 | 
				
			||||||
            var w = Weight != null ? $"{Weight}kg" : "";
 | 
					            var w = Weight != null ? $"{Weight}kg" : "";
 | 
				
			||||||
            return $"<{w}/{WeighingId}/{Date:yyyy-MM-dd}/{Time:HH:mm}>";
 | 
					            return $"<{w}/{WeighingId}/{Date:yyyy-MM-dd}/{Time:HH:mm}>";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
@@ -8,7 +7,7 @@ using System.Threading.Tasks;
 | 
				
			|||||||
namespace Elwig.Models.Dtos {
 | 
					namespace Elwig.Models.Dtos {
 | 
				
			||||||
    public class AreaComUnderDeliveryData : DataTable<AreaComUnderDeliveryRow> {
 | 
					    public class AreaComUnderDeliveryData : DataTable<AreaComUnderDeliveryRow> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly (string, string, string?, int)[] FieldNames = new[] {
 | 
					        private static readonly (string, string, string?, int)[] FieldNames = [
 | 
				
			||||||
            ("MgNr", "MgNr.", null, 12),
 | 
					            ("MgNr", "MgNr.", null, 12),
 | 
				
			||||||
            ("Name", "Name", null, 40),
 | 
					            ("Name", "Name", null, 40),
 | 
				
			||||||
            ("GivenName", "Vorname", null, 40),
 | 
					            ("GivenName", "Vorname", null, 40),
 | 
				
			||||||
@@ -20,7 +19,7 @@ namespace Elwig.Models.Dtos {
 | 
				
			|||||||
            ("DeliveryObligations", "Lieferpflicht", "kg", 22),
 | 
					            ("DeliveryObligations", "Lieferpflicht", "kg", 22),
 | 
				
			||||||
            ("Weights", "Geliefert", "kg", 22),
 | 
					            ("Weights", "Geliefert", "kg", 22),
 | 
				
			||||||
            ("UnderDeliveries", "Unterliefert", "kg|%", 34),
 | 
					            ("UnderDeliveries", "Unterliefert", "kg|%", 34),
 | 
				
			||||||
        };
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public AreaComUnderDeliveryData(IEnumerable<AreaComUnderDeliveryRow> rows, int year) :
 | 
					        public AreaComUnderDeliveryData(IEnumerable<AreaComUnderDeliveryRow> rows, int year) :
 | 
				
			||||||
            base($"Unterlieferungen FB", $"Unterlieferungen laut Flächenbindungen {year}", rows, FieldNames) {
 | 
					            base($"Unterlieferungen FB", $"Unterlieferungen laut Flächenbindungen {year}", rows, FieldNames) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,162 +1,180 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Elwig.Helpers.Billing;
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using Elwig.Models.Entities;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
 | 
					using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Models.Dtos {
 | 
					namespace Elwig.Models.Dtos {
 | 
				
			||||||
    public class CreditNoteData : DataTable<CreditNoteRow> {
 | 
					    public class CreditNoteData : DataTable<CreditNoteRow> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly (string, string, string?)[] FieldNames = new[] {
 | 
					        private static readonly (string, string, string?, int)[] FieldNames = [
 | 
				
			||||||
            ("", "", (string?)null), // TODO
 | 
					            ("MgNr", "MgNr.", null, 12),
 | 
				
			||||||
        };
 | 
					            ("Name", "Name", null, 40),
 | 
				
			||||||
 | 
					            ("GivenName", "Vorname", null, 40),
 | 
				
			||||||
 | 
					            ("Address", "Adresse", null, 60),
 | 
				
			||||||
 | 
					            ("Plz", "PLZ", null, 10),
 | 
				
			||||||
 | 
					            ("Locality", "Ort", null, 60),
 | 
				
			||||||
 | 
					            ("Iban", "IBAN", null, 45),
 | 
				
			||||||
 | 
					            ("TgNr", "TG-Nr.", null, 20),
 | 
				
			||||||
 | 
					            ("Sum", "Zwischens.", "€", 20),
 | 
				
			||||||
 | 
					            ("Surcharge", "Zuschlag", "€", 20),
 | 
				
			||||||
 | 
					            ("Total", "Gesamt", "€", 20),
 | 
				
			||||||
 | 
					            ("ConsideredSum", "Berückstgt.", "€", 20),
 | 
				
			||||||
 | 
					            ("Net", "Netto", "€", 20),
 | 
				
			||||||
 | 
					            ("Vat1", "10% MwSt.", "€", 20),
 | 
				
			||||||
 | 
					            ("Vat2", "13% MwSt.", "€", 20),
 | 
				
			||||||
 | 
					            ("Gross", "Brutto", "€", 20),
 | 
				
			||||||
 | 
					            ("Penalties", "Pönalen FB", "€", 20),
 | 
				
			||||||
 | 
					            ("Penalty", "Unterl. GA", "€", 20),
 | 
				
			||||||
 | 
					            ("AutoBs", "GA Nachz.", "€", 20),
 | 
				
			||||||
 | 
					            ("Others", "Sonstige", "€", 20),
 | 
				
			||||||
 | 
					            ("Considered", "Berückstgt.", "€", 20),
 | 
				
			||||||
 | 
					            ("Amount", "Betrag", "€", 20),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly int Year;
 | 
					        public CreditNoteData(IEnumerable<CreditNoteRow> rows, int year, string name) :
 | 
				
			||||||
        private readonly int? TgNr;
 | 
					            base($"Buchungsliste", $"Buchungsliste {name} {year}", rows, FieldNames) {
 | 
				
			||||||
        private readonly int? AvNr;
 | 
					 | 
				
			||||||
        private readonly int? MgNr;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private CreditNoteData(IEnumerable<CreditNoteRow> rows, int year, int? tgnr, int? avnr = null, int? mgnr = null) :
 | 
					 | 
				
			||||||
            base($"Traubengutschrift {year}/{tgnr}", rows, FieldNames) {
 | 
					 | 
				
			||||||
            Year = year;
 | 
					 | 
				
			||||||
            TgNr = tgnr;
 | 
					 | 
				
			||||||
            AvNr = avnr;
 | 
					 | 
				
			||||||
            MgNr = mgnr;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<IDictionary<int, CreditNoteData>> ForPaymentVariant(DbSet<CreditNoteRowSingle> table, DbSet<Season> seasons, int year, int avnr) {
 | 
					        public static async Task<CreditNoteData> ForPaymentVariant(AppDbContext ctx, int year, int avnr) {
 | 
				
			||||||
            return (await FromDbSet(table, year, avnr))
 | 
					            var variant = await ctx.PaymentVariants.FindAsync(year, avnr);
 | 
				
			||||||
                .GroupBy(
 | 
					            var name = variant!.Name;
 | 
				
			||||||
                    r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
 | 
					            var data = BillingData.FromJson(variant!.Data);
 | 
				
			||||||
                    (k, g) => new CreditNoteRow(g, seasons))
 | 
					            var rows = (await FromDbSet(ctx.CreditNoteRows, year, avnr)).Select(r => new CreditNoteRow(r, data)).ToList();
 | 
				
			||||||
                .GroupBy(
 | 
					            return new CreditNoteData(rows, year, name);
 | 
				
			||||||
                    r => new { r.Year, r.AvNr, r.MgNr, r.TgNr },
 | 
					 | 
				
			||||||
                    (k, g) => new CreditNoteData(g, k.Year, k.TgNr, mgnr: k.MgNr))
 | 
					 | 
				
			||||||
                .ToDictionary(d => d.MgNr ?? 0);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static async Task<IEnumerable<CreditNoteRowSingle>> FromDbSet(DbSet<CreditNoteRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) {
 | 
					        private static async Task<IEnumerable<CreditNoteRowSingle>> FromDbSet(DbSet<CreditNoteRowSingle> table, int year, int avnr) {
 | 
				
			||||||
            var y = year?.ToString() ?? "NULL";
 | 
					            return await table.FromSql($"""
 | 
				
			||||||
            var v = avnr?.ToString() ?? "NULL";
 | 
					                SELECT m.mgnr, m.family_name, m.given_name, p.plz, o.name AS ort, m.address, m.iban, c.tgnr, s.year, s.precision,
 | 
				
			||||||
            var m = mgnr?.ToString() ?? "NULL";
 | 
					                       p.amount - p.net_amount AS surcharge,
 | 
				
			||||||
            return await table.FromSqlRaw($"""
 | 
					                       c.net_amount, c.prev_net_amount, c.vat, c.vat_amount, c.gross_amount, c.modifiers, c.prev_modifiers, c.amount,
 | 
				
			||||||
                SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers,
 | 
					                       ROUND(COALESCE(u.total_penalty, 0) / POW(10, 4 - 2)) AS fb_penalty,
 | 
				
			||||||
                       b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount,
 | 
					                       ROUND(COALESCE(b.total_penalty, 0) / POW(10, s.precision - 2)) AS bs_penalty,
 | 
				
			||||||
                       s.name AS variant, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw
 | 
					                       ROUND(COALESCE(a.total_amount, 0) / POW(10, s.precision - 2)) AS auto_bs
 | 
				
			||||||
                FROM v_delivery d
 | 
					                FROM credit c
 | 
				
			||||||
                    JOIN wine_variety s ON s.sortid = d.sortid
 | 
					                    LEFT JOIN member m ON m.mgnr = c.mgnr
 | 
				
			||||||
                    LEFT JOIN wine_attribute a ON a.attrid = d.attrid
 | 
					                    LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (c.year, c.avnr, c.mgnr) 
 | 
				
			||||||
                    JOIN wine_quality_level q ON q.qualid = d.qualid
 | 
					                    LEFT JOIN AT_plz_dest p ON p.id = m.postal_dest
 | 
				
			||||||
                    LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr)
 | 
					                    LEFT JOIN AT_ort o ON o.okz = p.okz
 | 
				
			||||||
                    LEFT JOIN payment_variant v ON v.year = d.year
 | 
					                    LEFT JOIN season s ON s.year = c.year
 | 
				
			||||||
                    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 v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
 | 
				
			||||||
                    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 v_penalty_business_shares b ON (b.year, b.mgnr) = (s.year, m.mgnr)
 | 
				
			||||||
                    LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr)
 | 
					                    LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.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 c.year = {year} AND c.avnr = {avnr}
 | 
				
			||||||
                ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
 | 
					                ORDER BY m.mgnr
 | 
				
			||||||
                """).ToListAsync();
 | 
					                """).ToListAsync();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public class CreditNoteRow {
 | 
					    public class CreditNoteRow {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        public int Year;
 | 
					 | 
				
			||||||
        public int? TgNr;
 | 
					 | 
				
			||||||
        public int AvNr;
 | 
					 | 
				
			||||||
        public int MgNr;
 | 
					        public int MgNr;
 | 
				
			||||||
 | 
					        public string Name;
 | 
				
			||||||
 | 
					        public string GivenName;
 | 
				
			||||||
 | 
					        public string Address;
 | 
				
			||||||
 | 
					        public int Plz;
 | 
				
			||||||
 | 
					        public string Locality;
 | 
				
			||||||
 | 
					        public string Iban;
 | 
				
			||||||
 | 
					        public string TgNr;
 | 
				
			||||||
 | 
					        public decimal Sum;
 | 
				
			||||||
 | 
					        public decimal? Surcharge;
 | 
				
			||||||
 | 
					        public decimal Total;
 | 
				
			||||||
 | 
					        public decimal? ConsideredSum;
 | 
				
			||||||
 | 
					        public decimal Net;
 | 
				
			||||||
 | 
					        public decimal? Vat1, Vat2;
 | 
				
			||||||
 | 
					        public decimal Gross;
 | 
				
			||||||
 | 
					        public decimal? Penalties;
 | 
				
			||||||
 | 
					        public decimal? Penalty;
 | 
				
			||||||
 | 
					        public decimal? AutoBs;
 | 
				
			||||||
 | 
					        public decimal? Others;
 | 
				
			||||||
 | 
					        public decimal? Considered;
 | 
				
			||||||
 | 
					        public decimal Amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string LsNr;
 | 
					        public CreditNoteRow(CreditNoteRowSingle row, BillingData data) {
 | 
				
			||||||
        public int DPNr;
 | 
					            byte prec1 = 2, prec2 = row.Precision;
 | 
				
			||||||
        public string Variant;
 | 
					            MgNr = row.MgNr;
 | 
				
			||||||
        public string? Attribute;
 | 
					            Name = row.Name;
 | 
				
			||||||
        public string[] Modifiers;
 | 
					            GivenName = row.GivenName;
 | 
				
			||||||
        public string QualityLevel;
 | 
					            Address = row.Address;
 | 
				
			||||||
        public (double Oe, double Kmw) Gradation;
 | 
					            Plz = row.Plz;
 | 
				
			||||||
        public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets;
 | 
					            Locality = row.Locality;
 | 
				
			||||||
        public decimal? TotalModifiers;
 | 
					            Iban = Utils.FormatIban(row.Iban);
 | 
				
			||||||
        public decimal? Amount;
 | 
					            TgNr = $"{row.Year}/{row.TgNr}";
 | 
				
			||||||
 | 
					            Total = Utils.DecFromDb(row.NetAmount, prec1);
 | 
				
			||||||
        public CreditNoteRow(IEnumerable<CreditNoteRowSingle> rows, DbSet<Season> seasons) {
 | 
					            Surcharge = (row.Surcharge == null || row.Surcharge == 0) ? null : Utils.DecFromDb((long)row.Surcharge, prec2);
 | 
				
			||||||
            var f = rows.First();
 | 
					            Sum = Total - (Surcharge ?? 0);
 | 
				
			||||||
            Year = f.Year;
 | 
					            ConsideredSum = (row.PrevNetAmount == null ||row.PrevNetAmount == 0) ? null : -Utils.DecFromDb((long)row.PrevNetAmount, prec1);
 | 
				
			||||||
            TgNr = f.TgNr;
 | 
					            Net = Total + (ConsideredSum ?? 0);
 | 
				
			||||||
            MgNr = f.MgNr;
 | 
					            if (row.Vat == 0.10) {
 | 
				
			||||||
            var season = seasons.Find(Year);
 | 
					                Vat1 = Utils.DecFromDb(row.VatAmount, prec1);
 | 
				
			||||||
 | 
					            } else if (row.Vat == 0.13) {
 | 
				
			||||||
            LsNr = f.LsNr;
 | 
					                Vat2 = Utils.DecFromDb(row.VatAmount, prec1);
 | 
				
			||||||
            DPNr = f.DPNr;
 | 
					            }
 | 
				
			||||||
            Variant = f.Variant;
 | 
					            decimal mod = (row.Modifiers == null) ? 0 : Utils.DecFromDb((long)row.Modifiers, prec1);
 | 
				
			||||||
            Attribute = f.Attribute;
 | 
					            if (data.ConsiderContractPenalties)
 | 
				
			||||||
            var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',')
 | 
					                Penalties = (row.FbPenalty == null || row.FbPenalty == 0) ? null : Utils.DecFromDb((long)row.FbPenalty, prec1);
 | 
				
			||||||
                .Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m))
 | 
					            if (data.ConsiderTotalPenalty)
 | 
				
			||||||
                .Where(m => m != null)
 | 
					                Penalty = (row.BsPealty == null || row.BsPealty == 0) ? null : Utils.DecFromDb((long)row.BsPealty, prec1);
 | 
				
			||||||
                .OrderBy(m => m.Ordering)
 | 
					            if (data.ConsiderAutoBusinessShares)
 | 
				
			||||||
                .ToList();
 | 
					                AutoBs = (row.AutoBs == null || row.AutoBs == 0) ? null : -Utils.DecFromDb((long)row.AutoBs, prec1);
 | 
				
			||||||
            Modifiers = modifiers.Select(m => m.Name).ToArray();
 | 
					            mod -= (Penalties ?? 0) + (Penalty ?? 0) + (AutoBs ?? 0);
 | 
				
			||||||
            QualityLevel = f.QualityLevel;
 | 
					            Others = (mod == 0) ? null : mod;
 | 
				
			||||||
            Gradation = (f.Oe, f.Kmw);
 | 
					            Gross = Utils.DecFromDb(row.GrossAmount, prec1);
 | 
				
			||||||
            Buckets = rows
 | 
					            Considered = (row.PrevModifiers == null || row.PrevModifiers == 0) ? null : -Utils.DecFromDb((long)row.PrevModifiers, prec1);
 | 
				
			||||||
                .Where(b => b.Value > 0)
 | 
					            Amount = Utils.DecFromDb(row.Amount, prec1);
 | 
				
			||||||
                .OrderByDescending(b => b.BktNr)
 | 
					 | 
				
			||||||
                .Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
 | 
					 | 
				
			||||||
                              b.Price != null ? season?.DecFromDb((long)b.Price) : null,
 | 
					 | 
				
			||||||
                              b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
 | 
					 | 
				
			||||||
                .ToArray();
 | 
					 | 
				
			||||||
            Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
 | 
					 | 
				
			||||||
            var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
 | 
					 | 
				
			||||||
            TotalModifiers = Amount - netAmount;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Keyless]
 | 
					    [Keyless]
 | 
				
			||||||
    public class CreditNoteRowSingle {
 | 
					    public class CreditNoteRowSingle {
 | 
				
			||||||
        [Column("year")]
 | 
					 | 
				
			||||||
        public int Year { get; set; }
 | 
					 | 
				
			||||||
        [Column("tgnr")]
 | 
					 | 
				
			||||||
        public int? TgNr { get; set; }
 | 
					 | 
				
			||||||
        [Column("avnr")]
 | 
					 | 
				
			||||||
        public int? AvNr { get; set; }
 | 
					 | 
				
			||||||
        [Column("mgnr")]
 | 
					        [Column("mgnr")]
 | 
				
			||||||
        public int MgNr { get; set; }
 | 
					        public int MgNr { get; set; }
 | 
				
			||||||
        [Column("did")]
 | 
					        [Column("family_name")]
 | 
				
			||||||
        public int DId { get; set; }
 | 
					        public string Name { get; set; }
 | 
				
			||||||
        [Column("lsnr")]
 | 
					        [Column("given_name")]
 | 
				
			||||||
        public string LsNr { get; set; }
 | 
					        public string GivenName { get; set; }
 | 
				
			||||||
        [Column("dpnr")]
 | 
					        [Column("address")]
 | 
				
			||||||
        public int DPNr { get; set; }
 | 
					        public string Address { get; set; }
 | 
				
			||||||
        [Column("weight")]
 | 
					        [Column("plz")]
 | 
				
			||||||
        public int Weight { get; set; }
 | 
					        public int Plz { get; set; }
 | 
				
			||||||
        [Column("modifiers")]
 | 
					        [Column("ort")]
 | 
				
			||||||
        public string? Modifiers { get; set; }
 | 
					        public string LocalityFull { get; set; }
 | 
				
			||||||
        [Column("bktnr")]
 | 
					        [NotMapped]
 | 
				
			||||||
        public int BktNr { get; set; }
 | 
					        public string Locality => LocalityFull.Split(",")[0];
 | 
				
			||||||
        [Column("sortid")]
 | 
					        [Column("iban")]
 | 
				
			||||||
        public string SortId { get; set; }
 | 
					        public string Iban { get; set; }
 | 
				
			||||||
        [Column("discr")]
 | 
					        [Column("year")]
 | 
				
			||||||
        public string Discr { get; set; }
 | 
					        public int Year { get; set; }
 | 
				
			||||||
        [Column("value")]
 | 
					        [Column("precision")]
 | 
				
			||||||
        public int Value { get; set; }
 | 
					        public byte Precision { get; set; }
 | 
				
			||||||
        [Column("price")]
 | 
					        [Column("tgnr")]
 | 
				
			||||||
        public long? Price { get; set; }
 | 
					        public string TgNr { get; set; }
 | 
				
			||||||
        [Column("amount")]
 | 
					        [Column("surcharge")]
 | 
				
			||||||
        public long? Amount { get; set; }
 | 
					        public long? Surcharge { get; set; }
 | 
				
			||||||
        [Column("net_amount")]
 | 
					        [Column("net_amount")]
 | 
				
			||||||
        public long? NetAmount { get; set; }
 | 
					        public long NetAmount { get; set; }
 | 
				
			||||||
        [Column("total_amount")]
 | 
					        [Column("prev_net_amount")]
 | 
				
			||||||
        public long? TotalAmount { get; set; }
 | 
					        public long? PrevNetAmount { get; set; }
 | 
				
			||||||
        [Column("variant")]
 | 
					        [Column("vat")]
 | 
				
			||||||
        public string Variant { get; set; }
 | 
					        public double Vat { get; set; }
 | 
				
			||||||
        [Column("attribute")]
 | 
					        [Column("vat_amount")]
 | 
				
			||||||
        public string? Attribute { get; set; }
 | 
					        public long VatAmount { get; set; }
 | 
				
			||||||
        [Column("quality_level")]
 | 
					        [Column("gross_amount")]
 | 
				
			||||||
        public string QualityLevel { get; set; }
 | 
					        public long GrossAmount { get; set; }
 | 
				
			||||||
        [Column("oe")]
 | 
					        [Column("modifiers")]
 | 
				
			||||||
        public double Oe { get; set; }
 | 
					        public long? Modifiers { get; set; }
 | 
				
			||||||
        [Column("kmw")]
 | 
					        [Column("prev_modifiers")]
 | 
				
			||||||
        public double Kmw { get; set; }
 | 
					        public long? PrevModifiers { get; set; }
 | 
				
			||||||
 | 
					        [Column("amount")]
 | 
				
			||||||
 | 
					        public long Amount { get; set; }
 | 
				
			||||||
 | 
					        [Column("fb_penalty")]
 | 
				
			||||||
 | 
					        public long? FbPenalty { get; set; }
 | 
				
			||||||
 | 
					        [Column("bs_penalty")]
 | 
				
			||||||
 | 
					        public long? BsPealty { get; set; }
 | 
				
			||||||
 | 
					        [Column("auto_bs")]
 | 
				
			||||||
 | 
					        public long? AutoBs { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										159
									
								
								Elwig/Models/Dtos/CreditNoteDeliveryData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								Elwig/Models/Dtos/CreditNoteDeliveryData.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
				
			|||||||
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Elwig.Models.Dtos {
 | 
				
			||||||
 | 
					    public class CreditNoteDeliveryData : DataTable<CreditNoteDeliveryRow> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static readonly (string, string, string?)[] FieldNames = [
 | 
				
			||||||
 | 
					            ("", "", null), // TODO
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly int Year;
 | 
				
			||||||
 | 
					        private readonly int? TgNr;
 | 
				
			||||||
 | 
					        private readonly int? AvNr;
 | 
				
			||||||
 | 
					        private readonly int? MgNr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private CreditNoteDeliveryData(IEnumerable<CreditNoteDeliveryRow> rows, int year, int? tgnr, int? avnr = null, int? mgnr = null) :
 | 
				
			||||||
 | 
					            base($"Traubengutschrift {year}/{tgnr}", rows, FieldNames) {
 | 
				
			||||||
 | 
					            Year = year;
 | 
				
			||||||
 | 
					            TgNr = tgnr;
 | 
				
			||||||
 | 
					            AvNr = avnr;
 | 
				
			||||||
 | 
					            MgNr = mgnr;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static async Task<IDictionary<int, CreditNoteDeliveryData>> ForPaymentVariant(DbSet<CreditNoteDeliveryRowSingle> table, DbSet<Season> seasons, int year, int avnr) {
 | 
				
			||||||
 | 
					            return (await FromDbSet(table, year, avnr))
 | 
				
			||||||
 | 
					                .GroupBy(
 | 
				
			||||||
 | 
					                    r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr },
 | 
				
			||||||
 | 
					                    (k, g) => new CreditNoteDeliveryRow(g, seasons))
 | 
				
			||||||
 | 
					                .GroupBy(
 | 
				
			||||||
 | 
					                    r => new { r.Year, r.AvNr, r.MgNr, r.TgNr },
 | 
				
			||||||
 | 
					                    (k, g) => new CreditNoteDeliveryData(g, k.Year, k.TgNr, mgnr: k.MgNr))
 | 
				
			||||||
 | 
					                .ToDictionary(d => d.MgNr ?? 0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static async Task<IEnumerable<CreditNoteDeliveryRowSingle>> FromDbSet(DbSet<CreditNoteDeliveryRowSingle> table, int? year = null, int? avnr = null, int? mgnr = null) {
 | 
				
			||||||
 | 
					            var y = year?.ToString() ?? "NULL";
 | 
				
			||||||
 | 
					            var v = avnr?.ToString() ?? "NULL";
 | 
				
			||||||
 | 
					            var m = mgnr?.ToString() ?? "NULL";
 | 
				
			||||||
 | 
					            return await table.FromSqlRaw($"""
 | 
				
			||||||
 | 
					                SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers,
 | 
				
			||||||
 | 
					                       b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount,
 | 
				
			||||||
 | 
					                       s.name AS variety, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw
 | 
				
			||||||
 | 
					                FROM v_delivery d
 | 
				
			||||||
 | 
					                    JOIN wine_variety s ON s.sortid = d.sortid
 | 
				
			||||||
 | 
					                    LEFT JOIN wine_attribute a ON a.attrid = d.attrid
 | 
				
			||||||
 | 
					                    JOIN wine_quality_level q ON q.qualid = d.qualid
 | 
				
			||||||
 | 
					                    LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr)
 | 
				
			||||||
 | 
					                    LEFT JOIN payment_variant v ON v.year = d.year
 | 
				
			||||||
 | 
					                    LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr)
 | 
				
			||||||
 | 
					                    LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr, pb.avnr) = (b.year, b.did, b.dpnr, b.bktnr, v.avnr)
 | 
				
			||||||
 | 
					                    LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr)
 | 
				
			||||||
 | 
					                WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL)
 | 
				
			||||||
 | 
					                ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr
 | 
				
			||||||
 | 
					                """).ToListAsync();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class CreditNoteDeliveryRow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public int Year;
 | 
				
			||||||
 | 
					        public int? TgNr;
 | 
				
			||||||
 | 
					        public int AvNr;
 | 
				
			||||||
 | 
					        public int MgNr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string LsNr;
 | 
				
			||||||
 | 
					        public int DPNr;
 | 
				
			||||||
 | 
					        public string Variety;
 | 
				
			||||||
 | 
					        public string? Attribute;
 | 
				
			||||||
 | 
					        public string[] Modifiers;
 | 
				
			||||||
 | 
					        public string QualityLevel;
 | 
				
			||||||
 | 
					        public (double Oe, double Kmw) Gradation;
 | 
				
			||||||
 | 
					        public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets;
 | 
				
			||||||
 | 
					        public decimal? TotalModifiers;
 | 
				
			||||||
 | 
					        public decimal? Amount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public CreditNoteDeliveryRow(IEnumerable<CreditNoteDeliveryRowSingle> rows, DbSet<Season> seasons) {
 | 
				
			||||||
 | 
					            var f = rows.First();
 | 
				
			||||||
 | 
					            Year = f.Year;
 | 
				
			||||||
 | 
					            TgNr = f.TgNr;
 | 
				
			||||||
 | 
					            MgNr = f.MgNr;
 | 
				
			||||||
 | 
					            var season = seasons.Find(Year);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            LsNr = f.LsNr;
 | 
				
			||||||
 | 
					            DPNr = f.DPNr;
 | 
				
			||||||
 | 
					            Variety = f.Variety;
 | 
				
			||||||
 | 
					            Attribute = f.Attribute;
 | 
				
			||||||
 | 
					            var modifiers = (IEnumerable<Modifier>)(f.Modifiers ?? "").Split(',')
 | 
				
			||||||
 | 
					                .Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m))
 | 
				
			||||||
 | 
					                .Where(m => m != null)
 | 
				
			||||||
 | 
					                .OrderBy(m => m.Ordering)
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					            Modifiers = modifiers.Select(m => m.Name).ToArray();
 | 
				
			||||||
 | 
					            QualityLevel = f.QualityLevel;
 | 
				
			||||||
 | 
					            Gradation = (f.Oe, f.Kmw);
 | 
				
			||||||
 | 
					            Buckets = rows
 | 
				
			||||||
 | 
					                .Where(b => b.Value > 0)
 | 
				
			||||||
 | 
					                .OrderByDescending(b => b.BktNr)
 | 
				
			||||||
 | 
					                .Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value,
 | 
				
			||||||
 | 
					                              b.Price != null ? season?.DecFromDb((long)b.Price) : null,
 | 
				
			||||||
 | 
					                              b.Amount != null ? season?.DecFromDb((long)b.Amount) : null))
 | 
				
			||||||
 | 
					                .ToArray();
 | 
				
			||||||
 | 
					            Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null;
 | 
				
			||||||
 | 
					            var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null;
 | 
				
			||||||
 | 
					            TotalModifiers = Amount - netAmount;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [Keyless]
 | 
				
			||||||
 | 
					    public class CreditNoteDeliveryRowSingle {
 | 
				
			||||||
 | 
					        [Column("year")]
 | 
				
			||||||
 | 
					        public int Year { get; set; }
 | 
				
			||||||
 | 
					        [Column("tgnr")]
 | 
				
			||||||
 | 
					        public int? TgNr { get; set; }
 | 
				
			||||||
 | 
					        [Column("avnr")]
 | 
				
			||||||
 | 
					        public int? AvNr { get; set; }
 | 
				
			||||||
 | 
					        [Column("mgnr")]
 | 
				
			||||||
 | 
					        public int MgNr { get; set; }
 | 
				
			||||||
 | 
					        [Column("did")]
 | 
				
			||||||
 | 
					        public int DId { get; set; }
 | 
				
			||||||
 | 
					        [Column("lsnr")]
 | 
				
			||||||
 | 
					        public string LsNr { get; set; }
 | 
				
			||||||
 | 
					        [Column("dpnr")]
 | 
				
			||||||
 | 
					        public int DPNr { get; set; }
 | 
				
			||||||
 | 
					        [Column("weight")]
 | 
				
			||||||
 | 
					        public int Weight { get; set; }
 | 
				
			||||||
 | 
					        [Column("modifiers")]
 | 
				
			||||||
 | 
					        public string? Modifiers { get; set; }
 | 
				
			||||||
 | 
					        [Column("bktnr")]
 | 
				
			||||||
 | 
					        public int BktNr { get; set; }
 | 
				
			||||||
 | 
					        [Column("sortid")]
 | 
				
			||||||
 | 
					        public string SortId { get; set; }
 | 
				
			||||||
 | 
					        [Column("discr")]
 | 
				
			||||||
 | 
					        public string Discr { get; set; }
 | 
				
			||||||
 | 
					        [Column("value")]
 | 
				
			||||||
 | 
					        public int Value { get; set; }
 | 
				
			||||||
 | 
					        [Column("price")]
 | 
				
			||||||
 | 
					        public long? Price { get; set; }
 | 
				
			||||||
 | 
					        [Column("amount")]
 | 
				
			||||||
 | 
					        public long? Amount { get; set; }
 | 
				
			||||||
 | 
					        [Column("net_amount")]
 | 
				
			||||||
 | 
					        public long? NetAmount { get; set; }
 | 
				
			||||||
 | 
					        [Column("total_amount")]
 | 
				
			||||||
 | 
					        public long? TotalAmount { get; set; }
 | 
				
			||||||
 | 
					        [Column("variety")]
 | 
				
			||||||
 | 
					        public string Variety { get; set; }
 | 
				
			||||||
 | 
					        [Column("attribute")]
 | 
				
			||||||
 | 
					        public string? Attribute { get; set; }
 | 
				
			||||||
 | 
					        [Column("quality_level")]
 | 
				
			||||||
 | 
					        public string QualityLevel { get; set; }
 | 
				
			||||||
 | 
					        [Column("oe")]
 | 
				
			||||||
 | 
					        public double Oe { get; set; }
 | 
				
			||||||
 | 
					        [Column("kmw")]
 | 
				
			||||||
 | 
					        public double Kmw { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,38 +5,42 @@ using System.Linq;
 | 
				
			|||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Elwig.Models.Dtos {
 | 
					namespace Elwig.Models.Dtos {
 | 
				
			||||||
    public class DeliveryConfirmationData : DataTable<DeliveryConfirmationRow> {
 | 
					    public class DeliveryConfirmationDeliveryData : DataTable<DeliveryConfirmationDeliveryRow> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly (string, string, string?, int)[] FieldNames = new[] {
 | 
					        private static readonly (string, string, string?, int)[] FieldNames = [
 | 
				
			||||||
            ("LsNr", "LsNr.", null, 26),
 | 
					            ("LsNr", "LsNr.", null, 26),
 | 
				
			||||||
            ("DPNr", "Pos.", null, 8),
 | 
					            ("DPNr", "Pos.", null, 8),
 | 
				
			||||||
            ("Variant", "Sorte", null, 40),
 | 
					            ("Variety", "Sorte", null, 40),
 | 
				
			||||||
            ("Attribute", "Attribut", null, 20),
 | 
					            ("Attribute", "Attribut", null, 20),
 | 
				
			||||||
            ("Modifiers", "Zu-/Abschläge", null, 30),
 | 
					            ("Modifiers", "Zu-/Abschläge", null, 30),
 | 
				
			||||||
            ("QualityLevel", "Qualitätsstufe", null, 25),
 | 
					            ("QualityLevel", "Qualitätsstufe", null, 25),
 | 
				
			||||||
            ("Gradation", "Gradation", "°Oe|°KMW", 32),
 | 
					            ("Gradation", "Gradation", "°Oe|°KMW", 32),
 | 
				
			||||||
            ("Buckets", "Flächenbindung", "|kg", 36),
 | 
					            ("Buckets", "Flächenbindung", "|kg", 36),
 | 
				
			||||||
            ("Weight", "Gewicht", "kg", 16),
 | 
					            ("Weight", "Gewicht", "kg", 16),
 | 
				
			||||||
        };
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly int MgNr;
 | 
					        private readonly int MgNr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private DeliveryConfirmationData(IEnumerable<DeliveryConfirmationRow> rows, int year, Member m) :
 | 
					        private DeliveryConfirmationDeliveryData(IEnumerable<DeliveryConfirmationDeliveryRow> rows, int year, Member m) :
 | 
				
			||||||
            base($"Anlieferungsbestätigung", $"Anlieferungsbestätigung {year} – {m.AdministrativeName}", rows, FieldNames) {
 | 
					            base($"Anlieferungsbestätigung", $"Anlieferungsbestätigung {year} – {m.AdministrativeName}", rows, FieldNames) {
 | 
				
			||||||
            MgNr = m.MgNr;
 | 
					            MgNr = m.MgNr;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<IDictionary<int, DeliveryConfirmationData>> ForSeason(DbSet<DeliveryPart> table, int year) {
 | 
					        public static DeliveryConfirmationDeliveryData CreateEmpty(int year, Member m) {
 | 
				
			||||||
 | 
					            return new([], year, m);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static async Task<IDictionary<int, DeliveryConfirmationDeliveryData>> ForSeason(DbSet<DeliveryPart> table, int year) {
 | 
				
			||||||
            return (await FromDbSet(table, year))
 | 
					            return (await FromDbSet(table, year))
 | 
				
			||||||
                .GroupBy(
 | 
					                .GroupBy(
 | 
				
			||||||
                    p => p.Delivery.Member,
 | 
					                    p => p.Delivery.Member,
 | 
				
			||||||
                    p => new DeliveryConfirmationRow(p),
 | 
					                    p => new DeliveryConfirmationDeliveryRow(p),
 | 
				
			||||||
                    (k, g) => new DeliveryConfirmationData(g, year, k)
 | 
					                    (k, g) => new DeliveryConfirmationDeliveryData(g, year, k)
 | 
				
			||||||
                ).ToDictionary(d => d.MgNr, d => d);
 | 
					                ).ToDictionary(d => d.MgNr, d => d);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static async Task<DeliveryConfirmationData> ForMember(DbSet<DeliveryPart> table, int year, Member m) {
 | 
					        public static async Task<DeliveryConfirmationDeliveryData> ForMember(DbSet<DeliveryPart> table, int year, Member m) {
 | 
				
			||||||
            return new DeliveryConfirmationData((await FromDbSet(table, year, m.MgNr)).Select(p => new DeliveryConfirmationRow(p)), year, m);
 | 
					            return new DeliveryConfirmationDeliveryData((await FromDbSet(table, year, m.MgNr)).Select(p => new DeliveryConfirmationDeliveryRow(p)), year, m);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static async Task<IEnumerable<DeliveryPart>> FromDbSet(DbSet<DeliveryPart> table, int? year = null, int? mgnr = null) {
 | 
					        private static async Task<IEnumerable<DeliveryPart>> FromDbSet(DbSet<DeliveryPart> table, int? year = null, int? mgnr = null) {
 | 
				
			||||||
@@ -47,7 +51,7 @@ namespace Elwig.Models.Dtos {
 | 
				
			|||||||
            if (mgnr != null) q = q.Where(p => p.Delivery.MgNr == mgnr);
 | 
					            if (mgnr != null) q = q.Where(p => p.Delivery.MgNr == mgnr);
 | 
				
			||||||
            await q
 | 
					            await q
 | 
				
			||||||
                 .Include(p => p.Delivery)
 | 
					                 .Include(p => p.Delivery)
 | 
				
			||||||
                 .Include(p => p.Variant)
 | 
					                 .Include(p => p.Variety)
 | 
				
			||||||
                 .Include(p => p.Attribute)
 | 
					                 .Include(p => p.Attribute)
 | 
				
			||||||
                 .Include(p => p.Quality)
 | 
					                 .Include(p => p.Quality)
 | 
				
			||||||
                 .Include(p => p.Buckets)
 | 
					                 .Include(p => p.Buckets)
 | 
				
			||||||
@@ -64,10 +68,10 @@ namespace Elwig.Models.Dtos {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public class DeliveryConfirmationRow {
 | 
					    public class DeliveryConfirmationDeliveryRow {
 | 
				
			||||||
        public string LsNr;
 | 
					        public string LsNr;
 | 
				
			||||||
        public int DPNr;
 | 
					        public int DPNr;
 | 
				
			||||||
        public string Variant;
 | 
					        public string Variety;
 | 
				
			||||||
        public string? Attribute;
 | 
					        public string? Attribute;
 | 
				
			||||||
        public string QualityLevel;
 | 
					        public string QualityLevel;
 | 
				
			||||||
        public (double Oe, double Kmw) Gradation;
 | 
					        public (double Oe, double Kmw) Gradation;
 | 
				
			||||||
@@ -75,11 +79,11 @@ namespace Elwig.Models.Dtos {
 | 
				
			|||||||
        public int Weight;
 | 
					        public int Weight;
 | 
				
			||||||
        public (string Name, int Value)[] Buckets;
 | 
					        public (string Name, int Value)[] Buckets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public DeliveryConfirmationRow(DeliveryPart p) {
 | 
					        public DeliveryConfirmationDeliveryRow(DeliveryPart p) {
 | 
				
			||||||
            var d = p.Delivery;
 | 
					            var d = p.Delivery;
 | 
				
			||||||
            LsNr = d.LsNr;
 | 
					            LsNr = d.LsNr;
 | 
				
			||||||
            DPNr = p.DPNr;
 | 
					            DPNr = p.DPNr;
 | 
				
			||||||
            Variant = p.Variant.Name;
 | 
					            Variety = p.Variety.Name;
 | 
				
			||||||
            Attribute = p.Attribute?.Name;
 | 
					            Attribute = p.Attribute?.Name;
 | 
				
			||||||
            QualityLevel = p.Quality.Name;
 | 
					            QualityLevel = p.Quality.Name;
 | 
				
			||||||
            Gradation = (p.Oe, p.Kmw);
 | 
					            Gradation = (p.Oe, p.Kmw);
 | 
				
			||||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
 | 
				
			|||||||
namespace Elwig.Models.Dtos {
 | 
					namespace Elwig.Models.Dtos {
 | 
				
			||||||
    public class MemberDeliveryPerVariantData : DataTable<MemberDeliveryPerVariantRow> {
 | 
					    public class MemberDeliveryPerVariantData : DataTable<MemberDeliveryPerVariantRow> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly (string, string, string?, int)[] FieldNames = new[] {
 | 
					        private static readonly (string, string, string?, int)[] FieldNames = [
 | 
				
			||||||
            ("MgNr", "MgNr.", null, 12),
 | 
					            ("MgNr", "MgNr.", null, 12),
 | 
				
			||||||
            ("Name", "Name", null, 40),
 | 
					            ("Name", "Name", null, 40),
 | 
				
			||||||
            ("GivenName", "Vorname", null, 40),
 | 
					            ("GivenName", "Vorname", null, 40),
 | 
				
			||||||
@@ -20,7 +20,7 @@ namespace Elwig.Models.Dtos {
 | 
				
			|||||||
            ("Weights", "Geliefert", "kg", 22),
 | 
					            ("Weights", "Geliefert", "kg", 22),
 | 
				
			||||||
            ("Areas", "Fläche", "m²", 22),
 | 
					            ("Areas", "Fläche", "m²", 22),
 | 
				
			||||||
            ("Yields", "Ertrag", "kg/ha", 22),
 | 
					            ("Yields", "Ertrag", "kg/ha", 22),
 | 
				
			||||||
        };
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public MemberDeliveryPerVariantData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
 | 
					        public MemberDeliveryPerVariantData(IEnumerable<MemberDeliveryPerVariantRow> rows, int year) :
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 | 
				
			|||||||
namespace Elwig.Models.Dtos {
 | 
					namespace Elwig.Models.Dtos {
 | 
				
			||||||
    public class OverUnderDeliveryData : DataTable<OverUnderDeliveryRow> {
 | 
					    public class OverUnderDeliveryData : DataTable<OverUnderDeliveryRow> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly (string, string, string?, int)[] FieldNames = new[] {
 | 
					        private static readonly (string, string, string?, int)[] FieldNames = [
 | 
				
			||||||
            ("MgNr", "MgNr.", null, 12),
 | 
					            ("MgNr", "MgNr.", null, 12),
 | 
				
			||||||
            ("Name", "Name", null, 40),
 | 
					            ("Name", "Name", null, 40),
 | 
				
			||||||
            ("GivenName", "Vorname", null, 40),
 | 
					            ("GivenName", "Vorname", null, 40),
 | 
				
			||||||
@@ -19,7 +19,7 @@ namespace Elwig.Models.Dtos {
 | 
				
			|||||||
            ("DeliveryRight", "Lieferrecht", "kg", 22),
 | 
					            ("DeliveryRight", "Lieferrecht", "kg", 22),
 | 
				
			||||||
            ("Weight", "Geliefert", "kg", 22),
 | 
					            ("Weight", "Geliefert", "kg", 22),
 | 
				
			||||||
            ("OverUnderDelivery", "Über-/Unterliefert", "kg|%", 34),
 | 
					            ("OverUnderDelivery", "Ü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) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
using Elwig.Models.Entities;
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,7 +20,7 @@ namespace Elwig.Models.Dtos {
 | 
				
			|||||||
                .ToList();
 | 
					                .ToList();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string FormatAmountCent(long cents) => $"{cents / 100}.{cents % 100:00}";
 | 
					        public static string FormatAmountCent(long cents) => $"{cents / 100}.{Math.Abs(cents % 100):00}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string FormatAmount(decimal amount) => FormatAmountCent((int)(amount * 100));
 | 
					        public static string FormatAmount(decimal amount) => FormatAmountCent((int)(amount * 100));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,19 +68,32 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [InverseProperty("Delivery")]
 | 
					        [InverseProperty("Delivery")]
 | 
				
			||||||
        public virtual ISet<DeliveryPart> Parts { get; private set; }
 | 
					        public virtual ISet<DeliveryPart> Parts { get; private set; }
 | 
				
			||||||
 | 
					        [NotMapped]
 | 
				
			||||||
 | 
					        public IEnumerable<DeliveryPart> FilteredParts => PartFilter == null ? Parts : Parts.Where(p => PartFilter(p));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [NotMapped]
 | 
				
			||||||
 | 
					        public Predicate<DeliveryPart>? PartFilter { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public int Weight => Parts.Select(p => p.Weight).Sum();
 | 
					        public int Weight => Parts.Select(p => p.Weight).Sum();
 | 
				
			||||||
 | 
					        public int FilteredWeight => FilteredParts.Select(p => p.Weight).Sum();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public IEnumerable<string> SortIds => Parts
 | 
					        public IEnumerable<string> SortIds => Parts
 | 
				
			||||||
            .GroupBy(p => p.SortId)
 | 
					            .GroupBy(p => p.SortId)
 | 
				
			||||||
            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
					            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
				
			||||||
            .Select(g => g.Select(p => p.SortId).First());
 | 
					            .Select(g => g.Key);
 | 
				
			||||||
 | 
					        public IEnumerable<string> FilteredSortIds => FilteredParts
 | 
				
			||||||
 | 
					            .GroupBy(p => p.SortId)
 | 
				
			||||||
 | 
					            .OrderByDescending(g => g.Select(p => p.Weight).Sum())
 | 
				
			||||||
 | 
					            .Select(g => g.Key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string SortIdString => string.Join(", ", SortIds);
 | 
					        public string SortIdString => string.Join(", ", SortIds);
 | 
				
			||||||
 | 
					        public string FilteredSortIdString => string.Join(", ", FilteredSortIds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public double Kmw => Utils.AggregateDeliveryPartsKmw(Parts);
 | 
					        public double Kmw => Utils.AggregateDeliveryPartsKmw(Parts);
 | 
				
			||||||
 | 
					        public double FilteredKmw => Utils.AggregateDeliveryPartsKmw(FilteredParts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public double Oe => Utils.KmwToOe(Kmw);
 | 
					        public double Oe => Utils.KmwToOe(Kmw);
 | 
				
			||||||
 | 
					        public double FilteredOe => Utils.KmwToOe(FilteredKmw);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public int SearchScore(IEnumerable<string> keywords) {
 | 
					        public int SearchScore(IEnumerable<string> keywords) {
 | 
				
			||||||
            var list = new string?[] {
 | 
					            var list = new string?[] {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        public string SortId { get; set; }
 | 
					        public string SortId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [ForeignKey("SortId")]
 | 
					        [ForeignKey("SortId")]
 | 
				
			||||||
        public virtual WineVar Variant { get; private set; }
 | 
					        public virtual WineVar Variety { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Column("attrid")]
 | 
					        [Column("attrid")]
 | 
				
			||||||
        public string? AttrId { get; set; }
 | 
					        public string? AttrId { get; set; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,7 +65,6 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [Column("start_date")]
 | 
					        [Column("start_date")]
 | 
				
			||||||
        public string? StartDateString { get; set; }
 | 
					        public string? StartDateString { get; set; }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public DateOnly? StartDate {
 | 
					        public DateOnly? StartDate {
 | 
				
			||||||
            get => StartDateString != null ? DateOnly.ParseExact(StartDateString, "yyyy-MM-dd") : null;
 | 
					            get => StartDateString != null ? DateOnly.ParseExact(StartDateString, "yyyy-MM-dd") : null;
 | 
				
			||||||
@@ -74,13 +73,30 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        [Column("end_date")]
 | 
					        [Column("end_date")]
 | 
				
			||||||
        public string? EndDateString { get; set; }
 | 
					        public string? EndDateString { get; set; }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        [NotMapped]
 | 
					        [NotMapped]
 | 
				
			||||||
        public DateOnly? EndDate {
 | 
					        public DateOnly? EndDate {
 | 
				
			||||||
            get => EndDateString != null ? DateOnly.ParseExact(EndDateString, "yyyy-MM-dd") : null;
 | 
					            get => EndDateString != null ? DateOnly.ParseExact(EndDateString, "yyyy-MM-dd") : null;
 | 
				
			||||||
            set => EndDateString = value?.ToString("yyyy-MM-dd");
 | 
					            set => EndDateString = value?.ToString("yyyy-MM-dd");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Column("calc_mode")]
 | 
				
			||||||
 | 
					        public int CalcMode { get; set; }
 | 
				
			||||||
 | 
					        [NotMapped]
 | 
				
			||||||
 | 
					        public bool Billing_HonorGebunden {
 | 
				
			||||||
 | 
					            get => (CalcMode & 0x1) != 0;
 | 
				
			||||||
 | 
					            set => CalcMode = value ? CalcMode | 0x1 : CalcMode & ~0x1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        [NotMapped]
 | 
				
			||||||
 | 
					        public bool Billing_AllowAttrsIntoLower {
 | 
				
			||||||
 | 
					            get => (CalcMode & 0x4) != 0;
 | 
				
			||||||
 | 
					            set => CalcMode = value ? CalcMode | 0x4 : CalcMode & ~0x4;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        [NotMapped]
 | 
				
			||||||
 | 
					        public bool Billing_AvoidUnderDeliveries {
 | 
				
			||||||
 | 
					            get => (CalcMode & 0x2) != 0;
 | 
				
			||||||
 | 
					            set => CalcMode = value ? CalcMode | 0x2 : CalcMode & ~0x2;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [ForeignKey("CurrencyCode")]
 | 
					        [ForeignKey("CurrencyCode")]
 | 
				
			||||||
        public virtual Currency Currency { get; private set; }
 | 
					        public virtual Currency Currency { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,13 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        [Column("fill_lower")]
 | 
					        [Column("fill_lower")]
 | 
				
			||||||
        public int FillLower { get; set; }
 | 
					        public int FillLower { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public WineAttr() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public WineAttr(string attrId, string name) {
 | 
				
			||||||
 | 
					            AttrId = attrId;
 | 
				
			||||||
 | 
					            Name = name;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public override string ToString() {
 | 
					        public override string ToString() {
 | 
				
			||||||
            return Name;
 | 
					            return Name;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,13 @@ namespace Elwig.Models.Entities {
 | 
				
			|||||||
        public bool IsRed => Type == "R";
 | 
					        public bool IsRed => Type == "R";
 | 
				
			||||||
        public bool IsWhite => Type == "W";
 | 
					        public bool IsWhite => Type == "W";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public WineVar() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public WineVar(string sortId, string name) {
 | 
				
			||||||
 | 
					            SortId = sortId;
 | 
				
			||||||
 | 
					            Name = name;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public override string ToString() {
 | 
					        public override string ToString() {
 | 
				
			||||||
            return Name;
 | 
					            return Name;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
 | 
				
			|||||||
    <PublishDir>bin\Publish</PublishDir>
 | 
					    <PublishDir>bin\Publish</PublishDir>
 | 
				
			||||||
    <PublishProtocol>FileSystem</PublishProtocol>
 | 
					    <PublishProtocol>FileSystem</PublishProtocol>
 | 
				
			||||||
    <_TargetId>Folder</_TargetId>
 | 
					    <_TargetId>Folder</_TargetId>
 | 
				
			||||||
    <TargetFramework>net7.0-windows</TargetFramework>
 | 
					    <TargetFramework>net8.0-windows</TargetFramework>
 | 
				
			||||||
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
 | 
					    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
 | 
				
			||||||
    <SelfContained>true</SelfContained>
 | 
					    <SelfContained>true</SelfContained>
 | 
				
			||||||
    <PublishSingleFile>false</PublishSingleFile>
 | 
					    <PublishSingleFile>false</PublishSingleFile>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
-- schema version 11 to 12
 | 
					-- schema version 12 to 13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ALTER TABLE season ADD COLUMN bs_value INTEGER;
 | 
					ALTER TABLE season ADD COLUMN bs_value INTEGER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								Elwig/Resources/Sql/13-14.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Elwig/Resources/Sql/13-14.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					-- schema version 13 to 14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE VIEW v_penalty_area_commitments AS
 | 
				
			||||||
 | 
					SELECT year, mgnr,
 | 
				
			||||||
 | 
					      SUM(COALESCE(IIF(u.weight = 0, -t.penalty_none, 0), 0) +
 | 
				
			||||||
 | 
					          COALESCE(IIF(u.diff < 0, -t.penalty_amount, 0), 0) +
 | 
				
			||||||
 | 
					          COALESCE(u.diff * t.penalty_per_kg, 0)
 | 
				
			||||||
 | 
					      ) AS total_penalty
 | 
				
			||||||
 | 
					FROM v_under_delivery u
 | 
				
			||||||
 | 
					   JOIN area_commitment_type t ON t.vtrgid = u.bucket
 | 
				
			||||||
 | 
					GROUP BY year, mgnr
 | 
				
			||||||
 | 
					HAVING total_penalty < 0
 | 
				
			||||||
 | 
					ORDER BY year, mgnr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE VIEW v_penalty_business_shares AS
 | 
				
			||||||
 | 
					SELECT s.year, u.mgnr,
 | 
				
			||||||
 | 
					       (COALESCE(IIF(u.weight = 0, -s.penalty_none, 0), 0) +
 | 
				
			||||||
 | 
					        COALESCE(IIF(u.diff < 0, -s.penalty_amount, 0), 0) +
 | 
				
			||||||
 | 
					        COALESCE(u.diff * s.penalty_per_kg, 0)
 | 
				
			||||||
 | 
					       ) AS total_penalty
 | 
				
			||||||
 | 
					FROM v_total_under_delivery u
 | 
				
			||||||
 | 
					   JOIN season s ON s.year = u.year
 | 
				
			||||||
 | 
					WHERE u.diff < 0 AND total_penalty < 0
 | 
				
			||||||
 | 
					ORDER BY s.year, u.mgnr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE VIEW v_auto_business_shares AS
 | 
				
			||||||
 | 
					SELECT s.year, h.mgnr,
 | 
				
			||||||
 | 
					       SUM(h.business_shares) AS business_shares,
 | 
				
			||||||
 | 
					       SUM(h.business_shares) * s.bs_value AS total_amount
 | 
				
			||||||
 | 
					FROM member_history h, season s
 | 
				
			||||||
 | 
					WHERE h.type = 'auto' AND h.date >= s.year || '-01-01' AND h.date <= s.year || '-12-31'
 | 
				
			||||||
 | 
					GROUP BY s.year, h.mgnr
 | 
				
			||||||
 | 
					ORDER BY s.year, h.mgnr;
 | 
				
			||||||
							
								
								
									
										18
									
								
								Elwig/Resources/Sql/14-15.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Elwig/Resources/Sql/14-15.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					-- schema version 14 to 15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE season ADD COLUMN calc_mode INTEGER NOT NULL DEFAULT 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DROP TRIGGER t_payment_delivery_part_u;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TRIGGER t_payment_delivery_part_u
 | 
				
			||||||
 | 
					    AFTER UPDATE ON payment_delivery_part FOR EACH ROW
 | 
				
			||||||
 | 
					BEGIN
 | 
				
			||||||
 | 
					    UPDATE payment_member
 | 
				
			||||||
 | 
					    SET net_amount = net_amount - OLD.amount
 | 
				
			||||||
 | 
					    WHERE (year, avnr, mgnr) IN (SELECT year, OLD.avnr, mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did));
 | 
				
			||||||
 | 
					    INSERT INTO payment_member (year, avnr, mgnr, net_amount)
 | 
				
			||||||
 | 
					    SELECT d.year, v.avnr, d.mgnr, NEW.amount
 | 
				
			||||||
 | 
					    FROM delivery d, payment_variant v
 | 
				
			||||||
 | 
					    WHERE (d.year, d.did) = (NEW.year, NEW.did) AND (v.year, v.avnr) = (NEW.year, NEW.avnr)
 | 
				
			||||||
 | 
					    ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount;
 | 
				
			||||||
 | 
					END;
 | 
				
			||||||
							
								
								
									
										9
									
								
								Elwig/Resources/Sql/15-16.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Elwig/Resources/Sql/15-16.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					-- schema version 15 to 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					INSERT INTO AT_plz_dest (plz, okz, dest)
 | 
				
			||||||
 | 
					VALUES (2560,  3388, 'Grillenberg');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DELETE FROM AT_plz_dest WHERE (plz, okz) = (2561, 3388);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UPDATE AT_ort SET kgnr = 23351 WHERE okz = 5280;
 | 
				
			||||||
 | 
					UPDATE AT_ort SET kgnr = 4311 WHERE okz = 3388;
 | 
				
			||||||
							
								
								
									
										12
									
								
								Elwig/Resources/Sql/16-17.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Elwig/Resources/Sql/16-17.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					-- schema version 16 to 17
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE VIEW v_virtual_season AS
 | 
				
			||||||
 | 
					SELECT year, max_kg_per_ha
 | 
				
			||||||
 | 
					FROM season
 | 
				
			||||||
 | 
					UNION
 | 
				
			||||||
 | 
					SELECT strftime('%Y', date()) + 0, (SELECT max_kg_per_ha FROM season ORDER BY year DESC LIMIT 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PRAGMA writable_schema = ON;
 | 
				
			||||||
 | 
					UPDATE sqlite_schema SET sql = REPLACE(sql, 'season s', 'v_virtual_season s')
 | 
				
			||||||
 | 
					WHERE type = 'view' AND name = 'v_area_commitment_bucket_strict';
 | 
				
			||||||
 | 
					PRAGMA writable_schema = OFF;
 | 
				
			||||||
@@ -16,21 +16,21 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        public int MgNr => Member.MgNr;
 | 
					        public int MgNr => Member.MgNr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly Member Member;
 | 
					        private readonly Member Member;
 | 
				
			||||||
        private List<string> TextFilter = new();
 | 
					        private List<string> TextFilter = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public AreaComAdminWindow(int mgnr) {
 | 
					        public AreaComAdminWindow(int mgnr) {
 | 
				
			||||||
            InitializeComponent();
 | 
					            InitializeComponent();
 | 
				
			||||||
            Member = Context.Members.Find(mgnr) ?? throw new ArgumentException("MgNr argument has invalid value");
 | 
					            Member = Context.Members.Find(mgnr) ?? throw new ArgumentException("MgNr argument has invalid value");
 | 
				
			||||||
            Title = $"Flächenbindungen - {Member.AdministrativeName} - Elwig";
 | 
					            Title = $"Flächenbindungen - {Member.AdministrativeName} - Elwig";
 | 
				
			||||||
            ExemptInputs = new Control[] {
 | 
					            ExemptInputs = [
 | 
				
			||||||
                MgNrInput, AreaCommitmentList, NewAreaCommitmentButton, 
 | 
					                MgNrInput, AreaCommitmentList, NewAreaCommitmentButton, 
 | 
				
			||||||
                EditAreaCommitmentButton, DeleteAreaCommitmentButton, AreaCommitmentSaveButton,
 | 
					                EditAreaCommitmentButton, DeleteAreaCommitmentButton, AreaCommitmentSaveButton,
 | 
				
			||||||
                AreaCommitmentResetButton, AreaCommitmentCancelButton, SearchInput, ActiveAreaCommitmentInput
 | 
					                AreaCommitmentResetButton, AreaCommitmentCancelButton, SearchInput, ActiveAreaCommitmentInput
 | 
				
			||||||
            };
 | 
					            ];
 | 
				
			||||||
            RequiredInputs = new Control[] {
 | 
					            RequiredInputs = [
 | 
				
			||||||
                FbNrInput, YearFromInput, KgInput, RdInput,
 | 
					                FbNrInput, YearFromInput, KgInput, RdInput,
 | 
				
			||||||
                GstNrInput, AreaInput.TextBox, AreaComTypeInput, WineCultivationInput
 | 
					                GstNrInput, AreaInput.TextBox, AreaComTypeInput, WineCultivationInput
 | 
				
			||||||
            };
 | 
					            ];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void Window_Loaded(object sender, RoutedEventArgs e) {
 | 
					        private void Window_Loaded(object sender, RoutedEventArgs e) {
 | 
				
			||||||
@@ -76,10 +76,10 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async Task<(List<string>, IQueryable<AreaCom>, List<string>)> GetFilters() {
 | 
					        private async Task<(List<string>, IQueryable<AreaCom>, List<string>)> GetFilters() {
 | 
				
			||||||
            List<string> filterNames = new();
 | 
					            List<string> filterNames = [];
 | 
				
			||||||
            IQueryable<AreaCom> areaComQuery = Context.AreaCommitments.Where(a => a.MgNr == Member.MgNr).OrderBy(a => a.FbNr);
 | 
					            IQueryable<AreaCom> areaComQuery = Context.AreaCommitments.Where(a => a.MgNr == Member.MgNr).OrderBy(a => a.FbNr);
 | 
				
			||||||
            if (ActiveAreaCommitmentInput.IsChecked == true) {
 | 
					            if (ActiveAreaCommitmentInput.IsChecked == true) {
 | 
				
			||||||
                areaComQuery = areaComQuery.Where(a => (a.YearFrom <= Utils.CurrentNextSeason) && (a.YearTo == null || a.YearTo >= Utils.CurrentNextSeason));
 | 
					                areaComQuery = areaComQuery.Where(a => (a.YearFrom <= Utils.CurrentYear) && (a.YearTo == null || a.YearTo >= Utils.CurrentYear));
 | 
				
			||||||
                filterNames.Add("aktiv");
 | 
					                filterNames.Add("aktiv");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -151,7 +151,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            YearToInput.Text = a.YearTo.ToString();
 | 
					            YearToInput.Text = a.YearTo.ToString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            KgInput.SelectedItem = a.Kg.AtKg;
 | 
					            KgInput.SelectedItem = a.Kg.AtKg;
 | 
				
			||||||
            RdInput.SelectedItem = a.Rd;
 | 
					            RdInput.SelectedItem = a.Rd ?? RdInput.Items[0];
 | 
				
			||||||
            GstNrInput.Text = a.GstNr;
 | 
					            GstNrInput.Text = a.GstNr;
 | 
				
			||||||
            AreaInput.Text = a.Area.ToString();
 | 
					            AreaInput.Text = a.Area.ToString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -230,8 +230,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            a.RdNr = RdInput.SelectedItem.GetType() == typeof(NullItem) ? null : ((WbRd)RdInput.SelectedItem).RdNr;
 | 
					            a.RdNr = RdInput.SelectedItem.GetType() == typeof(NullItem) ? null : ((WbRd)RdInput.SelectedItem).RdNr;
 | 
				
			||||||
            a.GstNr = GstNrInput.Text;
 | 
					            a.GstNr = GstNrInput.Text;
 | 
				
			||||||
            a.Area = int.Parse(AreaInput.Text);
 | 
					            a.Area = int.Parse(AreaInput.Text);
 | 
				
			||||||
            a.VtrgId = (AreaComTypeInput.SelectedItem as AreaComType)?.VtrgId;
 | 
					            a.VtrgId = (AreaComTypeInput.SelectedItem as AreaComType)!.VtrgId;
 | 
				
			||||||
            a.CultId = (WineCultivationInput.SelectedItem as WineCult)?.CultId;
 | 
					            a.CultId = (WineCultivationInput.SelectedItem as WineCult)!.CultId;
 | 
				
			||||||
            a.Comment = (CommentInput.Text == "") ? null : CommentInput.Text;
 | 
					            a.Comment = (CommentInput.Text == "") ? null : CommentInput.Text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            EntityEntry<AreaCom>? tr = null;
 | 
					            EntityEntry<AreaCom>? tr = null;
 | 
				
			||||||
@@ -275,7 +275,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                MessageBox.Show(str, "Flächenbindung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					                MessageBox.Show(str, "Flächenbindung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return a;
 | 
					            return a!;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void AreaCommitmentSaveButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void AreaCommitmentSaveButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
@@ -287,7 +287,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            ShowAreaCommitmentNewEditDeleteButtons();
 | 
					            ShowAreaCommitmentNewEditDeleteButtons();
 | 
				
			||||||
            LockInputs();
 | 
					            LockInputs();
 | 
				
			||||||
            UnlockSearchInputs();
 | 
					            UnlockSearchInputs();
 | 
				
			||||||
            await RefreshAreaCommitmentList();
 | 
					            await App.HintContextChange();
 | 
				
			||||||
            AreaCommitmentList.SelectedItem = a;
 | 
					            AreaCommitmentList.SelectedItem = a;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -535,15 +535,18 @@
 | 
				
			|||||||
                        </GroupBox>
 | 
					                        </GroupBox>
 | 
				
			||||||
                        <GroupBox Header="Anlieferungsbestätigung" Margin="10,10,10,10" Height="250">
 | 
					                        <GroupBox Header="Anlieferungsbestätigung" Margin="10,10,10,10" Height="250">
 | 
				
			||||||
                            <Grid>
 | 
					                            <Grid>
 | 
				
			||||||
                                <Grid.ColumnDefinitions>
 | 
					 | 
				
			||||||
                                    <ColumnDefinition Width="*"/>
 | 
					 | 
				
			||||||
                                </Grid.ColumnDefinitions>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                <TextBox x:Name="TextElementDeliveryConfirmation" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
 | 
					                                <TextBox x:Name="TextElementDeliveryConfirmation" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
 | 
				
			||||||
                                         HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10" Height="Auto"
 | 
					                                         HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10" Height="Auto"
 | 
				
			||||||
                                         TextChanged="TextBox_TextChanged"/>
 | 
					                                         TextChanged="TextBox_TextChanged"/>
 | 
				
			||||||
                            </Grid>
 | 
					                            </Grid>
 | 
				
			||||||
                        </GroupBox>
 | 
					                        </GroupBox>
 | 
				
			||||||
 | 
					                        <GroupBox Header="Traubengutschrift" Margin="10,10,10,10" Height="250">
 | 
				
			||||||
 | 
					                            <Grid>
 | 
				
			||||||
 | 
					                                <TextBox x:Name="TextElementCreditNote" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
 | 
				
			||||||
 | 
					                                         HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10" Height="Auto"
 | 
				
			||||||
 | 
					                                         TextChanged="TextBox_TextChanged"/>
 | 
				
			||||||
 | 
					                            </Grid>
 | 
				
			||||||
 | 
					                        </GroupBox>
 | 
				
			||||||
                    </StackPanel>
 | 
					                    </StackPanel>
 | 
				
			||||||
                </ScrollViewer>
 | 
					                </ScrollViewer>
 | 
				
			||||||
            </TabItem>
 | 
					            </TabItem>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -125,6 +125,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            if (old != null) _branches[old] = id;
 | 
					            if (old != null) _branches[old] = id;
 | 
				
			||||||
            branch.ZwstId = id;
 | 
					            branch.ZwstId = id;
 | 
				
			||||||
            branch.Name = BranchNameInput.Text;
 | 
					            branch.Name = BranchNameInput.Text;
 | 
				
			||||||
 | 
					            branch.CountryNum = 40;
 | 
				
			||||||
            branch.PostalDestId = (BranchOrtInput.SelectedItem as AT_PlzDest)?.Id;
 | 
					            branch.PostalDestId = (BranchOrtInput.SelectedItem as AT_PlzDest)?.Id;
 | 
				
			||||||
            branch.Address = BranchAddressInput.Text;
 | 
					            branch.Address = BranchAddressInput.Text;
 | 
				
			||||||
            branch.PhoneNr = BranchPhoneNrInput.Text;
 | 
					            branch.PhoneNr = BranchPhoneNrInput.Text;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,13 +52,13 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var year = (SeasonList.SelectedItem as Season)?.Year;
 | 
					            var year = (SeasonList.SelectedItem as Season)?.Year;
 | 
				
			||||||
            foreach (var (modid, _) in _mods.Where(m => m.Value == null)) {
 | 
					            foreach (var (modid, _) in _mods.Where(m => m.Value == null)) {
 | 
				
			||||||
                Context.Remove(Context.Modifiers.Find(new object?[] { year, modid }));
 | 
					                Context.Remove(Context.Modifiers.Find(year, modid));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            foreach (var (mod, old) in _modIds) {
 | 
					            foreach (var (mod, old) in _modIds) {
 | 
				
			||||||
                mod.ModId = old;
 | 
					                mod.ModId = old;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            foreach (var (old, modid) in _mods.Where(m => m.Value != null)) {
 | 
					            foreach (var (old, modid) in _mods.Where(m => m.Value != null)) {
 | 
				
			||||||
                Context.Update(Context.Modifiers.Find(new object?[] { year, old }));
 | 
					                Context.Update(Context.Modifiers.Find(year, old));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await Context.SaveChangesAsync();
 | 
					            await Context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,8 +102,9 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            if (_modList == null || SeasonList.SelectedItem is not Season s) return;
 | 
					            if (_modList == null || SeasonList.SelectedItem is not Season s) return;
 | 
				
			||||||
            _modChanged = true;
 | 
					            _modChanged = true;
 | 
				
			||||||
            var idx = (SeasonModifierList.SelectedIndex != -1) ? SeasonModifierList.SelectedIndex + 1 : _modList.Count;
 | 
					            var idx = (SeasonModifierList.SelectedIndex != -1) ? SeasonModifierList.SelectedIndex + 1 : _modList.Count;
 | 
				
			||||||
            var item = Context.CreateProxy<Modifier>();
 | 
					            var item = new Modifier {
 | 
				
			||||||
            item.Year = s.Year;
 | 
					                Year = s.Year
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
            _modList.Insert(idx, item);
 | 
					            _modList.Insert(idx, item);
 | 
				
			||||||
            SeasonModifierList.SelectedIndex = idx;
 | 
					            SeasonModifierList.SelectedIndex = idx;
 | 
				
			||||||
            UpdateButtons();
 | 
					            UpdateButtons();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,11 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public BaseDataWindow() {
 | 
					        public BaseDataWindow() {
 | 
				
			||||||
            InitializeComponent();
 | 
					            InitializeComponent();
 | 
				
			||||||
            RequiredInputs = new Control[] {
 | 
					            RequiredInputs = [
 | 
				
			||||||
                ClientNameInput, ClientNameTypeInput, ClientNameTokenInput, ClientNameShortInput,
 | 
					                ClientNameInput, ClientNameTypeInput, ClientNameTokenInput, ClientNameShortInput,
 | 
				
			||||||
                ClientAddressInput, ClientPlzInput, ClientOrtInput,
 | 
					                ClientAddressInput, ClientPlzInput, ClientOrtInput,
 | 
				
			||||||
            };
 | 
					            ];
 | 
				
			||||||
            ExemptInputs = new Control[] {
 | 
					            ExemptInputs = [
 | 
				
			||||||
                ClientNameFull,
 | 
					                ClientNameFull,
 | 
				
			||||||
                BranchIdInput, BranchNameInput, BranchPlzInput, BranchOrtInput,
 | 
					                BranchIdInput, BranchNameInput, BranchPlzInput, BranchOrtInput,
 | 
				
			||||||
                BranchAddressInput, BranchPhoneNrInput, BranchFaxNrInput, BranchMobileNrInput,
 | 
					                BranchAddressInput, BranchPhoneNrInput, BranchFaxNrInput, BranchMobileNrInput,
 | 
				
			||||||
@@ -30,7 +30,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                SeasonMinKgPerBsInput.TextBox, SeasonMaxKgPerBsInput.TextBox, SeasonBsValueInput.TextBox,
 | 
					                SeasonMinKgPerBsInput.TextBox, SeasonMaxKgPerBsInput.TextBox, SeasonBsValueInput.TextBox,
 | 
				
			||||||
                SeasonPenaltyPerKgInput.TextBox, SeasonPenaltyInput.TextBox, SeasonPenaltyNoneInput.TextBox,
 | 
					                SeasonPenaltyPerKgInput.TextBox, SeasonPenaltyInput.TextBox, SeasonPenaltyNoneInput.TextBox,
 | 
				
			||||||
                SeasonModifierIdInput, SeasonModifierNameInput, SeasonModifierRelInput.TextBox, SeasonModifierAbsInput.TextBox,
 | 
					                SeasonModifierIdInput, SeasonModifierNameInput, SeasonModifierRelInput.TextBox, SeasonModifierAbsInput.TextBox,
 | 
				
			||||||
            };
 | 
					            ];
 | 
				
			||||||
            WineAttributeFillLowerInput.Visibility = Visibility.Hidden;
 | 
					            WineAttributeFillLowerInput.Visibility = Visibility.Hidden;
 | 
				
			||||||
            WineAttributeFillLowerLabel.Visibility = Visibility.Hidden;
 | 
					            WineAttributeFillLowerLabel.Visibility = Visibility.Hidden;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -264,6 +264,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            ClearInputStates();
 | 
					            ClearInputStates();
 | 
				
			||||||
            FillInputs(App.Client);
 | 
					            FillInputs(App.Client);
 | 
				
			||||||
            LockInputs();
 | 
					            LockInputs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await HintContextChange();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void FillInputs(ClientParameters p) {
 | 
					        private void FillInputs(ClientParameters p) {
 | 
				
			||||||
@@ -295,6 +297,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                case 3: ModeDeliveryNoteFull.IsChecked = true; break;
 | 
					                case 3: ModeDeliveryNoteFull.IsChecked = true; break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            TextElementDeliveryConfirmation.Text = p.TextDeliveryConfirmation;
 | 
					            TextElementDeliveryConfirmation.Text = p.TextDeliveryConfirmation;
 | 
				
			||||||
 | 
					            TextElementCreditNote.Text = p.TextCreditNote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            FinishInputFilling();
 | 
					            FinishInputFilling();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -320,6 +323,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            p.TextDeliveryNote = TextElementDeliveryNote.Text.Length > 0 ? TextElementDeliveryNote.Text : null;
 | 
					            p.TextDeliveryNote = TextElementDeliveryNote.Text.Length > 0 ? TextElementDeliveryNote.Text : null;
 | 
				
			||||||
            p.ModeDeliveryNoteStats = (ModeDeliveryNoteNone.IsChecked == true) ? 0 : (ModeDeliveryNoteGaOnly.IsChecked == true) ? 1 : (ModeDeliveryNoteShort.IsChecked == true) ? 2 : (ModeDeliveryNoteFull.IsChecked == true) ? 3 : 2;
 | 
					            p.ModeDeliveryNoteStats = (ModeDeliveryNoteNone.IsChecked == true) ? 0 : (ModeDeliveryNoteGaOnly.IsChecked == true) ? 1 : (ModeDeliveryNoteShort.IsChecked == true) ? 2 : (ModeDeliveryNoteFull.IsChecked == true) ? 3 : 2;
 | 
				
			||||||
            p.TextDeliveryConfirmation = TextElementDeliveryConfirmation.Text.Length > 0 ? TextElementDeliveryConfirmation.Text : null;
 | 
					            p.TextDeliveryConfirmation = TextElementDeliveryConfirmation.Text.Length > 0 ? TextElementDeliveryConfirmation.Text : null;
 | 
				
			||||||
 | 
					            p.TextCreditNote = TextElementCreditNote.Text.Length > 0 ? TextElementCreditNote.Text : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await p.UpdateValues();
 | 
					            await p.UpdateValues();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,17 @@
 | 
				
			|||||||
<local:AdministrationWindow
 | 
					<local:ContextWindow
 | 
				
			||||||
        x:Class="Elwig.Windows.ChartWindow"
 | 
					        x:Class="Elwig.Windows.ChartWindow"
 | 
				
			||||||
        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.Windows"
 | 
					        xmlns:local="clr-namespace:Elwig.Windows"
 | 
				
			||||||
 | 
					        xmlns:ctrl="clr-namespace:Elwig.Controls"
 | 
				
			||||||
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
 | 
					        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
 | 
				
			||||||
        xmlns:ScottPlot="clr-namespace:ScottPlot;assembly=ScottPlot.WPF"
 | 
					        xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
 | 
				
			||||||
        mc:Ignorable="d"
 | 
					        mc:Ignorable="d"
 | 
				
			||||||
        Title="Auszahlung - Elwig" Height="700" Width="1500"
 | 
					        Title="Auszahlung - Elwig" Height="700" Width="1500" MinWidth="1000" MinHeight="500"
 | 
				
			||||||
        Loaded="Window_Loaded">
 | 
					        Loaded="Window_Loaded"
 | 
				
			||||||
 | 
					        Closing="Window_Closing">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Window.Resources>
 | 
					    <Window.Resources>
 | 
				
			||||||
        <Style TargetType="Label">
 | 
					        <Style TargetType="Label">
 | 
				
			||||||
@@ -43,94 +45,82 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <Grid>
 | 
					    <Grid>
 | 
				
			||||||
        <Grid.RowDefinitions>
 | 
					        <Grid.RowDefinitions>
 | 
				
			||||||
            <RowDefinition Height="19"/>
 | 
					            <RowDefinition Height="40"/>
 | 
				
			||||||
            <RowDefinition Height="*"/>
 | 
					            <RowDefinition Height="*"/>
 | 
				
			||||||
        </Grid.RowDefinitions>
 | 
					        </Grid.RowDefinitions>
 | 
				
			||||||
        <Grid.ColumnDefinitions>
 | 
					        <Grid.ColumnDefinitions>
 | 
				
			||||||
            <ColumnDefinition Width="330"/>
 | 
					            <ColumnDefinition Width="300"/>
 | 
				
			||||||
            <ColumnDefinition Width="1*"/>
 | 
					            <ColumnDefinition Width="1*"/>
 | 
				
			||||||
            <ColumnDefinition Width="200"/>
 | 
					            <ColumnDefinition Width="200"/>
 | 
				
			||||||
        </Grid.ColumnDefinitions>
 | 
					        </Grid.ColumnDefinitions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Grid Grid.Row="1" Margin="5,0,0,0">
 | 
					        <Grid Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3">
 | 
				
			||||||
            <Grid.RowDefinitions>
 | 
					 | 
				
			||||||
                <RowDefinition Height="*"/>
 | 
					 | 
				
			||||||
                <RowDefinition Height="42"/>
 | 
					 | 
				
			||||||
            </Grid.RowDefinitions>
 | 
					 | 
				
			||||||
            <Grid.ColumnDefinitions>
 | 
					            <Grid.ColumnDefinitions>
 | 
				
			||||||
                <ColumnDefinition Width="*"/>
 | 
					                <ColumnDefinition Width="560"/>
 | 
				
			||||||
                <ColumnDefinition Width="*"/>
 | 
					                <ColumnDefinition Width="100"/>
 | 
				
			||||||
                <ColumnDefinition Width="*"/>
 | 
					 | 
				
			||||||
            </Grid.ColumnDefinitions>
 | 
					            </Grid.ColumnDefinitions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <DataGrid x:Name="GraphList" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Single"
 | 
					            <Label Content="Für:" Margin="10,-2,0,0" FontSize="14" Grid.Column="0" VerticalAlignment="Center"/>
 | 
				
			||||||
                      CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False"
 | 
					            <xctk:CheckComboBox x:Name="VaributeInput" Margin="50,0,0,0" Grid.Column="0" Width="500" Height="25" HorizontalAlignment="Left"
 | 
				
			||||||
                      SelectionChanged="GraphList_SelectionChanged"
 | 
					                                IsSelectAllActive="True" SelectAllContent="Alle Sorten" Delimiter=", " AllItemsSelectedContent="Alle Sorten"
 | 
				
			||||||
                      Margin="5,15,5,0" Grid.Row="0" FontSize="14" Grid.ColumnSpan="3">
 | 
					                                IsEnabled="False" ItemSelectionChanged="VaributeInput_Changed">
 | 
				
			||||||
                <DataGrid.Columns>
 | 
					                <xctk:CheckComboBox.ItemTemplate>
 | 
				
			||||||
                    <DataGridTextColumn Header="Nr."    Binding="{Binding Num}"       Width="40">
 | 
					                    <DataTemplate>
 | 
				
			||||||
                        <DataGridTextColumn.ElementStyle>
 | 
					                        <StackPanel Orientation="Horizontal">
 | 
				
			||||||
                            <Style>
 | 
					                            <TextBlock Text="{Binding Variety.Name}" Width="150"/>
 | 
				
			||||||
                                <Setter Property="TextBlock.TextWrapping" Value="Wrap" />
 | 
					                            <TextBlock Text="{Binding Variety.Type}" Width="30"/>
 | 
				
			||||||
                            </Style>
 | 
					                            <TextBlock Text="{Binding Attribute.Name}" Width="120"/>
 | 
				
			||||||
                        </DataGridTextColumn.ElementStyle>
 | 
					                            <TextBlock Text="{Binding AssignedGraphId}" Width="30"/>
 | 
				
			||||||
                    </DataGridTextColumn>
 | 
					                            <TextBlock Text="{Binding AssignedAbgewGraphId}" Width="30"/>
 | 
				
			||||||
                    <DataGridTextColumn Header="Typ"    Binding="{Binding Type}"       Width="40"/>
 | 
					                        </StackPanel>
 | 
				
			||||||
                    <DataGridTextColumn Header="Angewandte Verträge" Binding="{Binding Contracts}" Width="4*"/>
 | 
					                    </DataTemplate>
 | 
				
			||||||
                </DataGrid.Columns>
 | 
					                </xctk:CheckComboBox.ItemTemplate>
 | 
				
			||||||
            </DataGrid>
 | 
					            </xctk:CheckComboBox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <Button x:Name="NewButton" Content="Neu"
 | 
					            <CheckBox x:Name="AbgewertetInput" Content="Abgewertet" IsEnabled="False" Checked="AbgewertetInput_Changed" Unchecked="AbgewertetInput_Changed"
 | 
				
			||||||
                    HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="5,5,2.5,10" Grid.Column="0" Grid.Row="2"
 | 
					                              VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,0" Grid.Column="1"/>
 | 
				
			||||||
                    Click="NewButton_Click"/>
 | 
					        </Grid>
 | 
				
			||||||
            <Button x:Name="EditButton" Content="Bearbeiten" IsEnabled="False"
 | 
					
 | 
				
			||||||
                    HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,2.5,10" Grid.Column="1" Grid.Row="2"
 | 
					        <ListBox x:Name="GraphList" Margin="10,10,35,42" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" SelectionChanged="GraphList_SelectionChanged">
 | 
				
			||||||
                    Click="EditButton_Click"/>
 | 
					            <ListBox.ItemTemplate>
 | 
				
			||||||
            <Button x:Name="DeleteButton" Content="Löschen" IsEnabled="False"
 | 
					                <DataTemplate>
 | 
				
			||||||
                    HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,5,10" Grid.Column="2" Grid.Row="2"
 | 
					                    <StackPanel Orientation="Horizontal">
 | 
				
			||||||
 | 
					                        <TextBlock Text="{Binding Id}" Width="30"/>
 | 
				
			||||||
 | 
					                        <TextBlock Text="{Binding VaributeStringSimple}" ToolTip="{Binding VaributeString}"/>
 | 
				
			||||||
 | 
					                    </StackPanel>
 | 
				
			||||||
 | 
					                </DataTemplate>
 | 
				
			||||||
 | 
					            </ListBox.ItemTemplate>
 | 
				
			||||||
 | 
					        </ListBox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Button x:Name="SaveButton" Content="Speichern" IsEnabled="False"
 | 
				
			||||||
 | 
					            HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="10,5,35,10" Grid.Column="0" Grid.Row="2"
 | 
				
			||||||
 | 
					            Click="SaveButton_Click"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Button x:Name="AddButton" Content="" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0" ToolTip="Neue Auszahlungsvariante hinzufügen"
 | 
				
			||||||
 | 
					            VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,0,5,60" Grid.Column="0" Grid.RowSpan="2" Grid.Row="0"
 | 
				
			||||||
 | 
					            Click="AddButton_Click"/>
 | 
				
			||||||
 | 
					        <Button x:Name="CopyButton" Content="" FontFamily="Segoe MDL2 Assets" FontSize="12" Padding="0,0,0,0" IsEnabled="False" ToolTip="Ausgewählte Auszahlungsvariante duplizieren"
 | 
				
			||||||
 | 
					            VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="0,0,5,0" Grid.Column="0" Grid.RowSpan="2" Grid.Row="0"
 | 
				
			||||||
 | 
					            Click="CopyButton_Click"/>
 | 
				
			||||||
 | 
					        <Button x:Name="DeleteButton" Content="" FontFamily="Segoe MDL2 Assets" FontSize="11" Padding="0,1.5,0,0" IsEnabled="False" ToolTip="Ausgewählte Auszahlungsvariante löschen"
 | 
				
			||||||
 | 
					                VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,60,5,0" Grid.Column="0" Grid.RowSpan="2" Grid.Row="0"
 | 
				
			||||||
                Click="DeleteButton_Click"/>
 | 
					                Click="DeleteButton_Click"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <Button x:Name="SaveButton" Content="Speichern" IsEnabled="False" Visibility="Hidden"
 | 
					        <Grid Grid.Row="1" Grid.Column="1" Margin="0,0,10,10">
 | 
				
			||||||
                    HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="5,5,2.5,10" Grid.Column="0" Grid.Row="2"
 | 
					            <ScottPlot:WpfPlot x:Name="OechslePricePlot" IsEnabled="False"
 | 
				
			||||||
                    Click="SaveButton_Click"/>
 | 
					                               MouseWheel="OechslePricePlot_MouseWheel" MouseMove="OechslePricePlot_MouseMove" MouseDown="OechslePricePlot_MouseDown"/>
 | 
				
			||||||
            <Button x:Name="ResetButton" Content="Zurücksetzen" IsEnabled="False" Visibility="Hidden"
 | 
					 | 
				
			||||||
                    HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,2.5,10" Grid.Column="1" Grid.Row="2"
 | 
					 | 
				
			||||||
                    Click="ResetButton_Click"/>
 | 
					 | 
				
			||||||
            <Button x:Name="CancelButton" Content="Abbrechen" IsEnabled="False" Visibility="Hidden" IsCancel="True"
 | 
					 | 
				
			||||||
                    HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="2.5,5,5,10" Grid.Column="2" Grid.Row="2"
 | 
					 | 
				
			||||||
                    Click="CancelButton_Click"/>
 | 
					 | 
				
			||||||
        </Grid>
 | 
					        </Grid>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Grid Grid.Row="1" Grid.Column="1">
 | 
					        <Grid Grid.Row="1" Grid.Column="2" Margin="0,0,5,36">
 | 
				
			||||||
            <ScottPlot:WpfPlot x:Name="OechslePricePlot" MouseMove="OechslePricePlot_MouseMove" MouseDown="OechslePricePlot_MouseDown" IsEnabled="False"/>
 | 
					 | 
				
			||||||
        </Grid>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <Grid Grid.Row="1" Grid.Column="2" Margin="0,0,5,0">
 | 
					 | 
				
			||||||
            <Grid.RowDefinitions>
 | 
					            <Grid.RowDefinitions>
 | 
				
			||||||
                <RowDefinition Height="120"/>
 | 
					                <RowDefinition Height="120"/>
 | 
				
			||||||
                <RowDefinition Height="120"/>
 | 
					                <RowDefinition Height="90"/>
 | 
				
			||||||
                <RowDefinition Height="210"/>
 | 
					                <RowDefinition Height="210"/>
 | 
				
			||||||
 | 
					                <RowDefinition Height="1*"/>
 | 
				
			||||||
                <RowDefinition Height="110"/>
 | 
					                <RowDefinition Height="110"/>
 | 
				
			||||||
                <RowDefinition Height="42"/>
 | 
					 | 
				
			||||||
            </Grid.RowDefinitions>
 | 
					            </Grid.RowDefinitions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <GroupBox Header="Graph" Grid.Row="0" Margin="0,5,5,5">
 | 
					            <GroupBox Header="Datenpunkt" Grid.Row="0" Margin="0,5,5,5">
 | 
				
			||||||
                <Grid>
 | 
					 | 
				
			||||||
                    <Grid.ColumnDefinitions>
 | 
					 | 
				
			||||||
                        <ColumnDefinition Width="85"/>
 | 
					 | 
				
			||||||
                        <ColumnDefinition Width="*"/>
 | 
					 | 
				
			||||||
                    </Grid.ColumnDefinitions>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Label Content="Nummer:" Margin="10,10,0,0" Grid.Column="0"/>
 | 
					 | 
				
			||||||
                    <TextBox x:Name="GraphNumberInput" Grid.Column="1" HorizontalAlignment="Left" Margin="0,10,0,0" Text="" Width="90" TextChanged="GraphNumberInput_TextChanged" LostFocus="GraphNumberInput_LostFocus"/>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Label Content="Typ:" Margin="10,45,0,0" Grid.Column="0"/>
 | 
					 | 
				
			||||||
                    <RadioButton x:Name="OechsleGraphType_Input" GroupName="GraphType" Grid.Column="1" Margin="0,45,0,0">Oechsle</RadioButton>
 | 
					 | 
				
			||||||
                    <RadioButton x:Name="KmwGraphType_Input" GroupName="GraphType" Grid.Column="1" Margin="0,60,0,0">KMW</RadioButton>
 | 
					 | 
				
			||||||
                </Grid>
 | 
					 | 
				
			||||||
            </GroupBox>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <GroupBox Header="Datenpunkt" Grid.Row="1" Margin="0,5,5,5">
 | 
					 | 
				
			||||||
                <Grid>
 | 
					                <Grid>
 | 
				
			||||||
                    <Grid.ColumnDefinitions>
 | 
					                    <Grid.ColumnDefinitions>
 | 
				
			||||||
                        <ColumnDefinition Width="85"/>
 | 
					                        <ColumnDefinition Width="85"/>
 | 
				
			||||||
@@ -138,35 +128,44 @@
 | 
				
			|||||||
                    </Grid.ColumnDefinitions>
 | 
					                    </Grid.ColumnDefinitions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <Label Content="Oechsle:" Margin="10,10,0,0" Grid.Column="0"/>
 | 
					                    <Label Content="Oechsle:" Margin="10,10,0,0" Grid.Column="0"/>
 | 
				
			||||||
                    <TextBox x:Name="OechsleInput" Grid.Column="1" HorizontalAlignment="Left" Margin="0,10,0,0" Text="" Width="90" TextChanged="OechsleInput_TextChanged" LostFocus="OechsleInput_LostFocus"/>
 | 
					                    <ctrl:UnitTextBox x:Name="OechsleInput" Unit="°Oe" TextChanged="OechsleInput_TextChanged" IsEnabled="False" LostFocus="OechsleInput_LostFocus"
 | 
				
			||||||
 | 
					                        Grid.Column="1" Width="90" Margin="0,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <Label Content="Preis pro kg:" Margin="10,40,0,0" Grid.Column="0"/>
 | 
					 | 
				
			||||||
                    <TextBox x:Name="PriceInput" Grid.Column="1" HorizontalAlignment="Left" Margin="0,40,0,0" Text="" Width="90" TextChanged="PriceInput_TextChanged" LostFocus="PriceInput_LostFocus"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label Content="Preis:" Margin="10,40,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <ctrl:UnitTextBox x:Name="PriceInput" Unit="€/kg" TextChanged="PriceInput_TextChanged" IsEnabled="False" LostFocus="PriceInput_LostFocus"
 | 
				
			||||||
 | 
					                        Grid.Column="1" Width="90" Margin="0,40,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
 | 
				
			||||||
 | 
					                </Grid>
 | 
				
			||||||
 | 
					            </GroupBox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <GroupBox Header="Gebunden Aufschlag" Grid.Row="1" Margin="0,5,5,5">
 | 
				
			||||||
 | 
					                <Grid>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <StackPanel Margin="10,10,0,0">
 | 
				
			||||||
 | 
					                        <RadioButton x:Name="GebundenTypeFixed" GroupName="GebundenType" Checked="GebundenType_Checked" IsEnabled="False">Fix</RadioButton>
 | 
				
			||||||
 | 
					                        <RadioButton x:Name="GebundenTypeGraph" GroupName="GebundenType" Checked="GebundenType_Checked" IsEnabled="False">Frei</RadioButton>
 | 
				
			||||||
 | 
					                        <RadioButton x:Name="GebundenTypeNone" GroupName="GebundenType" Checked="GebundenType_Checked" IsEnabled="False">Nein</RadioButton>
 | 
				
			||||||
 | 
					                    </StackPanel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <ctrl:UnitTextBox x:Name="GebundenFlatBonus" Unit="€/kg" TextChanged="GebundenFlatBonus_TextChanged" IsEnabled="False"
 | 
				
			||||||
 | 
					                                      Width="90" Margin="5,5,5,5" HorizontalAlignment="Right" VerticalAlignment="Top" Grid.Column="1"/>
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
            </GroupBox>
 | 
					            </GroupBox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <GroupBox Header="Aktionen" Grid.Row="2" Margin="0,5,5,5">
 | 
					            <GroupBox Header="Aktionen" Grid.Row="2" Margin="0,5,5,5">
 | 
				
			||||||
                <Grid>
 | 
					                <Grid>
 | 
				
			||||||
                    <Grid.ColumnDefinitions>
 | 
					 | 
				
			||||||
                        <ColumnDefinition Width="*"/>
 | 
					 | 
				
			||||||
                    </Grid.ColumnDefinitions>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Button x:Name="LeftFlatButton" Content="Links flach" Click="LeftFlatButton_Click" IsEnabled="False"
 | 
					                    <Button x:Name="LeftFlatButton" Content="Links flach" Click="LeftFlatButton_Click" IsEnabled="False"
 | 
				
			||||||
                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,10,10,10"/>
 | 
					                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,10,10,10"/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Button x:Name="RightFlatButton" Content="Rechts flach" Click="RightFlatButton_Click" IsEnabled="False"
 | 
					                    <Button x:Name="RightFlatButton" Content="Rechts flach" Click="RightFlatButton_Click" IsEnabled="False"
 | 
				
			||||||
                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,50,10,10"/>
 | 
					                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,45,10,10"/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Button x:Name="InterpolateButton" Content="Interpolieren" Click="InterpolateButton_Click" IsEnabled="False"
 | 
					                    <Button x:Name="InterpolateButton" Content="Interpolieren" Click="InterpolateButton_Click" IsEnabled="False"
 | 
				
			||||||
                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,90,10,10"/>
 | 
					                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,80,10,10"/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <Button x:Name="LinearIncreaseButton" Content="Linear wachsen" Click="LinearIncreaseButton_Click" IsEnabled="False"
 | 
					                    <Button x:Name="LinearIncreaseButton" Content="Linear wachsen" Click="LinearIncreaseButton_Click" IsEnabled="False"
 | 
				
			||||||
                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,130,10,10"/>
 | 
					                            HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10,115,10,10"/>
 | 
				
			||||||
                </Grid>
 | 
					                </Grid>
 | 
				
			||||||
            </GroupBox>
 | 
					            </GroupBox>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <GroupBox Header="Optionen" Grid.Row="3" Margin="0,5,5,5">
 | 
					            <GroupBox Header="Optionen" Grid.Row="4" Margin="0,5,5,5">
 | 
				
			||||||
                <Grid>
 | 
					                <Grid>
 | 
				
			||||||
                    <Grid.ColumnDefinitions>
 | 
					                    <Grid.ColumnDefinitions>
 | 
				
			||||||
                        <ColumnDefinition Width="*"/>
 | 
					                        <ColumnDefinition Width="*"/>
 | 
				
			||||||
@@ -186,4 +185,4 @@
 | 
				
			|||||||
            </GroupBox>
 | 
					            </GroupBox>
 | 
				
			||||||
        </Grid>
 | 
					        </Grid>
 | 
				
			||||||
    </Grid>
 | 
					    </Grid>
 | 
				
			||||||
</local:AdministrationWindow>
 | 
					</local:ContextWindow>
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -100,12 +100,13 @@
 | 
				
			|||||||
                        <LineBreak/>
 | 
					                        <LineBreak/>
 | 
				
			||||||
                        Filtern nach:<LineBreak/>
 | 
					                        Filtern nach:<LineBreak/>
 | 
				
			||||||
                        <Bold>Sorte</Bold>: z.B. GV, ZW, rr, sa, !gv (ausgenommen GV), ...<LineBreak/>
 | 
					                        <Bold>Sorte</Bold>: z.B. GV, ZW, rr, sa, !gv (ausgenommen GV), ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Qualitätsstufe</Bold>: z.B. QUW, kab, ldw, ...<LineBreak/>
 | 
					                        <Bold>Rot/Weiß</Bold>: z.B. r, Rot, w, weiß, ...<LineBreak/>
 | 
				
			||||||
 | 
					                        <Bold>Qualitätsstufe</Bold>: z.B. QUW, kab, !ldw (ausgenommen LDW), abgew[ertet], ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Gradation</Bold>: z.B. >73, <15, 17-18, 15-, >17,5, 62-75, ...<LineBreak/>
 | 
					                        <Bold>Gradation</Bold>: z.B. >73, <15, 17-18, 15-, >17,5, 62-75, ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Mitglied</Bold>: z.B. 1234, 987, ...<LineBreak/>
 | 
					                        <Bold>Mitglied</Bold>: z.B. 1234, 987, ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Saison</Bold>: z.B. 2020, >2015, 2017-2019, <2005, 2019-, ...<LineBreak/>
 | 
					                        <Bold>Saison</Bold>: z.B. 2020, >2015, 2017-2019, <2005, 2019-, ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Zweigstelle</Bold>: z.B. musterort, ...<LineBreak/>
 | 
					                        <Bold>Zweigstelle</Bold>: z.B. musterort, ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Attribute</Bold>: z.B. kabinett, !kabinett (alle außer kabinett), ...<LineBreak/>
 | 
					                        <Bold>Attribut</Bold>: z.B. kabinett, !kabinett (alle außer kabinett), ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Datum</Bold>: z.B. 1.9., 15.9.-10.10., -15.10.2020, ...<LineBreak/>
 | 
					                        <Bold>Datum</Bold>: z.B. 1.9., 15.9.-10.10., -15.10.2020, ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/>
 | 
					                        <Bold>Uhrzeit</Bold>: z.B. 06:00-08:00, 18:00-, ...<LineBreak/>
 | 
				
			||||||
                        <Bold>Freitext</Bold>: z.B. Lieferscheinnummern, Anmerkung, "quw" (sucht nach dem Text "quw")
 | 
					                        <Bold>Freitext</Bold>: z.B. Lieferscheinnummern, Anmerkung, "quw" (sucht nach dem Text "quw")
 | 
				
			||||||
@@ -147,21 +148,21 @@
 | 
				
			|||||||
                            </Style>
 | 
					                            </Style>
 | 
				
			||||||
                        </DataGridTextColumn.CellStyle>
 | 
					                        </DataGridTextColumn.CellStyle>
 | 
				
			||||||
                    </DataGridTextColumn>
 | 
					                    </DataGridTextColumn>
 | 
				
			||||||
                    <DataGridTextColumn Header="Sorte" Binding="{Binding SortIdString}" Width="50">
 | 
					                    <DataGridTextColumn Header="Sorte" Binding="{Binding FilteredSortIdString}" Width="50">
 | 
				
			||||||
                        <DataGridTextColumn.CellStyle>
 | 
					                        <DataGridTextColumn.CellStyle>
 | 
				
			||||||
                            <Style>
 | 
					                            <Style>
 | 
				
			||||||
                                <Setter Property="TextBlock.TextAlignment" Value="Center"/>
 | 
					                                <Setter Property="TextBlock.TextAlignment" Value="Center"/>
 | 
				
			||||||
                            </Style>
 | 
					                            </Style>
 | 
				
			||||||
                        </DataGridTextColumn.CellStyle>
 | 
					                        </DataGridTextColumn.CellStyle>
 | 
				
			||||||
                    </DataGridTextColumn>
 | 
					                    </DataGridTextColumn>
 | 
				
			||||||
                    <DataGridTextColumn Header="Gewicht" Binding="{Binding Weight, StringFormat='{}{0:N0} kg '}" Width="75">
 | 
					                    <DataGridTextColumn Header="Gewicht" Binding="{Binding FilteredWeight, StringFormat='{}{0:N0} kg '}" Width="75">
 | 
				
			||||||
                        <DataGridTextColumn.CellStyle>
 | 
					                        <DataGridTextColumn.CellStyle>
 | 
				
			||||||
                            <Style>
 | 
					                            <Style>
 | 
				
			||||||
                                <Setter Property="TextBlock.TextAlignment" Value="Right"/>
 | 
					                                <Setter Property="TextBlock.TextAlignment" Value="Right"/>
 | 
				
			||||||
                            </Style>
 | 
					                            </Style>
 | 
				
			||||||
                        </DataGridTextColumn.CellStyle>
 | 
					                        </DataGridTextColumn.CellStyle>
 | 
				
			||||||
                    </DataGridTextColumn>
 | 
					                    </DataGridTextColumn>
 | 
				
			||||||
                    <DataGridTextColumn Header="Gradation" Binding="{Binding Kmw, StringFormat='{}{0:N1}° '}" Width="50">
 | 
					                    <DataGridTextColumn Header="Gradation" Binding="{Binding FilteredKmw, StringFormat='{}{0:N1}° '}" Width="50">
 | 
				
			||||||
                        <DataGridTextColumn.CellStyle>
 | 
					                        <DataGridTextColumn.CellStyle>
 | 
				
			||||||
                            <Style>
 | 
					                            <Style>
 | 
				
			||||||
                                <Setter Property="TextBlock.TextAlignment" Value="Right"/>
 | 
					                                <Setter Property="TextBlock.TextAlignment" Value="Right"/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
using Elwig.Documents;
 | 
					using Elwig.Documents;
 | 
				
			||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Elwig.Helpers.Export;
 | 
					using Elwig.Helpers.Export;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Weighing;
 | 
				
			||||||
using Elwig.Models.Entities;
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
using LinqKit;
 | 
					using LinqKit;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
@@ -10,6 +11,7 @@ using System;
 | 
				
			|||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Globalization;
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Linq.Expressions;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using System.Windows;
 | 
					using System.Windows;
 | 
				
			||||||
using System.Windows.Controls;
 | 
					using System.Windows.Controls;
 | 
				
			||||||
@@ -27,7 +29,10 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        private Member? Member = null;
 | 
					        private Member? Member = null;
 | 
				
			||||||
        private readonly DispatcherTimer Timer;
 | 
					        private readonly DispatcherTimer Timer;
 | 
				
			||||||
        private List<string> TextFilter = [];
 | 
					        private List<string> TextFilter = [];
 | 
				
			||||||
        private readonly RoutedCommand CtrlF = new();
 | 
					
 | 
				
			||||||
 | 
					        private readonly RoutedCommand CtrlF = new("CtrlF", typeof(DeliveryAdminWindow), [new KeyGesture(Key.F, ModifierKeys.Control)]);
 | 
				
			||||||
 | 
					        private readonly RoutedCommand CtrlP = new("CtrlP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control)]);
 | 
				
			||||||
 | 
					        private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private string? LastScaleError = null;
 | 
					        private string? LastScaleError = null;
 | 
				
			||||||
        private string? ManualWeighingReason = null;
 | 
					        private string? ManualWeighingReason = null;
 | 
				
			||||||
@@ -37,8 +42,9 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public DeliveryAdminWindow(bool receipt = false) {
 | 
					        public DeliveryAdminWindow(bool receipt = false) {
 | 
				
			||||||
            InitializeComponent();
 | 
					            InitializeComponent();
 | 
				
			||||||
            CtrlF.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control));
 | 
					 | 
				
			||||||
            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
 | 
					            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
 | 
				
			||||||
 | 
					            CommandBindings.Add(new CommandBinding(CtrlP, Menu_Print_ShowDeliveryNote_Click));
 | 
				
			||||||
 | 
					            CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_Print_PrintDeliveryNote_Click));
 | 
				
			||||||
            RequiredInputs = [
 | 
					            RequiredInputs = [
 | 
				
			||||||
                MgNrInput, MemberInput,
 | 
					                MgNrInput, MemberInput,
 | 
				
			||||||
                LsNrInput, DateInput, BranchInput,
 | 
					                LsNrInput, DateInput, BranchInput,
 | 
				
			||||||
@@ -70,17 +76,20 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            if (IsReceipt) {
 | 
					            if (IsReceipt) {
 | 
				
			||||||
                Title = $"Übernahme - {App.BranchName} - Elwig";
 | 
					                Title = $"Übernahme - {App.BranchName} - Elwig";
 | 
				
			||||||
                TodayOnlyInput.IsChecked = true;
 | 
					                TodayOnlyInput.IsChecked = true;
 | 
				
			||||||
                var n = App.Scales.Count;
 | 
					                var n = App.CommandScales.Count;
 | 
				
			||||||
                if (n < 1) WeighingAButton.Visibility = Visibility.Hidden;
 | 
					                if (n < 1) WeighingAButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
                if (n < 2) WeighingBButton.Visibility = Visibility.Hidden;
 | 
					                if (n < 2) WeighingBButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
                if (n < 3) WeighingCButton.Visibility = Visibility.Hidden;
 | 
					                if (n < 3) WeighingCButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
                if (n < 4) WeighingDButton.Visibility = Visibility.Hidden;
 | 
					                if (n < 4) WeighingDButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
                if (n == 1) WeighingAButton.Content = "Wiegen";
 | 
					                if (n == 1) WeighingAButton.Content = "Wiegen";
 | 
				
			||||||
                if (n > 1) WeighingAButton.Content = $"Wiegen {App.Scales[0].ScaleId}";
 | 
					                if (n > 1) WeighingAButton.Content = $"Wiegen {App.CommandScales[0].ScaleId}";
 | 
				
			||||||
                if (n >= 2) WeighingBButton.Content = $"Wiegen {App.Scales[1].ScaleId}";
 | 
					                if (n >= 2) WeighingBButton.Content = $"Wiegen {App.CommandScales[1].ScaleId}";
 | 
				
			||||||
                if (n >= 3) WeighingCButton.Content = $"Wiegen {App.Scales[2].ScaleId}";
 | 
					                if (n >= 3) WeighingCButton.Content = $"Wiegen {App.CommandScales[2].ScaleId}";
 | 
				
			||||||
                if (n >= 4) WeighingDButton.Content = $"Wiegen {App.Scales[3].ScaleId}";
 | 
					                if (n >= 4) WeighingDButton.Content = $"Wiegen {App.CommandScales[3].ScaleId}";
 | 
				
			||||||
                WeighingManualButton.Margin = new Thickness(10, 10 + n * 32, 10, 10);
 | 
					                WeighingManualButton.Margin = new Thickness(10, 10 + n * 32, 10, 10);
 | 
				
			||||||
 | 
					                foreach (var s in App.EventScales) {
 | 
				
			||||||
 | 
					                    s.WeighingEvent += Scale_Weighing;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                WeighingManualButton.Visibility = Visibility.Hidden;
 | 
					                WeighingManualButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
                WeighingAButton.Visibility = Visibility.Hidden;
 | 
					                WeighingAButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
@@ -168,7 +177,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async void Menu_Print_DeliveryJournal_ShowFilter_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void Menu_Print_DeliveryJournal_ShowFilter_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
					            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
            var (f, _, d, _) = await GetFilters();
 | 
					            var (f, _, d, _, _) = await GetFilters();
 | 
				
			||||||
            var doc = new DeliveryJournal(string.Join(" / ", f), d);
 | 
					            var doc = new DeliveryJournal(string.Join(" / ", f), d);
 | 
				
			||||||
            await doc.Generate();
 | 
					            await doc.Generate();
 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
@@ -177,7 +186,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async void Menu_Print_DeliveryJournal_PrintFilter_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void Menu_Print_DeliveryJournal_PrintFilter_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
					            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
            var (f, _, d, _) = await GetFilters();
 | 
					            var (f, _, d, _, _) = await GetFilters();
 | 
				
			||||||
            var doc = new DeliveryJournal(string.Join(" / ", f), d);
 | 
					            var doc = new DeliveryJournal(string.Join(" / ", f), d);
 | 
				
			||||||
            await doc.Generate();
 | 
					            await doc.Generate();
 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
@@ -212,7 +221,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void InitialDefaultInputs() {
 | 
					        private void InitialDefaultInputs() {
 | 
				
			||||||
            if (App.Client.HasRebler(BranchInput.SelectedValue as Branch)) {
 | 
					            if (App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
 | 
				
			||||||
                GerebeltGewogenInput.IsEnabled = false;
 | 
					                GerebeltGewogenInput.IsEnabled = false;
 | 
				
			||||||
                SetDefaultValue(GerebeltGewogenInput, true);
 | 
					                SetDefaultValue(GerebeltGewogenInput, true);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@@ -220,7 +229,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                UnsetDefaultValue(GerebeltGewogenInput);
 | 
					                UnsetDefaultValue(GerebeltGewogenInput);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (App.Client.HasKisten(BranchInput.SelectedValue as Branch)) {
 | 
					            if (!App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
 | 
				
			||||||
                LesewagenInput.IsEnabled = false;
 | 
					                LesewagenInput.IsEnabled = false;
 | 
				
			||||||
                SetDefaultValue(LesewagenInput, false);
 | 
					                SetDefaultValue(LesewagenInput, false);
 | 
				
			||||||
                HandPickedInput.IsThreeState = false;
 | 
					                HandPickedInput.IsThreeState = false;
 | 
				
			||||||
@@ -250,9 +259,9 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            ClearOriginalValues();
 | 
					            ClearOriginalValues();
 | 
				
			||||||
            ClearDefaultValues();
 | 
					            ClearDefaultValues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            GerebeltGewogenInput.IsChecked = App.Client.HasRebler(BranchInput.SelectedValue as Branch);
 | 
					            GerebeltGewogenInput.IsChecked = App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch);
 | 
				
			||||||
            LesewagenInput.IsChecked = false;
 | 
					            LesewagenInput.IsChecked = false;
 | 
				
			||||||
            HandPickedInput.IsChecked = App.Client.HasKisten(BranchInput.SelectedValue as Branch) ? true : null;
 | 
					            HandPickedInput.IsChecked = !App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch) ? true : null;
 | 
				
			||||||
            GebundenInput.IsChecked = null;
 | 
					            GebundenInput.IsChecked = null;
 | 
				
			||||||
            InitialDefaultInputs();
 | 
					            InitialDefaultInputs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -296,7 +305,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            await RefreshDeliveryListQuery();
 | 
					            await RefreshDeliveryListQuery();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, List<string>)> GetFilters() {
 | 
					        private async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, Predicate<DeliveryPart>, List<string>)> GetFilters() {
 | 
				
			||||||
            List<string> filterNames = [];
 | 
					            List<string> filterNames = [];
 | 
				
			||||||
            IQueryable<Delivery> deliveryQuery = Context.Deliveries;
 | 
					            IQueryable<Delivery> deliveryQuery = Context.Deliveries;
 | 
				
			||||||
            if (IsReceipt && App.BranchNum > 1) {
 | 
					            if (IsReceipt && App.BranchNum > 1) {
 | 
				
			||||||
@@ -316,16 +325,13 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                deliveryQuery = deliveryQuery.Where(d => d.Year == SeasonInput.Value);
 | 
					                deliveryQuery = deliveryQuery.Where(d => d.Year == SeasonInput.Value);
 | 
				
			||||||
                filterNames.Add(SeasonInput.Value.ToString() ?? "");
 | 
					                filterNames.Add(SeasonInput.Value.ToString() ?? "");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            IQueryable<DeliveryPart> dpq = deliveryQuery
 | 
					
 | 
				
			||||||
                .SelectMany(d => d.Parts)
 | 
					            Expression<Func<DeliveryPart, bool>> prd = p => true;
 | 
				
			||||||
                .OrderBy(p => p.Delivery.DateString)
 | 
					 | 
				
			||||||
                .ThenBy(p => p.Delivery.TimeString)
 | 
					 | 
				
			||||||
                .ThenBy(p => p.Delivery.LsNr)
 | 
					 | 
				
			||||||
                .ThenBy(p => p.DPNr);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filterVar = new List<string>();
 | 
					            var filterVar = new List<string>();
 | 
				
			||||||
            var filterNotVar = new List<string>();
 | 
					            var filterNotVar = new List<string>();
 | 
				
			||||||
            var filterQual = new List<string>();
 | 
					            var filterQual = new List<string>();
 | 
				
			||||||
 | 
					            var filterNotQual = new List<string>();
 | 
				
			||||||
            var filterMgNr = new List<int>();
 | 
					            var filterMgNr = new List<int>();
 | 
				
			||||||
            var filterZwst = new List<string>();
 | 
					            var filterZwst = new List<string>();
 | 
				
			||||||
            var filterAttr = new List<string>();
 | 
					            var filterAttr = new List<string>();
 | 
				
			||||||
@@ -346,7 +352,15 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                for (int i = 0; i < filter.Count; i++) {
 | 
					                for (int i = 0; i < filter.Count; i++) {
 | 
				
			||||||
                    var e = filter[i];
 | 
					                    var e = filter[i];
 | 
				
			||||||
                    if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
 | 
					                    if (e.ToLower() is "r" or "rot") {
 | 
				
			||||||
 | 
					                        filterVar.AddRange(var.Values.Where(v => v.IsRed).Select(v => v.SortId));
 | 
				
			||||||
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
 | 
					                        filterNames.Add("Rotweinsorten");
 | 
				
			||||||
 | 
					                    } else if (e.ToLower() is "w" or "weiß" or "weiss") {
 | 
				
			||||||
 | 
					                        filterVar.AddRange(var.Values.Where(v => v.IsWhite).Select(v => v.SortId));
 | 
				
			||||||
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
 | 
					                        filterNames.Add("Weißweinsorten");
 | 
				
			||||||
 | 
					                    } else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
 | 
				
			||||||
                        filterVar.Add(e.ToUpper());
 | 
					                        filterVar.Add(e.ToUpper());
 | 
				
			||||||
                        filter.RemoveAt(i--);
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
                        filterNames.Add(var[e.ToUpper()].Name);
 | 
					                        filterNames.Add(var[e.ToUpper()].Name);
 | 
				
			||||||
@@ -355,9 +369,23 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                        filter.RemoveAt(i--);
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
                        filterNames.Add("außer " + var[e[1..].ToUpper()].Name);
 | 
					                        filterNames.Add("außer " + var[e[1..].ToUpper()].Name);
 | 
				
			||||||
                    } else if (e.Length == 3 && qual.ContainsKey(e.ToUpper())) {
 | 
					                    } else if (e.Length == 3 && qual.ContainsKey(e.ToUpper())) {
 | 
				
			||||||
                        filterQual.Add(e.ToUpper());
 | 
					                        var qualId = e.ToUpper();
 | 
				
			||||||
 | 
					                        filterQual.Add(qualId);
 | 
				
			||||||
                        filter.RemoveAt(i--);
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
                        filterNames.Add(qual[e.ToUpper()].Name);
 | 
					                        filterNames.Add(qualId == "WEI" ? "abgewertet" : qual[e.ToUpper()].Name);
 | 
				
			||||||
 | 
					                    } else if (e[0] == '!' && qual.ContainsKey(e[1..].ToUpper())) {
 | 
				
			||||||
 | 
					                        var qualId = e[1..].ToUpper();
 | 
				
			||||||
 | 
					                        filterNotQual.Add(qualId);
 | 
				
			||||||
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
 | 
					                        filterNames.Add(qualId == "WEI" ? "nicht abgewertet" : "außer " + qual[e[1..].ToUpper()].Name);
 | 
				
			||||||
 | 
					                    } else if (e.Length >= 5 && e.Length <= 10 && "abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
 | 
				
			||||||
 | 
					                        filterQual.Add("WEI");
 | 
				
			||||||
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
 | 
					                        filterNames.Add("abgewertet");
 | 
				
			||||||
 | 
					                    } else if (e.Length >= 6 && e.Length <= 11 && "!abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
 | 
				
			||||||
 | 
					                        filterNotQual.Add("WEI");
 | 
				
			||||||
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
 | 
					                        filterNames.Add("nicht abgewertet");
 | 
				
			||||||
                    } else if (e.All(char.IsAsciiDigit) && mgnr.TryGetValue(e, out var member)) {
 | 
					                    } else if (e.All(char.IsAsciiDigit) && mgnr.TryGetValue(e, out var member)) {
 | 
				
			||||||
                        filterMgNr.Add(int.Parse(e));
 | 
					                        filterMgNr.Add(int.Parse(e));
 | 
				
			||||||
                        filter.RemoveAt(i--);
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
@@ -474,31 +502,32 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (filterYearGt > 0) dpq = dpq.Where(p => p.Year >= filterYearGt);
 | 
					                if (filterYearGt > 0) prd = prd.And(p => p.Year >= filterYearGt);
 | 
				
			||||||
                if (filterYearLt > 0) dpq = dpq.Where(p => p.Year < filterYearLt);
 | 
					                if (filterYearLt > 0) prd = prd.And(p => p.Year < filterYearLt);
 | 
				
			||||||
                if (filterMgNr.Count > 0) dpq = dpq.Where(p => filterMgNr.Contains(p.Delivery.MgNr));
 | 
					                if (filterMgNr.Count > 0) prd = prd.And(p => filterMgNr.Contains(p.Delivery.MgNr));
 | 
				
			||||||
                if (filterDate.Count > 0) {
 | 
					                if (filterDate.Count > 0) {
 | 
				
			||||||
                    var pr = PredicateBuilder.New<DeliveryPart>(false);
 | 
					                    var pr = PredicateBuilder.New<DeliveryPart>(false);
 | 
				
			||||||
                    foreach (var (d1, d2) in filterDate)
 | 
					                    foreach (var (d1, d2) in filterDate)
 | 
				
			||||||
                        pr.Or(p => (d1 == null || d1.CompareTo(p.Delivery.DateString.Substring(10 - d1.Length)) <= 0) && (d2 == null || d2.CompareTo(p.Delivery.DateString.Substring(10 - d2.Length)) >= 0));
 | 
					                        pr.Or(p => (d1 == null || d1.CompareTo(p.Delivery.DateString.Substring(10 - d1.Length)) <= 0) && (d2 == null || d2.CompareTo(p.Delivery.DateString.Substring(10 - d2.Length)) >= 0));
 | 
				
			||||||
                    dpq = dpq.Where(pr);
 | 
					                    prd = prd.And(pr);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (filterTime.Count > 0) {
 | 
					                if (filterTime.Count > 0) {
 | 
				
			||||||
                    var pr = PredicateBuilder.New<DeliveryPart>(false);
 | 
					                    var pr = PredicateBuilder.New<DeliveryPart>(false);
 | 
				
			||||||
                    foreach (var (t1, t2) in filterTime)
 | 
					                    foreach (var (t1, t2) in filterTime)
 | 
				
			||||||
                        pr.Or(p => (t1 == null || t1.CompareTo(p.Delivery.TimeString) <= 0) && (t2 == null || t2.CompareTo(p.Delivery.TimeString) > 0));
 | 
					                        pr.Or(p => (t1 == null || t1.CompareTo(p.Delivery.TimeString) <= 0) && (t2 == null || t2.CompareTo(p.Delivery.TimeString) > 0));
 | 
				
			||||||
                    dpq = dpq.Where(p => p.Delivery.TimeString != null).Where(pr);
 | 
					                    prd = prd.And(p => p.Delivery.TimeString != null).And(pr);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (filterVar.Count > 0) dpq = dpq.Where(p => filterVar.Contains(p.SortId));
 | 
					                if (filterVar.Count > 0) prd = prd.And(p => filterVar.Contains(p.SortId));
 | 
				
			||||||
                if (filterNotVar.Count > 0) dpq = dpq.Where(p => !filterNotVar.Contains(p.SortId));
 | 
					                if (filterNotVar.Count > 0) prd = prd.And(p => !filterNotVar.Contains(p.SortId));
 | 
				
			||||||
                if (filterQual.Count > 0) dpq = dpq.Where(p => filterQual.Contains(p.QualId));
 | 
					                if (filterQual.Count > 0) prd = prd.And(p => filterQual.Contains(p.QualId));
 | 
				
			||||||
                if (filterZwst.Count > 0) dpq = dpq.Where(p => filterZwst.Contains(p.Delivery.ZwstId));
 | 
					                if (filterNotQual.Count > 0) prd = prd.And(p => !filterNotQual.Contains(p.QualId));
 | 
				
			||||||
                if (filterAttr.Count > 0) dpq = dpq.Where(p => p.AttrId != null && filterAttr.Contains(p.AttrId));
 | 
					                if (filterZwst.Count > 0) prd = prd.And(p => filterZwst.Contains(p.Delivery.ZwstId));
 | 
				
			||||||
                if (filterNotAttr.Count > 0) dpq = dpq.Where(p => p.AttrId == null || !filterAttr.Contains(p.AttrId));
 | 
					                if (filterAttr.Count > 0) prd = prd.And(p => p.AttrId != null && filterAttr.Contains(p.AttrId));
 | 
				
			||||||
                if (filterKmwGt > 0) dpq = dpq.Where(p => p.Kmw >= filterKmwGt);
 | 
					                if (filterNotAttr.Count > 0) prd = prd.And(p => p.AttrId == null || !filterNotAttr.Contains(p.AttrId));
 | 
				
			||||||
                if (filterKmwLt > 0) dpq = dpq.Where(p => p.Kmw < filterKmwLt);
 | 
					                if (filterKmwGt > 0) prd = prd.And(p => p.Kmw >= filterKmwGt);
 | 
				
			||||||
                if (filterOeGt > 0) dpq = dpq.Where(p => p.Kmw * (4.54 + 0.022 * p.Kmw) >= filterOeGt);
 | 
					                if (filterKmwLt > 0) prd = prd.And(p => p.Kmw < filterKmwLt);
 | 
				
			||||||
                if (filterOeLt > 0) dpq = dpq.Where(p => p.Kmw * (4.54 + 0.022 * p.Kmw) < filterOeLt);
 | 
					                if (filterOeGt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) >= filterOeGt);
 | 
				
			||||||
 | 
					                if (filterOeLt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) < filterOeLt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (filterYearGt > 0 && filterYearLt > 0) {
 | 
					                if (filterYearGt > 0 && filterYearLt > 0) {
 | 
				
			||||||
                    filterNames.Insert(0, $"{filterYearGt}–{filterYearLt - 1}");
 | 
					                    filterNames.Insert(0, $"{filterYearGt}–{filterYearLt - 1}");
 | 
				
			||||||
@@ -523,7 +552,15 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return (filterNames, dpq.Select(p => p.Delivery).Distinct().OrderBy(d => d.DateString).ThenBy(d => d.TimeString), dpq, filter);
 | 
					            IQueryable<DeliveryPart> dpq = deliveryQuery
 | 
				
			||||||
 | 
					                .SelectMany(d => d.Parts)
 | 
				
			||||||
 | 
					                .Where(prd)
 | 
				
			||||||
 | 
					                .OrderBy(p => p.Delivery.DateString)
 | 
				
			||||||
 | 
					                .ThenBy(p => p.Delivery.TimeString)
 | 
				
			||||||
 | 
					                .ThenBy(p => p.Delivery.LsNr)
 | 
				
			||||||
 | 
					                .ThenBy(p => p.DPNr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return (filterNames, dpq.Select(p => p.Delivery).Distinct().OrderBy(d => d.DateString).ThenBy(d => d.TimeString), dpq, prd.Invoke, filter);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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 AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
 | 
				
			||||||
@@ -559,7 +596,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async Task RefreshDeliveryListQuery(bool updateSort = false) {
 | 
					        private async Task RefreshDeliveryListQuery(bool updateSort = false) {
 | 
				
			||||||
            var (_, deliveryQuery, deliveryPartsQuery, filter) = await GetFilters();
 | 
					            var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await GetFilters();
 | 
				
			||||||
            var deliveries = await deliveryQuery.ToListAsync();
 | 
					            var deliveries = await deliveryQuery.ToListAsync();
 | 
				
			||||||
            deliveries.Reverse();
 | 
					            deliveries.Reverse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -575,8 +612,10 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                    .ToList();
 | 
					                    .ToList();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            deliveries.ForEach(d => { d.PartFilter = predicate; });
 | 
				
			||||||
            ControlUtils.RenewItemsSource(DeliveryList, deliveries, d => ((d as Delivery)?.Year, (d as Delivery)?.DId),
 | 
					            ControlUtils.RenewItemsSource(DeliveryList, deliveries, d => ((d as Delivery)?.Year, (d as Delivery)?.DId),
 | 
				
			||||||
                DeliveryList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
 | 
					                DeliveryList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
 | 
				
			||||||
 | 
					            await RefreshDeliveryParts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList();
 | 
					            var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList();
 | 
				
			||||||
            StatusMembers.Text = $"Mitglieder: {members.Count}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : "");
 | 
					            StatusMembers.Text = $"Mitglieder: {members.Count}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : "");
 | 
				
			||||||
@@ -708,7 +747,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            Menu_Export_Bki.Items.Clear();
 | 
					            Menu_Export_Bki.Items.Clear();
 | 
				
			||||||
            foreach (var s in await Context.Seasons.OrderByDescending(s => s.Year).ToListAsync()) {
 | 
					            foreach (var s in await Context.Seasons.OrderByDescending(s => s.Year).ToListAsync()) {
 | 
				
			||||||
                var i = new MenuItem {
 | 
					                var i = new MenuItem {
 | 
				
			||||||
                    Header = $"Season {s.Year}",
 | 
					                    Header = $"Saison {s.Year}",
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                i.Click += Menu_Export_Bki_Click;
 | 
					                i.Click += Menu_Export_Bki_Click;
 | 
				
			||||||
                Menu_Export_Bki.Items.Add(i);
 | 
					                Menu_Export_Bki.Items.Add(i);
 | 
				
			||||||
@@ -746,7 +785,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        private async Task RefreshDeliveryParts() {
 | 
					        private async Task RefreshDeliveryParts() {
 | 
				
			||||||
            if (DeliveryList.SelectedItem is Delivery d) {
 | 
					            if (DeliveryList.SelectedItem is Delivery d) {
 | 
				
			||||||
                ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == d.Year).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
 | 
					                ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == d.Year).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
 | 
				
			||||||
                ControlUtils.RenewItemsSource(DeliveryPartList, d.Parts.OrderBy(p => p.DPNr).ToList(), i => ((i as DeliveryPart)?.Year, (i as DeliveryPart)?.DId, (i as DeliveryPart)?.DPNr), DeliveryPartList_SelectionChanged, ControlUtils.RenewSourceDefault.First);
 | 
					                ControlUtils.RenewItemsSource(DeliveryPartList, d.FilteredParts.OrderBy(p => p.DPNr).ToList(), i => ((i as DeliveryPart)?.Year, (i as DeliveryPart)?.DId, (i as DeliveryPart)?.DPNr), DeliveryPartList_SelectionChanged, ControlUtils.RenewSourceDefault.First);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == Utils.CurrentLastSeason).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
 | 
					                ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == Utils.CurrentLastSeason).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
 | 
				
			||||||
                DeliveryPartList.ItemsSource = null;
 | 
					                DeliveryPartList.ItemsSource = null;
 | 
				
			||||||
@@ -827,7 +866,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            var originalMemberKgNr = d?.Member?.DefaultKgNr;
 | 
					            var originalMemberKgNr = d?.Member?.DefaultKgNr;
 | 
				
			||||||
            if (d == null) {
 | 
					            if (d == null) {
 | 
				
			||||||
                d = Context.CreateProxy<Delivery>();
 | 
					                d = Context.CreateProxy<Delivery>();
 | 
				
			||||||
                year = Utils.CurrentNextSeason;
 | 
					                year = Utils.CurrentYear;
 | 
				
			||||||
                did = await Context.NextDId(year);
 | 
					                did = await Context.NextDId(year);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                year = d.Year;
 | 
					                year = d.Year;
 | 
				
			||||||
@@ -875,7 +914,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            p.Acid = (AcidInput.Text == "") ? null : double.Parse(AcidInput.Text);
 | 
					            p.Acid = (AcidInput.Text == "") ? null : double.Parse(AcidInput.Text);
 | 
				
			||||||
            p.Comment = (PartCommentInput.Text == "") ? null : PartCommentInput.Text;
 | 
					            p.Comment = (PartCommentInput.Text == "") ? null : PartCommentInput.Text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            p.Weight = int.Parse(WeightInput.Text.Replace("\u202f", ""));
 | 
					            p.Weight = int.Parse(WeightInput.Text.Replace(Utils.GroupSeparator, ""));
 | 
				
			||||||
            p.ManualWeighing = ManualWeighingInput.IsChecked ?? false;
 | 
					            p.ManualWeighing = ManualWeighingInput.IsChecked ?? false;
 | 
				
			||||||
            p.ScaleId = ScaleId;
 | 
					            p.ScaleId = ScaleId;
 | 
				
			||||||
            p.WeighingId = WeighingId;
 | 
					            p.WeighingId = WeighingId;
 | 
				
			||||||
@@ -931,34 +970,43 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            FinishButton.IsEnabled = false;
 | 
					            FinishButton.IsEnabled = false;
 | 
				
			||||||
            NewDeliveryPartButton.IsEnabled = false;
 | 
					            NewDeliveryPartButton.IsEnabled = false;
 | 
				
			||||||
            CancelCreatingButton.IsEnabled = false;
 | 
					            CancelCreatingButton.IsEnabled = false;
 | 
				
			||||||
 | 
					            var s = App.CommandScales[index];
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                var s = App.Scales[index];
 | 
					 | 
				
			||||||
                var res = await s.Weigh();
 | 
					                var res = await s.Weigh();
 | 
				
			||||||
 | 
					                OnWeighingResult(s, res);
 | 
				
			||||||
 | 
					            } catch (Exception ex) {
 | 
				
			||||||
 | 
					                LastScaleError = ex.Message.Split(": ")[^1];
 | 
				
			||||||
 | 
					                OnWeighingResult(s, new() { Weight = 0 });
 | 
				
			||||||
 | 
					                MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{ex.Message}", "Waagenfehler",
 | 
				
			||||||
 | 
					                       MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ManualWeighingReason = null;
 | 
				
			||||||
 | 
					            ManualWeighingInput.IsChecked = false;
 | 
				
			||||||
 | 
					            EnableWeighingButtons();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void OnWeighingResult(IScale scale, WeighingResult res) {
 | 
				
			||||||
            if ((res.Weight ?? 0) > 0 && res.FullWeighingId != null) {
 | 
					            if ((res.Weight ?? 0) > 0 && res.FullWeighingId != null) {
 | 
				
			||||||
                WeightInput.Text = $"{res.Weight:N0}";
 | 
					                WeightInput.Text = $"{res.Weight:N0}";
 | 
				
			||||||
                    ScaleId = s.ScaleId;
 | 
					                ScaleId = scale.ScaleId;
 | 
				
			||||||
                WeighingId = res.FullWeighingId;
 | 
					                WeighingId = res.FullWeighingId;
 | 
				
			||||||
 | 
					                ManualWeighingReason = null;
 | 
				
			||||||
 | 
					                ManualWeighingInput.IsChecked = false;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                WeightInput.Text = "";
 | 
					                WeightInput.Text = "";
 | 
				
			||||||
                ScaleId = null;
 | 
					                ScaleId = null;
 | 
				
			||||||
                WeighingId = null;
 | 
					                WeighingId = null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            LastScaleError = null;
 | 
					            LastScaleError = null;
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            TextBox_TextChanged(WeightInput, null);
 | 
				
			||||||
                LastScaleError = e.Message.Split(": ")[^1];
 | 
					 | 
				
			||||||
                WeightInput.Text = "";
 | 
					 | 
				
			||||||
                ScaleId = null;
 | 
					 | 
				
			||||||
                WeighingId = null;
 | 
					 | 
				
			||||||
                MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{e.Message}", "Waagenfehler",
 | 
					 | 
				
			||||||
                       MessageBoxButton.OK, MessageBoxImage.Error);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            ManualWeighingReason = null;
 | 
					 | 
				
			||||||
            ManualWeighingInput.IsChecked = false;
 | 
					 | 
				
			||||||
            base.TextBox_TextChanged(WeightInput, null);
 | 
					 | 
				
			||||||
            EnableWeighingButtons();
 | 
					 | 
				
			||||||
            UpdateButtons();
 | 
					            UpdateButtons();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void Scale_Weighing(object sender, WeighingEventArgs evt) {
 | 
				
			||||||
 | 
					            if (sender is not IScale scale) return;
 | 
				
			||||||
 | 
					            App.MainDispatcher.BeginInvoke(() => OnWeighingResult(scale, evt.Result));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void SearchInput_TextChanged(object sender, RoutedEventArgs evt) {
 | 
					        private async void SearchInput_TextChanged(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            TextFilter = SearchInput.Text.ToLower().Split(" ").ToList().FindAll(e => e.Length > 0);
 | 
					            TextFilter = SearchInput.Text.ToLower().Split(" ").ToList().FindAll(e => e.Length > 0);
 | 
				
			||||||
            await RefreshDeliveryListQuery(true);
 | 
					            await RefreshDeliveryListQuery(true);
 | 
				
			||||||
@@ -1045,8 +1093,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private void EmptyScale() {
 | 
					        private void EmptyScale() {
 | 
				
			||||||
            var scale = App.Scales.Where(s => s.ScaleId == ScaleId).FirstOrDefault();
 | 
					            var scale = App.Scales.Where(s => s.ScaleId == ScaleId).FirstOrDefault();
 | 
				
			||||||
            if (scale == null) return;
 | 
					            if (scale is not ICommandScale cs) return;
 | 
				
			||||||
            scale.Empty();
 | 
					            cs.Empty();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void NewDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void NewDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
@@ -1120,7 +1168,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                // switch to last delivery part
 | 
					                // switch to last delivery part
 | 
				
			||||||
                DeliveryPartList.IsEnabled = true;
 | 
					                DeliveryPartList.IsEnabled = true;
 | 
				
			||||||
                DeliveryPartList.SelectedItem = d.Parts.Last();
 | 
					                DeliveryPartList.SelectedItem = d.FilteredParts.Last();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1474,11 +1522,11 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private void EnableWeighingButtons() {
 | 
					        private void EnableWeighingButtons() {
 | 
				
			||||||
            WeighingManualButton.IsEnabled = true;
 | 
					            WeighingManualButton.IsEnabled = true;
 | 
				
			||||||
            var n = App.Scales.Count;
 | 
					            var n = App.CommandScales.Count;
 | 
				
			||||||
            WeighingAButton.IsEnabled = n > 0 && App.Scales[0].IsReady;
 | 
					            WeighingAButton.IsEnabled = n > 0 && App.CommandScales[0].IsReady;
 | 
				
			||||||
            WeighingBButton.IsEnabled = n > 1 && App.Scales[1].IsReady;
 | 
					            WeighingBButton.IsEnabled = n > 1 && App.CommandScales[1].IsReady;
 | 
				
			||||||
            WeighingCButton.IsEnabled = n > 2 && App.Scales[2].IsReady;
 | 
					            WeighingCButton.IsEnabled = n > 2 && App.CommandScales[2].IsReady;
 | 
				
			||||||
            WeighingDButton.IsEnabled = n > 3 && App.Scales[3].IsReady;
 | 
					            WeighingDButton.IsEnabled = n > 3 && App.CommandScales[3].IsReady;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async Task UpdateLsNr() {
 | 
					        private async Task UpdateLsNr() {
 | 
				
			||||||
@@ -1694,14 +1742,14 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
 | 
					        private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (App.Client.HasKisten(BranchInput.SelectedValue as Branch)) {
 | 
					            if (!App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
 | 
				
			||||||
                HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
 | 
					                HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            CheckBox_Changed(sender, evt);
 | 
					            CheckBox_Changed(sender, evt);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
 | 
					        private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (App.Client.HasKisten(BranchInput.SelectedValue as Branch)) {
 | 
					            if (!App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
 | 
				
			||||||
                GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
 | 
					                GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            CheckBox_Changed(sender, evt);
 | 
					            CheckBox_Changed(sender, evt);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,9 +74,9 @@ namespace Elwig.Dialogs {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            IEnumerable<Member> list = await members.ToListAsync();
 | 
					            IEnumerable<Member> list = await members.ToListAsync();
 | 
				
			||||||
            var data = await DeliveryConfirmationData.ForSeason(Context.DeliveryParts, Year);
 | 
					            var data = await DeliveryConfirmationDeliveryData.ForSeason(Context.DeliveryParts, Year);
 | 
				
			||||||
            using var doc = Document.Merge(list.Select(m =>
 | 
					            using var doc = Document.Merge(list.Select(m =>
 | 
				
			||||||
                new DeliveryConfirmation(Context, Year, m, data[m.MgNr]) {
 | 
					                new DeliveryConfirmation(Context, Year, m, data.TryGetValue(m.MgNr, out var d) ? d : DeliveryConfirmationDeliveryData.CreateEmpty(Year, m)) {
 | 
				
			||||||
                    //DoubleSided = true
 | 
					                    //DoubleSided = true
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            ));
 | 
					            ));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            InitializeComponent();
 | 
					            InitializeComponent();
 | 
				
			||||||
            var v = Assembly.GetExecutingAssembly().GetName().Version;
 | 
					            var v = Assembly.GetExecutingAssembly().GetName().Version;
 | 
				
			||||||
            VersionField.Text = "Version: " + (v == null ? "?" : $"{v.Major}.{v.Minor}.{v.Build}") + $" – {App.BranchName}";
 | 
					            VersionField.Text = "Version: " + (v == null ? "?" : $"{v.Major}.{v.Minor}.{v.Build}") + $" – {App.BranchName}";
 | 
				
			||||||
 | 
					            if (App.Client.Client == null) VersionField.Text += " (Unbekannt)";
 | 
				
			||||||
            if (!App.Config.Debug) {
 | 
					            if (!App.Config.Debug) {
 | 
				
			||||||
                TestWindowButton.Visibility = Visibility.Hidden;
 | 
					                TestWindowButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
                //QueryWindowButton.Visibility = Visibility.Hidden;
 | 
					                //QueryWindowButton.Visibility = Visibility.Hidden;
 | 
				
			||||||
@@ -27,11 +28,6 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            w.Show();
 | 
					            w.Show();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void MemberListButton_Click(object sender, RoutedEventArgs evt) {
 | 
					 | 
				
			||||||
            var w = new MemberListWindow();
 | 
					 | 
				
			||||||
            w.Show();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private void ReceiptButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private void ReceiptButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            App.FocusReceipt();
 | 
					            App.FocusReceipt();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,6 @@
 | 
				
			|||||||
        x:Class="Elwig.Windows.MemberAdminWindow"
 | 
					        x:Class="Elwig.Windows.MemberAdminWindow"
 | 
				
			||||||
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 | 
					 | 
				
			||||||
        xmlns:local="clr-namespace:Elwig.Windows"
 | 
					        xmlns:local="clr-namespace:Elwig.Windows"
 | 
				
			||||||
        Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150"
 | 
					        Title="Mitglieder - Elwig" Height="700" Width="1250" MinHeight="650" MinWidth="1150"
 | 
				
			||||||
        Loaded="Window_Loaded">
 | 
					        Loaded="Window_Loaded">
 | 
				
			||||||
@@ -239,14 +237,42 @@
 | 
				
			|||||||
                        <ColumnDefinition Width="2*"/>
 | 
					                        <ColumnDefinition Width="2*"/>
 | 
				
			||||||
                    </Grid.ColumnDefinitions>
 | 
					                    </Grid.ColumnDefinitions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <Label Content="E-Mail-Adresse (1):" Margin="10,10,0,0" Grid.Column="0"/>
 | 
					                    <Label x:Name="EmailAddress1Label" Content="E-Mail-Adresse:" Margin="10,10,0,0" Grid.Column="0"/>
 | 
				
			||||||
                    <TextBox x:Name="EmailAddress1Input" Margin="0,10,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
					                    <TextBox x:Name="EmailAddress1Input" Margin="0,10,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <Label Content="E-Mail-Adresse (2):" Margin="10,40,0,0" Grid.Column="0"/>
 | 
					                    <Label x:Name="EmailAddress2Label" Content="E-Mail-Adresse:" Margin="10,40,0,0" Grid.Column="0"/>
 | 
				
			||||||
                    <TextBox x:Name="EmailAddress2Input" Margin="0,40,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
					                    <TextBox x:Name="EmailAddress2Input" Margin="0,40,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label x:Name="EmailAddress3Label" Content="E-Mail-Adresse:" Margin="10,70,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <TextBox x:Name="EmailAddress3Input" Margin="0,70,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label x:Name="EmailAddress4Label" Content="E-Mail-Adresse:" Margin="10,100,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <TextBox x:Name="EmailAddress4Input" Margin="0,100,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label x:Name="EmailAddress5Label" Content="E-Mail-Adresse:" Margin="10,130,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <TextBox x:Name="EmailAddress5Input" Margin="0,130,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label x:Name="EmailAddress6Label" Content="E-Mail-Adresse:" Margin="10,160,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <TextBox x:Name="EmailAddress6Input" Margin="0,160,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label x:Name="EmailAddress7Label" Content="E-Mail-Adresse:" Margin="10,190,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <TextBox x:Name="EmailAddress7Input" Margin="0,190,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label x:Name="EmailAddress8Label" Content="E-Mail-Adresse:" Margin="10,210,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <TextBox x:Name="EmailAddress8Input" Margin="0,210,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Label x:Name="EmailAddress9Label" Content="E-Mail-Adresse:" Margin="10,250,0,0" Grid.Column="0"/>
 | 
				
			||||||
 | 
					                    <TextBox x:Name="EmailAddress9Input" Margin="0,250,10,0" Grid.Column="1" Grid.ColumnSpan="2"
 | 
				
			||||||
 | 
					                             TextChanged="EmailAddressInput_TextChanged" LostFocus="EmailAddressInput_LostFocus"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <ComboBox x:Name="PhoneNr1TypeInput" DisplayMemberPath="Value" Margin="6,70,5,0" FontSize="12" Padding="6,4,4,4"/>
 | 
					                    <ComboBox x:Name="PhoneNr1TypeInput" DisplayMemberPath="Value" Margin="6,70,5,0" FontSize="12" Padding="6,4,4,4"/>
 | 
				
			||||||
                    <TextBox x:Name="PhoneNr1Input" Margin="0,70,5,0" Grid.Column="1"
 | 
					                    <TextBox x:Name="PhoneNr1Input" Margin="0,70,5,0" Grid.Column="1"
 | 
				
			||||||
                             TextChanged="PhoneNrInput_TextChanged" LostFocus="PhoneNrInput_LostFocus"/>
 | 
					                             TextChanged="PhoneNrInput_TextChanged" LostFocus="PhoneNrInput_LostFocus"/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,15 +17,20 @@ namespace Elwig.Windows {
 | 
				
			|||||||
    public partial class MemberAdminWindow : AdministrationWindow {
 | 
					    public partial class MemberAdminWindow : AdministrationWindow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private List<string> TextFilter = [];
 | 
					        private List<string> TextFilter = [];
 | 
				
			||||||
        private readonly RoutedCommand CtrlF = new();
 | 
					        private readonly (ComboBox Type, TextBox Number, TextBox Comment)[] PhoneNrInputs;
 | 
				
			||||||
        private readonly (ComboBox, TextBox, TextBox)[] PhoneNrInputs;
 | 
					        private readonly (Label Label, TextBox Address)[] EmailAddressInputs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly RoutedCommand CtrlF = new("CtrlF", typeof(MemberAdminWindow), [new KeyGesture(Key.F, ModifierKeys.Control)]);
 | 
				
			||||||
 | 
					        private readonly RoutedCommand CtrlP = new("CtrlP", typeof(MemberAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control)]);
 | 
				
			||||||
 | 
					        private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(MemberAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static ObservableCollection<KeyValuePair<string, string>> PhoneNrTypes { get; set; } = new(Utils.PhoneNrTypes);
 | 
					        private static ObservableCollection<KeyValuePair<string, string>> PhoneNrTypes { get; set; } = new(Utils.PhoneNrTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public MemberAdminWindow() {
 | 
					        public MemberAdminWindow() {
 | 
				
			||||||
            InitializeComponent();
 | 
					            InitializeComponent();
 | 
				
			||||||
            CtrlF.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control));
 | 
					 | 
				
			||||||
            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
 | 
					            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
 | 
				
			||||||
 | 
					            CommandBindings.Add(new CommandBinding(CtrlP, Menu_Show_MemberDataSheet_Click));
 | 
				
			||||||
 | 
					            CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_Print_MemberDataSheet_Click));
 | 
				
			||||||
            ExemptInputs = [
 | 
					            ExemptInputs = [
 | 
				
			||||||
                SearchInput, ActiveMemberInput, MemberList,
 | 
					                SearchInput, ActiveMemberInput, MemberList,
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
@@ -34,6 +39,17 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                AddressInput, PlzInput, OrtInput, BillingOrtInput,
 | 
					                AddressInput, PlzInput, OrtInput, BillingOrtInput,
 | 
				
			||||||
                BusinessSharesInput, BranchInput, DefaultKgInput
 | 
					                BusinessSharesInput, BranchInput, DefaultKgInput
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
 | 
					            EmailAddressInputs = [
 | 
				
			||||||
 | 
					                (EmailAddress1Label, EmailAddress1Input),
 | 
				
			||||||
 | 
					                (EmailAddress2Label, EmailAddress2Input),
 | 
				
			||||||
 | 
					                (EmailAddress3Label, EmailAddress3Input),
 | 
				
			||||||
 | 
					                (EmailAddress4Label, EmailAddress4Input),
 | 
				
			||||||
 | 
					                (EmailAddress5Label, EmailAddress5Input),
 | 
				
			||||||
 | 
					                (EmailAddress6Label, EmailAddress6Input),
 | 
				
			||||||
 | 
					                (EmailAddress7Label, EmailAddress7Input),
 | 
				
			||||||
 | 
					                (EmailAddress8Label, EmailAddress8Input),
 | 
				
			||||||
 | 
					                (EmailAddress9Label, EmailAddress9Input),
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
            PhoneNrInputs = [
 | 
					            PhoneNrInputs = [
 | 
				
			||||||
                (PhoneNr1TypeInput, PhoneNr1Input, PhoneNr1CommentInput),
 | 
					                (PhoneNr1TypeInput, PhoneNr1Input, PhoneNr1CommentInput),
 | 
				
			||||||
                (PhoneNr2TypeInput, PhoneNr2Input, PhoneNr2CommentInput),
 | 
					                (PhoneNr2TypeInput, PhoneNr2Input, PhoneNr2CommentInput),
 | 
				
			||||||
@@ -59,7 +75,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            Menu_Print_MemberDataSheet.IsEnabled = App.IsPrintingReady;
 | 
					            Menu_Print_MemberDataSheet.IsEnabled = App.IsPrintingReady;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ActiveMemberInput.IsChecked = true;
 | 
					            ActiveMemberInput.IsChecked = true;
 | 
				
			||||||
            UpdatePhoneNrInputVisibility();
 | 
					            UpdateContactInfoVisibility();
 | 
				
			||||||
            LockInputs();
 | 
					            LockInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -144,7 +160,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                        filter.RemoveAt(i--);
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
                    } else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) {
 | 
					                    } else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) {
 | 
				
			||||||
                        filter[i] = e[1..^1];
 | 
					                        filter[i] = e[1..^1];
 | 
				
			||||||
                    } else if (e.Length <= 2) {
 | 
					                    } else if (e.Length < 2) {
 | 
				
			||||||
                        filter.RemoveAt(i--);
 | 
					                        filter.RemoveAt(i--);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -232,27 +248,55 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private void SetPhoneNrInput(int nr, string? type, string? number, string? comment) {
 | 
					        private void SetPhoneNrInput(int nr, string? type, string? number, string? comment) {
 | 
				
			||||||
            var inputs = PhoneNrInputs[nr];
 | 
					            var inputs = PhoneNrInputs[nr];
 | 
				
			||||||
            inputs.Item1.SelectedItem = (type == null) ? null : inputs.Item1.ItemsSource.Cast<KeyValuePair<string, string>>().FirstOrDefault(p => p.Key == type);
 | 
					            inputs.Type.SelectedItem = (type == null) ? null : inputs.Type.ItemsSource.Cast<KeyValuePair<string, string>>().FirstOrDefault(p => p.Key == type);
 | 
				
			||||||
            inputs.Item2.Text = number;
 | 
					            inputs.Number.Text = number;
 | 
				
			||||||
            inputs.Item3.Text = comment;
 | 
					            inputs.Comment.Text = comment;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void SetEmailAddressInput(int nr, string? address) {
 | 
				
			||||||
 | 
					            var inputs = EmailAddressInputs[nr];
 | 
				
			||||||
 | 
					            inputs.Address.Text = address;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private (string, string, string?)? GetPhoneNrInput(int nr) {
 | 
					        private (string, string, string?)? GetPhoneNrInput(int nr) {
 | 
				
			||||||
            var inputs = PhoneNrInputs[nr];
 | 
					            var inputs = PhoneNrInputs[nr];
 | 
				
			||||||
            var number = inputs.Item2.Text;
 | 
					            var number = inputs.Number.Text;
 | 
				
			||||||
            if (string.IsNullOrEmpty(number))
 | 
					            if (string.IsNullOrEmpty(number))
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            var type = (inputs.Item1.SelectedItem as KeyValuePair<string, string>?)?.Key ?? (number.StartsWith("+43 ") && number[4] == '6' ? "mobile" : "landline");
 | 
					            var type = (inputs.Type.SelectedItem as KeyValuePair<string, string>?)?.Key ?? (number.StartsWith("+43 ") && number[4] == '6' ? "mobile" : "landline");
 | 
				
			||||||
            var comment = inputs.Item3.Text;
 | 
					            var comment = inputs.Comment.Text;
 | 
				
			||||||
            return (type, number, comment == "" ? null : comment);
 | 
					            return (type, number, comment == "" ? null : comment);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void SetPhoneNrInputVisible(int nr, bool visible) {
 | 
					        private string? GetEmailAddressInput(int nr) {
 | 
				
			||||||
 | 
					            var inputs = EmailAddressInputs[nr];
 | 
				
			||||||
 | 
					            return inputs.Address.Text == "" ? null : inputs.Address.Text;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void SetPhoneNrInputVisible(int nr, bool visible, int? position = null) {
 | 
				
			||||||
            var inputs = PhoneNrInputs[nr];
 | 
					            var inputs = PhoneNrInputs[nr];
 | 
				
			||||||
 | 
					            if (position is int p) {
 | 
				
			||||||
 | 
					                var mt = 10 + p * 30;
 | 
				
			||||||
 | 
					                inputs.Type.Margin = new(6, mt, 5, 0);
 | 
				
			||||||
 | 
					                inputs.Number.Margin = new(0, mt, 5, 0);
 | 
				
			||||||
 | 
					                inputs.Comment.Margin = new(0, mt, 10, 0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            var vis = visible ? Visibility.Visible : Visibility.Hidden;
 | 
					            var vis = visible ? Visibility.Visible : Visibility.Hidden;
 | 
				
			||||||
            inputs.Item1.Visibility = vis;
 | 
					            inputs.Type.Visibility = vis;
 | 
				
			||||||
            inputs.Item2.Visibility = vis;
 | 
					            inputs.Number.Visibility = vis;
 | 
				
			||||||
            inputs.Item3.Visibility = vis;
 | 
					            inputs.Comment.Visibility = vis;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void SetEmailAddressInputVisible(int nr, bool visible, int? position = null) {
 | 
				
			||||||
 | 
					            var inputs = EmailAddressInputs[nr];
 | 
				
			||||||
 | 
					            if (position is int p) {
 | 
				
			||||||
 | 
					                var mt = 10 + p * 30;
 | 
				
			||||||
 | 
					                inputs.Label.Margin = new(10, mt, 0, 0);
 | 
				
			||||||
 | 
					                inputs.Address.Margin = new(0, mt, 10, 0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            var vis = visible ? Visibility.Visible : Visibility.Hidden;
 | 
				
			||||||
 | 
					            inputs.Label.Visibility = vis;
 | 
				
			||||||
 | 
					            inputs.Address.Visibility = vis;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void MemberList_SelectionChanged(object sender, RoutedEventArgs evt) {
 | 
					        private void MemberList_SelectionChanged(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
@@ -270,7 +314,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            HideNewEditDeleteButtons();
 | 
					            HideNewEditDeleteButtons();
 | 
				
			||||||
            ShowSaveResetCancelButtons();
 | 
					            ShowSaveResetCancelButtons();
 | 
				
			||||||
            UnlockInputs();
 | 
					            UnlockInputs();
 | 
				
			||||||
            UpdatePhoneNrInputVisibility(true);
 | 
					            UpdateContactInfoVisibility(true);
 | 
				
			||||||
            InitInputs();
 | 
					            InitInputs();
 | 
				
			||||||
            LockSearchInputs();
 | 
					            LockSearchInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -285,7 +329,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            HideNewEditDeleteButtons();
 | 
					            HideNewEditDeleteButtons();
 | 
				
			||||||
            ShowSaveResetCancelButtons();
 | 
					            ShowSaveResetCancelButtons();
 | 
				
			||||||
            UnlockInputs();
 | 
					            UnlockInputs();
 | 
				
			||||||
            UpdatePhoneNrInputVisibility(true);
 | 
					            UpdateContactInfoVisibility(true);
 | 
				
			||||||
            LockSearchInputs();
 | 
					            LockSearchInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -311,7 +355,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            HideSaveResetCancelButtons();
 | 
					            HideSaveResetCancelButtons();
 | 
				
			||||||
            ShowNewEditDeleteButtons();
 | 
					            ShowNewEditDeleteButtons();
 | 
				
			||||||
            LockInputs();
 | 
					            LockInputs();
 | 
				
			||||||
            UpdatePhoneNrInputVisibility();
 | 
					            UpdateContactInfoVisibility();
 | 
				
			||||||
            UnlockSearchInputs();
 | 
					            UnlockSearchInputs();
 | 
				
			||||||
            FinishInputFilling();
 | 
					            FinishInputFilling();
 | 
				
			||||||
            await RefreshMemberList();
 | 
					            await RefreshMemberList();
 | 
				
			||||||
@@ -338,7 +382,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            ShowNewEditDeleteButtons();
 | 
					            ShowNewEditDeleteButtons();
 | 
				
			||||||
            RefreshInputs();
 | 
					            RefreshInputs();
 | 
				
			||||||
            LockInputs();
 | 
					            LockInputs();
 | 
				
			||||||
            UpdatePhoneNrInputVisibility();
 | 
					            UpdateContactInfoVisibility();
 | 
				
			||||||
            UnlockSearchInputs();
 | 
					            UnlockSearchInputs();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -500,13 +544,24 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            ActiveMemberInput.IsEnabled = true;
 | 
					            ActiveMemberInput.IsEnabled = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void UpdatePhoneNrInputVisibility(bool extra = false) {
 | 
					        private void UpdateContactInfoVisibility(bool extra = false) {
 | 
				
			||||||
            bool lastVisible = true;
 | 
					            var m = MemberList.SelectedItem as Member;
 | 
				
			||||||
            var m = (Member)MemberList.SelectedItem;
 | 
					            bool lastVisible;
 | 
				
			||||||
 | 
					            int num = 0;
 | 
				
			||||||
 | 
					            lastVisible = true;
 | 
				
			||||||
 | 
					            for (int i = 0; i < EmailAddressInputs.Length; i++) {
 | 
				
			||||||
 | 
					                var input = EmailAddressInputs[i];
 | 
				
			||||||
 | 
					                var vis = !string.IsNullOrEmpty(input.Address.Text) || (m?.EmailAddresses.Any(a => a.Nr - 1 == i) ?? false);
 | 
				
			||||||
 | 
					                var cVis = vis || (extra && lastVisible);
 | 
				
			||||||
 | 
					                SetEmailAddressInputVisible(i, cVis, cVis ? num++ : null);
 | 
				
			||||||
 | 
					                lastVisible = vis;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            lastVisible = true;
 | 
				
			||||||
            for (int i = 0; i < PhoneNrInputs.Length; i++) {
 | 
					            for (int i = 0; i < PhoneNrInputs.Length; i++) {
 | 
				
			||||||
                var input = PhoneNrInputs[i];
 | 
					                var input = PhoneNrInputs[i];
 | 
				
			||||||
                var vis = !string.IsNullOrEmpty(input.Item2.Text) || (m?.TelephoneNumbers.Any(p => p.Nr - 1 == i) ?? false);
 | 
					                var vis = !string.IsNullOrEmpty(input.Number.Text) || (m?.TelephoneNumbers.Any(n => n.Nr - 1 == i) ?? false);
 | 
				
			||||||
                SetPhoneNrInputVisible(i, vis || (extra && lastVisible));
 | 
					                var cVis = vis || (extra && lastVisible);
 | 
				
			||||||
 | 
					                SetPhoneNrInputVisible(i, cVis, cVis ? num++ : null);
 | 
				
			||||||
                lastVisible = vis;
 | 
					                lastVisible = vis;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -601,17 +656,17 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for (int i = 0; i < 2; i++) {
 | 
					                for (int i = 0; i < EmailAddressInputs.Length; i++) {
 | 
				
			||||||
                    var input = i == 0 ? EmailAddress1Input : EmailAddress2Input;
 | 
					                    var input = GetEmailAddressInput(i);
 | 
				
			||||||
                    var emailAddr = m.EmailAddresses.FirstOrDefault(a => a.Nr - 1 == i);
 | 
					                    var emailAddr = m.EmailAddresses.FirstOrDefault(a => a.Nr - 1 == i);
 | 
				
			||||||
                    if (input.Text == "") {
 | 
					                    if (input == null || input == "") {
 | 
				
			||||||
                        if (emailAddr != null) {
 | 
					                        if (emailAddr != null) {
 | 
				
			||||||
                            Context.Remove(emailAddr);
 | 
					                            Context.Remove(emailAddr);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        MemberEmailAddr a = emailAddr ?? Context.CreateProxy<MemberEmailAddr>();
 | 
					                        MemberEmailAddr a = emailAddr ?? Context.CreateProxy<MemberEmailAddr>();
 | 
				
			||||||
                        a.Nr = i + 1;
 | 
					                        a.Nr = i + 1;
 | 
				
			||||||
                        a.Address = input.Text;
 | 
					                        a.Address = input ?? "";
 | 
				
			||||||
                        a.Comment = null;
 | 
					                        a.Comment = null;
 | 
				
			||||||
                        if (emailAddr == null) {
 | 
					                        if (emailAddr == null) {
 | 
				
			||||||
                            a.MgNr = newMgNr;
 | 
					                            a.MgNr = newMgNr;
 | 
				
			||||||
@@ -674,8 +729,14 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var emailAddrs = m.EmailAddresses.OrderBy(a => a.Nr).ToList();
 | 
					            var emailAddrs = m.EmailAddresses.OrderBy(a => a.Nr).ToList();
 | 
				
			||||||
            EmailAddress1Input.Text = emailAddrs.Count > 0 ? emailAddrs[0].Address : "";
 | 
					            for (int i = 0; i< EmailAddressInputs.Length; i++) {
 | 
				
			||||||
            EmailAddress2Input.Text = emailAddrs.Count > 1 ? emailAddrs[1].Address : "";
 | 
					                if (i < emailAddrs.Count) {
 | 
				
			||||||
 | 
					                    var emailAddr = emailAddrs[i];
 | 
				
			||||||
 | 
					                    SetEmailAddressInput(i, emailAddr.Address);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    SetEmailAddressInput(i, null);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var phoneNrs = m.TelephoneNumbers.OrderBy(p => p.Nr).ToList();
 | 
					            var phoneNrs = m.TelephoneNumbers.OrderBy(p => p.Nr).ToList();
 | 
				
			||||||
            for (int i = 0; i < PhoneNrInputs.Length; i++) {
 | 
					            for (int i = 0; i < PhoneNrInputs.Length; i++) {
 | 
				
			||||||
@@ -686,7 +747,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                    SetPhoneNrInput(i, null, null, null);
 | 
					                    SetPhoneNrInput(i, null, null, null);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            UpdatePhoneNrInputVisibility(IsEditing || IsCreating);
 | 
					            UpdateContactInfoVisibility(IsEditing || IsCreating);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            IbanInput.Text = m.Iban;
 | 
					            IbanInput.Text = m.Iban;
 | 
				
			||||||
            BicInput.Text = m.Bic;
 | 
					            BicInput.Text = m.Bic;
 | 
				
			||||||
@@ -782,9 +843,14 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            InputLostFocus((TextBox)sender, Validator.CheckPredecessorMgNr);
 | 
					            InputLostFocus((TextBox)sender, Validator.CheckPredecessorMgNr);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private new void EmailAddressInput_TextChanged(object sender, TextChangedEventArgs evt) {
 | 
				
			||||||
 | 
					            base.EmailAddressInput_TextChanged(sender, evt);
 | 
				
			||||||
 | 
					            UpdateContactInfoVisibility(IsEditing || IsCreating);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private new void PhoneNrInput_TextChanged(object sender, TextChangedEventArgs evt) {
 | 
					        private new void PhoneNrInput_TextChanged(object sender, TextChangedEventArgs evt) {
 | 
				
			||||||
            base.PhoneNrInput_TextChanged(sender, evt);
 | 
					            base.PhoneNrInput_TextChanged(sender, evt);
 | 
				
			||||||
            UpdatePhoneNrInputVisibility(IsEditing || IsCreating);
 | 
					            UpdateContactInfoVisibility(IsEditing || IsCreating);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void KgDetailsButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private void KgDetailsButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
<Window x:Class="Elwig.Windows.MemberListWindow"
 | 
					 | 
				
			||||||
        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.Windows"
 | 
					 | 
				
			||||||
        mc:Ignorable="d"
 | 
					 | 
				
			||||||
        Title="Mitgliederliste - Elwig" Height="450" Width="800">
 | 
					 | 
				
			||||||
    <Grid>
 | 
					 | 
				
			||||||
        <DataGrid x:Name="MemberList" AutoGenerateColumns="False" HeadersVisibility="Column" IsReadOnly="True" GridLinesVisibility="None" SelectionMode="Single"
 | 
					 | 
				
			||||||
                  CanUserDeleteRows="False" CanUserResizeRows="False" CanUserAddRows="False" FontSize="14">
 | 
					 | 
				
			||||||
            <DataGrid.Columns>
 | 
					 | 
				
			||||||
                <DataGridTextColumn Header="MgNr."          Binding="{Binding MgNr}"        Width="70"/>
 | 
					 | 
				
			||||||
                <DataGridTextColumn Header="Präfix"         Binding="{Binding Prefix}"      Width="100"/>
 | 
					 | 
				
			||||||
                <DataGridTextColumn Header="Vorname"        Binding="{Binding GivenName}"   Width="100"/>
 | 
					 | 
				
			||||||
                <DataGridTextColumn Header="Weitere Namen"  Binding="{Binding MiddleName}"  Width="100"/>
 | 
					 | 
				
			||||||
                <DataGridTextColumn Header="Nachname"       Binding="{Binding FamilyName}"  Width="100"/>
 | 
					 | 
				
			||||||
                <DataGridTextColumn Header="Suffix"         Binding="{Binding Suffix}"      Width="100"/>
 | 
					 | 
				
			||||||
            </DataGrid.Columns>
 | 
					 | 
				
			||||||
        </DataGrid>
 | 
					 | 
				
			||||||
    </Grid>
 | 
					 | 
				
			||||||
</Window>
 | 
					 | 
				
			||||||
@@ -1,27 +0,0 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					 | 
				
			||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using System.Text;
 | 
					 | 
				
			||||||
using System.Threading.Tasks;
 | 
					 | 
				
			||||||
using System.Windows;
 | 
					 | 
				
			||||||
using System.Windows.Controls;
 | 
					 | 
				
			||||||
using System.Windows.Data;
 | 
					 | 
				
			||||||
using System.Windows.Documents;
 | 
					 | 
				
			||||||
using System.Windows.Input;
 | 
					 | 
				
			||||||
using System.Windows.Media;
 | 
					 | 
				
			||||||
using System.Windows.Media.Imaging;
 | 
					 | 
				
			||||||
using System.Windows.Shapes;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Elwig.Windows {
 | 
					 | 
				
			||||||
    public partial class MemberListWindow : Window {
 | 
					 | 
				
			||||||
        private readonly AppDbContext Context = new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public MemberListWindow() {
 | 
					 | 
				
			||||||
            InitializeComponent();
 | 
					 | 
				
			||||||
            MemberList.ItemsSource = Context.Members.ToList();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -6,7 +6,7 @@
 | 
				
			|||||||
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 | 
					        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 | 
				
			||||||
        xmlns:local="clr-namespace:Elwig.Windows"
 | 
					        xmlns:local="clr-namespace:Elwig.Windows"
 | 
				
			||||||
        mc:Ignorable="d"
 | 
					        mc:Ignorable="d"
 | 
				
			||||||
        Title="Auszahlungsvarianten - Elwig" Height="500" Width="820" MinHeight="500" MinWidth="820">
 | 
					        Title="Auszahlungsvarianten - Elwig" Height="510" Width="820" MinHeight="500" MinWidth="820">
 | 
				
			||||||
    <Window.Resources>
 | 
					    <Window.Resources>
 | 
				
			||||||
        <Style TargetType="Label">
 | 
					        <Style TargetType="Label">
 | 
				
			||||||
            <Setter Property="HorizontalAlignment" Value="Left"/>
 | 
					            <Setter Property="HorizontalAlignment" Value="Left"/>
 | 
				
			||||||
@@ -42,7 +42,7 @@
 | 
				
			|||||||
        </Grid.ColumnDefinitions>
 | 
					        </Grid.ColumnDefinitions>
 | 
				
			||||||
        <Grid.RowDefinitions>
 | 
					        <Grid.RowDefinitions>
 | 
				
			||||||
            <RowDefinition Height="*"/>
 | 
					            <RowDefinition Height="*"/>
 | 
				
			||||||
            <RowDefinition Height="200"/>
 | 
					            <RowDefinition Height="220"/>
 | 
				
			||||||
        </Grid.RowDefinitions>
 | 
					        </Grid.RowDefinitions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <ListBox x:Name="PaymentVariantList" Margin="10,10,35,10" Grid.RowSpan="2" SelectionChanged="PaymentVariantList_SelectionChanged">
 | 
					        <ListBox x:Name="PaymentVariantList" Margin="10,10,35,10" Grid.RowSpan="2" SelectionChanged="PaymentVariantList_SelectionChanged">
 | 
				
			||||||
@@ -163,6 +163,9 @@
 | 
				
			|||||||
                    <Button x:Name="ExportButton" Content="Exportieren" FontSize="14" Width="180" Margin="10,10,10,10" Height="27" IsEnabled="False"
 | 
					                    <Button x:Name="ExportButton" Content="Exportieren" FontSize="14" Width="180" Margin="10,10,10,10" Height="27" IsEnabled="False"
 | 
				
			||||||
                            Click="ExportButton_Click"
 | 
					                            Click="ExportButton_Click"
 | 
				
			||||||
                            VerticalAlignment="Top" HorizontalAlignment="Left"/>
 | 
					                            VerticalAlignment="Top" HorizontalAlignment="Left"/>
 | 
				
			||||||
 | 
					                    <Button x:Name="TransactionButton" Content="Buchungsliste" FontSize="14" Width="180" Margin="10,42,10,10" Height="27" IsEnabled="False"
 | 
				
			||||||
 | 
					                            Click="TransactionButton_Click"
 | 
				
			||||||
 | 
					                            VerticalAlignment="Top" HorizontalAlignment="Left"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <ProgressBar x:Name="ProgressBar" Margin="10,10,10,74" Height="27" Width="180"
 | 
					                    <ProgressBar x:Name="ProgressBar" Margin="10,10,10,74" Height="27" Width="180"
 | 
				
			||||||
                                 VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
 | 
					                                 VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,9 +52,12 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                Arrow3.Content = locked ? "\xF0B0" : "\xF0AF";
 | 
					                Arrow3.Content = locked ? "\xF0B0" : "\xF0AF";
 | 
				
			||||||
                CopyButton.IsEnabled = true;
 | 
					                CopyButton.IsEnabled = true;
 | 
				
			||||||
                EditButton.Content = locked ? "Ansehen" : "Bearbeiten";
 | 
					                EditButton.Content = locked ? "Ansehen" : "Bearbeiten";
 | 
				
			||||||
 | 
					                EditButton.IsEnabled = true;
 | 
				
			||||||
 | 
					                SaveButton.IsEnabled = !locked;
 | 
				
			||||||
                ShowButton.IsEnabled = true;
 | 
					                ShowButton.IsEnabled = true;
 | 
				
			||||||
                PrintButton.IsEnabled = true;
 | 
					                PrintButton.IsEnabled = true;
 | 
				
			||||||
                ExportButton.IsEnabled = locked;
 | 
					                ExportButton.IsEnabled = locked;
 | 
				
			||||||
 | 
					                TransactionButton.IsEnabled = locked;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                NameInput.Text = v.Name;
 | 
					                NameInput.Text = v.Name;
 | 
				
			||||||
                NameInput.IsReadOnly = false;
 | 
					                NameInput.IsReadOnly = false;
 | 
				
			||||||
@@ -67,30 +70,27 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    BillingData = BillingData.FromJson(v.Data);
 | 
					                    BillingData = BillingData.FromJson(v.Data);
 | 
				
			||||||
                    ConsiderModifiersInput.IsChecked = BillingData.ConsiderDelieryModifiers;
 | 
					                    ConsiderModifiersInput.IsChecked = BillingData.ConsiderDelieryModifiers;
 | 
				
			||||||
                    ConsiderModifiersInput.IsEnabled = !locked;
 | 
					 | 
				
			||||||
                    ConsiderPenaltiesInput.IsChecked = BillingData.ConsiderContractPenalties;
 | 
					                    ConsiderPenaltiesInput.IsChecked = BillingData.ConsiderContractPenalties;
 | 
				
			||||||
                    ConsiderPenaltiesInput.IsEnabled = !locked;
 | 
					 | 
				
			||||||
                    ConsiderPenaltyInput.IsChecked = BillingData.ConsiderTotalPenalty;
 | 
					                    ConsiderPenaltyInput.IsChecked = BillingData.ConsiderTotalPenalty;
 | 
				
			||||||
                    ConsiderPenaltyInput.IsEnabled = !locked;
 | 
					 | 
				
			||||||
                    ConsiderAutoInput.IsChecked = BillingData.ConsiderAutoBusinessShares;
 | 
					                    ConsiderAutoInput.IsChecked = BillingData.ConsiderAutoBusinessShares;
 | 
				
			||||||
                    ConsiderAutoInput.IsEnabled = !locked;
 | 
					 | 
				
			||||||
                    DataInput.Text = JsonSerializer.Serialize(BillingData.Data, JsonOpt);
 | 
					                    DataInput.Text = JsonSerializer.Serialize(BillingData.Data, JsonOpt);
 | 
				
			||||||
                    DataInput.IsReadOnly = locked;
 | 
					 | 
				
			||||||
                } catch {
 | 
					                } catch {
 | 
				
			||||||
                    BillingData = null;
 | 
					                    BillingData = null;
 | 
				
			||||||
                    ConsiderModifiersInput.IsChecked = false;
 | 
					                    ConsiderModifiersInput.IsChecked = false;
 | 
				
			||||||
                    ConsiderModifiersInput.IsEnabled = false;
 | 
					 | 
				
			||||||
                    ConsiderPenaltiesInput.IsChecked = false;
 | 
					                    ConsiderPenaltiesInput.IsChecked = false;
 | 
				
			||||||
                    ConsiderPenaltiesInput.IsEnabled = false;
 | 
					 | 
				
			||||||
                    ConsiderPenaltyInput.IsChecked = false;
 | 
					                    ConsiderPenaltyInput.IsChecked = false;
 | 
				
			||||||
                    ConsiderPenaltyInput.IsEnabled = false;
 | 
					 | 
				
			||||||
                    ConsiderAutoInput.IsChecked = false;
 | 
					                    ConsiderAutoInput.IsChecked = false;
 | 
				
			||||||
                    ConsiderAutoInput.IsEnabled = false;
 | 
					 | 
				
			||||||
                    DataInput.Text = v.Data;
 | 
					                    DataInput.Text = v.Data;
 | 
				
			||||||
                    DataInput.IsEnabled = false;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                ConsiderModifiersInput.IsEnabled = !locked;
 | 
				
			||||||
 | 
					                ConsiderPenaltiesInput.IsEnabled = !locked;
 | 
				
			||||||
 | 
					                ConsiderPenaltyInput.IsEnabled = !locked;
 | 
				
			||||||
 | 
					                ConsiderAutoInput.IsEnabled = !locked;
 | 
				
			||||||
 | 
					                DataInput.IsReadOnly = locked;
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                EditButton.Content = "Bearbeiten";
 | 
					                EditButton.Content = "Bearbeiten";
 | 
				
			||||||
 | 
					                EditButton.IsEnabled = false;
 | 
				
			||||||
 | 
					                SaveButton.IsEnabled = false;
 | 
				
			||||||
                CopyButton.IsEnabled = false;
 | 
					                CopyButton.IsEnabled = false;
 | 
				
			||||||
                CalculateButton.IsEnabled = false;
 | 
					                CalculateButton.IsEnabled = false;
 | 
				
			||||||
                CommitButton.IsEnabled = false;
 | 
					                CommitButton.IsEnabled = false;
 | 
				
			||||||
@@ -102,6 +102,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                ShowButton.IsEnabled = false;
 | 
					                ShowButton.IsEnabled = false;
 | 
				
			||||||
                PrintButton.IsEnabled = false;
 | 
					                PrintButton.IsEnabled = false;
 | 
				
			||||||
                ExportButton.IsEnabled = false;
 | 
					                ExportButton.IsEnabled = false;
 | 
				
			||||||
 | 
					                TransactionButton.IsEnabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                BillingData = null;
 | 
					                BillingData = null;
 | 
				
			||||||
                NameInput.Text = "";
 | 
					                NameInput.Text = "";
 | 
				
			||||||
@@ -130,11 +131,13 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        private void UpdateSaveButton() {
 | 
					        private void UpdateSaveButton() {
 | 
				
			||||||
            SaveButton.IsEnabled = PaymentVariantList.SelectedItem != null &&
 | 
					            SaveButton.IsEnabled = PaymentVariantList.SelectedItem != null &&
 | 
				
			||||||
                ((DataChanged && DataValid) || NameChanged || CommentChanged ||
 | 
					                ((DataChanged && DataValid) || NameChanged || CommentChanged ||
 | 
				
			||||||
                (TransferDateChanged && TransferDateValid)) ||
 | 
					                (TransferDateChanged && TransferDateValid) ||
 | 
				
			||||||
                (ConsiderModifiersInput.IsChecked != BillingData?.ConsiderDelieryModifiers) ||
 | 
					                (ConsiderModifiersInput.IsChecked != BillingData?.ConsiderDelieryModifiers) ||
 | 
				
			||||||
                (ConsiderPenaltiesInput.IsChecked != BillingData?.ConsiderContractPenalties) ||
 | 
					                (ConsiderPenaltiesInput.IsChecked != BillingData?.ConsiderContractPenalties) ||
 | 
				
			||||||
                (ConsiderPenaltyInput.IsChecked != BillingData?.ConsiderTotalPenalty) ||
 | 
					                (ConsiderPenaltyInput.IsChecked != BillingData?.ConsiderTotalPenalty) ||
 | 
				
			||||||
                (ConsiderAutoInput.IsChecked != BillingData?.ConsiderAutoBusinessShares);
 | 
					                (ConsiderAutoInput.IsChecked != BillingData?.ConsiderAutoBusinessShares));
 | 
				
			||||||
 | 
					            CalculateButton.IsEnabled = !SaveButton.IsEnabled && PaymentVariantList.SelectedItem is PaymentVar { TestVariant: true };
 | 
				
			||||||
 | 
					            CommitButton.IsEnabled = CalculateButton.IsEnabled;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void UpdateSums() {
 | 
					        private void UpdateSums() {
 | 
				
			||||||
@@ -173,7 +176,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                v.Name = "Neue Auszahlungsvariante";
 | 
					                v.Name = "Neue Auszahlungsvariante";
 | 
				
			||||||
                v.TestVariant = true;
 | 
					                v.TestVariant = true;
 | 
				
			||||||
                v.DateString = $"{DateTime.Today:yyyy-MM-dd}";
 | 
					                v.DateString = $"{DateTime.Today:yyyy-MM-dd}";
 | 
				
			||||||
                v.Data = "{\"mode\": \"elwig\", \"version\": 1, \"payment\": 1.0, \"curves\": []}";
 | 
					                v.Data = "{\"mode\": \"elwig\", \"version\": 1, \"payment\": {}, \"curves\": []}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                await Context.AddAsync(v);
 | 
					                await Context.AddAsync(v);
 | 
				
			||||||
                await Context.SaveChangesAsync();
 | 
					                await Context.SaveChangesAsync();
 | 
				
			||||||
@@ -216,7 +219,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            try {
 | 
					            try {
 | 
				
			||||||
                Context.Remove(v);
 | 
					                Context.Remove(v);
 | 
				
			||||||
                await Context.SaveChangesAsync();
 | 
					                await Context.SaveChangesAsync();
 | 
				
			||||||
                await HintContextChange();
 | 
					                await App.HintContextChange();
 | 
				
			||||||
            } catch (Exception exc) {
 | 
					            } catch (Exception exc) {
 | 
				
			||||||
                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
 | 
					                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
 | 
				
			||||||
                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
					                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
 | 
				
			||||||
@@ -259,8 +262,12 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            CommitButton.IsEnabled = false;
 | 
					            CommitButton.IsEnabled = false;
 | 
				
			||||||
            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
					            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
                var b = new BillingVariant(v.Year, v.AvNr);
 | 
					                var b = new BillingVariant(v.Year, v.AvNr);
 | 
				
			||||||
                await b.Commit();
 | 
					                await b.Commit();
 | 
				
			||||||
 | 
					            } catch (Exception exc) {
 | 
				
			||||||
 | 
					                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
            RevertButton.IsEnabled = true;
 | 
					            RevertButton.IsEnabled = true;
 | 
				
			||||||
            await App.HintContextChange();
 | 
					            await App.HintContextChange();
 | 
				
			||||||
@@ -279,9 +286,9 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
					            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
            var b = new BillingVariant(v.Year, v.AvNr);
 | 
					            var b = new BillingVariant(v.Year, v.AvNr);
 | 
				
			||||||
            await b.Revert();
 | 
					            await b.Revert();
 | 
				
			||||||
 | 
					            await App.HintContextChange();
 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
            CommitButton.IsEnabled = true;
 | 
					            CommitButton.IsEnabled = true;
 | 
				
			||||||
            await App.HintContextChange();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void ExportButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void ExportButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
@@ -300,13 +307,42 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            if (d.ShowDialog() == true) {
 | 
					            if (d.ShowDialog() == true) {
 | 
				
			||||||
                ExportButton.IsEnabled = false;
 | 
					                ExportButton.IsEnabled = false;
 | 
				
			||||||
                Mouse.OverrideCursor = Cursors.AppStarting;
 | 
					                Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
                using var e = new Ebics(v, d.FileName);
 | 
					                try {
 | 
				
			||||||
 | 
					                    using var e = new Ebics(v, d.FileName, 9);
 | 
				
			||||||
                    await e.ExportAsync(Transaction.FromPaymentVariant(v));
 | 
					                    await e.ExportAsync(Transaction.FromPaymentVariant(v));
 | 
				
			||||||
 | 
					                } catch (Exception exc) {
 | 
				
			||||||
 | 
					                    MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                Mouse.OverrideCursor = null;
 | 
					                Mouse.OverrideCursor = null;
 | 
				
			||||||
                ExportButton.IsEnabled = true;
 | 
					                ExportButton.IsEnabled = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async void TransactionButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
 | 
					            if (PaymentVariantList.SelectedValue is not PaymentVar v) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            var d = new SaveFileDialog() {
 | 
				
			||||||
 | 
					                FileName = $"{App.Client.NameToken}-Buchungsliste-{v.Year}-{v.Name.Trim().Replace(' ', '-')}.ods",
 | 
				
			||||||
 | 
					                DefaultExt = "ods",
 | 
				
			||||||
 | 
					                Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
 | 
				
			||||||
 | 
					                Title = $"Buchungsliste speichern unter - Elwig"
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            if (d.ShowDialog() == true) {
 | 
				
			||||||
 | 
					                TransactionButton.IsEnabled = false;
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    var tbl = await CreditNoteData.ForPaymentVariant(Context, v.Year, v.AvNr);
 | 
				
			||||||
 | 
					                    using var ods = new OdsFile(d.FileName);
 | 
				
			||||||
 | 
					                    await ods.AddTable(tbl);
 | 
				
			||||||
 | 
					                } catch (Exception exc) {
 | 
				
			||||||
 | 
					                    MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Mouse.OverrideCursor = null;
 | 
				
			||||||
 | 
					                TransactionButton.IsEnabled = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (PaymentVariantList.SelectedItem is not PaymentVar v || BillingData == null) return;
 | 
					            if (PaymentVariantList.SelectedItem is not PaymentVar v || BillingData == null) return;
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
@@ -396,13 +432,13 @@ namespace Elwig.Windows {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                var json = BillingData.ParseJson(DataInput.Text);
 | 
					                var data = BillingData.FromJson(DataInput.Text);
 | 
				
			||||||
                var origJson = v.Data;
 | 
					                var origJson = v.Data;
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    origJson = JsonSerializer.Serialize(BillingData.ParseJson(v.Data));
 | 
					                    origJson = JsonSerializer.Serialize(BillingData.FromJson(v.Data).Data);
 | 
				
			||||||
                } catch { }
 | 
					                } catch { }
 | 
				
			||||||
                DataValid = true;
 | 
					                DataValid = true;
 | 
				
			||||||
                if (JsonSerializer.Serialize(json) != origJson) {
 | 
					                if (JsonSerializer.Serialize(data.Data) != origJson) {
 | 
				
			||||||
                    ControlUtils.SetInputChanged(DataInput);
 | 
					                    ControlUtils.SetInputChanged(DataInput);
 | 
				
			||||||
                    DataChanged = true;
 | 
					                    DataChanged = true;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
@@ -484,11 +520,19 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            members = members.OrderBy(m => m.MgNr);
 | 
					            members = members.OrderBy(m => m.MgNr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            IEnumerable<Member> list = await members.ToListAsync();
 | 
					            IEnumerable<Member> list = await members.ToListAsync();
 | 
				
			||||||
            var data = await CreditNoteData.ForPaymentVariant(Context.CreditNoteRows, Context.Seasons, v.Year, v.AvNr);
 | 
					            var data = await CreditNoteDeliveryData.ForPaymentVariant(Context.CreditNoteDeliveryRows, Context.Seasons, v.Year, v.AvNr);
 | 
				
			||||||
            var payments = await Context.MemberPayments.Where(p => p.Year == v.Year && p.AvNr == v.AvNr).ToDictionaryAsync(c => c.MgNr);
 | 
					            var payments = await Context.MemberPayments.Where(p => p.Year == v.Year && p.AvNr == v.AvNr).ToDictionaryAsync(c => c.MgNr);
 | 
				
			||||||
            await Context.GetMemberAreaCommitmentBuckets(Year, 0);
 | 
					            await Context.GetMemberAreaCommitmentBuckets(Year, 0);
 | 
				
			||||||
            using var doc = Document.Merge(list.Select(m =>
 | 
					            using var doc = Document.Merge(list.Select(m =>
 | 
				
			||||||
                new CreditNote(Context, payments[m.MgNr], data[m.MgNr], Context.GetMemberUnderDelivery(Year, m.MgNr).GetAwaiter().GetResult())
 | 
					                new CreditNote(
 | 
				
			||||||
 | 
					                    Context,
 | 
				
			||||||
 | 
					                    payments[m.MgNr],
 | 
				
			||||||
 | 
					                    data[m.MgNr],
 | 
				
			||||||
 | 
					                    BillingData?.ConsiderContractPenalties ?? false,
 | 
				
			||||||
 | 
					                    BillingData?.ConsiderTotalPenalty ?? false,
 | 
				
			||||||
 | 
					                    BillingData?.ConsiderAutoBusinessShares ?? false,
 | 
				
			||||||
 | 
					                    Context.GetMemberUnderDelivery(Year, m.MgNr).GetAwaiter().GetResult()
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            ));
 | 
					            ));
 | 
				
			||||||
            await doc.Generate(new Progress<double>(v => {
 | 
					            await doc.Generate(new Progress<double>(v => {
 | 
				
			||||||
                ProgressBar.Value = v;
 | 
					                ProgressBar.Value = v;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
using Elwig.Dialogs;
 | 
					 | 
				
			||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Elwig.Helpers.Billing;
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using Elwig.Helpers.Export;
 | 
					using Elwig.Helpers.Export;
 | 
				
			||||||
using Elwig.Models.Dtos;
 | 
					using Elwig.Models.Dtos;
 | 
				
			||||||
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
using Microsoft.Win32;
 | 
					using Microsoft.Win32;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
@@ -33,19 +33,31 @@ namespace Elwig.Windows {
 | 
				
			|||||||
            OverUnderDeliveryButton.IsEnabled = valid;
 | 
					            OverUnderDeliveryButton.IsEnabled = valid;
 | 
				
			||||||
            AutoBusinessSharesButton.IsEnabled = valid;
 | 
					            AutoBusinessSharesButton.IsEnabled = valid;
 | 
				
			||||||
            PaymentButton.IsEnabled = valid;
 | 
					            PaymentButton.IsEnabled = valid;
 | 
				
			||||||
 | 
					            AllowAttrIntoLowerInput.IsEnabled = valid && last;
 | 
				
			||||||
 | 
					            AvoidUnderDeliveriesInput.IsEnabled = valid && last;
 | 
				
			||||||
 | 
					            HonorGebundenInput.IsEnabled = valid && last;
 | 
				
			||||||
 | 
					            AllowAttrIntoLowerInput.IsChecked = s0?.Billing_AllowAttrsIntoLower;
 | 
				
			||||||
 | 
					            AvoidUnderDeliveriesInput.IsChecked = s0?.Billing_AvoidUnderDeliveries;
 | 
				
			||||||
 | 
					            HonorGebundenInput.IsChecked = s0?.Billing_HonorGebunden;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async void CalculateBucketsButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void CalculateBucketsButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (SeasonInput.Value is not int year)
 | 
					            if (SeasonInput.Value is not int year || await Context.Seasons.FindAsync(year) is not Season s)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            CalculateBucketsButton.IsEnabled = false;
 | 
					            CalculateBucketsButton.IsEnabled = false;
 | 
				
			||||||
            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
					            Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                s.Billing_AllowAttrsIntoLower = AllowAttrIntoLowerInput.IsChecked ?? false;
 | 
				
			||||||
 | 
					                s.Billing_AvoidUnderDeliveries = AvoidUnderDeliveriesInput.IsChecked ?? false;
 | 
				
			||||||
 | 
					                s.Billing_HonorGebunden = HonorGebundenInput.IsChecked ?? false;
 | 
				
			||||||
 | 
					                Context.Update(s);
 | 
				
			||||||
 | 
					                await Context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            } catch { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var b = new Billing(year);
 | 
					            var b = new Billing(year);
 | 
				
			||||||
            await b.FinishSeason();
 | 
					            await b.FinishSeason();
 | 
				
			||||||
            await b.CalculateBuckets(
 | 
					            await b.CalculateBuckets();
 | 
				
			||||||
                AllowAttrIntoLowerInput.IsChecked ?? false,
 | 
					 | 
				
			||||||
                AvoidUnderDeliveriesInput.IsChecked ?? false,
 | 
					 | 
				
			||||||
                HonorGebundenInput.IsChecked ?? false);
 | 
					 | 
				
			||||||
            Mouse.OverrideCursor = null;
 | 
					            Mouse.OverrideCursor = null;
 | 
				
			||||||
            CalculateBucketsButton.IsEnabled = true;
 | 
					            CalculateBucketsButton.IsEnabled = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -86,7 +98,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        private async void AutoBusinessSharesButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void AutoBusinessSharesButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            if (SeasonInput.Value is not int year)
 | 
					            if (SeasonInput.Value is not int year)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            if (App.Client.IsMatzen) {
 | 
					            if (false && App.Client.IsMatzen) {
 | 
				
			||||||
                AutoBusinessSharesButton.IsEnabled = false;
 | 
					                AutoBusinessSharesButton.IsEnabled = false;
 | 
				
			||||||
                Mouse.OverrideCursor = Cursors.AppStarting;
 | 
					                Mouse.OverrideCursor = Cursors.AppStarting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ using Elwig.Documents;
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Elwig.Helpers.Billing;
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using Elwig.Helpers.Export;
 | 
					using Elwig.Helpers.Export;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Weighing;
 | 
				
			||||||
using Elwig.Models.Dtos;
 | 
					using Elwig.Models.Dtos;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
@@ -27,7 +28,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async void WeighingButton1_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void WeighingButton1_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                var res = await App.Scales[0].GetCurrentWeight();
 | 
					                if (App.Scales[0] is not ICommandScale cs) return;
 | 
				
			||||||
 | 
					                var res = await cs.GetCurrentWeight();
 | 
				
			||||||
                Output.Text = res.ToString();
 | 
					                Output.Text = res.ToString();
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            } catch (Exception e) {
 | 
				
			||||||
                MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{e.Message}", "Waagenfehler",
 | 
					                MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{e.Message}", "Waagenfehler",
 | 
				
			||||||
@@ -37,7 +39,8 @@ namespace Elwig.Windows {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async void WeighingButton2_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void WeighingButton2_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                var res = await App.Scales[0].Weigh();
 | 
					                if (App.Scales[0] is not ICommandScale cs) return;
 | 
				
			||||||
 | 
					                var res = await cs.Weigh();
 | 
				
			||||||
                Output.Text = res.ToString();
 | 
					                Output.Text = res.ToString();
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            } catch (Exception e) {
 | 
				
			||||||
                MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{e.Message}", "Waagenfehler",
 | 
					                MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{e.Message}", "Waagenfehler",
 | 
				
			||||||
@@ -48,7 +51,7 @@ namespace Elwig.Windows {
 | 
				
			|||||||
        private async void ZipButton_Click(object sender, RoutedEventArgs evt) {
 | 
					        private async void ZipButton_Click(object sender, RoutedEventArgs evt) {
 | 
				
			||||||
            using var ctx = new AppDbContext();
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
            using var ods = new OdsFile(@"C:\Users\Lorenz\Desktop\test.ods");
 | 
					            using var ods = new OdsFile(@"C:\Users\Lorenz\Desktop\test.ods");
 | 
				
			||||||
            await ods.AddTable(await DeliveryConfirmationData.ForMember(ctx.DeliveryParts, 2023, ctx.Members.Find(2948)));
 | 
					            await ods.AddTable(await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, 2023, ctx.Members.Find(2948)));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
::mkdir "C:\Program Files\Elwig"
 | 
					::mkdir "C:\Program Files\Elwig"
 | 
				
			||||||
::curl -s "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "C:\Program Files\Elwig\PDFtoPrinter.exe" -o "C:\Program Files\Elwig\PDFtoPrinter.exe"
 | 
					::curl -s -L "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "C:\Program Files\Elwig\PDFtoPrinter.exe" -o "C:\Program Files\Elwig\PDFtoPrinter.exe"
 | 
				
			||||||
mkdir "C:\ProgramData\Elwig\resources"
 | 
					mkdir "C:\ProgramData\Elwig\resources"
 | 
				
			||||||
copy /b /y Documents\*.css "C:\ProgramData\Elwig\resources"
 | 
					copy /b /y Documents\*.css "C:\ProgramData\Elwig\resources"
 | 
				
			||||||
copy /b /y Documents\*.cshtml "C:\ProgramData\Elwig\resources"
 | 
					copy /b /y Documents\*.cshtml "C:\ProgramData\Elwig\resources"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,8 +11,8 @@ file = database.sqlite3
 | 
				
			|||||||
;log = db.log
 | 
					;log = db.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;[scale.1]
 | 
					;[scale.1]
 | 
				
			||||||
;type = systec
 | 
					;type = SysTec-IT
 | 
				
			||||||
;model = IT3000A
 | 
					;model = IT3000
 | 
				
			||||||
;connection = serial://COM1:9600,8,N,1
 | 
					;connection = serial://COM1:9600,8,N,1
 | 
				
			||||||
;empty = COM2:RTS:1000
 | 
					;empty = COM2:RTS:1000
 | 
				
			||||||
;filling = DTR
 | 
					;filling = DTR
 | 
				
			||||||
@@ -22,8 +22,8 @@ file = database.sqlite3
 | 
				
			|||||||
;log = waage.log
 | 
					;log = waage.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;[scale.B]
 | 
					;[scale.B]
 | 
				
			||||||
;type = Type
 | 
					;type = Schember-Async
 | 
				
			||||||
;model = Model
 | 
					;model = L320
 | 
				
			||||||
;connection = tcp://10.0.0.1:1234
 | 
					;connection = tcp://10.0.0.1:1234
 | 
				
			||||||
;empty = OUT1:3000
 | 
					;empty = OUT1:3000
 | 
				
			||||||
;filling = OUT2
 | 
					;filling = OUT2
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,8 +25,8 @@
 | 
				
			|||||||
    </Task>
 | 
					    </Task>
 | 
				
			||||||
  </UsingTask>
 | 
					  </UsingTask>
 | 
				
			||||||
  <Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
 | 
					  <Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
 | 
				
			||||||
    <Exec Command="curl -s "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "$(TargetDir)PDFtoPrinter.exe" -o "$(TargetDir)PDFtoPrinter.exe"" />
 | 
					    <Exec Command="curl -s -L "http://www.columbia.edu/~em36/PDFtoPrinter.exe" -z "$(ProjectDir)\Files\PDFtoPrinter.exe" -o "$(ProjectDir)\Files\PDFtoPrinter.exe"" />
 | 
				
			||||||
    <Exec Command="dotnet publish "$(SolutionDir)Elwig\Elwig.csproj" "/p:PublishProfile=$(SolutionDir)\Elwig\Properties\PublishProfiles\FolderProfile.pubxml"" />
 | 
					    <Exec Command="dotnet publish "$(ProjectDir)\..\Elwig\Elwig.csproj" "/p:PublishProfile=$(ProjectDir)\..\Elwig\Properties\PublishProfiles\FolderProfile.pubxml"" />
 | 
				
			||||||
    <GetFileVersion AssemblyPath="..\Elwig\bin\Publish\Elwig.exe">
 | 
					    <GetFileVersion AssemblyPath="..\Elwig\bin\Publish\Elwig.exe">
 | 
				
			||||||
      <Output TaskParameter="Version" PropertyName="ElwigFileVersion" />
 | 
					      <Output TaskParameter="Version" PropertyName="ElwigFileVersion" />
 | 
				
			||||||
    </GetFileVersion>
 | 
					    </GetFileVersion>
 | 
				
			||||||
@@ -60,6 +60,6 @@
 | 
				
			|||||||
    <None Include="Files\config.ini" />
 | 
					    <None Include="Files\config.ini" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <PackageReference Include="WixToolset.Heat" Version="4.0.1" />
 | 
					    <PackageReference Include="WixToolset.Heat" Version="4.0.3" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
@@ -11,7 +11,7 @@
 | 
				
			|||||||
        <File Source="$(ProjectDir)\Files\WinziPrint.exe" Id="WinziPrint.exe"/>
 | 
					        <File Source="$(ProjectDir)\Files\WinziPrint.exe" Id="WinziPrint.exe"/>
 | 
				
			||||||
      </Component>
 | 
					      </Component>
 | 
				
			||||||
      <Component Directory="InstallFolder">
 | 
					      <Component Directory="InstallFolder">
 | 
				
			||||||
        <File Source="$(TargetDir)\PDFtoPrinter.exe" Id="PDFtoPrinter.exe"/>
 | 
					        <File Source="$(ProjectDir)\Files\PDFtoPrinter.exe" Id="PDFtoPrinter.exe"/>
 | 
				
			||||||
      </Component>
 | 
					      </Component>
 | 
				
			||||||
    </ComponentGroup>
 | 
					    </ComponentGroup>
 | 
				
			||||||
  </Fragment>
 | 
					  </Fragment>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,28 @@
 | 
				
			|||||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
 | 
					<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
 | 
				
			||||||
  <Bundle Name="Elwig" Manufacturer="Elwig" Version="!(bind.packageVersion.ElwigMsi)" UpgradeCode="f3c8fcab-c37c-43aa-9ab8-e42f4bb518b7" IconSourceFile="$(var.ElwigProjectDir)\Resources\Images\Elwig.ico" >
 | 
					  <Bundle Name="Elwig" Manufacturer="Elwig" Version="!(bind.packageVersion.ElwigMsi)" UpgradeCode="f3c8fcab-c37c-43aa-9ab8-e42f4bb518b7"
 | 
				
			||||||
 | 
					          IconSourceFile="$(var.ElwigProjectDir)\Resources\Images\Elwig.ico">
 | 
				
			||||||
    <BootstrapperApplication>
 | 
					    <BootstrapperApplication>
 | 
				
			||||||
      <bal:WixStandardBootstrapperApplication LicenseUrl="" Theme="hyperlinkLicense" LogoFile="$(var.ElwigProjectDir)\Resources\Images\Elwig.png" SuppressOptionsUI="yes" ShowVersion="yes" />
 | 
					      <bal:WixStandardBootstrapperApplication LicenseUrl="" Theme="hyperlinkLicense" LogoFile="$(var.ElwigProjectDir)\Resources\Images\Elwig.png"
 | 
				
			||||||
 | 
					                                              SuppressOptionsUI="yes" ShowVersion="yes"/>
 | 
				
			||||||
    </BootstrapperApplication>
 | 
					    </BootstrapperApplication>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <util:RegistrySearch Id="VCredistx86" Variable="VCredistx86" Result="exists" Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\VC,redist.x86,x86,14.36,bundle" />
 | 
					    <util:RegistrySearch Id="VCredistx86" Variable="VCredistx86" Result="exists"
 | 
				
			||||||
    <util:RegistrySearch Id="Webview2Machine" Variable="Webview2Machine" Result="exists" Root="HKLM" Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" />
 | 
					                         Root="HKLM" Key="SOFTWARE\Classes\Installer\Dependencies\VC,redist.x86,x86,14.36,bundle"/>
 | 
				
			||||||
    <util:RegistrySearch Id="Webview2User" Variable="Webview2User" Result="exists" Root="HKCU" Key="Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" />
 | 
					    <util:RegistrySearch Id="Webview2Machine" Variable="Webview2Machine" Result="exists"
 | 
				
			||||||
 | 
					                         Root="HKLM" Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"/>
 | 
				
			||||||
 | 
					    <util:RegistrySearch Id="Webview2User" Variable="Webview2User" Result="exists"
 | 
				
			||||||
 | 
					                         Root="HKCU" Key="Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Chain>
 | 
					    <Chain>
 | 
				
			||||||
      <ExePackage Id="VCredistx86Installer" DisplayName="VC Redist x86 installer" Name="VC_redist.x86.exe" Cache="remove" Compressed="yes" PerMachine="yes"
 | 
					      <ExePackage Id="VCredistx86Installer" DisplayName="VC Redist x86 installer" Name="VC_redist.x86.exe"
 | 
				
			||||||
				  Permanent="yes" Vital="yes" SourceFile="$(TargetDir)VC_redist.x86.exe" InstallArguments="/install /passive /norestart" DetectCondition="VCredistx86">
 | 
					                  SourceFile="$(ProjectDir)\Files\VC_redist.x86.exe"
 | 
				
			||||||
      </ExePackage>
 | 
					                  Cache="remove" Compressed="yes" PerMachine="yes" Permanent="yes" Vital="yes"
 | 
				
			||||||
 | 
					                  InstallArguments="/install /passive /norestart" DetectCondition="VCredistx86"/>
 | 
				
			||||||
      <ExePackage Id="MicrosoftEdgeWebview2" DisplayName="Microsoft Edge Webview2 Runtime" Name="MicrosoftEdgeWebview2Setup.exe" Cache="remove"
 | 
					      <ExePackage Id="MicrosoftEdgeWebview2" DisplayName="Microsoft Edge Webview2 Runtime" Name="MicrosoftEdgeWebview2Setup.exe"
 | 
				
			||||||
				  Compressed="yes" PerMachine="yes" Permanent ="yes" Vital ="no" SourceFile="$(TargetDir)MicrosoftEdgeWebview2Setup.exe" InstallArguments="/silent /install"
 | 
					                  SourceFile="$(ProjectDir)\Files\MicrosoftEdgeWebview2Setup.exe"
 | 
				
			||||||
				  UninstallArguments="/silent /uninstall" DetectCondition="Webview2Machine OR Webview2User" >
 | 
					                  Cache="remove" Compressed="yes" PerMachine="yes" Permanent ="yes" Vital="no"
 | 
				
			||||||
      </ExePackage>
 | 
					                  InstallArguments="/silent /install" UninstallArguments="/silent /uninstall" DetectCondition="Webview2Machine OR Webview2User"/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
      <MsiPackage Id="ElwigMsi" SourceFile="$(var.Installer.TargetDir)\Elwig.msi" Permanent="no" Compressed="yes" Vital="yes"/>
 | 
					      <MsiPackage Id="ElwigMsi" SourceFile="$(var.Installer.TargetDir)\Elwig.msi" Permanent="no" Compressed="yes" Vital="yes"/>
 | 
				
			||||||
    </Chain>
 | 
					    </Chain>
 | 
				
			||||||
	  
 | 
					 | 
				
			||||||
  </Bundle>
 | 
					  </Bundle>
 | 
				
			||||||
</Wix>
 | 
					</Wix>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								Setup/Files/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								Setup/Files/.gitkeep
									
									
									
									
									
										Normal file
									
								
							@@ -5,15 +5,15 @@
 | 
				
			|||||||
    <Cultures>de-AT</Cultures>
 | 
					    <Cultures>de-AT</Cultures>
 | 
				
			||||||
  </PropertyGroup>
 | 
					  </PropertyGroup>
 | 
				
			||||||
  <Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
 | 
					  <Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
 | 
				
			||||||
    <Exec Command='curl -L -s "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -z "$(TargetDir)MicrosoftEdgeWebview2Setup.exe" -o "$(TargetDir)MicrosoftEdgeWebview2Setup.exe"' />
 | 
					    <Exec Command='curl -s -L "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -z "$(ProjectDir)\Files\MicrosoftEdgeWebview2Setup.exe" -o "$(ProjectDir)\Files\MicrosoftEdgeWebview2Setup.exe"' />
 | 
				
			||||||
    <Exec Command='curl -L -s "https://aka.ms/vs/17/release/vc_redist.x86.exe" -z "$(TargetDir)VC_redist.x86.exe" -o "$(TargetDir)VC_redist.x86.exe"' />
 | 
					    <Exec Command='curl -s -L "https://aka.ms/vs/17/release/vc_redist.x86.exe" -z "$(ProjectDir)\Files\VC_redist.x86.exe" -o "$(ProjectDir)\Files\VC_redist.x86.exe"' />
 | 
				
			||||||
    <PropertyGroup>
 | 
					    <PropertyGroup>
 | 
				
			||||||
      <DefineConstants>ElwigProjectDir=..\Elwig</DefineConstants>
 | 
					      <DefineConstants>ElwigProjectDir=..\Elwig</DefineConstants>
 | 
				
			||||||
    </PropertyGroup>
 | 
					    </PropertyGroup>
 | 
				
			||||||
  </Target>
 | 
					  </Target>
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <ProjectReference Include="..\Installer\Installer.wixproj" />
 | 
					    <ProjectReference Include="..\Installer\Installer.wixproj" />
 | 
				
			||||||
    <PackageReference Include="WixToolset.Bal.wixext" Version="4.0.1" />
 | 
					    <PackageReference Include="WixToolset.Bal.wixext" Version="4.0.3" />
 | 
				
			||||||
    <PackageReference Include="WixToolset.Util.wixext" Version="4.0.1" />
 | 
					    <PackageReference Include="WixToolset.Util.wixext" Version="4.0.3" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
</Project>
 | 
					</Project>
 | 
				
			||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig;
 | 
				
			||||||
 | 
					using Elwig.Helpers;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
using Microsoft.Data.Sqlite;
 | 
					using Microsoft.Data.Sqlite;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,11 +11,23 @@ namespace Tests {
 | 
				
			|||||||
        private SqliteConnection? Connection;
 | 
					        private SqliteConnection? Connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [OneTimeSetUp]
 | 
					        [OneTimeSetUp]
 | 
				
			||||||
        public async Task SetupDatabase() {
 | 
					        public async Task Setup_1_Database() {
 | 
				
			||||||
            AppDbContext.ConnectionStringOverride = $"Data Source=ElwigTestDB; Mode=Memory; Foreign Keys=True; Cache=Shared";
 | 
					            AppDbContext.ConnectionStringOverride = $"Data Source=ElwigTestDB; Mode=Memory; Foreign Keys=True; Cache=Shared";
 | 
				
			||||||
            Connection = await AppDbContext.ConnectAsync();
 | 
					            Connection = await AppDbContext.ConnectAsync();
 | 
				
			||||||
            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Create.sql");
 | 
					            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Create.sql");
 | 
				
			||||||
            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Insert.sql");
 | 
					            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.Insert.sql");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [OneTimeSetUp]
 | 
				
			||||||
 | 
					        public void Setup_2_Client() {
 | 
				
			||||||
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
 | 
					            App.Client = new ClientParameters(ctx);
 | 
				
			||||||
 | 
					            App.SetBranch(ctx.Branches.Single());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [OneTimeSetUp]
 | 
				
			||||||
 | 
					        public async Task Setup_3_BillingData() {
 | 
				
			||||||
 | 
					            await BillingData.Init();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [OneTimeTearDown]
 | 
					        [OneTimeTearDown]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								Tests/DocumentTests/DeliveryNoteTest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Tests/DocumentTests/DeliveryNoteTest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					using Elwig.Documents;
 | 
				
			||||||
 | 
					using Elwig.Helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Tests.DocumentTests {
 | 
				
			||||||
 | 
					    [TestFixture]
 | 
				
			||||||
 | 
					    public class DeliveryNoteTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly AppDbContext Context = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_01_OneDeliveryPart() {
 | 
				
			||||||
 | 
					            var d = await Context.Deliveries.FindAsync(2020, 1);
 | 
				
			||||||
 | 
					            using var doc = new DeliveryNote(d!, Context);
 | 
				
			||||||
 | 
					            var text = await Utils.GeneratePdfText(doc);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(text, Contains.Substring("""
 | 
				
			||||||
 | 
					                MUSTERMANN Max
 | 
				
			||||||
 | 
					                Winzerstraße 1
 | 
				
			||||||
 | 
					                2223 Hohenruppersdorf
 | 
				
			||||||
 | 
					                """));
 | 
				
			||||||
 | 
					                Assert.That(text, Contains.Substring("1472583"));  // Betriebsnummer
 | 
				
			||||||
 | 
					                Assert.That(text, Contains.Substring("pauschaliert"));
 | 
				
			||||||
 | 
					                Assert.That(text, Contains.Substring($"Wolkersdorf, am {DateTime.Now:dd.MM.yyyy}"));
 | 
				
			||||||
 | 
					                Assert.That(text, Contains.Substring("Traubenübernahmeschein Nr. 20201001X001"));
 | 
				
			||||||
 | 
					                // TODO
 | 
				
			||||||
 | 
					                Assert.That(text, Contains.Substring("Ich bin der Text, der auf einem Traubenübernahmeschein steht!"));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,30 +1,37 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Microsoft.Data.Sqlite;
 | 
					 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					using Microsoft.Data.Sqlite;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Printing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Tests.Helpers {
 | 
					namespace Tests.DocumentTests {
 | 
				
			||||||
    [TestFixture]
 | 
					    [SetUpFixture]
 | 
				
			||||||
    public class BillingTest {
 | 
					    public class Setup {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private SqliteConnection? Connection;
 | 
					        private SqliteConnection? Connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [OneTimeSetUp]
 | 
					        [OneTimeSetUp]
 | 
				
			||||||
        public async Task SetupDatabase() {
 | 
					        public async Task SetupDatabase() {
 | 
				
			||||||
            Connection = await AppDbContext.ConnectAsync();
 | 
					            Connection = await AppDbContext.ConnectAsync();
 | 
				
			||||||
            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.BillingInsert.sql");
 | 
					            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentInsert.sql");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [OneTimeTearDown]
 | 
					        [OneTimeTearDown]
 | 
				
			||||||
        public async Task TeardownDatabase() {
 | 
					        public async Task TeardownDatabase() {
 | 
				
			||||||
            if (Connection == null) return;
 | 
					            if (Connection == null) return;
 | 
				
			||||||
            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.BillingDelete.sql");
 | 
					            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.DocumentDelete.sql");
 | 
				
			||||||
            await Connection.DisposeAsync();
 | 
					            await Connection.DisposeAsync();
 | 
				
			||||||
            Connection = null;
 | 
					            Connection = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [OneTimeSetUp]
 | 
				
			||||||
        public void Test() {
 | 
					        public async Task SetupPrinting() {
 | 
				
			||||||
            // TODO
 | 
					            await Html.Init();
 | 
				
			||||||
 | 
					            await Pdf.Init();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [OneTimeTearDown]
 | 
				
			||||||
 | 
					        public void TeardownPrinting() {
 | 
				
			||||||
 | 
					            // no teardown needed yet
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								Tests/DocumentTests/Utils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Tests/DocumentTests/Utils.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					using Elwig.Documents;
 | 
				
			||||||
 | 
					using NReco.PdfRenderer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Tests.DocumentTests {
 | 
				
			||||||
 | 
					    public static class Utils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static readonly string FileName = Path.Combine(Path.GetTempPath(), "test_document.pdf");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static async Task<string> GeneratePdfText(Document doc) {
 | 
				
			||||||
 | 
					            await doc.Generate();
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                doc.SaveTo(FileName);
 | 
				
			||||||
 | 
					                var conv = new PdfToTextConverter { CustomArgs = "-raw " };
 | 
				
			||||||
 | 
					                return conv.GenerateText(FileName);
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                File.Delete(FileName);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,16 +1,14 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using Elwig.Helpers.Billing;
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
 | 
					using System.Text.Json;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Tests.Helpers {
 | 
					namespace Tests.HelperTests {
 | 
				
			||||||
    [TestFixture]
 | 
					    [TestFixture]
 | 
				
			||||||
    public class BillingDataTest {
 | 
					    public class BillingDataTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly string[] AttributeVariants = ["GV", "GVD", "GVK", "GVS", "GVZ", "WR", "WRS", "ZW", "ZWS", "ZWZ"];
 | 
					        private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };
 | 
				
			||||||
 | 
					        private static readonly string[] Vaributes = ["GV", "GVD", "GVK", "GVS", "GVZ", "WR", "WRS", "ZW", "ZWS", "ZWZ"];
 | 
				
			||||||
        [OneTimeSetUp]
 | 
					 | 
				
			||||||
        public async Task SetupBilling() {
 | 
					 | 
				
			||||||
            await BillingData.Init();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static (string, string?) GetSortIdAttrId(string bucket) {
 | 
					        private static (string, string?) GetSortIdAttrId(string bucket) {
 | 
				
			||||||
            return (bucket[..2], bucket.Length > 2 ? bucket[2..] : null);
 | 
					            return (bucket[..2], bucket.Length > 2 ? bucket[2..] : null);
 | 
				
			||||||
@@ -26,14 +24,14 @@ namespace Tests.Helpers {
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static void TestCalcOe(BillingData data, string bucket, double oe, decimal expected, string? qualid = null, bool geb = false) {
 | 
					        private static void TestCalcOe(PaymentBillingData data, string bucket, double oe, decimal expected, string? qualid = null, bool geb = false) {
 | 
				
			||||||
            var (sortid, attrid) = GetSortIdAttrId(bucket);
 | 
					            var (sortid, attrid) = GetSortIdAttrId(bucket);
 | 
				
			||||||
            var kmw = Utils.OeToKmw(oe);
 | 
					            var kmw = Utils.OeToKmw(oe);
 | 
				
			||||||
            var v = data.CalculatePrice(sortid, attrid, qualid ?? GetQualId(kmw), geb, oe, kmw);
 | 
					            var v = data.CalculatePrice(sortid, attrid, qualid ?? GetQualId(kmw), geb, oe, kmw);
 | 
				
			||||||
            Assert.That(Math.Round(v, 6), Is.EqualTo(expected));
 | 
					            Assert.That(Math.Round(v, 6), Is.EqualTo(expected));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static void TestCalcKmw(BillingData data, string bucket, double kmw, decimal expected, string? qualid = null, bool geb = false) {
 | 
					        private static void TestCalcKmw(PaymentBillingData data, string bucket, double kmw, decimal expected, string? qualid = null, bool geb = false) {
 | 
				
			||||||
            var (sortid, attrid) = GetSortIdAttrId(bucket);
 | 
					            var (sortid, attrid) = GetSortIdAttrId(bucket);
 | 
				
			||||||
            var oe = Utils.KmwToOe(kmw);
 | 
					            var oe = Utils.KmwToOe(kmw);
 | 
				
			||||||
            var v = data.CalculatePrice(sortid, attrid, qualid ?? GetQualId(kmw), geb, oe, kmw);
 | 
					            var v = data.CalculatePrice(sortid, attrid, qualid ?? GetQualId(kmw), geb, oe, kmw);
 | 
				
			||||||
@@ -41,15 +39,15 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_01_Flatrate() {
 | 
					        public void TestRead_01_Flatrate() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "elwig",
 | 
					                  "mode": "elwig",
 | 
				
			||||||
                  "version": 1,
 | 
					                  "version": 1,
 | 
				
			||||||
                  "payment": 0.5,
 | 
					                  "payment": 0.5,
 | 
				
			||||||
                  "curves": []
 | 
					                  "curves": []
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcOe(data, "GV",  73, 0.5m);
 | 
					                TestCalcOe(data, "GV",  73, 0.5m);
 | 
				
			||||||
                TestCalcOe(data, "WRS", 74, 0.5m);
 | 
					                TestCalcOe(data, "WRS", 74, 0.5m);
 | 
				
			||||||
@@ -57,8 +55,8 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_02_Simple() {
 | 
					        public void TestRead_02_Simple() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "elwig",
 | 
					                  "mode": "elwig",
 | 
				
			||||||
                  "version": 1,
 | 
					                  "version": 1,
 | 
				
			||||||
@@ -74,7 +72,7 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                    "geb": 0.10
 | 
					                    "geb": 0.10
 | 
				
			||||||
                  }]
 | 
					                  }]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcOe(data, "GV", 70, 0.25m);
 | 
					                TestCalcOe(data, "GV", 70, 0.25m);
 | 
				
			||||||
                TestCalcOe(data, "GV", 72, 0.25m);
 | 
					                TestCalcOe(data, "GV", 72, 0.25m);
 | 
				
			||||||
@@ -92,8 +90,8 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_03_GreaterThanAndLessThan() {
 | 
					        public void TestRead_03_GreaterThanAndLessThan() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "elwig",
 | 
					                  "mode": "elwig",
 | 
				
			||||||
                  "version": 1,
 | 
					                  "version": 1,
 | 
				
			||||||
@@ -111,7 +109,7 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  }]
 | 
					                  }]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcKmw(data, "GV", 13.00, 0.10m);
 | 
					                TestCalcKmw(data, "GV", 13.00, 0.10m);
 | 
				
			||||||
                TestCalcKmw(data, "GV", 13.50, 0.10m);
 | 
					                TestCalcKmw(data, "GV", 13.50, 0.10m);
 | 
				
			||||||
@@ -131,8 +129,8 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_04_VariantsAndAttributes() {
 | 
					        public void TestRead_04_VariantsAndAttributes() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "elwig",
 | 
					                  "mode": "elwig",
 | 
				
			||||||
                  "version": 1,
 | 
					                  "version": 1,
 | 
				
			||||||
@@ -145,7 +143,7 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  "curves": []
 | 
					                  "curves": []
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcOe(data, "WR",  73, 0.10m);
 | 
					                TestCalcOe(data, "WR",  73, 0.10m);
 | 
				
			||||||
                TestCalcOe(data, "WRS", 73, 0.15m);
 | 
					                TestCalcOe(data, "WRS", 73, 0.15m);
 | 
				
			||||||
@@ -161,8 +159,8 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_05_QualityLevel() {
 | 
					        public void TestRead_05_QualityLevel() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "elwig",
 | 
					                  "mode": "elwig",
 | 
				
			||||||
                  "version": 1,
 | 
					                  "version": 1,
 | 
				
			||||||
@@ -176,7 +174,7 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  "curves": []
 | 
					                  "curves": []
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcOe(data, "GV",  75, 0.30m, qualid: "WEI");
 | 
					                TestCalcOe(data, "GV",  75, 0.30m, qualid: "WEI");
 | 
				
			||||||
                TestCalcOe(data, "ZW",  76, 0.25m, qualid: "WEI");
 | 
					                TestCalcOe(data, "ZW",  76, 0.25m, qualid: "WEI");
 | 
				
			||||||
@@ -192,8 +190,8 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_06_ModeOeAndKmw() {
 | 
					        public void TestRead_06_ModeOeAndKmw() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "elwig",
 | 
					                  "mode": "elwig",
 | 
				
			||||||
                  "version": 1,
 | 
					                  "version": 1,
 | 
				
			||||||
@@ -218,7 +216,7 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  }]
 | 
					                  }]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcKmw(data, "GV", 15.0, 2.0m);
 | 
					                TestCalcKmw(data, "GV", 15.0, 2.0m);
 | 
				
			||||||
                TestCalcKmw(data, "GV", 15.5, 2.272727m);
 | 
					                TestCalcKmw(data, "GV", 15.5, 2.272727m);
 | 
				
			||||||
@@ -234,8 +232,8 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_07_MultipleCurves() {
 | 
					        public void TestRead_07_MultipleCurves() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "elwig",
 | 
					                  "mode": "elwig",
 | 
				
			||||||
                  "version": 1,
 | 
					                  "version": 1,
 | 
				
			||||||
@@ -278,7 +276,7 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  }]
 | 
					                  }]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcKmw(data, "GV",  15.0, 0.75m);
 | 
					                TestCalcKmw(data, "GV",  15.0, 0.75m);
 | 
				
			||||||
                TestCalcKmw(data, "GVS", 15.0, 0.50m);
 | 
					                TestCalcKmw(data, "GVS", 15.0, 0.50m);
 | 
				
			||||||
@@ -303,8 +301,8 @@ namespace Tests.Helpers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Test]
 | 
					        [Test]
 | 
				
			||||||
        public void Test_08_WgMaster() {
 | 
					        public void TestRead_08_WgMaster() {
 | 
				
			||||||
            var data = BillingData.FromJson("""
 | 
					            var data = PaymentBillingData.FromJson("""
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                  "mode": "wgmaster",
 | 
					                  "mode": "wgmaster",
 | 
				
			||||||
                  "Grundbetrag": 0.033,
 | 
					                  "Grundbetrag": 0.033,
 | 
				
			||||||
@@ -337,7 +335,7 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  }]
 | 
					                  }]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                """, AttributeVariants);
 | 
					                """, Vaributes);
 | 
				
			||||||
            Assert.Multiple(() => {
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
                TestCalcOe(data, "GVK", 73, 0.032m);
 | 
					                TestCalcOe(data, "GVK", 73, 0.032m);
 | 
				
			||||||
                TestCalcOe(data, "ZWS", 74, 0.033m);
 | 
					                TestCalcOe(data, "ZWS", 74, 0.033m);
 | 
				
			||||||
@@ -345,5 +343,162 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                TestCalcOe(data, "GVK", 115, 0.065m);
 | 
					                TestCalcOe(data, "GVK", 115, 0.065m);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static List<Varibute> GetSelection(IEnumerable<string> attVars) {
 | 
				
			||||||
 | 
					            return attVars.Select(s => {
 | 
				
			||||||
 | 
					                var sortid = s[..2];
 | 
				
			||||||
 | 
					                var attrid = s.Length > 2 ? s[2..] : null;
 | 
				
			||||||
 | 
					                return new Varibute(
 | 
				
			||||||
 | 
					                    new WineVar(sortid, sortid),
 | 
				
			||||||
 | 
					                    attrid == null ? null : new WineAttr(attrid, attrid)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }).ToList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void TestWrite_01_Empty() {
 | 
				
			||||||
 | 
					            List<GraphEntry> entries = [
 | 
				
			||||||
 | 
					                new GraphEntry(1, 4, BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                    [73] = 0.5m
 | 
				
			||||||
 | 
					                }, null)
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					            var updated = BillingData.FromGraphEntries(entries);
 | 
				
			||||||
 | 
					            Assert.That(updated.ToJsonString(JsonOpts), Is.EqualTo("""
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "mode": "elwig",
 | 
				
			||||||
 | 
					                  "version": 1,
 | 
				
			||||||
 | 
					                  "payment": 0,
 | 
				
			||||||
 | 
					                  "curves": []
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                """));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void TestWrite_02_Flatrate() {
 | 
				
			||||||
 | 
					            List<GraphEntry> entries = [
 | 
				
			||||||
 | 
					                new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                    [73] = 0.5m
 | 
				
			||||||
 | 
					                }, null), GetSelection(["GV"]))
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					            var data = BillingData.FromGraphEntries(entries);
 | 
				
			||||||
 | 
					            Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "mode": "elwig",
 | 
				
			||||||
 | 
					                  "version": 1,
 | 
				
			||||||
 | 
					                  "payment": 0.5,
 | 
				
			||||||
 | 
					                  "curves": []
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                """));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void TestWrite_03_SingleCurve() {
 | 
				
			||||||
 | 
					            List<GraphEntry> entries = [
 | 
				
			||||||
 | 
					               new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                   [73] = 0.5m,
 | 
				
			||||||
 | 
					                   [83] = 1.0m
 | 
				
			||||||
 | 
					               }, null), GetSelection(["GV"]))
 | 
				
			||||||
 | 
					           ];
 | 
				
			||||||
 | 
					            var data = BillingData.FromGraphEntries(entries);
 | 
				
			||||||
 | 
					            Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "mode": "elwig",
 | 
				
			||||||
 | 
					                  "version": 1,
 | 
				
			||||||
 | 
					                  "payment": "curve:1",
 | 
				
			||||||
 | 
					                  "curves": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                      "id": 1,
 | 
				
			||||||
 | 
					                      "mode": "oe",
 | 
				
			||||||
 | 
					                      "data": {
 | 
				
			||||||
 | 
					                        "73oe": 0.5,
 | 
				
			||||||
 | 
					                        "83oe": 1
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  ]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                """));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void TestWrite_04_Simple() {
 | 
				
			||||||
 | 
					            List<GraphEntry> entries = [
 | 
				
			||||||
 | 
					               new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                   [73] = 0.5m,
 | 
				
			||||||
 | 
					                   [84] = 1.0m
 | 
				
			||||||
 | 
					               }, null), GetSelection(["GV", "ZW"])),
 | 
				
			||||||
 | 
					                new GraphEntry(10, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                    [73] = 0.75m,
 | 
				
			||||||
 | 
					                }, null), GetSelection(["WR"]))
 | 
				
			||||||
 | 
					           ];
 | 
				
			||||||
 | 
					            var data = BillingData.FromGraphEntries(entries);
 | 
				
			||||||
 | 
					            Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "mode": "elwig",
 | 
				
			||||||
 | 
					                  "version": 1,
 | 
				
			||||||
 | 
					                  "payment": {
 | 
				
			||||||
 | 
					                    "WR/": 0.75,
 | 
				
			||||||
 | 
					                    "default": "curve:1"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "curves": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                      "id": 1,
 | 
				
			||||||
 | 
					                      "mode": "oe",
 | 
				
			||||||
 | 
					                      "data": {
 | 
				
			||||||
 | 
					                        "73oe": 0.5,
 | 
				
			||||||
 | 
					                        "84oe": 1
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  ]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                """));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void TestWrite_05_Attribute() {
 | 
				
			||||||
 | 
					            List<GraphEntry> entries = [
 | 
				
			||||||
 | 
					               new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                   [73] = 0.5m,
 | 
				
			||||||
 | 
					                   [84] = 1.0m
 | 
				
			||||||
 | 
					               }, null), GetSelection(["GVB", "ZWB"])),
 | 
				
			||||||
 | 
					                new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                    [73] = 0.75m,
 | 
				
			||||||
 | 
					                }, null), GetSelection(["WR", "BL", "RR", "FV"])),
 | 
				
			||||||
 | 
					                new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() {
 | 
				
			||||||
 | 
					                    [73] = 0.65m,
 | 
				
			||||||
 | 
					                    [84] = 1.2m
 | 
				
			||||||
 | 
					                }, null), GetSelection(["BP", "SA"]))
 | 
				
			||||||
 | 
					           ];
 | 
				
			||||||
 | 
					            var data = BillingData.FromGraphEntries(entries);
 | 
				
			||||||
 | 
					            Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo("""
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "mode": "elwig",
 | 
				
			||||||
 | 
					                  "version": 1,
 | 
				
			||||||
 | 
					                  "payment": {
 | 
				
			||||||
 | 
					                    "BP/": "curve:2",
 | 
				
			||||||
 | 
					                    "SA/": "curve:2",
 | 
				
			||||||
 | 
					                    "default": 0.75,
 | 
				
			||||||
 | 
					                    "/B": "curve:1"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "curves": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                      "id": 1,
 | 
				
			||||||
 | 
					                      "mode": "oe",
 | 
				
			||||||
 | 
					                      "data": {
 | 
				
			||||||
 | 
					                        "73oe": 0.5,
 | 
				
			||||||
 | 
					                        "84oe": 1
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                      "id": 2,
 | 
				
			||||||
 | 
					                      "mode": "oe",
 | 
				
			||||||
 | 
					                      "data": {
 | 
				
			||||||
 | 
					                        "73oe": 0.65,
 | 
				
			||||||
 | 
					                        "84oe": 1.2
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  ]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                """));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										349
									
								
								Tests/HelperTests/BillingTest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								Tests/HelperTests/BillingTest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,349 @@
 | 
				
			|||||||
 | 
					using Elwig.Helpers;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Billing;
 | 
				
			||||||
 | 
					using Microsoft.Data.Sqlite;
 | 
				
			||||||
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Tests.HelperTests {
 | 
				
			||||||
 | 
					    [TestFixture]
 | 
				
			||||||
 | 
					    public class BillingTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private const int Year1 = 2020, Year2 = 2021;
 | 
				
			||||||
 | 
					        private const int MgNr1 = 101, MgNr2 = 102, MgNr3 = 103, MgNr4 = 104;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private const decimal
 | 
				
			||||||
 | 
					            GV_ungeb  = 0.50m,
 | 
				
			||||||
 | 
					            GV_geb    = 0.60m,
 | 
				
			||||||
 | 
					            GVB_ungeb = 0.54m,
 | 
				
			||||||
 | 
					            GVB_geb   = 0.64m,
 | 
				
			||||||
 | 
					            GVK_ungeb = 0.61m,
 | 
				
			||||||
 | 
					            GVK_geb   = 0.71m,
 | 
				
			||||||
 | 
					            WEI       = 0.10m;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private SqliteConnection? Connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [OneTimeSetUp]
 | 
				
			||||||
 | 
					        public async Task SetupDatabase() {
 | 
				
			||||||
 | 
					            Connection = await AppDbContext.ConnectAsync();
 | 
				
			||||||
 | 
					            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingInsert.sql");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [OneTimeTearDown]
 | 
				
			||||||
 | 
					        public async Task TeardownDatabase() {
 | 
				
			||||||
 | 
					            if (Connection == null) return;
 | 
				
			||||||
 | 
					            await AppDbContext.ExecuteEmbeddedScript(Connection, Assembly.GetExecutingAssembly(), "Tests.Resources.Sql.BillingDelete.sql");
 | 
				
			||||||
 | 
					            await Connection.DisposeAsync();
 | 
				
			||||||
 | 
					            Connection = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [SetUp]
 | 
				
			||||||
 | 
					        public async Task CreatePaymentVariant() {
 | 
				
			||||||
 | 
					            var json = """
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  "mode": "elwig",
 | 
				
			||||||
 | 
					                  "version": 1,
 | 
				
			||||||
 | 
					                  "payment": {
 | 
				
			||||||
 | 
					                    "GV/": "curve:0",
 | 
				
			||||||
 | 
					                    "GV/B": "curve:1",
 | 
				
			||||||
 | 
					                    "GV/K": "curve:2"
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  "quality": {"WEI": 0.1},
 | 
				
			||||||
 | 
					                  "curves": [{
 | 
				
			||||||
 | 
					                    "id": 0,
 | 
				
			||||||
 | 
					                    "mode": "oe",
 | 
				
			||||||
 | 
					                    "data": {"15kmw": 0.5},
 | 
				
			||||||
 | 
					                    "geb": 0.1
 | 
				
			||||||
 | 
					                  }, {
 | 
				
			||||||
 | 
					                    "id": 1,
 | 
				
			||||||
 | 
					                    "mode": "oe",
 | 
				
			||||||
 | 
					                    "data": {"15kmw": 0.54},
 | 
				
			||||||
 | 
					                    "geb": 0.1
 | 
				
			||||||
 | 
					                  }, {
 | 
				
			||||||
 | 
					                    "id": 2,
 | 
				
			||||||
 | 
					                    "mode": "oe",
 | 
				
			||||||
 | 
					                    "data": {"15kmw": 0.61},
 | 
				
			||||||
 | 
					                    "geb": 0.1
 | 
				
			||||||
 | 
					                  }]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                """;
 | 
				
			||||||
 | 
					            await InsertPaymentVariant(Year1, 1, json);
 | 
				
			||||||
 | 
					            await InsertPaymentVariant(Year2, 1, json);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TearDown]
 | 
				
			||||||
 | 
					        public async Task CleanupDatabasePayment() {
 | 
				
			||||||
 | 
					            if (Connection == null) return;
 | 
				
			||||||
 | 
					            await AppDbContext.ExecuteBatch(Connection, """
 | 
				
			||||||
 | 
					                DELETE FROM credit;
 | 
				
			||||||
 | 
					                DELETE FROM payment_variant;
 | 
				
			||||||
 | 
					                DELETE FROM delivery_part_bucket;
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Task<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr) {
 | 
				
			||||||
 | 
					            var ctx = new AppDbContext();
 | 
				
			||||||
 | 
					            return ctx.GetMemberAreaCommitmentBuckets(year, mgnr, Connection);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Task<Dictionary<string, int>> GetMemberDeliveryBuckets(int year, int mgnr) {
 | 
				
			||||||
 | 
					            var ctx = new AppDbContext();
 | 
				
			||||||
 | 
					            return ctx.GetMemberDeliveryBuckets(year, mgnr, Connection);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Task<Dictionary<string, int>> GetMemberPaymentBuckets(int year, int mgnr) {
 | 
				
			||||||
 | 
					            var ctx = new AppDbContext();
 | 
				
			||||||
 | 
					            return ctx.GetMemberPaymentBuckets(year, mgnr, Connection);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private async Task<Dictionary<(string, string), (int, decimal)>> GetMemberDeliveryPrices(int year, int mgnr) {
 | 
				
			||||||
 | 
					            var buckets = new Dictionary<(string, string), (int, decimal)>();
 | 
				
			||||||
 | 
					            using (var cmd = Connection!.CreateCommand()) {
 | 
				
			||||||
 | 
					                cmd.CommandText = $"""
 | 
				
			||||||
 | 
					                    SELECT lsnr || '/' ||  d.dpnr, d.sortid || b.discr, b.value, p.price
 | 
				
			||||||
 | 
					                    FROM v_delivery d
 | 
				
			||||||
 | 
					                        LEFT JOIN payment_delivery_part_bucket p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
 | 
				
			||||||
 | 
					                        LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr, b.bktnr) = (p.year, p.did, p.dpnr, p.bktnr)
 | 
				
			||||||
 | 
					                    WHERE d.year = {year} AND mgnr = {mgnr}
 | 
				
			||||||
 | 
					                    """;
 | 
				
			||||||
 | 
					                using var reader = await cmd.ExecuteReaderAsync();
 | 
				
			||||||
 | 
					                while (await reader.ReadAsync()) {
 | 
				
			||||||
 | 
					                    var lsnr = reader.GetString(0);
 | 
				
			||||||
 | 
					                    var bucket = reader.GetString(1);
 | 
				
			||||||
 | 
					                    buckets[(lsnr, bucket)] = (reader.GetInt32(2), Utils.DecFromDb(reader.GetInt32(3), 4));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return buckets;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Task InsertPaymentVariant(int year, int avnr, string data) {
 | 
				
			||||||
 | 
					            return AppDbContext.ExecuteBatch(Connection!, $"""
 | 
				
			||||||
 | 
					                INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, data)
 | 
				
			||||||
 | 
					                VALUES ({year}, {avnr}, 'Test', '2021-01-15', NULL, TRUE, NULL, '{data}');
 | 
				
			||||||
 | 
					                """);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_01_NoActiveAreaComs() {
 | 
				
			||||||
 | 
					            int mgnr = MgNr1, year = Year1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var areaCom = await GetMemberAreaCommitmentBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.That(areaCom, Is.Empty);
 | 
				
			||||||
 | 
					            var delivery = await GetMemberDeliveryBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(delivery, Has.Count.EqualTo(4));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GV"],  Is.EqualTo(16_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GVB"], Is.EqualTo( 8_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GVK"], Is.EqualTo( 4_000));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            BillingVariant b = new(year, 1);
 | 
				
			||||||
 | 
					            await b.CalculateBuckets(false, false, false);
 | 
				
			||||||
 | 
					            var payment = await GetMemberPaymentBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(payment, Has.Count.EqualTo(1));
 | 
				
			||||||
 | 
					                Assert.That(payment["GV_"], Is.EqualTo(17_000));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await b.Calculate();
 | 
				
			||||||
 | 
					            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(prices, Has.Count.EqualTo(6));
 | 
				
			||||||
 | 
					                // Kabinett
 | 
				
			||||||
 | 
					                Assert.That(prices[("20201001X001/1", "GV_")], Is.EqualTo((4_000, GV_ungeb)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20201001X001/2", "GV_")], Is.EqualTo((4_000, GV_ungeb)));
 | 
				
			||||||
 | 
					                // Bio
 | 
				
			||||||
 | 
					                Assert.That(prices[("20201001X002/1", "GV_")], Is.EqualTo((4_000, GVB_ungeb)));
 | 
				
			||||||
 | 
					                // Bio
 | 
				
			||||||
 | 
					                Assert.That(prices[("20201001X002/2", "GV_")], Is.EqualTo((4_000, GVB_ungeb)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20201001X003/1", "GV_")], Is.EqualTo((  500, WEI)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20201001X003/2", "GV_")], Is.EqualTo((  500, GV_ungeb)));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_02_SimpleNotStrictAreaComs() {
 | 
				
			||||||
 | 
					            int mgnr = MgNr1, year = Year2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var areaCom = await GetMemberAreaCommitmentBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(areaCom, Has.Count.EqualTo(1));
 | 
				
			||||||
 | 
					                Assert.That(areaCom["GV"], Is.EqualTo(new AreaComBucket(10_000, 5_000, 10_000)));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            var delivery = await GetMemberDeliveryBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(delivery, Has.Count.EqualTo(4));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GV"],  Is.EqualTo(16_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GVB"], Is.EqualTo( 8_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GVK"], Is.EqualTo( 4_000));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            BillingVariant b = new(year, 1);
 | 
				
			||||||
 | 
					            await b.CalculateBuckets(false, false, false, Connection);
 | 
				
			||||||
 | 
					            var payment = await GetMemberPaymentBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(payment, Has.Count.EqualTo(2));
 | 
				
			||||||
 | 
					                Assert.That(payment["GV_"], Is.EqualTo( 7_000));
 | 
				
			||||||
 | 
					                Assert.That(payment["GV"],  Is.EqualTo(10_000));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await b.Calculate(false, false, false);
 | 
				
			||||||
 | 
					            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(prices, Has.Count.EqualTo(10));
 | 
				
			||||||
 | 
					                // Kabinett
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X001/1", "GV_")], Is.EqualTo((2_000, GV_ungeb)));
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X001/1", "GV")] , Is.EqualTo((2_000, GV_geb)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X001/2", "GV_")], Is.EqualTo((4_000, GV_ungeb)));
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X001/2", "GV")],  Is.EqualTo((    0, GV_geb)));
 | 
				
			||||||
 | 
					                // Bio
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X002/1", "GV_")], Is.EqualTo((    0, GVB_ungeb)));
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X002/1", "GV")],  Is.EqualTo((4_000, GVB_geb)));
 | 
				
			||||||
 | 
					                // Bio
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X002/2", "GV_")], Is.EqualTo((    0, GVB_ungeb)));
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X002/2", "GV")],  Is.EqualTo((4_000, GVB_geb)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X003/1", "GV_")], Is.EqualTo((  500, WEI)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X003/2", "GV_")], Is.EqualTo((  500, GV_ungeb)));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_03_SimpleNotStrictAreaComs_HonorGebunden() {
 | 
				
			||||||
 | 
					            int mgnr = MgNr1, year = Year2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var areaCom = await GetMemberAreaCommitmentBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(areaCom, Has.Count.EqualTo(1));
 | 
				
			||||||
 | 
					                Assert.That(areaCom["GV"], Is.EqualTo(new AreaComBucket(10_000, 5_000, 10_000)));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            var delivery = await GetMemberDeliveryBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(delivery, Has.Count.EqualTo(4));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GV"],  Is.EqualTo(16_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GV_"], Is.EqualTo( 1_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GVB"], Is.EqualTo( 8_000));
 | 
				
			||||||
 | 
					                Assert.That(delivery["GVK"], Is.EqualTo( 4_000));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            BillingVariant b = new(year, 1);
 | 
				
			||||||
 | 
					            await b.CalculateBuckets(true, false, false, Connection);
 | 
				
			||||||
 | 
					            var payment = await GetMemberPaymentBuckets(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(payment, Has.Count.EqualTo(2));
 | 
				
			||||||
 | 
					                Assert.That(payment["GV_"], Is.EqualTo(9_000));
 | 
				
			||||||
 | 
					                Assert.That(payment["GV"],  Is.EqualTo(8_000));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await b.Calculate(true, false, false);
 | 
				
			||||||
 | 
					            var prices = await GetMemberDeliveryPrices(year, mgnr);
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(prices, Has.Count.EqualTo(8));
 | 
				
			||||||
 | 
					                // Kabinett
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X001/1", "GV_")], Is.EqualTo((    0, GV_ungeb)));
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X001/1", "GV")],  Is.EqualTo((4_000, GV_geb)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X001/2", "GV_")], Is.EqualTo((4_000, GV_ungeb)));
 | 
				
			||||||
 | 
					                // Bio
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X002/1", "GV_")], Is.EqualTo((    0, GVB_ungeb)));
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X002/1", "GV")],  Is.EqualTo((4_000, GVB_geb)));
 | 
				
			||||||
 | 
					                // Bio
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X002/2", "GV_")], Is.EqualTo((4_000, GVB_ungeb)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X003/1", "GV_")], Is.EqualTo((  500, WEI)));
 | 
				
			||||||
 | 
					                // ohne Attribut
 | 
				
			||||||
 | 
					                Assert.That(prices[("20211001X003/2", "GV_")], Is.EqualTo((  500, GV_ungeb)));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_04_ComplexNotStrictAreaComs() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_05_ComplexNotStrictAreaComs_HonorGebunden() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_06_StrictAreaComs_NoFillLower_NotAllowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_07_StrictAreaComs_NoFillLower_Allowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_08_StrictAreaComs_NoFillLower_Allowed_AvoidUnderDeliveries() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_09_StrictAreaComs_FillLowerUntilObligation_NotAllowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_10_StrictAreaComs_FillLowerUntilObligation_Allowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_11_StrictAreaComs_FillLowerUntilObligation_Allowed_AvoidUnderDeliveries() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_12_StrictAreaComs_FillLowerUntilObligation_NotAllowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_13_StrictAreaComs_FillLowerUntilObligation_Allowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_14_StrictAreaComs_FillLowerUntilObligation_Allowed_AvoidUnderDeliveries() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_15_StrictAreaComs_FillLowerUntilRight_NotAllowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_16_StrictAreaComs_FillLowerUntilRight_Allowed() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Not implemented yet")]
 | 
				
			||||||
 | 
					        public async Task Test_17_StrictAreaComs_FillLowerUntilRight_Allowed_AvoidUnderDeliveries() {
 | 
				
			||||||
 | 
					            // TODO
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										113
									
								
								Tests/HelperTests/EbicsTest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Tests/HelperTests/EbicsTest.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					using Elwig.Helpers;
 | 
				
			||||||
 | 
					using Elwig.Helpers.Export;
 | 
				
			||||||
 | 
					using Elwig.Models.Dtos;
 | 
				
			||||||
 | 
					using Elwig.Models.Entities;
 | 
				
			||||||
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					using System.Xml;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Tests.HelperTests {
 | 
				
			||||||
 | 
					    // see https://www.iso20022.org/iso-20022-message-definitions
 | 
				
			||||||
 | 
					    // and https://www.iso20022.org/catalogue-messages/iso-20022-messages-archive?search=pain
 | 
				
			||||||
 | 
					    [TestFixture]
 | 
				
			||||||
 | 
					    public class EbicsTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static readonly string FileName = Path.Combine(Path.GetTempPath(), "test_ebics.xml");
 | 
				
			||||||
 | 
					        public static readonly string Iban = "AT123456789012345678";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static void ValidateSchema(string xmlPath, int version) {
 | 
				
			||||||
 | 
					            XmlDocument xml = new();
 | 
				
			||||||
 | 
					            xml.Load(xmlPath);
 | 
				
			||||||
 | 
					            var schema = new XmlTextReader(Assembly.GetExecutingAssembly()
 | 
				
			||||||
 | 
					                .GetManifestResourceStream($"Tests.Resources.Schemas.pain.001.001.{version:00}.xsd")!);
 | 
				
			||||||
 | 
					            xml.Schemas.Add(null, schema);
 | 
				
			||||||
 | 
					            xml.Validate(null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static async Task CreateXmlFile(int version) {
 | 
				
			||||||
 | 
					            var v = new PaymentVar() {
 | 
				
			||||||
 | 
					                Year = 2020,
 | 
				
			||||||
 | 
					                AvNr = 1,
 | 
				
			||||||
 | 
					                Name = "Endauszahlung",
 | 
				
			||||||
 | 
					                TransferDate = new DateOnly(2021, 6, 15),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            using var ctx = new AppDbContext();
 | 
				
			||||||
 | 
					            var members = ctx.Members.ToList();
 | 
				
			||||||
 | 
					            Assert.That(members, Has.Count.GreaterThan(0));
 | 
				
			||||||
 | 
					            using var exporter = new Ebics(v, FileName, version);
 | 
				
			||||||
 | 
					            await exporter.ExportAsync(members.Select(m => new Transaction(m, 1234.56m, "EUR", m.MgNr % 100)));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [TearDown]
 | 
				
			||||||
 | 
					        public static void RemoveXmlFile() {
 | 
				
			||||||
 | 
					            File.Delete(FileName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Version has no need to be supported")]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV01() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(1);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 1));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        [Ignore("Version has no need to be supported")]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV02() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(2);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 2));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV03() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(3);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 3));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV04() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(4);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 4));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV05() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(5);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 5));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV06() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(6);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 6));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV07() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(7);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 7));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV08() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(8);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 8));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV09() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(9);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 9));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV10() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(10);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 10));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public async Task Test_CustomerCreditTransferInitiationV11() {
 | 
				
			||||||
 | 
					            await CreateXmlFile(11);
 | 
				
			||||||
 | 
					            Assert.DoesNotThrow(() => ValidateSchema(FileName, 11));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Tests.Helpers {
 | 
					namespace Tests.HelperTests {
 | 
				
			||||||
    [TestFixture]
 | 
					    [TestFixture]
 | 
				
			||||||
    public class UtilsTest {
 | 
					    public class UtilsTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -100,5 +100,52 @@ namespace Tests.Helpers {
 | 
				
			|||||||
                Assert.That(Utils.SplitName("ABC GesbR", "Bauer"), Is.EqualTo(((string, string?))("ABC GesbR", null)));
 | 
					                Assert.That(Utils.SplitName("ABC GesbR", "Bauer"), Is.EqualTo(((string, string?))("ABC GesbR", null)));
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [Test]
 | 
				
			||||||
 | 
					        public void Test_CalcCrc16Modbus() {
 | 
				
			||||||
 | 
					            Assert.Multiple(() => {
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus(""), Is.EqualTo(0xFFFF));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("abcd"), Is.EqualTo(0x1D97));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("ABCD"), Is.EqualTo(0x0F85));
 | 
				
			||||||
 | 
					                // Matzen (SysTec IT3000A)
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:03   01       0       0       0kg     1"), Is.EqualTo(43166));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:04   01       0       0       0kg     1"), Is.EqualTo(21615));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:05   01       0       0       0kg     1"), Is.EqualTo(40446));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:06   01       0       0       0kg     1"), Is.EqualTo(34638));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:07   01       0       0       0kg     1"), Is.EqualTo(20191));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:08   01       0       0       0kg     1"), Is.EqualTo(16047));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:09   01       0       0       0kg     1"), Is.EqualTo(63294));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:10   01       0       0       0kg     1"), Is.EqualTo(7718));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000011.08.2319:12   01       0       0       0kg     1"), Is.EqualTo(52487));
 | 
				
			||||||
 | 
					                // Wolkersdorf, Waage 1 (SysTec IT6000E)
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:50   51       0       0       0kg     A"), Is.EqualTo(10187));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:50   61       0       0       0kg     A"), Is.EqualTo(20683));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:50   71       0       0       0kg     A"), Is.EqualTo(48586));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:50   81       0       0       0kg     A"), Is.EqualTo(22217));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:50   91       0       0       0kg     A"), Is.EqualTo(48072));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51  101       0       0       0kg     A"), Is.EqualTo(30119));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51  111       0       0       0kg     A"), Is.EqualTo(39078));
 | 
				
			||||||
 | 
					                // Wolkersdorf, Waage 2 (SysTec IT6000E)
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51   41       0       0       0kg     B"), Is.EqualTo(539));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51   51       0       0       0kg     B"), Is.EqualTo(61210));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51   61       0       0       0kg     B"), Is.EqualTo(38938));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51   71       0       0       0kg     B"), Is.EqualTo(29979));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51   81       0       0       0kg     B"), Is.EqualTo(40472));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51   91       0       0       0kg     B"), Is.EqualTo(29465));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000004.09.2315:51  101       0       0       0kg     B"), Is.EqualTo(29927));
 | 
				
			||||||
 | 
					                // Gr.Inzersdorf (L246/IT3)
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:49   11       0       0       0kg   001"), Is.EqualTo(27556));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:49   21       0       0       0kg   001"), Is.EqualTo(7332));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:49   31       0       0       0kg   001"), Is.EqualTo(61861));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:49   41       0       0       0kg   001"), Is.EqualTo(62116));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:49   51       0       0       0kg   001"), Is.EqualTo(8101));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:49   61       0       0       0kg   001"), Is.EqualTo(26789));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:49   71      50       0      50kg   001"), Is.EqualTo(16188));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:50   81      35       0      35kg   001"), Is.EqualTo(12015));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:50   91      40       0      40kg   001"), Is.EqualTo(60047));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:50  101      40       0      40kg   001"), Is.EqualTo(60785));
 | 
				
			||||||
 | 
					                Assert.That(Utils.CalcCrc16Modbus("000019.02.2410:50  111      45       0      45kg   001"), Is.EqualTo(35918));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
using Elwig.Helpers;
 | 
					using Elwig.Helpers;
 | 
				
			||||||
using System.Windows.Controls;
 | 
					using System.Windows.Controls;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Tests.Helpers {
 | 
					namespace Tests.HelperTests {
 | 
				
			||||||
    [TestFixture]
 | 
					    [TestFixture]
 | 
				
			||||||
    [Apartment(ApartmentState.STA)]
 | 
					    [Apartment(ApartmentState.STA)]
 | 
				
			||||||
    public class ValidatorTest {
 | 
					    public class ValidatorTest {
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user