Compare commits

..

29 Commits

Author SHA1 Message Date
35e5a1dfff Revert wix extensions to v4.0.5
Some checks failed
Deploy / Build and Deploy (push) Has been cancelled
2024-05-01 12:35:35 +02:00
179c8bd4f7 Bump version to 0.8.0 2024-05-01 12:08:26 +02:00
fd17d294b9 Update dependencies 2024-05-01 10:19:57 +02:00
2a4e8d69d0 MainWindow: Add WeightBreakdownButton 2024-04-30 23:34:31 +02:00
8bf8362480 App: Replace DataTemplate/ControlTemplate with TemplateSelector 2024-04-30 13:02:45 +02:00
c4d68d11bc Controls: Rewrite UnitTextBox as extension of TextBox instead of UserControl 2024-04-28 20:07:09 +02:00
21fe5bc094 Update dependencies 2024-04-18 16:13:09 +02:00
e7bfc69842 [#3] Elwig: Add feature to sync deliveries 2024-04-15 15:22:04 +02:00
f53371ab19 Helpers: Collapse extensions into one single file 2024-04-15 13:04:49 +02:00
5f8688f0cd Small fixes 2024-04-15 11:36:55 +02:00
fa00eaaefc Tests: Remove Console.WriteLine 2024-04-13 18:00:54 +02:00
443e111594 Weighing: Add Gassner scale and update tests 2024-04-13 18:00:17 +02:00
c6905bbb42 Elwig: Update dependencies 2024-04-06 17:37:41 +02:00
c360e6b6a7 MainWindow: Add feedback for users when App.CheckForUpdates finds no updates 2024-04-06 17:35:30 +02:00
9062d55b20 BaseDataWindow: Fix Parameter in FillInputs() 2024-03-31 20:43:53 +02:00
a9f38a3ccb Windows: Remove SeasonFinishWindow and TestWindow 2024-03-31 17:11:10 +02:00
cbc0d0ebff Tests: Rename class 2024-03-30 15:21:15 +01:00
27b5d653e6 WineQualityStatistics: Add and fix grouping by KMW 2024-03-30 15:21:02 +01:00
869f652afc WineQualityStatistics: Add KMW mode 2024-03-30 13:12:54 +01:00
80e91ad776 Tests: Add CreditNoteTest 2024-03-30 12:42:22 +01:00
bce709efe4 Tests: Remove Console.WriteLine()s 2024-03-30 11:28:28 +01:00
12eb53cb44 Tests: Add DeliveryConfirmationTest 2024-03-30 11:16:51 +01:00
657910ff48 Tests: Add WineQualityStatisticsTest 2024-03-30 11:09:39 +01:00
5c3cf41d3d Tests: Add DeliveryJournalTest 2024-03-30 10:59:42 +01:00
b8851fb241 Tests: Rename LetterheadTest 2024-03-30 09:57:45 +01:00
66898714bb Tests: Add more DocumentsTests 2024-03-30 09:51:48 +01:00
1047bc6e8f BaseDataWindow: Fix data renewal 2024-03-30 08:49:11 +01:00
1419c834ac BaseDataWindow: Fix season modifier crash 2024-03-30 08:39:57 +01:00
eddea88e77 Documents: Use border thickness of 0.5pt 2024-03-28 20:34:59 +01:00
80 changed files with 2286 additions and 629 deletions

View File

@ -7,7 +7,7 @@
Exit="Application_Exit">
<Application.Resources>
<ctrl:BoolToStringConverter x:Key="BoolToStarConverter" FalseValue="" TrueValue="*"/>
<ctrl:WidthToPaddingConverter x:Key="WidthToPaddingConverter"/>
<ctrl:WidthToMarginConverter x:Key="WidthToMarginConverter"/>
<DataTemplate x:Key="PostalDestTemplate">
<StackPanel Orientation="Horizontal">
@ -29,25 +29,17 @@
</StackPanel>
</DataTemplate>
<ControlTemplate x:Key="WineVarietyTemplateSimple">
<DataTemplate x:Key="WineVarietyTemplateCollapsed">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="WineVarietyTemplateExtended">
</DataTemplate>
<DataTemplate x:Key="WineVarietyTemplateExpanded">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SortId}" MinWidth="36" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding CommentFormat}" FontSize="10" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
</StackPanel>
</ControlTemplate>
<DataTemplate x:Key="WineVarietyTemplate">
<Control x:Name="Control" Focusable="False" Template="{StaticResource WineVarietyTemplateExtended}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="Control" Property="Template" Value="{StaticResource WineVarietyTemplateSimple}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="ModifierTemplate">
@ -63,25 +55,17 @@
</StackPanel>
</DataTemplate>
<ControlTemplate x:Key="WineQualityLevelTemplateSimple">
<DataTemplate x:Key="WineQualityLevelTemplateCollapsed">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="WineQualityLevelTemplateExtended">
</DataTemplate>
<DataTemplate x:Key="WineQualityLevelTemplateExpanded">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding IsPredicate, Converter={StaticResource BoolToStarConverter}}" MinWidth="6"/>
<TextBlock Text="{Binding Name}" MinWidth="100" Margin="0,0,10,0"/>
<TextBlock Text="{Binding MinKmwStr}"/>
</StackPanel>
</ControlTemplate>
<DataTemplate x:Key="WineQualityLevelTemplate">
<Control x:Name="Control" Focusable="False" Template="{StaticResource WineQualityLevelTemplateExtended}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="Control" Property="Template" Value="{StaticResource WineQualityLevelTemplateSimple}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="WineOriginTemplate">
@ -90,24 +74,16 @@
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<ControlTemplate x:Key="WineOriginTemplateSimple">
<DataTemplate x:Key="WineOriginTemplateCollapsed">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</ControlTemplate>
<ControlTemplate x:Key="WineOriginTemplateExtended">
</DataTemplate>
<DataTemplate x:Key="WineOriginTemplateExpanded">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HkIdLevel}" MinWidth="70" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</ControlTemplate>
<DataTemplate x:Key="WineOriginComboTemplate">
<Control x:Name="Control" Focusable="False" Template="{StaticResource WineOriginTemplateExtended}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="Control" Property="Template" Value="{StaticResource WineOriginTemplateSimple}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="GemTemplate">

View File

@ -212,11 +212,11 @@ namespace Elwig {
if (w is UpdateDialog) return;
}
if (Utils.HasInternetConnectivity()) {
Utils.RunBackground("Auto Updater", CheckForUpdates);
Utils.RunBackground("Auto Updater", async () => await CheckForUpdates());
}
}
public static async Task CheckForUpdates() {
public static async Task CheckForUpdates(bool showSuccess = false) {
if (Config.UpdateUrl == null) return;
var latest = await Utils.GetLatestInstallerUrl(Config.UpdateUrl);
if (latest != null && new Version(latest.Value.Version) > new Version(Version)) {
@ -227,6 +227,9 @@ namespace Elwig {
Current.Shutdown();
}
});
} else if (showSuccess) {
MessageBox.Show("Elwig ist auf dem aktuellsten Stand!", "Nach Updates suchen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
@ -273,10 +276,6 @@ namespace Elwig {
return w;
}
public static SeasonFinishWindow FocusSeasonFinish() {
return FocusWindow<SeasonFinishWindow>(() => new());
}
public static OriginHierarchyWindow FocusOriginHierarchy() {
return FocusWindow<OriginHierarchyWindow>(() => new());
}

View File

@ -0,0 +1,17 @@
using System.Windows;
using System.Windows.Controls;
namespace Elwig.Controls {
public class UnitTextBox : TextBox {
public static readonly DependencyProperty UnitProperty = DependencyProperty.Register("Unit", typeof(string), typeof(UnitTextBox), new FrameworkPropertyMetadata(""));
public string Unit {
get => (string)GetValue(UnitProperty);
set => SetValue(UnitProperty, value);
}
static UnitTextBox() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(UnitTextBox), new FrameworkPropertyMetadata(typeof(UnitTextBox)));
}
}
}

View File

@ -1,11 +1,30 @@
<UserControl x:Class="Elwig.Controls.UnitTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Height="25">
<TextBox x:Name="TextBox" TextAlignment="Right" FontSize="14" VerticalAlignment="Stretch"
Padding="{Binding ElementName=UnitBlock, Path=ActualWidth, Converter={StaticResource WidthToPaddingConverter}}"
Text="{Binding Path=Text}" TextChanged="TextBox_TextChanged"/>
<TextBlock x:Name="UnitBlock" Text="{Binding Path=Unit}" Margin="0,0,4,4" FontSize="10"
HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
</UserControl>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:Elwig.Controls">
<Style TargetType="ctrl:UnitTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrl:UnitTextBox">
<Border BorderThickness="{Binding Path=BorderThickness, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
BorderBrush="{Binding Path=BorderBrush, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
SnapsToDevicePixels="True">
<Grid>
<ScrollViewer x:Name="PART_ContentHost" VerticalAlignment="Bottom">
<ScrollViewer.Margin>
<Binding ElementName="UnitBlock" Path="ActualWidth">
<Binding.Converter>
<ctrl:WidthToMarginConverter/>
</Binding.Converter>
</Binding>
</ScrollViewer.Margin>
</ScrollViewer>
<TextBlock x:Name="UnitBlock" Text="{Binding Path=Unit, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
FontSize="10" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="3"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="TextAlignment" Value="Right"/>
</Style>
</ResourceDictionary>

View File

@ -1,37 +0,0 @@
using System.Windows;
using System.Windows.Controls;
namespace Elwig.Controls {
public partial class UnitTextBox : UserControl {
public event TextChangedEventHandler? TextChanged;
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UnitTextBox));
public string Text {
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value ?? "");
}
public static readonly DependencyProperty UnitProperty = DependencyProperty.Register("Unit", typeof(string), typeof(UnitTextBox));
public string Unit {
get => (string)GetValue(UnitProperty);
set => SetValue(UnitProperty, value ?? "");
}
public UnitTextBox() {
Text = "";
Unit = "";
InitializeComponent();
DataContext = this;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs evt) {
Text = TextBox.Text;
TextChanged?.Invoke(sender, evt);
}
public new void Focus() {
TextBox.Focus();
}
}
}

View File

@ -4,13 +4,13 @@ using System.Windows.Data;
using System.Globalization;
namespace Elwig.Controls {
public class WidthToPaddingConverter : IValueConverter {
public class WidthToMarginConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return new Thickness(2, 2, 4 + (double)value, 2);
return new Thickness(0, 0, 2 + (double)value, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return ((Thickness)value).Right - 4;
return ((Thickness)value).Right - 2;
}
}
}

View File

@ -0,0 +1,15 @@
using System.Windows.Controls;
using System.Windows;
namespace Elwig.Controls {
public class WineOriginTemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
ContentPresenter presenter = (ContentPresenter)container;
if (presenter.TemplatedParent is ComboBox) {
return (DataTemplate)presenter.FindResource("WineOriginTemplateCollapsed");
} else {
return (DataTemplate)presenter.FindResource("WineOriginTemplateExpanded");
}
}
}
}

View File

@ -0,0 +1,15 @@
using System.Windows.Controls;
using System.Windows;
namespace Elwig.Controls {
public class WineQualityLevelTemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
ContentPresenter presenter = (ContentPresenter)container;
if (presenter.TemplatedParent is ComboBox) {
return (DataTemplate)presenter.FindResource("WineQualityLevelTemplateCollapsed");
} else {
return (DataTemplate)presenter.FindResource("WineQualityLevelTemplateExpanded");
}
}
}
}

View File

@ -0,0 +1,15 @@
using System.Windows.Controls;
using System.Windows;
namespace Elwig.Controls {
public class WineVarietyTemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
ContentPresenter presenter = (ContentPresenter)container;
if (presenter.TemplatedParent is ComboBox) {
return (DataTemplate)presenter.FindResource("WineVarietyTemplateCollapsed");
} else {
return (DataTemplate)presenter.FindResource("WineVarietyTemplateExpanded");
}
}
}
}

View File

@ -2,7 +2,7 @@
:root {
font-family: "Times New Roman", serif;
line-height: 1;
--border-thickness: 0.05pt;
--border-thickness: 0.5pt;
}
* {

View File

@ -17,6 +17,7 @@ namespace Elwig.Documents {
public string Filter;
public WineQualityStatisticsData Data;
public bool UseOe => Data.UseOe;
public WineQualityStatistics(string filter, WineQualityStatisticsData data) : base($"{Name} {filter}") {
Filter = filter;

View File

@ -1,4 +1,5 @@
@using RazorLight
@using Elwig.Helpers
@inherits TemplatePage<Elwig.Documents.WineQualityStatistics>
@model Elwig.Documents.WineQualityStatistics
@{ Layout = "Document"; }
@ -26,15 +27,15 @@
@foreach (var qualIds in Model.QualIds) {
<td class="container">
<div class="row">
<span class="units">[°Oe]</span>
<span class="units">[@(Model.UseOe ? "°Oe" : "°KMW")]</span>
<span class="units">[#]</span>
<span class="units">[kg]</span>
</div>
@foreach (var qualId in qualIds) {
<h4>@(Model.QualityLevels.GetValueOrDefault(qualId, qualId))</h4>
@foreach (var (oe, num, weight) in sec.Data.GetValueOrDefault(qualId, Array.Empty<(int, int, int)>())) {
@foreach (var (grad, avgKmw, num, weight) in sec.Data.GetValueOrDefault(qualId, Array.Empty<(double, double, int, int)>())) {
<div class="row">
<span class="oe">@oe</span>
<span class="gradation">@(Model.UseOe ? $"{grad:N0}" : $"{grad:N1}")</span>
<span class="number">@($"{num:N0}")</span>
<span class="number">@($"{weight:N0}")</span>
</div>
@ -45,13 +46,13 @@
</tr>
<tr>
@foreach (var qualIds in Model.QualIds) {
var quals = qualIds.Select(q => sec.Data.GetValueOrDefault(q, Array.Empty<(int Oe, int Num, int Weight)>()));
var quals = qualIds.Select(q => sec.Data.GetValueOrDefault(q, Array.Empty<(double Grad, double AvgKmw, int Num, int Weight)>()));
var weight = quals.Sum(q => q.Sum(kv => kv.Weight));
var num = quals.Sum(q => q.Sum(kv => kv.Num));
var oe = quals.Sum(q => q.Sum(kv => (double)kv.Oe * kv.Weight)) / weight;
var kmw = quals.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / weight;
<td class="container bold">
<div class="row">
<span class="oe">@(weight == 0 ? "-" : $"{oe:N0}")</span>
<span class="gradation">@(weight == 0 ? "-" : Model.UseOe ? $"{Utils.KmwToOe(kmw):N0}" : $"{kmw:N1}")</span>
<span class="number">@($"{num:N0}")</span>
<span class="number">@($"{weight:N0}")</span>
</div>
@ -64,11 +65,11 @@
@{
var totalWeight = sec.Data.Values.Sum(q => q.Sum(kv => kv.Weight));
var totalNum = sec.Data.Values.Sum(q => q.Sum(kv => kv.Num));
var totalOe = sec.Data.Values.Sum(q => q.Sum(kv => (double)kv.Oe * kv.Weight)) / totalWeight;
var totalKmw = sec.Data.Values.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / totalWeight;
}
<td colspan="4" class="container bold footer @(sec.Type == "R" ? "red" : sec.Type == "W" ? "green" : "")">
<div class="row" style="width: 24%; margin-left: 76%;">
<span class="oe">@(totalWeight == 0 ? "-" : $"{totalOe:N0}")</span>
<span class="gradation">@(totalWeight == 0 ? "-" : Model.UseOe ? $"{Utils.KmwToOe(totalKmw):N0}" : $"{totalKmw:N1}")</span>
<span class="number">@($"{totalNum:N0}")</span>
<span class="number">@($"{totalWeight:N0}")</span>
</div>

View File

@ -85,7 +85,7 @@ table .footer.green {
padding: 1mm;
}
.oe {
.gradation {
text-align: center;
}

View File

@ -7,7 +7,7 @@
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
<ApplicationIcon>Resources\Images\Elwig.ico</ApplicationIcon>
<Version>0.7.2</Version>
<Version>0.8.0</Version>
<SatelliteResourceLanguages>de-AT</SatelliteResourceLanguages>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
@ -27,15 +27,15 @@
<ItemGroup>
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.0" />
<PackageReference Include="LinqKit" Version="1.2.5" />
<PackageReference Include="MailKit" Version="4.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.27" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="MailKit" Version="4.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.29" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2365.46" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2478.35" />
<PackageReference Include="NJsonSchema" Version="11.0.0" />
<PackageReference Include="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.21" />
<PackageReference Include="ScottPlot.WPF" Version="5.0.31" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup>

View File

@ -64,6 +64,7 @@ namespace Elwig.Helpers {
public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; }
public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; }
private readonly StreamWriter? LogFile = null;
public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile);

View File

@ -9,7 +9,7 @@ namespace Elwig.Helpers {
public static class AppDbUpdater {
// Don't forget to update value in Tests/fetch-resources.bat!
public static readonly int RequiredSchemaVersion = 18;
public static readonly int RequiredSchemaVersion = 19;
private static int VersionOffset = 0;

View File

@ -57,6 +57,7 @@ namespace Elwig.Helpers {
public string? Website;
public int ModeDeliveryNoteStats;
public int ModeWineQualityStatistics;
public int OrderingMemberList;
public string? TextDeliveryNote;
@ -103,6 +104,13 @@ namespace Elwig.Helpers {
case "SHORT": ModeDeliveryNoteStats = 2; break;
case "FULL": ModeDeliveryNoteStats = 3; break;
}
switch (parameters.GetValueOrDefault("MODE_WINEQUALITYSTATISTICS", "OE")?.ToUpper()) {
case "OE": ModeWineQualityStatistics = 0; break;
case "KMW/1": ModeWineQualityStatistics = 1; break;
case "KMW/2": ModeWineQualityStatistics = 2; break;
case "KMW/5": ModeWineQualityStatistics = 3; break;
case "KMW/10": ModeWineQualityStatistics = 4; break;
}
switch (parameters.GetValueOrDefault("ORDERING_MEMBERLIST", "")?.ToUpper()) {
case "MGNR": OrderingMemberList = 0; break;
case "NAME": OrderingMemberList = 1; break;
@ -133,6 +141,14 @@ namespace Elwig.Helpers {
case 2: deliveryNoteStats = "SHORT"; break;
case 3: deliveryNoteStats = "FULL"; break;
}
string modeWineQualityStatistics = "OE";
switch (ModeWineQualityStatistics) {
case 0: modeWineQualityStatistics = "OE"; break;
case 1: modeWineQualityStatistics = "KMW/1"; break;
case 2: modeWineQualityStatistics = "KMW/2"; break;
case 3: modeWineQualityStatistics = "KMW/5"; break;
case 4: modeWineQualityStatistics = "KMW/10"; break;
}
string orderingMemberList = "MGNR";
switch (OrderingMemberList) {
case 0: orderingMemberList = "MGNR"; break;
@ -157,6 +173,7 @@ namespace Elwig.Helpers {
("CLIENT_BIC", Bic),
("CLIENT_IBAN", Iban),
("MODE_DELIVERYNOTE_STATS", deliveryNoteStats),
("MODE_WINEQUALITYSTATISTICS", modeWineQualityStatistics),
("ORDERING_MEMBERLIST", orderingMemberList),
("DOCUMENT_SENDER", Sender2),
("TEXT_DELIVERYNOTE", TextDeliveryNote),

View File

@ -41,6 +41,9 @@ namespace Elwig.Helpers {
public string? Branch = null;
public string? UpdateUrl = null;
public bool UpdateAuto = false;
public string? SyncUrl = null;
public string SyncUsername = "";
public string SyncPassword = "";
public string? SmtpHost = null;
public int? SmtpPort = null;
@ -71,6 +74,9 @@ namespace Elwig.Helpers {
Debug = TrueValues.Contains(config["general:debug"]?.ToLower());
UpdateUrl = config["update:url"];
UpdateAuto = TrueValues.Contains(config["update:auto"]?.ToLower());
SyncUrl = config["sync:url"];
SyncUsername = config["sync:username"] ?? "";
SyncPassword = config["sync:password"] ?? "";
SmtpHost = config["smtp:host"];
SmtpPort = config["smtp:port"]?.All(char.IsAsciiDigit) == true && config["smtp:port"]?.Length > 0 ? int.Parse(config["smtp:port"]!) : null;

View File

@ -0,0 +1,251 @@
using System.IO.Compression;
using System.IO;
using System.Threading.Tasks;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System;
using System.Text.Json.Nodes;
using System.Linq;
using System.Windows;
using Microsoft.EntityFrameworkCore;
namespace Elwig.Helpers.Export {
public static class ElwigData {
public static readonly string ImportedTxt = Path.Combine(App.DataPath, "imported.txt");
public static async Task<string[]> GetImportedFiles() {
try {
return await File.ReadAllLinesAsync(ImportedTxt, Utils.UTF8);
} catch {
return [];
}
}
public static async Task AddImportedFiles(IEnumerable<string> filenames) {
await File.AppendAllLinesAsync(ImportedTxt, filenames, Utils.UTF8);
}
public static Task Import(string filename, bool interactive) => Import([filename], interactive);
public static async Task Import(IEnumerable<string> filenames, bool interactive) {
try {
using var ctx = new AppDbContext();
var currentDids = await ctx.Deliveries
.GroupBy(d => d.Year)
.ToDictionaryAsync(g => g.Key, g => g.Max(d => d.DId));
var deliveries = new List<Delivery>();
var deliveryParts = new List<DeliveryPart>();
var modifiers = new List<DeliveryPartModifier>();
var metaData = new List<(string Name, int DeliveryNum, string Filters)>();
foreach (var filename in filenames) {
using var zip = ZipFile.Open(filename, ZipArchiveMode.Read);
var version = zip.GetEntry("version");
using (var reader = new StreamReader(version!.Open(), Utils.UTF8)) {
if (await reader.ReadToEndAsync() != "elwig:1")
throw new FileFormatException("Ungültige Export-Datei");
}
var metaJson = zip.GetEntry("meta.json");
var meta = await JsonNode.ParseAsync(metaJson!.Open());
var deliveryCount = meta!["deliveries"]?["count"]!.AsValue().GetValue<int>();
var deliveryFilters = meta!["deliveries"]?["filters"]!.AsArray().Select(f => f!.AsValue().GetValue<string>()).ToArray();
if (deliveryCount != null && deliveryFilters != null)
metaData.Add((Path.GetFileName(filename), (int)deliveryCount, string.Join(" / ", deliveryFilters)));
var membersJson = zip.GetEntry("members.json");
if (membersJson != null) {
using var reader = new StreamReader(membersJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
// TODO import members.json
}
}
var areaComsJson = zip.GetEntry("area_commitments.json");
if (areaComsJson != null) {
using var reader = new StreamReader(areaComsJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
// TODO import area_commitments.json
}
}
var deliveriesJson = zip.GetEntry("deliveries.json");
if (deliveriesJson != null) {
using var reader = new StreamReader(deliveriesJson.Open(), Utils.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) != null) {
var obj = JsonNode.Parse(line)!.AsObject();
var (d, parts, mods) = JsonToDelivery(obj, currentDids);
deliveries.Add(d);
deliveryParts.AddRange(parts);
modifiers.AddRange(mods);
}
}
}
var lsnrs = deliveries.Select(d => d.LsNr).ToList();
var duplicateLsNrs = await ctx.Deliveries
.Where(d => lsnrs.Contains(d.LsNr))
.Select(d => d.LsNr)
.ToListAsync();
var duplicateDIds = deliveries
.Where(d => duplicateLsNrs.Contains(d.LsNr))
.Select(d => (d.Year, d.DId))
.ToList();
bool overwriteDelivieries = false;
if (duplicateLsNrs.Count > 0) {
var res = MessageBox.Show($"Sollen {duplicateLsNrs.Count} Lieferungen überschreiben werden?", "Lieferungen überschreiben",
MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
overwriteDelivieries = res == MessageBoxResult.Yes;
}
if (overwriteDelivieries) {
ctx.RemoveRange(ctx.Deliveries.Where(d => duplicateLsNrs.Contains(d.LsNr)));
ctx.AddRange(deliveries);
ctx.AddRange(deliveryParts);
ctx.AddRange(modifiers);
} else {
ctx.AddRange(deliveries.Where(d => !duplicateDIds.Contains((d.Year, d.DId))));
ctx.AddRange(deliveryParts.Where(p => !duplicateDIds.Contains((p.Year, p.DId))));
ctx.AddRange(modifiers.Where(m => !duplicateDIds.Contains((m.Year, m.DId))));
}
await ctx.SaveChangesAsync();
await AddImportedFiles(filenames.Select(f => Path.GetFileName(f)));
await App.HintContextChange();
MessageBox.Show(
$"Das importieren der Daten war erfolgreich!\n" +
$"Folgendes wurde importiert:\n" +
$" Lieferungen: {deliveries.Count}\n" +
string.Join("\n", metaData.Select(d => $" {d.Name} ({d.DeliveryNum})\n {d.Filters}")) +
"\n", "Importieren erfolgreich",
MessageBoxButton.OK, MessageBoxImage.Information);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public static async Task ExportDeliveries(string filename, IEnumerable<Delivery> deliveries, IEnumerable<string> filters) {
File.Delete(filename);
using var zip = ZipFile.Open(filename, ZipArchiveMode.Create);
var version = zip.CreateEntry("version", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(version.Open(), Utils.UTF8)) {
await writer.WriteAsync("elwig:1");
}
var meta = zip.CreateEntry("meta.json", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(meta.Open(), Utils.UTF8)) {
await writer.WriteAsync(
$"{{\"timestamp\": \"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ssZ}\", " +
$"\"zwstid\": \"{App.ZwstId}\", \"device\": \"{Environment.MachineName}\", " +
$"\"deliveries\": {{" +
$"\"count\": {deliveries.Count()}, " +
$"\"parts\": {deliveries.Sum(d => d.Parts.Count)}, " +
$"\"filters\": [{string.Join(", ", filters.Select(f => '"' + f + '"'))}]" +
$"}}}}");
}
var json = zip.CreateEntry("deliveries.json");
using (var writer = new StreamWriter(json.Open(), Utils.UTF8)) {
foreach (var d in deliveries) {
await writer.WriteLineAsync(DeliveryToJson(d).ToJsonString());
}
}
}
public static JsonObject DeliveryToJson(Delivery d) {
return new JsonObject {
["lsnr"] = d.LsNr,
["year"] = d.Year,
["date"] = $"{d.Date:yyyy-MM-dd}",
["zwstid"] = d.ZwstId,
["lnr"] = d.LNr,
["time"] = d.Time != null ? $"{d.Time:HH:mm:ss}" : null,
["mgnr"] = d.MgNr,
["parts"] = new JsonArray(d.Parts.OrderBy(p => p.DPNr).Select(p => {
var obj = new JsonObject {
["dpnr"] = p.DPNr,
["sortid"] = p.SortId,
["attrid"] = p.AttrId,
["cultid"] = p.CultId,
["weight"] = p.Weight,
["kmw"] = p.Kmw,
["qualid"] = p.QualId,
["hkid"] = p.HkId,
["kgnr"] = p.KgNr,
["rdnr"] = p.RdNr,
["net_weight"] = p.IsNetWeight,
["manual_weighing"] = p.IsManualWeighing,
["modids"] = new JsonArray(p.Modifiers.Select(m => (JsonNode)m.ModId).ToArray()),
["comment"] = p.Comment,
};
if (p.IsSplCheck) obj["spl_check"] = p.IsSplCheck;
if (p.IsHandPicked != null) obj["hand_picked"] = p.IsHandPicked;
if (p.IsLesewagen != null) obj["lesewagen"] = p.IsLesewagen;
if (p.IsGebunden != null) obj["gebunden"] = p.IsGebunden;
if (p.Temperature != null) obj["temperature"] = p.Temperature;
if (p.Acid != null) obj["acid"] = p.Acid;
if (p.ScaleId != null) obj["scale_id"] = p.ScaleId;
if (p.WeighingId != null) obj["weighing_id"] = p.WeighingId;
if (p.WeighingReason != null) obj["weighing_reason"] = p.WeighingReason;
return obj;
}).ToArray()),
["comment"] = d.Comment,
};
}
public static (Delivery, List<DeliveryPart>, List<DeliveryPartModifier>) JsonToDelivery(JsonNode json, Dictionary<int, int> currentDids) {
var year = json["year"]!.AsValue().GetValue<int>();
var did = ++currentDids[year];
return (new Delivery {
Year = year,
DId = did,
DateString = json["date"]!.AsValue().GetValue<string>(),
TimeString = json["time"]?.AsValue().GetValue<string>(),
ZwstId = json["zwstid"]!.AsValue().GetValue<string>(),
LNr = json["lnr"]!.AsValue().GetValue<int>(),
LsNr = json["lsnr"]!.AsValue().GetValue<string>(),
MgNr = json["mgnr"]!.AsValue().GetValue<int>(),
Comment = json["comment"]?.AsValue().GetValue<string>(),
}, json["parts"]!.AsArray().Select(p => p!.AsObject()).Select(p => new DeliveryPart {
Year = year,
DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
SortId = p["sortid"]!.AsValue().GetValue<string>(),
AttrId = p["attrid"]?.AsValue().GetValue<string>(),
CultId = p["cultid"]?.AsValue().GetValue<string>(),
Weight = p["weight"]!.AsValue().GetValue<int>(),
Kmw = p["kmw"]!.AsValue().GetValue<double>(),
QualId = p["qualid"]!.AsValue().GetValue<string>(),
HkId = p["hkid"]!.AsValue().GetValue<string>(),
KgNr = p["kgnr"]?.AsValue().GetValue<int>(),
RdNr = p["rdnr"]?.AsValue().GetValue<int>(),
IsNetWeight = p["net_weight"]!.AsValue().GetValue<bool>(),
IsManualWeighing = p["manual_weighing"]!.AsValue().GetValue<bool>(),
Comment = p["comment"]?.AsValue().GetValue<string>(),
IsSplCheck = p["spl_check"]?.AsValue().GetValue<bool>() ?? false,
IsHandPicked = p["hand_picked"]?.AsValue().GetValue<bool>(),
IsLesewagen = p["lesewagen"]?.AsValue().GetValue<bool>(),
IsGebunden = p["gebunden"]?.AsValue().GetValue<bool>(),
Temperature = p["temperature"]?.AsValue().GetValue<double>(),
Acid = p["acid"]?.AsValue().GetValue<double>(),
ScaleId = p["scale_id"]?.AsValue().GetValue<string>(),
WeighingId = p["weighing_id"]?.AsValue().GetValue<string>(),
WeighingReason = p["weighing_reason"]?.AsValue().GetValue<string>(),
}).ToList(), json["parts"]!.AsArray().SelectMany(p => p!["modids"]!.AsArray().Select(m => new DeliveryPartModifier {
Year = year,
DId = did,
DPNr = p["dpnr"]!.AsValue().GetValue<int>(),
ModId = m!.AsValue().GetValue<string>(),
})).ToList());
}
}
}

View File

@ -305,6 +305,7 @@ namespace Elwig.Helpers.Export {
case "°KMW": n = 1; data = $"{v:N1}"; break;
case "°Oe": n = 0; data = $"{v:N0}"; break;
case "m²": n = 0; data = $"{v:N0}"; break;
case "kg": n = 0; data = $"{v:N0}"; break;
}
if (n >= 0) add = string.Join(' ', add.Split(' ').Select(p => p.StartsWith("table:style-name=") ? $"table:style-name=\"N{n}\"" : p));
}

View File

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

View File

@ -0,0 +1,99 @@
using System;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Elwig.Helpers {
static partial class Extensions {
[LibraryImport("msvcrt.dll")]
[UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
private static unsafe partial int memcmp(void* b1, void* b2, long count);
public static unsafe int CompareBuffers(char[] buffer1, int offset1, char[] buffer2, int offset2, int count) {
fixed (char* b1 = buffer1, b2 = buffer2) {
return memcmp(b1 + offset1, b2 + offset2, count);
}
}
public static string? ReadUntil(this StreamReader reader, string delimiter) {
return ReadUntil(reader, delimiter.ToCharArray());
}
public static string? ReadUntil(this StreamReader reader, char delimiter) {
return ReadUntil(reader, new char[] { delimiter });
}
public static string? ReadUntil(this StreamReader reader, char[] delimiter) {
var buf = new char[512];
int bufSize = 0, ret;
while (!reader.EndOfStream && bufSize < buf.Length - 1) {
if ((ret = reader.Read()) == -1)
return null;
buf[bufSize++] = (char)ret;
if (bufSize >= delimiter.Length && CompareBuffers(buf, bufSize - delimiter.Length, delimiter, 0, delimiter.Length) == 0)
return new string(buf, 0, bufSize);
}
return null;
}
public static Task<string?> ReadUntilAsync(this StreamReader reader, string delimiter) {
return ReadUntilAsync(reader, delimiter.ToCharArray());
}
public static Task<string?> ReadUntilAsync(this StreamReader reader, char delimiter) {
return ReadUntilAsync(reader, new char[] { delimiter });
}
public static async Task<string?> ReadUntilAsync(this StreamReader reader, char[] delimiter) {
var buf = new char[512];
int bufSize = 0;
var tmpBuf = new char[1];
while (!reader.EndOfStream && bufSize < buf.Length - 1) {
if ((await reader.ReadAsync(tmpBuf, 0, 1)) != 1)
return null;
buf[bufSize++] = tmpBuf[0];
if (bufSize >= delimiter.Length && CompareBuffers(buf, bufSize - delimiter.Length, delimiter, 0, delimiter.Length) == 0)
return new string(buf, 0, bufSize);
}
return null;
}
public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long>? progress = null, CancellationToken cancellationToken = default) {
ArgumentNullException.ThrowIfNull(source);
if (!source.CanRead) throw new ArgumentException("Has to be readable", nameof(source));
ArgumentNullException.ThrowIfNull(destination);
if (!destination.CanWrite) throw new ArgumentException("Has to be writable", nameof(destination));
ArgumentOutOfRangeException.ThrowIfNegative(bufferSize);
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) {
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<double>? progress = null, CancellationToken cancellationToken = default) {
using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var contentLength = response.Content.Headers.ContentLength;
using var download = await response.Content.ReadAsStreamAsync(cancellationToken);
if (progress == null || !contentLength.HasValue) {
await download.CopyToAsync(destination, cancellationToken);
return;
}
var relativeProgress = new Progress<long>(totalBytes => progress.Report((double)totalBytes / contentLength.Value));
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
progress.Report(100.0);
}
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Elwig.Helpers {
public static class HttpClientExtensions {
public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<double>? progress = null, CancellationToken cancellationToken = default) {
using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
var contentLength = response.Content.Headers.ContentLength;
using var download = await response.Content.ReadAsStreamAsync(cancellationToken);
if (progress == null || !contentLength.HasValue) {
await download.CopyToAsync(destination, cancellationToken);
return;
}
var relativeProgress = new Progress<long>(totalBytes => progress.Report((double)totalBytes / contentLength.Value));
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
progress.Report(100.0);
}
}
}

View File

@ -59,7 +59,7 @@ namespace Elwig.Helpers.Printing {
progress?.Report(0.0);
using var client = new TcpClient("127.0.0.1", 30983);
using var stream = client.GetStream();
await stream.WriteAsync(Encoding.UTF8.GetBytes(
await stream.WriteAsync(Utils.UTF8.GetBytes(
"-e utf-8;-p;" + (doublePaged ? "-2;" : "") +
$"{string.Join(';', htmlPath)};{pdfPath}" +
"\r\n"));

View File

@ -1,25 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Elwig.Helpers {
public static class StreamExtensions {
public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long>? progress = null, CancellationToken cancellationToken = default) {
ArgumentNullException.ThrowIfNull(source);
if (!source.CanRead) throw new ArgumentException("Has to be readable", nameof(source));
ArgumentNullException.ThrowIfNull(destination);
if (!destination.CanWrite) throw new ArgumentException("Has to be writable", nameof(destination));
ArgumentOutOfRangeException.ThrowIfNegative(bufferSize);
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) {
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
progress?.Report(totalBytesRead);
}
}
}
}

View File

@ -81,7 +81,7 @@ namespace Elwig.Helpers {
return PhoneNrTypes.Where(t => t.Key == type).Select(t => t.Value).FirstOrDefault(type);
}
private static readonly string[] TempWildcards = ["*.html", "*.pdf", "*.exe"];
private static readonly string[] TempWildcards = ["*.html", "*.pdf", "*.exe", "*.zip"];
private static readonly ushort[] Crc16ModbusTable = [
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
@ -409,21 +409,48 @@ namespace Elwig.Helpers {
return InternetGetConnectedState(out var _, 0);
}
public static HttpClient GetHttpClient(string? username = null, string? password = null, string? accept = null) {
var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.Accept.Clear();
if (accept != null)
client.DefaultRequestHeaders.Accept.Add(new(accept));
if (username != null || password != null)
client.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(
Utils.UTF8.GetBytes($"{username}:{password}")));
return client;
}
public static async Task<(string Version, string Url, long Size)?> GetLatestInstallerUrl(string url) {
try {
using var client = new HttpClient() {
Timeout = TimeSpan.FromSeconds(5),
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new("application/json"));
var res = JsonNode.Parse(await client.GetStringAsync(url));
var data = res!["data"]![0]!;
using var client = GetHttpClient(accept: "application/json");
var resJson = JsonNode.Parse(await client.GetStringAsync(url));
var data = resJson!["data"]![0]!;
return ((string)data["version"]!, (string)data["url"]!, (long)data["size"]!);
} catch {
return null;
}
}
public static async Task UploadExportData(string zip, string url, string username, string password) {
if (!url.EndsWith('/')) url += "/";
using var client = GetHttpClient(username, password, accept: "application/json");
var content = new StreamContent(new FileStream(zip, FileMode.Open, FileAccess.Read));
content.Headers.ContentType = new("application/zip");
using var res = await client.PutAsync(url + Path.GetFileName(zip), content);
res.EnsureSuccessStatusCode();
}
public static async Task<JsonArray> GetExportMetaData(string url, string username, string password) {
using var client = GetHttpClient(username, password, accept: "application/json");
using var res = await client.GetAsync(url);
res.EnsureSuccessStatusCode();
var resJson = JsonNode.Parse(await res.Content.ReadAsStringAsync());
var data = resJson!["data"]!;
return data.AsArray();
}
public static void CleanupTempFiles() {
var dir = new DirectoryInfo(App.TempPath);
foreach (var file in TempWildcards.SelectMany(dir.EnumerateFiles)) {

View File

@ -26,6 +26,8 @@ namespace Elwig.Helpers.Weighing {
Model = model;
IsReady = true;
HasFillingClearance = false;
Stream.WriteTimeout = -1;
Stream.ReadTimeout = -1;
BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
BackgroundThread.Start();
}
@ -46,7 +48,8 @@ namespace Elwig.Helpers.Weighing {
while (IsRunning) {
try {
var data = await Receive();
RaiseWeighingEvent(new WeighingEventArgs(data));
if (data != null)
RaiseWeighingEvent(new WeighingEventArgs(data.Value));
} catch (Exception ex) {
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error);
@ -54,13 +57,12 @@ namespace Elwig.Helpers.Weighing {
}
}
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 != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
protected async Task<WeighingResult?> Receive() {
var line = await Reader.ReadUntilAsync("\r\n");
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
if (line == null || line == "") {
return null;
} else if (line.Length != 35 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
throw new IOException($"Invalid event from scale: '{line}'");
}

View File

@ -0,0 +1,122 @@
using System;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
namespace Elwig.Helpers.Weighing {
public class GassnerScale : Scale, ICommandScale {
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 GassnerScale(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;
Stream.WriteTimeout = 250;
Stream.ReadTimeout = 11000;
}
protected Task SendCommand(char command) => SendCommand(Convert.ToByte(command));
protected async Task SendCommand(byte command) {
byte[] cmd = [command];
await Stream.WriteAsync(cmd);
if (LogPath != null) await File.AppendAllTextAsync(LogPath, Encoding.ASCII.GetString(cmd));
}
protected async Task<string> ReceiveResponse() {
var line = await Reader.ReadUntilAsync('\x03');
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
if (line == null || line.Length < 4 || !line.StartsWith('\x02')) {
throw new IOException("Invalid response from scale");
}
var status = line[1..3];
if (status[0] == 'E' || status[1] != 'S') {
string msg = $"Unbekannter Fehler (Fehler code {status})";
switch (status[1]) {
case 'M': msg = "Waage in Bewegung"; break;
}
throw new IOException($"Waagenfehler {status}: {msg}");
} else if (status[0] != ' ') {
throw new IOException($"Invalid response from scale (error code {status})");
}
return line[1..^1];
}
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
await SendCommand(incIdentNr ? '\x05' : '?');
string record = await ReceiveResponse();
if (record.Length != 45)
throw new IOException("Invalid response from scale: Received record has invalid size");
var line = record[2..];
var status = line[ 0.. 2];
var brutto = line[ 2.. 9].Trim();
var tara = line[ 9..16].Trim();
var netto = line[16..23].Trim();
var scaleNr = line[23..25].Trim();
var identNr = line[25..31].Trim();
var date = line[31..39];
var time = line[39..45];
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
var parsedDate = DateOnly.ParseExact(date, "yyyyMMdd");
return new() {
Weight = int.Parse(netto),
WeighingId = identNr,
FullWeighingId = identNr,
Date = parsedDate,
Time = TimeOnly.ParseExact(time, "HHmmss"),
};
}
public async Task<WeighingResult> Weigh() {
return await Weigh(true);
}
public async Task<WeighingResult> GetCurrentWeight() {
return await Weigh(false);
}
public async Task Empty() {
SerialPort? p = ControlSerialEmpty ?? Serial;
if (EmptyMode == Output.RTS && p != null) {
p.RtsEnable = true;
await Task.Delay(EmptyDelay);
p.RtsEnable = false;
} else if (EmptyMode == Output.DTR && p != null) {
p.DtrEnable = true;
await Task.Delay(EmptyDelay);
p.DtrEnable = false;
}
}
protected Task SetFillingClearance(bool status) {
SerialPort? p = ControlSerialFilling ?? Serial;
if (FillingClearanceMode == Output.RTS && p != null) {
p.RtsEnable = status;
} else if (FillingClearanceMode == Output.DTR && p != null) {
p.DtrEnable = status;
}
return Task.CompletedTask;
}
public async Task GrantFillingClearance() {
await SetFillingClearance(true);
}
public async Task RevokeFillingClearance() {
await SetFillingClearance(false);
}
}
}

View File

@ -2,6 +2,7 @@
using System.IO;
using System.Net.Sockets;
using System;
using System.Text;
namespace Elwig.Helpers.Weighing {
public abstract class Scale : IDisposable {
@ -12,6 +13,7 @@ namespace Elwig.Helpers.Weighing {
protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
protected TcpClient? Tcp = null;
protected Stream Stream;
protected StreamReader Reader;
protected readonly Output? EmptyMode = null;
protected readonly Output? FillingClearanceMode = null;
@ -26,6 +28,8 @@ namespace Elwig.Helpers.Weighing {
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else if (config.Type == "Avery-Async") {
return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else if (config.Type == "Gassner") {
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
} else {
throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
}
@ -41,6 +45,7 @@ namespace Elwig.Helpers.Weighing {
} else {
throw new ArgumentException($"Unsupported scheme: \"{cnx.Split(':')[0]}\"");
}
Reader = new(Stream, Encoding.ASCII, false, 512);
LogPath = log;
@ -73,6 +78,7 @@ namespace Elwig.Helpers.Weighing {
}
public void Dispose() {
Reader.Close();
Stream.Close();
Serial?.Close();
ControlSerialEmpty?.Close();

View File

@ -31,12 +31,9 @@ namespace Elwig.Helpers.Weighing {
}
protected async Task<string> ReceiveResponse() {
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 < 4 || !line.StartsWith('<') || !line.EndsWith('>')) {
var line = await Reader.ReadUntilAsync("\r\n");
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
throw new IOException("Invalid response from scale");
}
@ -68,7 +65,7 @@ namespace Elwig.Helpers.Weighing {
throw new IOException($"Invalid response from scale (error code {error})");
}
return line[1..^1];
return line[1..^3];
}
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
@ -82,7 +79,7 @@ namespace Elwig.Helpers.Weighing {
var date = line[ 2..10];
var time = line[10..15];
var identNr = line[15..19].Trim();
var scaleNr = line[19..20];
var scaleNr = line[19..20].Trim();
var brutto = line[20..28].Trim();
var tara = line[28..36].Trim();
var netto = line[36..44].Trim();

View File

@ -0,0 +1,58 @@
using Elwig.Models.Entities;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Threading.Tasks;
namespace Elwig.Models.Dtos {
public class WeightBreakdownData : DataTable<WeightBreakdownRow> {
private static readonly (string, string, string?, int?)[] FieldNames = [
("Type", "R/W", null, 10),
("SortId", "Sorte", null, 10),
("AttrId", "Attr.", null, 10),
("CultId", "Bewirt.", null, 15),
("QualId", "Qual.", null, 15),
("Geb", "gebunden", null, 20),
("Weight", "Gewicht", "kg", 20),
];
public WeightBreakdownData(IEnumerable<WeightBreakdownRow> rows, int year, string name) :
base(name, $"Sorten-/Qualitätsaufschlüsselung {year}", name, rows, FieldNames) {
}
public static async Task<WeightBreakdownData> ForSeason(DbSet<WeightBreakdownRow> table, int year, Branch? branch = null) {
return new(await FromDbSet(table, year, branch?.ZwstId), year, branch?.Name ?? "Gesamt");
}
private static async Task<IEnumerable<WeightBreakdownRow>> FromDbSet(DbSet<WeightBreakdownRow> table, int year, string? zwstid) {
zwstid = zwstid == null ? "NULL" : $"'{zwstid}'";
return await table.FromSqlRaw($"""
SELECT type, sortid, attrid, cultid, v.qualid, geb, SUM(weight) AS weight
FROM v_stat_total v
LEFT JOIN wine_quality_level q ON q.qualid = v.qualid
WHERE year = {year} AND ({zwstid} IS NULL OR zwstid = {zwstid})
GROUP BY type, sortid, attrid, cultid, v.qualid, geb
ORDER BY type DESC, sortid, attrid, cultid, q.min_kmw, geb
""").ToListAsync();
}
}
[Keyless]
public class WeightBreakdownRow {
[Column("type")]
public required string Type { get; set; }
[Column("sortid")]
public required string SortId { get; set; }
[Column("attrid")]
public string? AttrId { get; set; }
[Column("cultid")]
public string? CultId { get; set; }
[Column("qualid")]
public required string QualId { get; set; }
[Column("geb")]
public required string Geb { get; set; }
[Column("weight")]
public int Weight { get; set; }
}
}

View File

@ -5,14 +5,14 @@ using System.Collections.Generic;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace Elwig.Models.Dtos {
public class WineQualityStatisticsData {
public record struct QualityRow(string? Variety, string? Attribute, string? Cultivation, string? Type, string QualId, int Oe, int Num, int Weight);
public record struct QualitySection(string Name, string? Type, Dictionary<string, (int Oe, int Num, int Weight)[]> Data);
public record struct QualityRow(string? Variety, string? Attribute, string? Cultivation, string? Type, string QualId, double AvgKmw, double Grad, int Num, int Weight);
public record struct QualitySection(string Name, string? Type, Dictionary<string, (double Grad, double AvgKmw, int Num, int Weight)[]> Data);
public bool UseOe = true;
public QualitySection[] Sections;
public WineQualityStatisticsData(QualitySection[] sections) {
@ -21,8 +21,8 @@ namespace Elwig.Models.Dtos {
private static QualitySection[] GetQualitySections(IEnumerable<QualityRow> rows) {
var data = new List<QualitySection>();
var currentQual = new Dictionary<int, (int Num, int Weight)>();
var current = new Dictionary<string, (int, int, int)[]>();
var currentQual = new Dictionary<double, (double AvgKmw, int Num, int Weight)>();
var current = new Dictionary<string, (double, double, int, int)[]>();
string? lastSection = null;
string? lastType = null;
string? lastQual = null;
@ -31,25 +31,25 @@ namespace Elwig.Models.Dtos {
$"{(row.Attribute != null ? " / " : "")}{row.Attribute}" +
$"{(row.Cultivation != null ? " / " : "")}{row.Cultivation}";
if (lastQual != null && lastQual != row.QualId) {
current[lastQual] = currentQual.Select(kv => (kv.Key, kv.Value.Num, kv.Value.Weight)).ToArray();
current[lastQual] = currentQual.Select(kv => (kv.Key, kv.Value.AvgKmw, kv.Value.Num, kv.Value.Weight)).ToArray();
currentQual.Clear();
}
if (lastSection != null && lastSection != sec) {
if (!current.ContainsKey(lastQual!)) {
current[lastQual!] = currentQual.Select(kv => (kv.Key, kv.Value.Num, kv.Value.Weight)).ToArray();
current[lastQual!] = currentQual.Select(kv => (kv.Key, kv.Value.AvgKmw, kv.Value.Num, kv.Value.Weight)).ToArray();
currentQual.Clear();
}
data.Add(new(lastSection, lastType, current));
current = [];
currentQual.Clear();
}
currentQual[row.Oe] = (row.Num, row.Weight);
currentQual[row.Grad] = (row.AvgKmw, row.Num, row.Weight);
lastSection = sec;
lastType = row.Type;
lastQual = row.QualId;
}
if (lastQual != null) {
current[lastQual] = currentQual.Select(kv => (kv.Key, kv.Value.Num, kv.Value.Weight)).ToArray();
current[lastQual] = currentQual.Select(kv => (kv.Key, kv.Value.AvgKmw, kv.Value.Num, kv.Value.Weight)).ToArray();
currentQual.Clear();
}
if (lastSection != null) {
@ -60,7 +60,7 @@ namespace Elwig.Models.Dtos {
return [.. data];
}
public static async Task<WineQualityStatisticsData> FromQuery(IQueryable<DeliveryPart> query) {
public static async Task<WineQualityStatisticsData> FromQuery(IQueryable<DeliveryPart> query, int mode = 0) {
var rows = (await query
.GroupBy(p => new {
p.Variety.Type,
@ -68,15 +68,24 @@ namespace Elwig.Models.Dtos {
Attribute = p.Attribute!.Name,
Cultivation = p.Cultivation!.Name,
p.QualId,
Oe = (int)Math.Round(p.Kmw * (4.54 + 0.022 * p.Kmw), 0),
}, (k, g) => new { Key = k, Num = g.Count(), Weight = g.Sum(p => p.Weight) })
Grad = mode == 0 ? Math.Round(p.Kmw * (4.54 + 0.022 * p.Kmw), 0) :
mode == 1 ? Math.Floor(p.Kmw) :
mode == 2 ? Math.Floor(p.Kmw * 2) / 2 :
mode == 3 ? Math.Floor(p.Kmw * 5) / 5 :
Math.Round(p.Kmw, 1),
}, (k, g) => new {
Key = k,
Num = g.Count(),
Weight = g.Sum(p => p.Weight),
AvgKmw = g.Sum(p => p.Weight * p.Kmw) / g.Sum(p => p.Weight),
})
.OrderBy(g => g.Key.Variety)
.ThenBy(g => g.Key.Attribute)
.ThenBy(g => g.Key.Cultivation)
.ThenBy(g => g.Key.QualId)
.ThenBy(g => g.Key.Oe)
.ThenBy(g => g.Key.Grad)
.ToListAsync())
.Select(r => new QualityRow(r.Key.Variety, r.Key.Attribute, r.Key.Cultivation, r.Key.Type, r.Key.QualId, r.Key.Oe, r.Num, r.Weight))
.Select(r => new QualityRow(r.Key.Variety, r.Key.Attribute, r.Key.Cultivation, r.Key.Type, r.Key.QualId, r.AvgKmw, r.Key.Grad, r.Num, r.Weight))
.ToList();
var data = GetQualitySections(rows);
@ -84,22 +93,36 @@ namespace Elwig.Models.Dtos {
return new(data);
var typeRows = rows
.GroupBy(s => new { s.Type, s.QualId, s.Oe }, (k, g) => new QualityRow(null, null, null, k.Type, k.QualId, k.Oe, g.Sum(g => g.Num), g.Sum(p => p.Weight)))
.GroupBy(s => new { s.Type, s.QualId, s.Grad }, (k, g) => new QualityRow(
null, null, null,
k.Type, k.QualId,
g.Sum(p => p.Weight * p.AvgKmw) / g.Sum(p => p.Weight),
k.Grad,
g.Sum(p => p.Num),
g.Sum(p => p.Weight)
))
.OrderBy(g => g.Type)
.ThenBy(g => g.QualId)
.ThenBy(g => g.Oe)
.ThenBy(g => g.Grad)
.ToList();
var typeData = GetQualitySections(typeRows);
if (typeData.Length <= 1)
return new([.. typeData, .. data]);
var totalRows = rows
.GroupBy(s => new { s.QualId, s.Oe }, (k, g) => new QualityRow(null, null, null, null, k.QualId, k.Oe, g.Sum(p => p.Num), g.Sum(p => p.Weight)))
.GroupBy(s => new { s.QualId, s.Grad }, (k, g) => new QualityRow(
null, null, null, null,
k.QualId,
g.Sum(p => p.Weight * p.AvgKmw) / g.Sum(p => p.Weight),
k.Grad,
g.Sum(p => p.Num),
g.Sum(p => p.Weight)
))
.OrderBy(g => g.QualId)
.ThenBy(g => g.Oe)
.ThenBy(g => g.Grad)
.ToList();
var totalData = GetQualitySections(totalRows);
return new([.. totalData, .. typeData, .. data]);
return new([.. totalData, .. typeData, .. data]) { UseOe = mode == 0 };
}
}
}

View File

@ -0,0 +1,17 @@
-- schema version 18 to 19
CREATE VIEW v_stat_total AS
SELECT d.year, d.zwstid, v.type, v.sortid,
IIF(b.discr = a.attrid OR NOT a.area_com, a.attrid, NULL) AS attrid,
p.cultid, q.qualid,
IIF(b.discr = '_', 'ungeb', 'geb') AS geb,
SUM(value) AS weight
FROM delivery_part p
LEFT JOIN delivery d ON (d.year, d.did) = (p.year, p.did)
LEFT JOIN wine_variety v ON v.sortid = p.sortid
LEFT JOIN wine_quality_level q ON q.qualid = p.qualid
LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (p.year, p.did, p.dpnr)
LEFT JOIN v_wine_attribute a ON a.attrid = p.attrid
GROUP BY d.year, d.zwstid, v.type, v.sortid, IIF(b.discr = a.attrid OR NOT a.area_com, a.attrid, NULL), p.cultid, q.qualid, geb
HAVING SUM(value) > 0
ORDER BY d.year, d.zwstid, v.type DESC, v.sortid, IIF(b.discr = a.attrid OR NOT a.area_com, a.attrid, NULL), p.cultid, q.min_kmw, geb;

View File

@ -0,0 +1,5 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Elwig;component/Controls/UnitTextBox.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@ -133,7 +133,6 @@ namespace Elwig.Windows {
}
protected void ValidateInput(Control input, bool valid) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
Valid[input] = valid;
}
@ -234,7 +233,6 @@ namespace Elwig.Windows {
}
protected void SetOriginalValue(Control input, object? value) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
OriginalValues[input] = Utils.GetEntityIdentifier(value);
if (InputHasChanged(input)) {
ControlUtils.SetInputChanged(input);
@ -244,18 +242,15 @@ namespace Elwig.Windows {
}
protected void SetOriginalValue(Control input) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
SetOriginalValue(input, ControlUtils.GetInputHashCode(input));
}
protected void UnsetOriginalValue(Control input) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
OriginalValues.Remove(input);
ControlUtils.ClearInputState(input);
}
protected void SetDefaultValue(Control input, object? value) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
DefaultValues[input] = Utils.GetEntityIdentifier(value);
if (!InputHasChanged(input)) {
if (InputIsNotDefault(input)) {
@ -267,12 +262,10 @@ namespace Elwig.Windows {
}
protected void SetDefaultValue(Control input) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
SetDefaultValue(input, ControlUtils.GetInputHashCode(input));
}
protected void UnsetDefaultValue(Control input) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
DefaultValues.Remove(input);
if (!InputHasChanged(input)) {
ControlUtils.ClearInputState(input);
@ -296,12 +289,10 @@ namespace Elwig.Windows {
protected bool IsValid => Valid.All(kv => kv.Value);
protected bool GetInputValid(Control input) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
return Valid[input];
}
protected bool InputHasChanged(Control input) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
if (!OriginalValues.TryGetValue(input, out int? original)) {
return false;
} else {
@ -311,7 +302,6 @@ namespace Elwig.Windows {
}
protected bool InputIsNotDefault(Control input) {
if (input is UnitTextBox utbx) input = utbx.TextBox;
if (!DefaultValues.TryGetValue(input, out int? defaultValue)) {
return false;
} else {
@ -436,7 +426,7 @@ namespace Elwig.Windows {
protected void TextBox_TextChanged(object sender, RoutedEventArgs? evt) {
var input = (Control)sender;
var tb = input as TextBox ?? (input as UnitTextBox)?.TextBox;
var tb = input as TextBox;
if (SenderIsRequired(input) && tb?.Text.Length == 0) {
ValidateInput(input, false);
ControlUtils.SetInputInvalid(input);
@ -482,12 +472,12 @@ namespace Elwig.Windows {
protected void IntegerInput_TextChanged(object sender, TextChangedEventArgs evt) {
// FIXME
InputTextChanged((sender as UnitTextBox)?.TextBox ?? (TextBox)sender, Validator.CheckInteger);
InputTextChanged((TextBox)sender, Validator.CheckInteger);
}
protected void DecimalInput_TextChanged(object sender, TextChangedEventArgs evt) {
// FIXME
InputTextChanged((sender as UnitTextBox)?.TextBox ?? (TextBox)sender, Validator.CheckDecimal);
InputTextChanged((TextBox)sender, Validator.CheckDecimal);
}
protected void PartialDateInput_TextChanged(object sender, TextChangedEventArgs evt) {

View File

@ -29,7 +29,7 @@ namespace Elwig.Windows {
];
RequiredInputs = [
FbNrInput, YearFromInput, KgInput, RdInput,
GstNrInput, AreaInput.TextBox, AreaComTypeInput, WineCultivationInput
GstNrInput, AreaInput, AreaComTypeInput, WineCultivationInput
];
}

View File

@ -24,6 +24,14 @@
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ctrl:UnitTextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ComboBox">
<Setter Property="Height" Value="25"/>
<Setter Property="FontSize" Value="14"/>
@ -347,8 +355,12 @@
<Label Content="Sorte:" Margin="10,40,0,10"/>
<ComboBox x:Name="AreaCommitmentTypeWineVariantInput" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,40,10,10" Width="250" HorizontalAlignment="Left"
ItemTemplate="{StaticResource WineVarietyTemplate}" TextSearch.TextPath="Name"
SelectionChanged="AreaCommitmentType_Changed"/>
TextSearch.TextPath="Name"
SelectionChanged="AreaCommitmentType_Changed">
<ComboBox.ItemTemplateSelector>
<ctrl:WineVarietyTemplateSelector/>
</ComboBox.ItemTemplateSelector>
</ComboBox>
<Label Content="Attribut:" Margin="10,70,0,10"/>
<ComboBox x:Name="AreaCommitmentTypeWineAttributeInput" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,70,10,10" Width="250" HorizontalAlignment="Left"
@ -505,7 +517,22 @@
</Grid>
</TabItem>
<TabItem Header="Parameter">
<Grid>
<GroupBox x:Name="ParameterAreaComGroup" Header="Berechnung Flächenbindungen (aktuelle Saison)" Margin="10,10,10,10" VerticalAlignment="Top">
<Grid>
<CheckBox x:Name="ParameterAllowAttrIntoLowerInput" Content="Erlauben Lieferungen auch auf (konfigurierte) &quot;schlechtere&quot; Flächenbindungen aufzuteilen"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,10,10,10"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"/>
<CheckBox x:Name="ParameterAvoidUnderDeliveriesInput" Content="Unterlieferungen durch Abzug bei &quot;besseren&quot; Flächenbindungen vermeiden"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,30,10,10"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"/>
<CheckBox x:Name="ParameterHonorGebundenInput" Margin="10,50,10,10" VerticalAlignment="Top"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed">
<TextBlock>Bei Lieferungen das Feld <Italic>Gebunden</Italic> berücksichtigen</TextBlock>
</CheckBox>
</Grid>
</GroupBox>
</Grid>
</TabItem>
<TabItem Header="Textelemente">
<ScrollViewer VerticalScrollBarVisibility="Visible">

View File

@ -31,7 +31,11 @@ namespace Elwig.Windows {
}
private async Task AreaCommitmentTypesFinishEditing(AppDbContext ctx) {
ControlUtils.RenewItemsSource(AreaCommitmentTypeList, await ctx.AreaCommitmentTypes.OrderBy(v => v.SortId).ToListAsync());
ControlUtils.RenewItemsSource(AreaCommitmentTypeList, await ctx.AreaCommitmentTypes
.OrderBy(v => v.VtrgId)
.Include(t => t.WineVar)
.Include(t => t.WineAttr)
.ToListAsync());
_actList = null;
_acts = null;
_actIds = null;

View File

@ -22,7 +22,10 @@ namespace Elwig.Windows {
private async Task ModifiersInitEditing(AppDbContext ctx) {
SeasonList.IsEnabled = false;
var year = (SeasonList.SelectedItem as Season)?.Year;
_modList = new(await ctx.Modifiers.Where(m => m.Year == year).OrderBy(m => m.Ordering).ToListAsync());
_modList = new(await ctx.Modifiers
.Where(m => m.Year == year)
.OrderBy(m => m.Ordering)
.ToListAsync());
_mods = _modList.ToDictionary(m => m.ModId, m => (string?)m.ModId);
_modIds = _modList.ToDictionary(m => m, m => m.ModId);
ControlUtils.RenewItemsSource(SeasonModifierList, _modList);
@ -31,7 +34,10 @@ namespace Elwig.Windows {
private async Task ModifiersFinishEditing(AppDbContext ctx) {
var year = (SeasonList.SelectedItem as Season)?.Year;
ControlUtils.RenewItemsSource(SeasonModifierList, await ctx.Modifiers.Where(m => m.Year == year).OrderBy(m => m.Ordering).ToListAsync());
ControlUtils.RenewItemsSource(SeasonModifierList, await ctx.Modifiers
.Where(m => m.Year == year)
.OrderBy(m => m.Ordering)
.ToListAsync());
_modList = null;
_mods = null;
_modIds = null;

View File

@ -13,12 +13,18 @@ namespace Elwig.Windows {
private bool _seasonUpdate = false;
private async Task SeasonsInitEditing(AppDbContext ctx) {
ControlUtils.RenewItemsSource(SeasonList, await ctx.Seasons.OrderByDescending(s => s.Year).ToListAsync());
ControlUtils.RenewItemsSource(SeasonList, await ctx.Seasons
.OrderByDescending(s => s.Year)
.Include(s => s.Modifiers)
.ToListAsync());
SeasonList_SelectionChanged(null, null);
}
private async Task SeasonsFinishEditing(AppDbContext ctx) {
ControlUtils.RenewItemsSource(SeasonList, await ctx.Seasons.OrderByDescending(s => s.Year).Include(s => s.Modifiers).ToListAsync());
ControlUtils.RenewItemsSource(SeasonList, await ctx.Seasons
.OrderByDescending(s => s.Year)
.Include(s => s.Modifiers)
.ToListAsync());
_seasonChanged = false;
}

View File

@ -19,7 +19,9 @@ namespace Elwig.Windows {
private bool _attrUpdate = false;
private async Task WineAttributesInitEditing(AppDbContext ctx) {
_attrList = new(await ctx.WineAttributes.OrderBy(a => a.Name).ToListAsync());
_attrList = new(await ctx.WineAttributes
.OrderBy(a => a.Name)
.ToListAsync());
_attrs = _attrList.ToDictionary(a => a.AttrId, a => (string?)a.AttrId);
_attrIds = _attrList.ToDictionary(a => a, a => a.AttrId);
ControlUtils.RenewItemsSource(WineAttributeList, _attrList);
@ -27,7 +29,9 @@ namespace Elwig.Windows {
}
private async Task WineAttributesFinishEditing(AppDbContext ctx) {
ControlUtils.RenewItemsSource(WineAttributeList, await ctx.WineAttributes.OrderBy(a => a.Name).ToListAsync());
ControlUtils.RenewItemsSource(WineAttributeList, await ctx.WineAttributes
.OrderBy(a => a.Name)
.ToListAsync());
_attrList = null;
_attrs = null;
_attrIds = null;

View File

@ -19,7 +19,9 @@ namespace Elwig.Windows {
private bool _cultUpdate = false;
private async Task WineCultivationsInitEditing(AppDbContext ctx) {
_cultList = new(await ctx.WineCultivations.OrderBy(c => c.Name).ToListAsync());
_cultList = new(await ctx.WineCultivations
.OrderBy(c => c.Name)
.ToListAsync());
_cults = _cultList.ToDictionary(c => c.CultId, c => (string?)c.CultId);
_cultIds = _cultList.ToDictionary(c => c, c => c.CultId);
ControlUtils.RenewItemsSource(WineCultivationList, _cultList);
@ -27,7 +29,9 @@ namespace Elwig.Windows {
}
private async Task WineCultivationsFinishEditing(AppDbContext ctx) {
ControlUtils.RenewItemsSource(WineCultivationList, await ctx.WineCultivations.OrderBy(c => c.Name).ToListAsync());
ControlUtils.RenewItemsSource(WineCultivationList, await ctx.WineCultivations
.OrderBy(c => c.Name)
.ToListAsync());
_cultList = null;
_cults = null;
_cultIds = null;

View File

@ -23,18 +23,19 @@ namespace Elwig.Windows {
BranchIdInput, BranchNameInput, BranchPlzInput, BranchOrtInput,
BranchAddressInput, BranchPhoneNrInput, BranchFaxNrInput, BranchMobileNrInput,
WineAttributeIdInput, WineAttributeNameInput, WineAttributeActiveInput,
WineAttributeMaxKgPerHaInput.TextBox, WineAttributeStrictInput, WineAttributeFillLowerInput,
WineAttributeMaxKgPerHaInput, WineAttributeStrictInput, WineAttributeFillLowerInput,
WineCultivationIdInput, WineCultivationNameInput, WineCultivationDescriptionInput,
AreaCommitmentTypeIdInput, AreaCommitmentTypeWineVariantInput, AreaCommitmentTypeWineAttributeInput,
AreaCommitmentTypeMinKgPerHaInput.TextBox, AreaCommitmentTypePenaltyPerKgInput.TextBox,
AreaCommitmentTypePenaltyInput.TextBox, AreaCommitmentTypePenaltyNoneInput.TextBox,
SeasonMaxKgPerHaInput.TextBox, SeasonVatNormalInput.TextBox, SeasonVatFlatrateInput.TextBox, SeasonStartInput, SeasonEndInput,
SeasonMinKgPerBsInput.TextBox, SeasonMaxKgPerBsInput.TextBox, SeasonBsValueInput.TextBox,
SeasonPenaltyPerKgInput.TextBox, SeasonPenaltyInput.TextBox, SeasonPenaltyNoneInput.TextBox,
SeasonModifierIdInput, SeasonModifierNameInput, SeasonModifierRelInput.TextBox, SeasonModifierAbsInput.TextBox,
AreaCommitmentTypeMinKgPerHaInput, AreaCommitmentTypePenaltyPerKgInput,
AreaCommitmentTypePenaltyInput, AreaCommitmentTypePenaltyNoneInput,
SeasonMaxKgPerHaInput, SeasonVatNormalInput, SeasonVatFlatrateInput, SeasonStartInput, SeasonEndInput,
SeasonMinKgPerBsInput, SeasonMaxKgPerBsInput, SeasonBsValueInput,
SeasonPenaltyPerKgInput, SeasonPenaltyInput, SeasonPenaltyNoneInput,
SeasonModifierIdInput, SeasonModifierNameInput, SeasonModifierRelInput, SeasonModifierAbsInput,
];
WineAttributeFillLowerInput.Visibility = Visibility.Hidden;
WineAttributeFillLowerLabel.Visibility = Visibility.Hidden;
ParameterAreaComGroup.Header = ParameterAreaComGroup.Header.ToString()!.Split('(')[0] + $"({Utils.CurrentLastSeason})";
}
protected override void ShortcutNew() { }
@ -56,7 +57,7 @@ namespace Elwig.Windows {
WineAttributeIdInput.IsReadOnly = true;
WineAttributeNameInput.IsReadOnly = true;
WineAttributeActiveInput.IsEnabled = false;
WineAttributeMaxKgPerHaInput.TextBox.IsReadOnly = true;
WineAttributeMaxKgPerHaInput.IsReadOnly = true;
WineAttributeStrictInput.IsEnabled = false;
WineAttributeFillLowerInput.IsEnabled = false;
@ -66,25 +67,29 @@ namespace Elwig.Windows {
AreaCommitmentTypeWineVariantInput.IsEnabled = false;
AreaCommitmentTypeWineAttributeInput.IsEnabled = false;
AreaCommitmentTypeMinKgPerHaInput.TextBox.IsReadOnly = true;
AreaCommitmentTypePenaltyPerKgInput.TextBox.IsReadOnly = true;
AreaCommitmentTypePenaltyInput.TextBox.IsReadOnly = true;
AreaCommitmentTypePenaltyNoneInput.TextBox.IsReadOnly = true;
AreaCommitmentTypeMinKgPerHaInput.IsReadOnly = true;
AreaCommitmentTypePenaltyPerKgInput.IsReadOnly = true;
AreaCommitmentTypePenaltyInput.IsReadOnly = true;
AreaCommitmentTypePenaltyNoneInput.IsReadOnly = true;
SeasonMaxKgPerHaInput.TextBox.IsReadOnly = true;
SeasonVatNormalInput.TextBox.IsReadOnly = true;
SeasonVatFlatrateInput.TextBox.IsReadOnly = true;
SeasonMinKgPerBsInput.TextBox.IsReadOnly = true;
SeasonMaxKgPerBsInput.TextBox.IsReadOnly = true;
SeasonPenaltyPerKgInput.TextBox.IsReadOnly = true;
SeasonPenaltyInput.TextBox.IsReadOnly = true;
SeasonPenaltyNoneInput.TextBox.IsReadOnly = true;
SeasonBsValueInput.TextBox.IsReadOnly = true;
SeasonMaxKgPerHaInput.IsReadOnly = true;
SeasonVatNormalInput.IsReadOnly = true;
SeasonVatFlatrateInput.IsReadOnly = true;
SeasonMinKgPerBsInput.IsReadOnly = true;
SeasonMaxKgPerBsInput.IsReadOnly = true;
SeasonPenaltyPerKgInput.IsReadOnly = true;
SeasonPenaltyInput.IsReadOnly = true;
SeasonPenaltyNoneInput.IsReadOnly = true;
SeasonBsValueInput.IsReadOnly = true;
SeasonModifierIdInput.IsReadOnly = true;
SeasonModifierNameInput.IsReadOnly = true;
SeasonModifierRelInput.TextBox.IsReadOnly = true;
SeasonModifierAbsInput.TextBox.IsReadOnly = true;
SeasonModifierRelInput.IsReadOnly = true;
SeasonModifierAbsInput.IsReadOnly = true;
ParameterAllowAttrIntoLowerInput.IsEnabled = false;
ParameterAvoidUnderDeliveriesInput.IsEnabled = false;
ParameterHonorGebundenInput.IsEnabled = false;
}
new protected void UnlockInputs() {
@ -102,7 +107,7 @@ namespace Elwig.Windows {
WineAttributeIdInput.IsReadOnly = false;
WineAttributeNameInput.IsReadOnly = false;
WineAttributeActiveInput.IsEnabled = true;
WineAttributeMaxKgPerHaInput.TextBox.IsReadOnly = false;
WineAttributeMaxKgPerHaInput.IsReadOnly = false;
WineAttributeStrictInput.IsEnabled = true;
WineAttributeFillLowerInput.IsEnabled = true;
@ -112,25 +117,29 @@ namespace Elwig.Windows {
AreaCommitmentTypeWineVariantInput.IsEnabled = true;
AreaCommitmentTypeWineAttributeInput.IsEnabled = true;
AreaCommitmentTypeMinKgPerHaInput.TextBox.IsReadOnly = false;
AreaCommitmentTypePenaltyPerKgInput.TextBox.IsReadOnly = false;
AreaCommitmentTypePenaltyInput.TextBox.IsReadOnly = false;
AreaCommitmentTypePenaltyNoneInput.TextBox.IsReadOnly = false;
AreaCommitmentTypeMinKgPerHaInput.IsReadOnly = false;
AreaCommitmentTypePenaltyPerKgInput.IsReadOnly = false;
AreaCommitmentTypePenaltyInput.IsReadOnly = false;
AreaCommitmentTypePenaltyNoneInput.IsReadOnly = false;
SeasonMaxKgPerHaInput.TextBox.IsReadOnly = false;
SeasonVatNormalInput.TextBox.IsReadOnly = false;
SeasonVatFlatrateInput.TextBox.IsReadOnly = false;
SeasonMinKgPerBsInput.TextBox.IsReadOnly = false;
SeasonMaxKgPerBsInput.TextBox.IsReadOnly = false;
SeasonPenaltyPerKgInput.TextBox.IsReadOnly = false;
SeasonPenaltyInput.TextBox.IsReadOnly = false;
SeasonPenaltyNoneInput.TextBox.IsReadOnly = false;
SeasonBsValueInput.TextBox.IsReadOnly = false;
SeasonMaxKgPerHaInput.IsReadOnly = false;
SeasonVatNormalInput.IsReadOnly = false;
SeasonVatFlatrateInput.IsReadOnly = false;
SeasonMinKgPerBsInput.IsReadOnly = false;
SeasonMaxKgPerBsInput.IsReadOnly = false;
SeasonPenaltyPerKgInput.IsReadOnly = false;
SeasonPenaltyInput.IsReadOnly = false;
SeasonPenaltyNoneInput.IsReadOnly = false;
SeasonBsValueInput.IsReadOnly = false;
SeasonModifierIdInput.IsReadOnly = false;
SeasonModifierNameInput.IsReadOnly = false;
SeasonModifierRelInput.TextBox.IsReadOnly = false;
SeasonModifierAbsInput.TextBox.IsReadOnly = false;
SeasonModifierRelInput.IsReadOnly = false;
SeasonModifierAbsInput.IsReadOnly = false;
ParameterAllowAttrIntoLowerInput.IsEnabled = true;
ParameterAvoidUnderDeliveriesInput.IsEnabled = true;
ParameterHonorGebundenInput.IsEnabled = true;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
@ -139,9 +148,10 @@ namespace Elwig.Windows {
protected override async Task OnRenewContext(AppDbContext ctx) {
await base.OnRenewContext(ctx);
FillInputs(App.Client);
FillInputs(App.Client, (await ctx.Seasons.FindAsync(Utils.CurrentLastSeason))!);
ControlUtils.RenewItemsSource(SeasonList, await ctx.Seasons
.OrderByDescending(s => s.Year)
.Include(s => s.Modifiers)
.ToListAsync(), null, ControlUtils.RenewSourceDefault.First);
var year = (SeasonList.SelectedItem as Season)?.Year;
ControlUtils.RenewItemsSource(BranchList, await ctx.Branches
@ -210,6 +220,7 @@ namespace Elwig.Windows {
private async Task Save() {
await UpdateClientParameters(App.Client);
await UpdateParameters(Utils.CurrentLastSeason);
using var tx = await EditContext!.Database.BeginTransactionAsync();
await BranchesSave(EditContext!);
await WineAttributesSave(EditContext!);
@ -261,8 +272,9 @@ namespace Elwig.Windows {
await FinishEditing();
using var ctx = new AppDbContext();
ClearInputStates();
FillInputs(App.Client);
FillInputs(App.Client, (await ctx.Seasons.FindAsync(Utils.CurrentLastSeason))!);
LockInputs();
}
@ -280,8 +292,9 @@ namespace Elwig.Windows {
await InitEditing();
using var ctx = new AppDbContext();
ClearInputStates();
FillInputs(App.Client);
FillInputs(App.Client, (await ctx.Seasons.FindAsync(Utils.CurrentLastSeason))!);
UpdateButtons();
}
@ -310,14 +323,16 @@ namespace Elwig.Windows {
await FinishEditing();
ClearInputStates();
FillInputs(App.Client);
LockInputs();
using (var ctx = new AppDbContext()) {
ClearInputStates();
FillInputs(App.Client, (await ctx.Seasons.FindAsync(Utils.CurrentLastSeason))!);
LockInputs();
}
await HintContextChange();
}
private void FillInputs(ClientParameters p) {
private void FillInputs(ClientParameters p, Season s) {
ClearOriginalValues();
ClearDefaultValues();
@ -348,6 +363,10 @@ namespace Elwig.Windows {
TextElementDeliveryConfirmation.Text = p.TextDeliveryConfirmation;
TextElementCreditNote.Text = p.TextCreditNote;
ParameterAllowAttrIntoLowerInput.IsChecked = s.Billing_AllowAttrsIntoLower;
ParameterAvoidUnderDeliveriesInput.IsChecked = s.Billing_AvoidUnderDeliveries;
ParameterHonorGebundenInput.IsChecked = s.Billing_HonorGebunden;
FinishInputFilling();
}
@ -382,5 +401,19 @@ namespace Elwig.Windows {
ClientNameFull.Text = $"{ClientNameInput.Text}{(suffix != null ? $", {suffix}," : "")} {ClientNameTypeInput.Text}";
TextBox_TextChanged(sender, evt);
}
private async Task UpdateParameters(int year) {
try {
using var ctx = new AppDbContext();
if (await ctx.Seasons.FindAsync(year) is not Season s)
return;
s.Billing_AllowAttrsIntoLower = ParameterAllowAttrIntoLowerInput.IsChecked ?? false;
s.Billing_AvoidUnderDeliveries = ParameterAvoidUnderDeliveriesInput.IsChecked ?? false;
s.Billing_HonorGebunden = ParameterHonorGebundenInput.IsChecked ?? false;
ctx.Update(s);
await ctx.SaveChangesAsync();
} catch { }
}
}
}

View File

@ -29,6 +29,15 @@
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ctrl:UnitTextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ComboBox">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="Height" Value="25"/>

View File

@ -429,7 +429,7 @@ namespace Elwig.Windows {
private void PriceInput_TextChanged(object sender, TextChangedEventArgs evt) {
if (PrimaryMarkedPoint != -1 && ActiveGraph != null && PriceInput.IsKeyboardFocusWithin == true) {
var res = Validator.CheckDecimal(PriceInput.TextBox, true, 2, Season.Precision);
var res = Validator.CheckDecimal(PriceInput, true, 2, Season.Precision);
if (res.IsValid && double.TryParse(PriceInput.Text, out double price)) {
ActiveGraph.SetPriceAt(PrimaryMarkedPoint, price);
PrimaryMarkedPointPlot.Location = new Coordinates(PrimaryMarkedPointPlot.Location.X, price);
@ -592,13 +592,13 @@ namespace Elwig.Windows {
Pixel mousePixel = new(p.X, p.Y - 30);
Coordinates mouseLocation = OechslePricePlot.Plot.GetCoordinates(mousePixel);
TooltipPlot = OechslePricePlot.Plot.Add.Text($"Oechsle: {pointX:N2}, Preis: {Math.Round(pointY, Season.Precision)}€/kg", mouseLocation.X, mouseLocation.Y);
TooltipPlot.Label.FontSize = 12;
TooltipPlot.Label.Bold = true;
TooltipPlot.Label.BorderColor = Colors.Black;
TooltipPlot.Label.BorderWidth = 2;
TooltipPlot.Label.BackColor = Colors.White;
TooltipPlot.Label.Padding = 10;
TooltipPlot.Label.Alignment = Alignment.MiddleLeft;
TooltipPlot.LabelFontSize = 12;
TooltipPlot.LabelBold = true;
TooltipPlot.LabelBorderColor = Colors.Black;
TooltipPlot.LabelBorderWidth = 2;
TooltipPlot.LabelBackgroundColor = Colors.White;
TooltipPlot.LabelPadding = 10;
TooltipPlot.LabelAlignment = Alignment.MiddleLeft;
}
LastHighlighted = (g, pointIndex);
HoverChanged = false;
@ -667,13 +667,13 @@ namespace Elwig.Windows {
private void EnableUnitTextBox(UnitTextBox u) {
if (PaymentVar.TestVariant) {
u.IsEnabled = true;
u.TextBox.IsReadOnly = false;
u.IsReadOnly = false;
}
}
private void DisableUnitTextBox(UnitTextBox u) {
u.IsEnabled = false;
u.TextBox.IsReadOnly = true;
u.IsReadOnly = true;
}
private void ChangeActiveGraph(Graph? g) {
@ -716,7 +716,7 @@ namespace Elwig.Windows {
private void GebundenFlatBonus_TextChanged(object sender, TextChangedEventArgs e) {
if (FillingInputs) return;
var r = Validator.CheckDecimal(GebundenFlatBonus.TextBox, true, 2, Season.Precision);
var r = Validator.CheckDecimal(GebundenFlatBonus, true, 2, Season.Precision);
if (r.IsValid && SelectedGraphEntry != null) {
SelectedGraphEntry.GebundenFlatBonus = double.Parse(GebundenFlatBonus.Text);
ResetPlot();

View File

@ -21,6 +21,14 @@
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ctrl:UnitTextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ComboBox">
<Setter Property="Height" Value="25"/>
<Setter Property="FontSize" Value="14"/>
@ -81,7 +89,7 @@
<MenuItem x:Name="Menu_DeliveryJournal_PrintToday" Header="...von heute drucken"
Click="Menu_DeliveryJournal_PrintToday_Click" InputGestureText="Strg+J"/>
</MenuItem>
<MenuItem Header="Qualitätsstatistik">
<MenuItem Header="Qualitätsstatistik" x:Name="Menu_WineQualityStatistics">
<MenuItem x:Name="Menu_WineQualityStatistics_ShowFilters" Header="...aus Filtern anzeigen (PDF)"
Click="Menu_WineQualityStatistics_ShowFilters_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_SavePdfFilters" Header="...aus Filtern speichern... (PDF)"
@ -95,10 +103,32 @@
Click="Menu_WineQualityStatistics_SavePdfToday_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_PrintToday" Header="...von heute drucken"
Click="Menu_WineQualityStatistics_PrintToday_Click" InputGestureText="Strg+Q"/>
<Separator/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeOe" Header="...nach °Oe aufschlüsseln" IsCheckable="True" IsChecked="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw10" Header="...nach °KMW aufschlüsseln (&#x2152;)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw5" Header="...nach °KMW aufschlüsseln (&#x2155;)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw2" Header="...nach °KMW aufschlüsseln (&#x00BD;)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
<MenuItem x:Name="Menu_WineQualityStatistics_ModeKmw1" Header="...nach °KMW aufschlüsseln (ganze)" IsCheckable="True"
Click="Menu_WineQualityStatistics_Mode_Click"/>
</MenuItem>
<MenuItem Header="BKI">
<MenuItem x:Name="Menu_Bki_SaveList" Header="Traubentransportscheinliste speichern..."/>
</MenuItem>
<MenuItem Header="Export">
<MenuItem x:Name="Menu_Export_ExportFilters" Header="...aus Filtern speichern..."
Click="Menu_Export_ExportFilters_Click"/>
<MenuItem x:Name="Menu_Export_UploadFilters" Header="...aus Filtern hochladen"
Click="Menu_Export_UploadFilters_Click"/>
<Separator/>
<MenuItem x:Name="Menu_Export_ExportToday" Header="...von heute speichern..."
Click="Menu_Export_ExportToday_Click"/>
<MenuItem x:Name="Menu_Export_UploadToday" Header="...von heute hochladen"
Click="Menu_Export_UploadToday_Click" InputGestureText="Strg+H"/>
</MenuItem>
<MenuItem Header="Einstellungen">
<MenuItem x:Name="Menu_Settings_EnableFreeEditing" Header="Freie Bearbeitung aktivieren"
IsCheckable="True" Checked="Menu_Settings_EnableFreeEditing_Checked" Unchecked="Menu_Settings_EnableFreeEditing_Unchecked"/>
@ -353,8 +383,12 @@
<TextBox x:Name="SortIdInput" Width="36" Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" HorizontalAlignment="Left"
TextChanged="SortIdInput_TextChanged" LostFocus="SortIdInput_LostFocus" KeyUp="Input_KeyUp"/>
<ComboBox x:Name="WineVarietyInput" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="41,10,10,10"
ItemTemplate="{StaticResource WineVarietyTemplate}" TextSearch.TextPath="Name"
SelectionChanged="WineVarietyInput_SelectionChanged" KeyUp="Input_KeyUp"/>
TextSearch.TextPath="Name"
SelectionChanged="WineVarietyInput_SelectionChanged" KeyUp="Input_KeyUp">
<ComboBox.ItemTemplateSelector>
<ctrl:WineVarietyTemplateSelector/>
</ComboBox.ItemTemplateSelector>
</ComboBox>
<Label Content="Attr./Bewirt.:" Margin="10,40,0,0" Grid.Column="0"/>
<ComboBox x:Name="AttributeInput" Grid.Row="1" Grid.Column="1" Margin="0,40,5,10"
@ -382,8 +416,12 @@
<Label Content="Qualitätsstufe:" Margin="10,40,10,10"/>
<ComboBox x:Name="WineQualityLevelInput" Width="146" Margin="0,40,10,10" Grid.Column="1" HorizontalAlignment="Left"
ItemTemplate="{StaticResource WineQualityLevelTemplate}"
SelectionChanged="WineQualityLevelInput_SelectionChanged" KeyUp="Input_KeyUp"/>
TextSearch.TextPath="Name"
SelectionChanged="WineQualityLevelInput_SelectionChanged" KeyUp="Input_KeyUp">
<ComboBox.ItemTemplateSelector>
<ctrl:WineQualityLevelTemplateSelector/>
</ComboBox.ItemTemplateSelector>
</ComboBox>
<CheckBox x:Name="AbgewertetInput" Content="Abgewertet" IsEnabled="False"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,75,10,10" Grid.Column="0" Grid.ColumnSpan="2"/>
@ -505,7 +543,11 @@
<Label Content="Weinbaugebiet:" Margin="10,10,0,10" Grid.Column="0"/>
<ComboBox x:Name="WineOriginInput" Margin="0,10,10,10" Grid.Column="1"
ItemTemplate="{StaticResource WineOriginComboTemplate}"/>
TextSearch.TextPath="Name">
<ComboBox.ItemTemplateSelector>
<ctrl:WineOriginTemplateSelector/>
</ComboBox.ItemTemplateSelector>
</ComboBox>
<Label Content="Weinbau-KG:" Margin="10,40,0,10" Grid.Column="0"/>
<ComboBox x:Name="WineKgInput" Margin="0,40,10,10" Grid.Column="1"

View File

@ -11,6 +11,7 @@ using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
@ -37,6 +38,7 @@ namespace Elwig.Windows {
private readonly RoutedCommand CtrlO = new("CtrlO", typeof(DeliveryAdminWindow), [new KeyGesture(Key.O, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlJ = new("CtrlJ", typeof(DeliveryAdminWindow), [new KeyGesture(Key.J, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlQ = new("CtrlQ", typeof(DeliveryAdminWindow), [new KeyGesture(Key.Q, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlH = new("CtrlH", typeof(DeliveryAdminWindow), [new KeyGesture(Key.H, ModifierKeys.Control)]);
private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]);
private readonly RoutedCommand CtrlShiftO = new("CtrlShiftO", typeof(DeliveryAdminWindow), [new KeyGesture(Key.O, ModifierKeys.Control | ModifierKeys.Shift)]);
@ -54,15 +56,16 @@ namespace Elwig.Windows {
CommandBindings.Add(new CommandBinding(CtrlO, Menu_DeliveryJournal_ShowFilters_Click));
CommandBindings.Add(new CommandBinding(CtrlJ, Menu_DeliveryJournal_PrintToday_Click));
CommandBindings.Add(new CommandBinding(CtrlQ, Menu_WineQualityStatistics_PrintToday_Click));
CommandBindings.Add(new CommandBinding(CtrlH, Menu_Export_UploadToday_Click));
CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_DeliveryNote_Print_Click));
CommandBindings.Add(new CommandBinding(CtrlShiftO, Menu_DeliveryJournal_PrintFilters_Click));
RequiredInputs = [
MgNrInput, MemberInput,
LsNrInput, DateInput, BranchInput,
SortIdInput, WineVarietyInput,
GradationOeInput.TextBox, GradationKmwInput.TextBox, WineQualityLevelInput,
GradationOeInput, GradationKmwInput, WineQualityLevelInput,
WineOriginInput, WineKgInput,
WeightInput.TextBox
WeightInput
];
ExemptInputs = [
SearchInput, SeasonInput, TodayOnlyInput, AllSeasonsInput,
@ -108,6 +111,22 @@ namespace Elwig.Windows {
WeighingCButton.Visibility = Visibility.Hidden;
WeighingDButton.Visibility = Visibility.Hidden;
}
Menu_WineQualityStatistics_ModeOe.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw1.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw2.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw5.IsChecked = false;
Menu_WineQualityStatistics_ModeKmw10.IsChecked = false;
switch (App.Client.OrderingMemberList) {
case 0: Menu_WineQualityStatistics_ModeOe.IsChecked = true; break;
case 1: Menu_WineQualityStatistics_ModeKmw1.IsChecked = true; break;
case 2: Menu_WineQualityStatistics_ModeKmw2.IsChecked = true; break;
case 3: Menu_WineQualityStatistics_ModeKmw5.IsChecked = true; break;
case 4: Menu_WineQualityStatistics_ModeKmw10.IsChecked = true; break;
}
Menu_Export_UploadFilters.IsEnabled = App.Config.SyncUrl != null;
Menu_Export_UploadToday.IsEnabled = App.Config.SyncUrl != null;
}
public DeliveryAdminWindow(int mgnr) : this() {
@ -209,6 +228,15 @@ namespace Elwig.Windows {
await GenerateDeliveryJournal(1, ExportMode.Show);
}
private async void Menu_Export_ExportToday_Click(object sender, RoutedEventArgs evt) {
await GenerateDeliveryJournal(1, ExportMode.Export);
}
private async void Menu_Export_UploadToday_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null) return;
await GenerateDeliveryJournal(1, ExportMode.Upload);
}
private async void Menu_DeliveryJournal_PrintToday_Click(object sender, RoutedEventArgs evt) {
await GenerateDeliveryJournal(1, ExportMode.Print);
}
@ -229,6 +257,15 @@ namespace Elwig.Windows {
await GenerateDeliveryJournal(0, ExportMode.Print);
}
private async void Menu_Export_ExportFilters_Click(object sender, RoutedEventArgs evt) {
await GenerateDeliveryJournal(0, ExportMode.Export);
}
private async void Menu_Export_UploadFilters_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null) return;
await GenerateDeliveryJournal(0, ExportMode.Upload);
}
private async Task GenerateDeliveryJournal(int modeWho, ExportMode exportMode) {
using var ctx = new AppDbContext();
IQueryable<DeliveryPart> query;
@ -243,6 +280,10 @@ namespace Elwig.Windows {
.Where(p => p.Delivery.DateString == date);
filterNames.Add($"{Utils.Today:dd.MM.yyyy}");
}
if (exportMode == ExportMode.Upload && !filterNames.Contains($"Zweigstelle {App.BranchName}")) {
query = query.Where(p => p.Delivery.ZwstId == App.ZwstId);
filterNames.Add($"Zweigstelle {App.BranchName}");
}
query = query
.OrderBy(p => p.Delivery.DateString)
@ -268,6 +309,41 @@ namespace Elwig.Windows {
}
Mouse.OverrideCursor = null;
}
} else if (exportMode == ExportMode.Export) {
var d = new SaveFileDialog() {
FileName = $"Lieferungen.zip",
DefaultExt = "zip",
Filter = "ZIP-Datei (*.zip)|*.zip",
Title = $"{DeliveryJournal.Name} speichern unter - Elwig"
};
if (d.ShowDialog() == true) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
await ElwigData.ExportDeliveries(d.FileName, await query.Select(p => p.Delivery).Distinct().ToListAsync(), filterNames);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
} else if (exportMode == ExportMode.Upload && App.Config.SyncUrl != null) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var filename = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{App.ZwstId}.zip";
var path = Path.Combine(App.TempPath, filename);
var list = await query.Select(p => p.Delivery).Distinct().ToListAsync();
if (list.Count == 0) {
MessageBox.Show("Es wurden keine Lieferungen zum Hochladen ausgewählt!", "Fehler",
MessageBoxButton.OK, MessageBoxImage.Error);
} else {
await ElwigData.ExportDeliveries(path, list, filterNames);
await Utils.UploadExportData(path, App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
MessageBox.Show($"Lieferungen erfolgreich hochgeladen!", "Lieferungen hochgeladen",
MessageBoxButton.OK, MessageBoxImage.Information);
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
} else {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
@ -322,7 +398,7 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await WineQualityStatisticsData.FromQuery(query);
var data = await WineQualityStatisticsData.FromQuery(query, App.Client.OrderingMemberList);
using var doc = new WineQualityStatistics(string.Join(" / ", filterNames), data);
await Utils.ExportDocument(doc, exportMode);
} catch (Exception exc) {
@ -331,6 +407,27 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null;
}
private async void Menu_WineQualityStatistics_Mode_Click(object sender, RoutedEventArgs evt) {
Menu_WineQualityStatistics.IsSubmenuOpen = true;
if (sender == Menu_WineQualityStatistics_ModeOe) {
App.Client.OrderingMemberList = 0;
} else if (sender == Menu_WineQualityStatistics_ModeKmw1) {
App.Client.OrderingMemberList = 1;
} else if (sender == Menu_WineQualityStatistics_ModeKmw2) {
App.Client.OrderingMemberList = 2;
} else if (sender == Menu_WineQualityStatistics_ModeKmw5) {
App.Client.OrderingMemberList = 3;
} else if (sender == Menu_WineQualityStatistics_ModeKmw10) {
App.Client.OrderingMemberList = 4;
}
Menu_WineQualityStatistics_ModeOe.IsChecked = App.Client.OrderingMemberList == 0;
Menu_WineQualityStatistics_ModeKmw1.IsChecked = App.Client.OrderingMemberList == 1;
Menu_WineQualityStatistics_ModeKmw2.IsChecked = App.Client.OrderingMemberList == 2;
Menu_WineQualityStatistics_ModeKmw5.IsChecked = App.Client.OrderingMemberList == 3;
Menu_WineQualityStatistics_ModeKmw10.IsChecked = App.Client.OrderingMemberList == 4;
await App.Client.UpdateValues();
}
private void Menu_Settings_EnableFreeEditing_Checked(object sender, RoutedEventArgs evt) {
if (IsEditing || IsCreating) {
DateInput.IsReadOnly = false;
@ -432,7 +529,7 @@ namespace Elwig.Windows {
SortIdInput.SelectAll();
} else if (ctrl == SortIdInput || ctrl == WineVarietyInput || ctrl == AttributeInput || ctrl == CultivationInput) {
GradationOeInput.Focus();
GradationOeInput.TextBox.SelectAll();
GradationOeInput.SelectAll();
} else if (ctrl == GradationKmwInput || ctrl == GradationOeInput || ctrl == WineQualityLevelInput) {
if (WeighingAButton.IsVisible) WeighingAButton.Focus();
else WeighingManualButton.Focus();
@ -1797,7 +1894,7 @@ namespace Elwig.Windows {
WineOriginInput.IsEnabled = false;
if (WineKgInput.SelectedItem == null)
WineRdInput.IsEnabled = false;
WeightInput.TextBox.IsReadOnly = true;
WeightInput.IsReadOnly = true;
AbgewertetInput.IsEnabled = false;
ManualWeighingInput.IsEnabled = false;
LsNrInput.IsReadOnly = true;
@ -1899,17 +1996,17 @@ namespace Elwig.Windows {
private void UpdateGradationKmw() {
IsUpdatingGradation = true;
var caret = GradationKmwInput.TextBox.CaretIndex;
var caret = GradationKmwInput.CaretIndex;
GradationKmwInput.Text = $"{Utils.OeToKmw(double.Parse(GradationOeInput.Text)):#.0}";
GradationKmwInput.TextBox.CaretIndex = caret;
GradationKmwInput.CaretIndex = caret;
IsUpdatingGradation = false;
}
private void UpdateGradationOe() {
IsUpdatingGradation = true;
var caret = GradationOeInput.TextBox.CaretIndex;
var caret = GradationOeInput.CaretIndex;
GradationOeInput.Text = $"{Utils.KmwToOe(double.Parse(GradationKmwInput.Text)):#}";
GradationOeInput.TextBox.CaretIndex = caret;
GradationOeInput.CaretIndex = caret;
IsUpdatingGradation = false;
}

View File

@ -449,7 +449,20 @@ namespace Elwig.Windows {
if (doc.Type == DocType.DeliveryConfirmation) {
var details = ((int, bool))doc.Details!;
var year = details.Item1;
dcData[year] = await DeliveryConfirmationDeliveryData.ForSeason(ctx.DeliveryParts, year);
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
await App.HintContextChange();
dcData[year] = await DeliveryConfirmationDeliveryData.ForSeason(ctx.DeliveryParts, year);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
GenerateButton.IsEnabled = true;
Mouse.OverrideCursor = null;
return;
}
} else if (doc.Type == DocType.CreditNote) {
var details = ((int, int))doc.Details!;
var year = details.Item1;

View File

@ -1,6 +1,9 @@
<Window x:Class="Elwig.Windows.MainWindow"
<local:ContextWindow
x:Class="Elwig.Windows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Elwig.Windows"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="Elwig" Height="390" Width="520" ResizeMode="CanMinimize"
Loaded="Window_Loaded" Closing="Window_Closing">
<Window.Resources>
@ -16,14 +19,18 @@
<Grid>
<Menu BorderThickness="0,0,0,1" VerticalAlignment="Top" Height="19" BorderBrush="LightGray" Background="White">
<MenuItem Header="Datenbank">
<MenuItem Header="Daten exportieren..." Click="Menu_Database_Export_Click"/>
<MenuItem Header="Daten importieren..." Click="Menu_Database_Import_Click"/>
<MenuItem x:Name="Menu_Database_Sync" Header="Daten synchronisieren" Click="Menu_Database_Sync_Click"/>
<Separator/>
<MenuItem Header="Abfragen stellen" Click="Menu_Database_Query_Click"/>
<!--MenuItem Header="Backup erstellen"/-->
<MenuItem Header="Speicherort öffnen..." Click="Menu_Database_Open_Click"/>
</MenuItem>
<MenuItem x:Name="HelpMenu" Header="Hilfe">
<MenuItem Header="Über"/>
<MenuItem x:Name="Menu_Help_Update" Header="Nach Updates suchen" Click="Menu_Help_Update_Click"/>
<MenuItem x:Name="Menu_Help_Smtp" Header="E-Mail-Einstellungen testen" Click="Menu_Help_Smtp_Click"/>
<MenuItem x:Name="Menu_Help_TestWindow" Header="Test-Fenster" Click="Menu_Help_TestWindow_Click"/>
<MenuItem x:Name="Menu_Help_Config" Header="Konfigurationsspeicherort öffnen..." Click="Menu_Help_Config_Click"/>
</MenuItem>
</Menu>
@ -56,7 +63,38 @@
Margin="210,220,0,0"/>
<Button x:Name="BaseDataButton" Content="Stammdaten" Click="BaseDataButton_Click"
Margin="0,260,210,0"/>
<Button x:Name="SeasonFinishButton" Content="Leseabschluss" Click="SeasonFinishButton_Click"
<Button x:Name="RegistrationButton" Content="Anmeldungen" IsEnabled="False"
Margin="210,260,0,0"/>
<Expander x:Name="SeasonFinish" Header="Leseabschluss" Expanded="SeasonFinish_Expanded" Collapsed="SeasonFinish_Collapsed"
HorizontalAlignment="Center" Width="410" Margin="0,300,0,0" VerticalAlignment="Top">
<Grid>
<Border BorderBrush="LightGray" BorderThickness="1"/>
<Label Content="Saison:" Margin="0,10,100,0" VerticalAlignment="Top" HorizontalAlignment="Center" Padding="2,4,2,4" Height="25"/>
<xctk:IntegerUpDown Name="SeasonInput" Height="25" Width="56" FontSize="14" Minimum="1000" Maximum="9999"
Margin="0,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Center"
ValueChanged="SeasonInput_ValueChanged"/>
<Button x:Name="DeliveryConfirmationButton" Content="Anlieferungsbestätigung"
Click="DeliveryConfirmationButton_Click"
Margin="0,50,200,10" Width="190"/>
<Button x:Name="PaymentButton" Content="Auszahlung"
Click="PaymentButton_Click"
Margin="200,50,0,10" Width="190"/>
<Button x:Name="OverUnderDeliveryButton" Content="Über-/Unterlieferungen"
Click="OverUnderDeliveryButton_Click"
Margin="0,90,200,10" Width="190"/>
<Button x:Name="AutoBusinessSharesButton" Content="Autom. GA nachzeichnen"
Click="AutoBusinessSharesButton_Click" IsEnabled="False"
Margin="200,90,0,10" Width="190"/>
<Button x:Name="BreakdownButton" Content="Sorten-/Qual.aufteilung"
Click="BreakdownButton_Click"
Margin="0,130,200,10" Width="190"/>
</Grid>
</Expander>
</Grid>
</Window>
</local:ContextWindow>

View File

@ -1,27 +1,37 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos;
using Microsoft.EntityFrameworkCore;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Elwig.Windows {
public partial class MainWindow : Window {
public partial class MainWindow : ContextWindow {
public MainWindow() {
InitializeComponent();
var v = Assembly.GetExecutingAssembly().GetName().Version;
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) {
HelpMenu.Items.Remove(Menu_Help_TestWindow);
//QueryWindowButton.Visibility = Visibility.Hidden;
}
if (App.Config.UpdateUrl == null) Menu_Help_Update.IsEnabled = false;
if (App.Config.Smtp == null) Menu_Help_Smtp.IsEnabled = false;
Menu_Help_Update.IsEnabled = App.Config.UpdateUrl != null;
Menu_Help_Smtp.IsEnabled = App.Config.Smtp != null;
Menu_Database_Sync.IsEnabled = App.Config.SyncUrl != null;
}
private void Window_Loaded(object sender, RoutedEventArgs evt) { }
private void Window_Loaded(object sender, RoutedEventArgs evt) {
SeasonInput.Value = Utils.CurrentLastSeason;
}
private void Window_Closing(object sender, CancelEventArgs evt) {
if (App.NumWindows > 1 && !App.ForceShutdown) {
@ -35,13 +45,8 @@ namespace Elwig.Windows {
}
}
private void Menu_Help_TestWindow_Click(object sender, RoutedEventArgs evt) {
var w = new TestWindow();
w.Show();
}
private async void Menu_Help_Update_Click(object sender, RoutedEventArgs evt) {
await App.CheckForUpdates();
await App.CheckForUpdates(true);
}
private async void Menu_Help_Smtp_Click(object sender, RoutedEventArgs evt) {
@ -56,11 +61,69 @@ namespace Elwig.Windows {
Mouse.OverrideCursor = null;
}
private void Menu_Help_Config_Click(object sender, RoutedEventArgs evt) {
Process.Start("explorer.exe", App.DataPath);
}
private void Menu_Database_Query_Click(object sender, RoutedEventArgs evt) {
var w = new QueryWindow();
w.Show();
}
private void Menu_Database_Open_Click(object sender, RoutedEventArgs evt) {
if (Path.GetDirectoryName(App.Config.DatabaseFile) is string path)
Process.Start("explorer.exe", path);
}
private void Menu_Database_Export_Click(object sender, RoutedEventArgs evt) {
// TODO Menu_Database_Export_Click
}
private void Menu_Database_Import_Click(object sender, RoutedEventArgs evt) {
// TODO Menu_Database_Import_Click
}
private async void Menu_Database_Sync_Click(object sender, RoutedEventArgs evt) {
if (App.Config.SyncUrl == null)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var data = await Utils.GetExportMetaData(App.Config.SyncUrl, App.Config.SyncUsername, App.Config.SyncPassword);
var files = data
.Select(f => new {
Name = f!["name"]!.AsValue().GetValue<string>(),
Timestamp = f!["timestamp"] != null && DateTime.TryParseExact(f!["timestamp"]!.AsValue().GetValue<string>(), "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : (DateTime?)null,
ZwstId = f!["zwstid"]?.AsValue().GetValue<string>(),
DeliveryNum = f!["meta"]?["deliveries"]?["count"]!.AsValue().GetValue<int>(),
DeliveryPartNum = f!["meta"]?["deliveries"]?["parts"]!.AsValue().GetValue<int>(),
Filters = f!["meta"]?["deliveries"]?["filters"]!.AsArray().Select(i => i!.AsValue().GetValue<string>()).ToArray(),
Device = f!["meta"]?["device"]!.AsValue().GetValue<string>(),
Url = f!["url"]!.AsValue().GetValue<string>(),
Size = f!["size"]!.AsValue().GetValue<long>(),
})
.Where(f => f.DeliveryNum > 0 && f.Timestamp >= new DateTime(Utils.CurrentLastSeason, 7, 1))
.ToList();
var imported = await ElwigData.GetImportedFiles();
var import = files
.Where(f => f.Filters != null && f.Filters.Length > 0 && f.Device != Environment.MachineName && !imported.Contains(f.Name))
.ToList();
var paths = new List<string>();
using (var client = Utils.GetHttpClient(App.Config.SyncUsername, App.Config.SyncPassword)) {
foreach (var f in import) {
var filename = Path.Combine(App.TempPath, f.Name);
using var stream = new FileStream(filename, FileMode.Create);
await client.DownloadAsync(f.Url, stream);
paths.Add(filename);
}
}
await ElwigData.Import(paths, false);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private void MemberAdminButton_Click(object sender, RoutedEventArgs evt) {
var w = new MemberAdminWindow();
w.Show();
@ -79,12 +142,132 @@ namespace Elwig.Windows {
App.FocusBaseData();
}
private void SeasonFinishButton_Click(object sender, RoutedEventArgs evt) {
App.FocusSeasonFinish();
}
private void MailButton_Click(object sender, RoutedEventArgs evt) {
App.FocusMailWindow();
}
protected override Task OnRenewContext(AppDbContext ctx) {
SeasonInput_ValueChanged(null, null);
return Task.CompletedTask;
}
private void SeasonFinish_Expanded(object sender, RoutedEventArgs evt) {
Height = 570;
}
private void SeasonFinish_Collapsed(object sender, RoutedEventArgs evt) {
Height = 390;
}
private async void SeasonInput_ValueChanged(object? sender, RoutedEventArgs? evt) {
using var ctx = new AppDbContext();
var s0 = await ctx.Seasons.FindAsync(SeasonInput.Value);
var valid = (s0 != null);
DeliveryConfirmationButton.IsEnabled = valid;
OverUnderDeliveryButton.IsEnabled = valid;
AutoBusinessSharesButton.IsEnabled = valid && false;
PaymentButton.IsEnabled = valid;
BreakdownButton.IsEnabled = valid;
}
private void DeliveryConfirmationButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
var w = App.FocusMailWindow(year);
w.AddDeliveryConfirmation();
}
private async void OverUnderDeliveryButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
var d = new SaveFileDialog() {
FileName = $"Über-Unterlieferungen-{year}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Über-/Unterlieferungen {year} speichern unter - Elwig"
};
if (d.ShowDialog() == false)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
await App.HintContextChange();
using var ctx = new AppDbContext();
var tbl1 = await OverUnderDeliveryData.ForSeason(ctx.OverUnderDeliveryRows, year);
var tbl2 = await AreaComUnderDeliveryData.ForSeason(ctx.AreaComUnderDeliveryRows, year);
var tbl3 = await MemberDeliveryPerVariantData.ForSeason(ctx.MemberDeliveryPerVariantRows, year);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl1);
await ods.AddTable(tbl2);
await ods.AddTable(tbl3);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private async void AutoBusinessSharesButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
if (false && App.Client.IsMatzen) {
AutoBusinessSharesButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new Billing(year);
await b.AutoAdjustBusinessShare();
Mouse.OverrideCursor = null;
AutoBusinessSharesButton.IsEnabled = true;
} else {
MessageBox.Show(
"Es ist kein automatisches Nachzeichnen der Geschäftsanteile\n" +
"für diese Genossenschaft eingestellt!\n" +
"Bitte wenden Sie sich an die Programmierer!", "Fehler",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private void PaymentButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
App.FocusPaymentVariants(year);
}
private async void BreakdownButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
var d = new SaveFileDialog() {
FileName = $"Aufschlüsselung-{year}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Sorten-/Qualitätsaufschlüsselung {year} speichern unter - Elwig"
};
if (d.ShowDialog() == false)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
await App.HintContextChange();
using var ctx = new AppDbContext();
using var ods = new OdsFile(d.FileName);
var tblTotal = await WeightBreakdownData.ForSeason(ctx.WeightBreakDownRows, year);
await ods.AddTable(tblTotal);
foreach (var branch in await ctx.Branches.OrderBy(b => b.Name).ToListAsync()) {
var tbl = await WeightBreakdownData.ForSeason(ctx.WeightBreakDownRows, year, branch);
await ods.AddTable(tbl);
}
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
}
}

View File

@ -14,6 +14,7 @@ using System.Diagnostics;
using Elwig.Models.Dtos;
using Elwig.Helpers.Export;
using Microsoft.Win32;
using Elwig.Helpers.Billing;
namespace Elwig.Windows {
public partial class MemberAdminWindow : AdministrationWindow {
@ -637,6 +638,11 @@ namespace Elwig.Windows {
private static async Task GenerateDeliveryConfirmation(Member m, int year, ExportMode mode) {
Mouse.OverrideCursor = Cursors.AppStarting;
try {
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
await App.HintContextChange();
using var ctx = new AppDbContext();
var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, year, m);
using var doc = new DeliveryConfirmation(ctx, year, m, data);

View File

@ -23,6 +23,14 @@
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ctrl:UnitTextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="Height" Value="25"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
<Style TargetType="ComboBox">
<Setter Property="Height" Value="25"/>
<Setter Property="FontSize" Value="14"/>

View File

@ -94,7 +94,7 @@ namespace Elwig.Windows {
WeightModifierInput.Text = "";
DataInput.Text = v.Data;
}
WeightModifierInput.TextBox.IsReadOnly = false;
WeightModifierInput.IsReadOnly = false;
ConsiderModifiersInput.IsEnabled = !locked;
ConsiderPenaltiesInput.IsEnabled = !locked;
ConsiderPenaltyInput.IsEnabled = !locked;
@ -126,7 +126,7 @@ namespace Elwig.Windows {
TransferDateInput.Text = "";
TransferDateInput.IsReadOnly = true;
WeightModifierInput.Text = "";
WeightModifierInput.TextBox.IsReadOnly = true;
WeightModifierInput.IsReadOnly = true;
ConsiderModifiersInput.IsChecked = false;
ConsiderModifiersInput.IsEnabled = false;
ConsiderPenaltiesInput.IsChecked = false;
@ -554,18 +554,18 @@ namespace Elwig.Windows {
}
private void WeightModifierInput_TextChanged(object? sender, TextChangedEventArgs? evt) {
var res = Validator.CheckDecimal(WeightModifierInput.TextBox, false, 3, 2, true);
var res = Validator.CheckDecimal(WeightModifierInput, false, 3, 2, true);
if (BillingData == null) {
ControlUtils.ClearInputState(WeightModifierInput.TextBox);
ControlUtils.ClearInputState(WeightModifierInput);
return;
}
var val = WeightModifierInput.Text.Length > 0 && res.IsValid ? double.Parse(WeightModifierInput.Text) : 0;
WeightModifierChanged = (val != Math.Round(BillingData.NetWeightModifier * 100.0, 8) && val != Math.Round(BillingData.GrossWeightModifier * 100.0, 8)) ||
(val == 0 && (BillingData.NetWeightModifier != 0 || BillingData.GrossWeightModifier != 0));
if (WeightModifierChanged) {
ControlUtils.SetInputChanged(WeightModifierInput.TextBox);
ControlUtils.SetInputChanged(WeightModifierInput);
} else {
ControlUtils.ClearInputState(WeightModifierInput.TextBox);
ControlUtils.ClearInputState(WeightModifierInput);
}
UpdateSaveButton();
}

View File

@ -1,54 +0,0 @@
<local:ContextWindow x:Class="Elwig.Windows.SeasonFinishWindow"
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"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Loaded="Window_Loaded"
Title="Leseabschluss - Elwig" Height="450" Width="800">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="9,3"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Width" Value="200"/>
</Style>
</Window.Resources>
<Grid>
<Label Content="Saison:" Margin="50,40,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Padding="2,4,2,4" Height="25"/>
<xctk:IntegerUpDown Name="SeasonInput" Height="25" Width="56" FontSize="14" Minimum="1000" Maximum="9999"
Margin="110,40,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"
ValueChanged="SeasonInput_ValueChanged"/>
<Button x:Name="CalculateBucketsButton" Content="Aufteilung Berechnen"
Click="CalculateBucketsButton_Click"
Margin="50,80,0,0"/>
<CheckBox x:Name="AllowAttrIntoLowerInput" Content="Erlauben Lieferungen auch auf (konfigurierte) &quot;schlechtere&quot; Flächenbindungen aufzuteilen"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="255,68,0,0"/>
<CheckBox x:Name="AvoidUnderDeliveriesInput" Content="Unterlieferungen durch Abzug bei &quot;besseren&quot; Flächenbindungen vermeiden"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="255,88,0,0"/>
<CheckBox x:Name="HonorGebundenInput" Margin="255,108,0,0" VerticalAlignment="Top">
<TextBlock>Bei Lieferungen das Feld <Italic>Gebunden</Italic> berücksichtigen</TextBlock>
</CheckBox>
<Button x:Name="DeliveryConfirmationButton" Content="Anlieferungsbestätigungen"
Click="DeliveryConfirmationButton_Click"
Margin="50,120,0,0"/>
<Button x:Name="OverUnderDeliveryButton" Content="Über-/Unterlieferungen"
Click="OverUnderDeliveryButton_Click"
Margin="50,160,0,0"/>
<Button x:Name="AutoBusinessSharesButton" Content="Autom. GA nachzeichnen"
Click="AutoBusinessSharesButton_Click"
Margin="50,200,0,0"/>
<Button x:Name="PaymentButton" Content="Auszahlung"
Click="PaymentButton_Click"
Margin="50,240,0,0"/>
</Grid>
</local:ContextWindow>

View File

@ -1,135 +0,0 @@
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Microsoft.Win32;
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Elwig.Windows {
public partial class SeasonFinishWindow : ContextWindow {
public SeasonFinishWindow() {
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs evt) {
SeasonInput.Value = Utils.CurrentLastSeason;
}
protected override Task OnRenewContext(AppDbContext ctx) {
SeasonInput_ValueChanged(null, null);
return Task.CompletedTask;
}
private async void SeasonInput_ValueChanged(object? sender, RoutedEventArgs? evt) {
using var ctx = new AppDbContext();
var s0 = await ctx.Seasons.FindAsync(SeasonInput.Value);
var s1 = await ctx.Seasons.FindAsync(SeasonInput.Value + 1);
var valid = (s0 != null);
var last = (s1 == null);
CalculateBucketsButton.IsEnabled = valid && last;
DeliveryConfirmationButton.IsEnabled = valid;
OverUnderDeliveryButton.IsEnabled = valid;
AutoBusinessSharesButton.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) {
if (SeasonInput.Value is not int year)
return;
CalculateBucketsButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ctx = new AppDbContext();
if (await ctx.Seasons.FindAsync(year) is not Season s)
return;
s.Billing_AllowAttrsIntoLower = AllowAttrIntoLowerInput.IsChecked ?? false;
s.Billing_AvoidUnderDeliveries = AvoidUnderDeliveriesInput.IsChecked ?? false;
s.Billing_HonorGebunden = HonorGebundenInput.IsChecked ?? false;
ctx.Update(s);
await ctx.SaveChangesAsync();
} catch { }
var b = new Billing(year);
await b.FinishSeason();
await b.CalculateBuckets();
await App.HintContextChange();
Mouse.OverrideCursor = null;
CalculateBucketsButton.IsEnabled = true;
}
private void DeliveryConfirmationButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
var w = App.FocusMailWindow(year);
w.AddDeliveryConfirmation();
}
private async void OverUnderDeliveryButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
var d = new SaveFileDialog() {
FileName = $"Über-Unterlieferungen-{year}.ods",
DefaultExt = "ods",
Filter = "OpenDocument Format Spreadsheet (*.ods)|*.ods",
Title = $"Über-/Unterlieferungen {year} speichern unter - Elwig"
};
if (d.ShowDialog() == false)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
try {
using var ctx = new AppDbContext();
var tbl1 = await OverUnderDeliveryData.ForSeason(ctx.OverUnderDeliveryRows, year);
var tbl2 = await AreaComUnderDeliveryData.ForSeason(ctx.AreaComUnderDeliveryRows, year);
var tbl3 = await MemberDeliveryPerVariantData.ForSeason(ctx.MemberDeliveryPerVariantRows, year);
using var ods = new OdsFile(d.FileName);
await ods.AddTable(tbl1);
await ods.AddTable(tbl2);
await ods.AddTable(tbl3);
} catch (Exception exc) {
MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
}
Mouse.OverrideCursor = null;
}
private async void AutoBusinessSharesButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
if (false && App.Client.IsMatzen) {
AutoBusinessSharesButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.AppStarting;
var b = new Billing(year);
await b.AutoAdjustBusinessShare();
Mouse.OverrideCursor = null;
AutoBusinessSharesButton.IsEnabled = true;
} else {
MessageBox.Show(
"Es ist kein automatisches Nachzeichnen der Geschäftsanteile\n" +
"für diese Genossenschaft eingestellt!\n" +
"Bitte wenden Sie sich an die Programmierer!", "Fehler",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private void PaymentButton_Click(object sender, RoutedEventArgs evt) {
if (SeasonInput.Value is not int year)
return;
App.FocusPaymentVariants(year);
}
}
}

View File

@ -1,22 +0,0 @@
<Window x:Class="Elwig.Windows.TestWindow"
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"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="Test Fenster - Elwig" MinHeight="400" MinWidth="325" Height="450" Width="800" ResizeMode="CanResize">
<Grid>
<xctk:CheckComboBox x:Name="MyComboBox" HorizontalAlignment="Left" Margin="216,186,0,0" VerticalAlignment="Top" Delimiter=", "
SelectedValue="{Binding SelectedValue}"
SelectedItemsOverride="{Binding SelectedItems}"
ItemSelectionChanged="OnItemSelectionChanged"/>
<TextBlock x:Name="MyText" HorizontalAlignment="Left" Margin="318,246,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
<ListBox x:Name="MyListBox" HorizontalAlignment="Left" Margin="492,125,0,0" VerticalAlignment="Top"/>
<Button x:Name="WeighingButton1" Click="WeighingButton1_Click" Height="30" Content="Aktuelles Gewicht" Width="110" Margin="515,246,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button x:Name="WeighingButton2" Click="WeighingButton2_Click" Height="30" Content="Wiegen" Width="110" Margin="515,285,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBlock x:Name="Output" Height="20" Width="200" Margin="470,329,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button x:Name="ZipButton" Content="ZIP-File" Click="ZipButton_Click"
Margin="50,270,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</Window>

View File

@ -1,57 +0,0 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Helpers.Billing;
using Elwig.Helpers.Export;
using Elwig.Helpers.Weighing;
using Elwig.Models.Dtos;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Xceed.Wpf.Toolkit.Primitives;
namespace Elwig.Windows {
public partial class TestWindow : Window {
public TestWindow() {
InitializeComponent();
MyComboBox.ItemsSource = new string[] { "Klasse A" , "Klasse B", "Klasse C", "Klasse D", "Klasse E", "Klasse F" };
MyListBox.ItemsSource = new string[] { "Test 1", "Test 2", "Test 3", "Test 4" };
}
private void OnItemSelectionChanged(object sender, ItemSelectionChangedEventArgs e) {
MyText.Text = string.Join(", ", MyComboBox.SelectedItems.Cast<string>());
}
private async void WeighingButton1_Click(object sender, RoutedEventArgs evt) {
try {
if (App.Scales[0] is not ICommandScale cs) return;
var res = await cs.GetCurrentWeight();
Output.Text = res.ToString();
} catch (Exception e) {
MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{e.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async void WeighingButton2_Click(object sender, RoutedEventArgs evt) {
try {
if (App.Scales[0] is not ICommandScale cs) return;
var res = await cs.Weigh();
Output.Text = res.ToString();
} catch (Exception e) {
MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{e.Message}", "Waagenfehler",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async void ZipButton_Click(object sender, RoutedEventArgs evt) {
using var ctx = new AppDbContext();
using var ods = new OdsFile(@"C:\Users\Lorenz\Desktop\test.ods");
await ods.AddTable(await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, 2023, ctx.Members.Find(2948)));
}
}
}

View File

@ -11,9 +11,14 @@ file = database.sqlite3
;log = db.log
[update]
url = https://www.necronda.net/elwig/files/elwig/latest?format=json
url = https://www.necronda.net/elwig/files/elwig/latest
auto = true
[sync]
;url = https://www.necronda.net/elwig/clients/WGX/
;username = ""
;password = ""
[smtp]
;host =
;port =

View File

@ -60,6 +60,6 @@
<None Include="Files\config.ini" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WixToolset.Heat" Version="4.0.3" />
<PackageReference Include="WixToolset.Heat" Version="5.0.0" />
</ItemGroup>
</Project>
</Project>

View File

@ -5,15 +5,15 @@
<Cultures>de-AT</Cultures>
</PropertyGroup>
<Target Name="CustomBeforeBuild" BeforeTargets="BeforeBuild">
<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 -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"' />
<Exec Command="curl -s -L &quot;https://go.microsoft.com/fwlink/p/?LinkId=2124703&quot; -z &quot;$(ProjectDir)\Files\MicrosoftEdgeWebview2Setup.exe&quot; -o &quot;$(ProjectDir)\Files\MicrosoftEdgeWebview2Setup.exe&quot;" />
<Exec Command="curl -s -L &quot;https://aka.ms/vs/17/release/vc_redist.x86.exe&quot; -z &quot;$(ProjectDir)\Files\VC_redist.x86.exe&quot; -o &quot;$(ProjectDir)\Files\VC_redist.x86.exe&quot;" />
<PropertyGroup>
<DefineConstants>ElwigProjectDir=..\Elwig</DefineConstants>
</PropertyGroup>
</Target>
<ItemGroup>
<ProjectReference Include="..\Installer\Installer.wixproj" />
<PackageReference Include="WixToolset.Bal.wixext" Version="4.0.3" />
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.3" />
<PackageReference Include="WixToolset.Bal.wixext" Version="4.0.5" />
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.5" />
</ItemGroup>
</Project>
</Project>

View File

@ -0,0 +1,43 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
using Microsoft.EntityFrameworkCore;
namespace Tests.DocumentTests {
[TestFixture]
public class CreditNoteTest {
[Test]
public async Task Test_01_VirtualCreditNote() {
using var ctx = new AppDbContext();
var m = await ctx.Members.FindAsync(101);
var p = await ctx.MemberPayments.Where(p => p.Year == 2020 && p.AvNr == 1).SingleAsync();
var data = await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.Seasons, 2020, 1);
using var doc = new CreditNote(ctx, p, data[m!.MgNr], false, false, false,
ctx.GetMemberUnderDelivery(2020, m!.MgNr).GetAwaiter().GetResult());
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("Traubengutschrift Max Mustermann Probevariante"));
Assert.That(text, Contains.Substring("AT12 3456 7890 1234 5678"));
Assert.That(text, Contains.Substring("""
20201001X001 1 Grüner Veltliner 73 15,0 ungeb.: 3 219 - -
20201001X003 1 Grüner Veltliner 75 15,4 ungeb.: 2 561 - -
20201001X003 2 Grüner Veltliner Kabinett 87 17,6 ungeb.: 3 129 - -
20201001X003 3 Grüner Veltliner 79 16,1 ungeb.: 1 280 - -
20201001X005 1 Welschriesling 84 17,0 ungeb.: 3 192 - -
20201001X005 2 Welschriesling 84 17,1 ungeb.: 2 190 - -
"""));
Assert.That(text, Contains.Substring("Gesamtbetrag: € 1 000,00"));
Assert.That(text, Contains.Substring("Auszahlungsbetrag: € 1 000,00"));
});
}
}
}

View File

@ -0,0 +1,44 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
[TestFixture]
public class DeliveryConfirmationTest {
[Test]
public async Task Test_01_SimpleDeliveryConfirmation() {
using var ctx = new AppDbContext();
var m = await ctx.Members.FindAsync(101);
var data = await DeliveryConfirmationDeliveryData.ForMember(ctx.DeliveryParts, 2020, m!);
using var doc = new DeliveryConfirmation(ctx, 2020, m!, data);
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("Anlieferungsbestätigung 2020"));
Assert.That(text, Contains.Substring("""
20201001X003 2 Grüner Veltliner Kabinett Kabinett 87 17,6 ungeb.: 3 129 3 129 n
20201001X003 3 Grüner Veltliner Qualitätswein 79 16,1 ungeb.: 1 280 1 280 n
20201001X003 1 Grüner Veltliner Qualitätswein 75 15,4 ungeb.: 2 561 2 561 n
20201001X001 1 Grüner Veltliner Qualitätswein 73 15,0 ungeb.: 3 219 3 219 n
20201001X005 2 Welschriesling Kabinett 84 17,1 ungeb.: 2 190 2 190 n
20201001X005 1 Welschriesling Kabinett 84 17,0 ungeb.: 3 192 3 192 n
"""));
Assert.That(text, Contains.Substring("Gesamt: 15 571"));
Assert.That(text, Contains.Substring("""
Sortenaufteilung [kg] ohne Attr./Bewirt. Kabinett Gesamt
Grüner Veltliner 7 060 3 129 10 189
Welschriesling 5 382 - 5 382
12 442 3 129 15 571
"""));
});
}
}
}

View File

@ -0,0 +1,47 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
[TestFixture]
public class DeliveryJournalTest {
[Test]
public async Task Test_01_AllDeliveries2020() {
using var ctx = new AppDbContext();
var data = await DeliveryJournalData.FromQuery(ctx.Deliveries.Where(d => d.Year == 2020).SelectMany(d => d.Parts), ["Saison 2020"]);
using var doc = new DeliveryJournal("Saison 2020", data);
var text = await Utils.GeneratePdfText(doc, true);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("Lieferjournal"));
Assert.That(text, Contains.Substring("Saison 2020"));
Assert.That(text, Contains.Substring("""
20201001X001 1 01.10.2020 09:03 101 MUSTERMANN Max Grüner Veltliner 73 15,0 3 219
20201001X002 1 01.10.2020 09:35 102 WEINBAUER Wernhardt Grüner Veltliner 86 17,5 2 987
20201001X002 2 01.10.2020 09:35 102 WEINBAUER Wernhardt Grüner Veltliner 87 17,7 1 873
20201001X003 1 01.10.2020 10:24 101 MUSTERMANN Max Grüner Veltliner 75 15,4 2 561
20201001X003 2 01.10.2020 10:24 101 MUSTERMANN Max Grüner Veltliner 87 17,6 3 129
20201001X003 3 01.10.2020 10:24 101 MUSTERMANN Max Grüner Veltliner 79 16,1 1 280
20201001X004 1 01.10.2020 11:13 102 WEINBAUER Wernhardt Grüner Veltliner 82 16,7 4 002
20201001X004 2 01.10.2020 11:13 102 WEINBAUER Wernhardt Grüner Veltliner 75 15,3 481
20201001X005 1 01.10.2020 12:45 101 MUSTERMANN Max Welschriesling 84 17,0 3 192
20201001X005 2 01.10.2020 12:45 101 MUSTERMANN Max Welschriesling 84 17,1 2 190
20201001X006 1 01.10.2020 13:18 102 WEINBAUER Wernhardt Grüner Veltliner 74 15,2 1 732
20201002X001 1 02.10.2020 09:13 103 MUSTERBAUER Matthäus Grüner Veltliner 80 16,3 3 198
20201002X001 2 02.10.2020 09:13 103 MUSTERBAUER Matthäus Grüner Veltliner 75 15,4 2 134
20201002X002 1 02.10.2020 09:28 103 MUSTERBAUER Matthäus Grüner Veltliner 78 16,0 2 901
20201002X002 2 02.10.2020 09:28 103 MUSTERBAUER Matthäus Grüner Veltliner 85 17,3 3 321
20201002X003 1 02.10.2020 10:11 103 MUSTERBAUER Matthäus Welschriesling 85 17,2 3 998
20201003X001 1 03.10.2020 14:13 104 WINZER Waltraud Zweigelt 73 15,0 1 212
20201003X001 2 03.10.2020 14:13 104 WINZER Waltraud Zweigelt 74 15,2 2 471
20201003X001 3 03.10.2020 14:13 104 WINZER Waltraud Zweigelt 77 15,7 842
20201003X002 1 03.10.2020 14:39 104 WINZER Waltraud Zweigelt 84 17,0 3 578
20201003X002 2 03.10.2020 14:39 104 WINZER Waltraud Zweigelt 85 17,2 3 862
20201003X003 1 03.10.2020 15:15 104 WINZER Waltraud Blauer Portugieser 89 18,0 2 410
20201003X003 2 03.10.2020 15:15 104 WINZER Waltraud Blauer Portugieser 89 18,1 2 313
Gesamt: (Teil-)Lieferungen: 12 (23) 82 16,6 58 886
"""));
});
}
}
}

View File

@ -5,12 +5,11 @@ 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);
using var ctx = new AppDbContext();
var d = await ctx.Deliveries.FindAsync(2020, 1);
using var doc = new DeliveryNote(d!, ctx);
var text = await Utils.GeneratePdfText(doc);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("""
@ -22,8 +21,121 @@ namespace Tests.DocumentTests {
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!"));
Assert.That(text, Contains.Substring("""
1 Grüner Veltliner Qualitätswein 73 15,0 3 219
Herkunft: Österreich / Weinland / Niederösterreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
});
}
[Test]
public async Task Test_02_TwoDeliveryParts() {
using var ctx = new AppDbContext();
var d = await ctx.Deliveries.FindAsync(2020, 4);
using var doc = new DeliveryNote(d!, ctx);
var text = await Utils.GeneratePdfText(doc);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("""
W&B Weinbauer GesbR
WEINBAUER Wernhardt
Winzerstraße 2
2223 Hohenruppersdorf
"""));
Assert.That(text, Contains.Substring("4725836")); // 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. 20201001X004"));
Assert.That(text, Contains.Substring("Ich bin der Text, der auf einem Traubenübernahmeschein steht!"));
Assert.That(text, Contains.Substring("""
1 Grüner Veltliner Qualitätswein 82 16,7 4 002
Herkunft: Österreich / Weinland / Niederösterreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("""
2 Grüner Veltliner Qualitätswein 75 15,3 481
Herkunft: Österreich / Weinland / Niederösterreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("Gesamt: 81 16,5 4 483"));
});
}
[Test]
public async Task Test_03_DeliveryPartsWithAttribute() {
using var ctx = new AppDbContext();
var d = await ctx.Deliveries.FindAsync(2020, 3);
using var doc = new DeliveryNote(d!, ctx);
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. 20201001X003"));
Assert.That(text, Contains.Substring("Ich bin der Text, der auf einem Traubenübernahmeschein steht!"));
Assert.That(text, Contains.Substring("""
1 Grüner Veltliner Qualitätswein 75 15,4 2 561
Herkunft: Österreich / Weinland / Niederösterreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("""
2 Grüner Veltliner Kabinett Kabinett 87 17,6 3 129
Herkunft: Österreich / Weinland / Niederösterreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("""
3 Grüner Veltliner Qualitätswein 79 16,1 1 280
Herkunft: Österreich / Weinland / Niederösterreich
/ Matzner Hügel / Hohenruppersdorf / KG Hohenruppersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("Gesamt: 81 16,5 6 970"));
});
}
[Test]
public async Task Test_04_DeliveryPartsWithCultivation() {
using var ctx = new AppDbContext();
var d = await ctx.Deliveries.FindAsync(2020, 7);
using var doc = new DeliveryNote(d!, ctx);
var text = await Utils.GeneratePdfText(doc);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("""
MUSTERBAUER Matthäus
Brünner Straße 10
2120 Wolkersdorf im Weinviertel
"""));
Assert.That(text, Contains.Substring("7258369")); // 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. 20201002X001"));
Assert.That(text, Contains.Substring("Ich bin der Text, der auf einem Traubenübernahmeschein steht!"));
Assert.That(text, Contains.Substring("""
1 Grüner Veltliner Qualitätswein 80 16,3 3 198
Bewirtschaftung: Bio (AT-BIO-302)
Herkunft: Österreich / Weinland / Niederösterreich
/ Wolkersdorfer Hochleithen / Wolkersdorf im Weinviertel / KG Wolkersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("""
2 Grüner Veltliner Qualitätswein 75 15,4 2 134
Bewirtschaftung: Bio (AT-BIO-302)
Herkunft: Österreich / Weinland / Niederösterreich
/ Wolkersdorfer Hochleithen / Wolkersdorf im Weinviertel / KG Wolkersdorf
Waage: ?, ID: ? (netto/gerebelt gewogen)
"""));
Assert.That(text, Contains.Substring("Gesamt: 78 15,9 5 332"));
});
}
}

View File

@ -0,0 +1,24 @@
using Elwig.Documents;
using Elwig.Helpers;
namespace Tests.DocumentTests {
[TestFixture]
public class LetterheadTest {
[Test]
public async Task Test_01_SimpleLetterhead() {
using var ctx = new AppDbContext();
var m = await ctx.Members.FindAsync(104);
using var doc = new Letterhead(m!);
var text = await Utils.GeneratePdfText(doc);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("WG Test | Genossenschaftsstraße 1 | 2120 Wolkersdorf"));
Assert.That(text, Contains.Substring("""
WINZER Waltraud
Wiener Straße 15
2120 Wolkersdorf im Weinviertel
"""));
});
}
}
}

View File

@ -0,0 +1,41 @@
using Elwig.Documents;
using Elwig.Helpers;
namespace Tests.DocumentTests {
[TestFixture]
public class MemberDataSheetTest {
[Test]
public async Task Test_01_SimpleMember() {
using var ctx = new AppDbContext();
var m = await ctx.Members.FindAsync(104);
using var doc = new MemberDataSheet(m!, ctx);
var text = await Utils.GeneratePdfText(doc);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("""
WINZER Waltraud
Wiener Straße 15
2120 Wolkersdorf im Weinviertel
"""));
Assert.That(text, Contains.Substring("Stammdatenblatt WINZER Waltraud"));
Assert.That(text, Contains.Substring("""
Titel (vorangestellt) Vorname Nachname Titel (nachgestellt)
Waltraud Winzer
"""));
Assert.That(text, Contains.Substring("Mitglieds-Nr.: 104"));
Assert.That(text, Contains.Substring("Adresse: Wiener Straße 15"));
Assert.That(text, Contains.Substring("PLZ/Ort: 2120 Wolkersdorf im Weinviertel (Wolkersdorf im Weinviertel)"));
Assert.That(text, Contains.Substring("Adresse: Wiener Straße 15"));
Assert.That(text, Contains.Substring("""
Rechnungsadresse (optional)
Name: Weinbau Waltraud Winzer GmbH
Adresse: Hauptstraße 1
PLZ/Ort: 2122 Riedenthal (Riedenthal)
"""));
Assert.That(text, Contains.Substring("IBAN: AT12 3456 7890 1234 5678"));
Assert.That(text, Contains.Substring("Betriebs-Nr.: 2583691"));
Assert.That(text, Contains.Substring("Stammgemeinde: Wolkersdorf"));
});
}
}
}

View File

@ -0,0 +1,26 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
[TestFixture]
public class MemberListTest {
[Test]
public async Task Test_01_TwoMembers() {
using var ctx = new AppDbContext();
var data = await MemberListData.FromQuery(ctx.Members, []);
using var doc = new MemberList("Alle Mitglieder", data);
var text = await Utils.GeneratePdfText(doc, true);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("Mitgliederliste"));
Assert.That(text, Contains.Substring("Alle Mitglieder"));
Assert.That(text, Contains.Substring("""
101 MUSTERMANN Max Winzerstraße 1 2223 Hohenruppersdorf 1472583 0 Hohenruppersdorf
102 WEINBAUER Wernhardt Winzerstraße 2 2223 Hohenruppersdorf 4725836 0 Hohenruppersdorf
W&B Weinbauer GesbR Winzerstraße 2 2223 Hohenruppersdorf
"""));
});
}
}
}

View File

@ -6,11 +6,11 @@ namespace Tests.DocumentTests {
private static readonly string FileName = Path.Combine(Path.GetTempPath(), "test_document.pdf");
public static async Task<string> GeneratePdfText(Document doc) {
public static async Task<string> GeneratePdfText(Document doc, bool preserveLayout = false) {
await doc.Generate();
try {
doc.SaveTo(FileName);
var conv = new PdfToTextConverter { CustomArgs = "-raw " };
var conv = new PdfToTextConverter { CustomArgs = preserveLayout ? "-layout " : "-raw " };
return conv.GenerateText(FileName);
} finally {
File.Delete(FileName);

View File

@ -0,0 +1,45 @@
using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Models.Dtos;
namespace Tests.DocumentTests {
[TestFixture]
public class WineQualityStatisticsTest {
[Test]
public async Task Test_01_AllDeliveries2020() {
using var ctx = new AppDbContext();
var data = await WineQualityStatisticsData.FromQuery(ctx.Deliveries.Where(d => d.Year == 2020).SelectMany(d => d.Parts));
using var doc = new WineQualityStatistics("Saison 2020", data);
var text = await Utils.GeneratePdfText(doc);
Assert.Multiple(() => {
Assert.That(text, Contains.Substring("Qualitätsstatistik"));
Assert.That(text, Contains.Substring("Saison 2020"));
Assert.That(text, Contains.Substring("""
Qualitätswein
73 2 4 431
74 2 4 203
75 3 5 176
77 1 842
78 1 2 901
79 1 1 280
80 1 3 198
82 1 4 002
"""));
Assert.That(text, Contains.Substring("""
Kabinett
84 3 8 960
85 3 11 181
86 1 2 987
87 2 5 002
89 2 4 723
"""));
Assert.That(text, Contains.Substring(
"- 0 0 " +
"- 0 0 " +
"77 12 " + "26 033 " +
"86 11 " + "32 853"));
});
}
}
}

View File

@ -1,5 +1,7 @@
-- deletes for DocumentTests
DELETE FROM payment_variant;
DELETE FROM delivery;
DELETE FROM season;
DELETE FROM wine_attribute;
DELETE FROM wine_cultivation;

View File

@ -1,7 +1,9 @@
-- inserts for DocumentTests
INSERT INTO wine_cultivation (cultid, name, description) VALUES
('B', 'Bio', 'AT-BIO-302');
INSERT INTO wine_attribute (attrid, name, active, max_kg_per_ha, strict, fill_lower) VALUES
('B', 'Bio', TRUE, NULL, FALSE, 0),
('K', 'Kabinett', TRUE, NULL, FALSE, 0);
INSERT INTO season (year, currency, min_kg_per_bs, max_kg_per_bs, penalty_per_kg, penalty_amount, penalty_none, start_date, end_date) VALUES
@ -12,9 +14,54 @@ INSERT INTO modifier (year, modid, ordering, name, abs, rel, standard, quick_sel
(2020, 'A', 0, 'Keine Voranmeldung', -1000, NULL, FALSE, FALSE);
INSERT INTO delivery (mgnr, year, did, date, time, zwstid, lnr) VALUES
(101, 2020, 1, '2020-10-01', NULL, 'X', 1),
(101, 2020, 2, '2020-10-01', NULL, 'X', 2),
(101, 2020, 3, '2020-10-01', NULL, 'X', 3);
(101, 2020, 1, '2020-10-01', '09:03:12', 'X', 1),
(102, 2020, 2, '2020-10-01', '09:35:56', 'X', 2),
(101, 2020, 3, '2020-10-01', '10:24:09', 'X', 3),
(102, 2020, 4, '2020-10-01', '11:13:52', 'X', 4),
(101, 2020, 5, '2020-10-01', '12:45:29', 'X', 5),
(102, 2020, 6, '2020-10-01', '13:18:43', 'X', 6),
(103, 2020, 7, '2020-10-02', '09:13:43', 'X', 1),
(103, 2020, 8, '2020-10-02', '09:28:02', 'X', 2),
(103, 2020, 9, '2020-10-02', '10:11:13', 'X', 3),
(104, 2020, 10, '2020-10-03', '14:13:32', 'X', 1),
(104, 2020, 11, '2020-10-03', '14:39:22', 'X', 2),
(104, 2020, 12, '2020-10-03', '15:15:41', 'X', 3);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_id, weighing_id, weighing_reason) VALUES
(2020, 1, 1, 'GV', NULL, 4000, 17, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL);
INSERT INTO delivery_part (year, did, dpnr, sortid, attrid, cultid, weight, kmw, qualid, hkid, kgnr, net_weight, manual_weighing, spl_check, scale_id, weighing_id, weighing_reason) VALUES
(2020, 1, 1, 'GV', NULL, NULL, 3219, 15.0, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 2, 1, 'GV', 'K', NULL, 2987, 17.5, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 2, 2, 'GV', 'K', NULL, 1873, 17.7, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 1, 'GV', NULL, NULL, 2561, 15.4, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 2, 'GV', 'K', NULL, 3129, 17.6, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 3, 3, 'GV', NULL, NULL, 1280, 16.1, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 4, 1, 'GV', NULL, NULL, 4002, 16.7, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 4, 2, 'GV', NULL, NULL, 481, 15.3, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 5, 1, 'WR', NULL, NULL, 3192, 17.0, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 5, 2, 'WR', NULL, NULL, 2190, 17.1, 'KAB', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 6, 1, 'GV', NULL, 'B', 1732, 15.2, 'QUW', 'WLNO', 06109, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 7, 1, 'GV', NULL, 'B', 3198, 16.3, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 7, 2, 'GV', NULL, 'B', 2134, 15.4, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 8, 1, 'GV', NULL, 'B', 2901, 16.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 8, 2, 'GV', NULL, 'B', 3321, 17.3, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 9, 1, 'WR', NULL, 'B', 3998, 17.2, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 10, 1, 'ZW', NULL, NULL, 1212, 15.0, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 10, 2, 'ZW', NULL, NULL, 2471, 15.2, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 10, 3, 'ZW', NULL, NULL, 842, 15.7, 'QUW', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 11, 1, 'ZW', NULL, NULL, 3578, 17.0, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 11, 2, 'ZW', NULL, NULL, 3862, 17.2, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 12, 1, 'BP', NULL, NULL, 2410, 18.0, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL),
(2020, 12, 2, 'BP', NULL, NULL, 2313, 18.1, 'KAB', 'WLNO', 15224, TRUE, FALSE, FALSE, NULL, NULL, NULL);
INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) VALUES
(2020, 1, 1, 0, '_', 3219),
(2020, 3, 1, 0, '_', 2561),
(2020, 3, 2, 0, '_', 3129),
(2020, 3, 3, 0, '_', 1280),
(2020, 5, 1, 0, '_', 3192),
(2020, 5, 2, 0, '_', 2190);
INSERT INTO payment_variant (year, avnr, name, date, transfer_date, test_variant, calc_time, comment, data) VALUES
(2020, 1, 'Probevariante', '2021-01-15', '2021-01-15', TRUE, NULL, NULL, '{}');
INSERT INTO payment_member (year, avnr, mgnr, net_amount) VALUES
(2020, 1, 101, 10000000);

View File

@ -21,13 +21,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NReco.PdfRenderer" Version="1.5.3" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -1,6 +1,7 @@
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.IO;
namespace Tests.WeighingTests {
public abstract class MockScale : IDisposable {
@ -63,9 +64,46 @@ namespace Tests.WeighingTests {
}
public class EventMockScale : MockScale {
private readonly Func<int, string?, int, (string, bool)> Handler;
private readonly Thread ServerThread;
private TcpClient? Client;
private bool IsRunning = true;
public EventMockScale(int port, Func<int, string?, int, (string, bool)> handler) :
base(port) {
// TODO
Handler = handler;
ServerThread = new Thread(new ParameterizedThreadStart(Serve));
ServerThread.Start();
}
private async void Serve(object? parameters) {
while (IsRunning) {
try {
Client = await Server.AcceptTcpClientAsync();
} catch (Exception) {
// ignore
}
}
}
public async Task Weigh(int weight) {
Weight = weight;
await Weigh();
}
public async Task Weigh() {
var (res, inc) = Handler(Weight, Error, IdentNr + 1);
if (inc) IdentNr++;
await Client!.GetStream().WriteAsync(Encoding.ASCII.GetBytes(res));
}
public new void Dispose() {
Client?.Dispose();
IsRunning = false;
ServerThread.Interrupt();
ServerThread.Join();
base.Dispose();
}
}
}

View File

@ -0,0 +1,70 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
[TestFixture]
public class ScaleTestBadenL320 {
private EventMockScale? Mock;
private AveryEventScale? Scale;
private static (string, bool) ScaleHandler(int weight, string? error, int identNr) {
var modes = error?.Split(';') ?? [];
var invalid = modes.Contains("invalid");
var unit = modes.Contains("unit");
if (invalid) {
return ("abcd\r\n", false);
}
bool incr = true;
return ($" {new DateTime(2020, 9, 28, 9, 8, 0):dd.MM.yy HH:mm} {identNr,4} {weight,9}{(unit ? "lb" : "kg")} \r\n", incr);
}
[OneTimeSetUp]
public void SetupScale() {
Mock = new EventMockScale(12345, ScaleHandler);
Scale = new("1", "L320", "tcp://127.0.0.1:12345");
}
[OneTimeTearDown]
public void TeardownScale() {
Mock?.Dispose();
Scale?.Dispose();
}
[SetUp]
public void ResetScale() {
Mock!.IdentNr = 0;
Mock!.Weight = 0;
Mock!.Error = null;
}
[Test]
public async Task Test_01_Normal() {
WeighingResult? res = null;
Scale!.WeighingEvent += (sender, evt) => {
res = evt.Result;
};
await Mock!.Weigh(2345);
await Task.Delay(100);
Assert.That(res, Is.Not.Null);
Assert.That(res, Is.EqualTo(new WeighingResult {
Weight = 2345, WeighingId = "1",
FullWeighingId = $"2020-09-28/1",
Date = new DateOnly(2020, 9, 28), Time = new TimeOnly(9, 8),
}));
await Mock!.Weigh(4215);
await Task.Delay(100);
Assert.That(res, Is.Not.Null);
Assert.That(res, Is.EqualTo(new WeighingResult {
Weight = 4215,
WeighingId = "2",
FullWeighingId = $"2020-09-28/2",
Date = new DateOnly(2020, 9, 28),
Time = new TimeOnly(9, 8),
}));
}
}
}

View File

@ -0,0 +1,144 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
[TestFixture]
class ScaleTestGrInzersdorfL246 {
private MockScale? Mock;
private SysTecITScale? Scale;
private static (string, bool) ScaleHandler(string req, int weight, string? error, int identNr) {
var modes = error?.Split(';') ?? [];
var overloaded = modes.Contains("overloaded");
var moving = modes.Contains("moving");
var invalid = modes.Contains("invalid");
var crc = modes.Contains("crc");
var unit = modes.Contains("unit");
if (invalid) {
return ("abcd\r\n", false);
} else if (!req.StartsWith('<') || !req.EndsWith('>')) {
return ("<31>\r\n", false);
}
req = req[1..^1];
bool incr;
if (req.Length > 3) {
return ("<32>\r\n", false);
} else if (req.StartsWith("RN")) {
incr = true;
} else if (req.StartsWith("RM")) {
incr = false;
} else {
return ("<32>\r\n", false);
}
if (overloaded)
return ("<12>\r\n", false);
if (moving && incr)
return ("<13>\r\n", false);
string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 17, 14, 23, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {"001",3}";
ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
if (crc) checksum += 10;
return ($"<{data}{checksum,8}>\r\n", incr);
}
[OneTimeSetUp]
public void SetupScale() {
Mock = new CommandMockScale(12345, ScaleHandler);
Scale = new("1", "L246", "tcp://127.0.0.1:12345");
}
[OneTimeTearDown]
public void TeardownScale() {
Mock?.Dispose();
Scale?.Dispose();
}
[SetUp]
public void ResetScale() {
Mock!.IdentNr = 0;
Mock!.Weight = 0;
Mock!.Error = null;
}
[Test]
public async Task Test_01_CurrentWeight() {
Mock!.Weight = 1235;
Assert.That(await Scale!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
Weight = 1235, Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
}));
Mock!.Weight = 1240;
Assert.That(await Scale!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
Weight = 1240, Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
}));
Mock!.Weight = 1245;
Assert.That(await Scale!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
Weight = 1245, Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
}));
}
[Test]
public async Task Test_02_Normal() {
Mock!.Weight = 1235;
Assert.That(await Scale!.Weigh(), Is.EqualTo(new WeighingResult {
Weight = 1235, WeighingId = "1",
FullWeighingId = $"2020-10-17/1",
Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
}));
Mock!.Weight = 3335;
Assert.That(await Scale!.Weigh(), Is.EqualTo(new WeighingResult {
Weight = 3335, WeighingId = "2",
FullWeighingId = $"2020-10-17/2",
Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
}));
Mock!.Weight = 6420;
Assert.That(await Scale!.Weigh(), Is.EqualTo(new WeighingResult {
Weight = 6420, WeighingId = "3",
FullWeighingId = $"2020-10-17/3",
Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
}));
}
[Test]
public void Test_03_Moving() {
Mock!.Weight = 1_000;
Mock!.Error = "moving";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
}
[Test]
public void Test_04_Overloaded() {
Mock!.Weight = 10_000;
Mock!.Error = "overloaded";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
}
[Test]
public void Test_05_InvalidResponse() {
Mock!.Weight = 1_000;
Mock!.Error = "invalid";
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
}
[Test]
public void Test_06_InvalidCrc() {
Mock!.Weight = 1_000;
Mock!.Error = "crc";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
}
[Test]
public void Test_07_InvalidUnit() {
Mock!.Weight = 1_000;
Mock!.Error = "unit";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
}
}
}

View File

@ -1,6 +1,6 @@
namespace Tests.WeighingTests {
[TestFixture]
public class ScaleTestWolkersdorf {
public class ScaleTestHaugsdorf {
// TODO
}
}

View File

@ -2,7 +2,7 @@
namespace Tests.WeighingTests {
[TestFixture]
class ScaleTestMatzen {
class ScaleTestMatzenIT3000A {
private MockScale? Mock;
private SysTecITScale? Scale;
@ -43,7 +43,7 @@ namespace Tests.WeighingTests {
return ("<13>\r\n", false);
string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 15, 12, 34, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {1,3}";
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {"1",3}";
ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
if (crc) checksum += 10;
return ($"<{data}{checksum,8}>\r\n", incr);

View File

@ -1,6 +1,6 @@
namespace Tests.WeighingTests {
[TestFixture]
public class ScaleTestGrInzersdorf {
public class ScaleTestSitzendorf {
// TODO
}
}

View File

@ -0,0 +1,163 @@
using Elwig.Helpers.Weighing;
namespace Tests.WeighingTests {
[TestFixture]
class ScaleTestWolkersdorfIT6000E {
private MockScale? MockA;
private MockScale? MockB;
private SysTecITScale? ScaleA;
private SysTecITScale? ScaleB;
private static (string, bool) ScaleHandler(string req, int weight, string? error, int identNr, string terminalNr) {
var modes = error?.Split(';') ?? [];
var overloaded = modes.Contains("overloaded");
var moving = modes.Contains("moving");
var invalid = modes.Contains("invalid");
var crc = modes.Contains("crc");
var unit = modes.Contains("unit");
if (invalid) {
return ("abcd\r\n", false);
} else if (!req.StartsWith('<') || !req.EndsWith('>')) {
return ("<31>\r\n", false);
}
req = req[1..^1];
bool incr;
if (req.Length > 3) {
return ("<32>\r\n", false);
} else if (req.StartsWith("RN")) {
incr = true;
} else if (req.StartsWith("RM")) {
incr = false;
} else {
return ("<32>\r\n", false);
}
if (overloaded)
return ("<12>\r\n", false);
if (moving && incr)
return ("<13>\r\n", false);
string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 8, 8, 47, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {terminalNr,3}";
ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
if (crc) checksum += 10;
return ($"<{data}{checksum,8}>\r\n", incr);
}
[OneTimeSetUp]
public void SetupScale() {
MockA = new CommandMockScale(12345, (req, weight, error, identNr) => ScaleHandler(req, weight, error, identNr, "A"));
MockB = new CommandMockScale(12346, (req, weight, error, identNr) => ScaleHandler(req, weight, error, identNr, "B"));
ScaleA = new("A", "IT3000E", "tcp://127.0.0.1:12345");
ScaleB = new("B", "IT3000E", "tcp://127.0.0.1:12346");
}
[OneTimeTearDown]
public void TeardownScale() {
MockA?.Dispose();
MockB?.Dispose();
ScaleA?.Dispose();
ScaleB?.Dispose();
}
[SetUp]
public void ResetScale() {
MockA!.IdentNr = 0;
MockA!.Weight = 0;
MockA!.Error = null;
MockB!.IdentNr = 0;
MockB!.Weight = 0;
MockB!.Error = null;
}
[Test]
public async Task Test_01_CurrentWeight() {
MockA!.Weight = 1234;
Assert.That(await ScaleA!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
Weight = 1234, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
MockB!.Weight = 3456;
Assert.That(await ScaleB!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
Weight = 3456, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
MockA!.Weight = 1236;
Assert.That(await ScaleA!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
Weight = 1236, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
MockB!.Weight = 3457;
Assert.That(await ScaleB!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
Weight = 3457, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
}
[Test]
public async Task Test_02_Normal() {
MockA!.Weight = 1234;
Assert.That(await ScaleA!.Weigh(), Is.EqualTo(new WeighingResult {
Weight = 1234, WeighingId = "1",
FullWeighingId = $"2020-10-08/1",
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
MockB!.Weight = 3456;
Assert.That(await ScaleB!.Weigh(), Is.EqualTo(new WeighingResult {
Weight = 3456, WeighingId = "1",
FullWeighingId = $"2020-10-08/1",
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
MockA!.Weight = 4321;
Assert.That(await ScaleA!.Weigh(), Is.EqualTo(new WeighingResult {
Weight = 4321, WeighingId = "2",
FullWeighingId = $"2020-10-08/2",
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
MockB!.Weight = 3333;
Assert.That(await ScaleB!.Weigh(), Is.EqualTo(new WeighingResult {
Weight = 3333, WeighingId = "2",
FullWeighingId = $"2020-10-08/2",
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
}));
}
[Test]
public void Test_03_Moving() {
MockA!.Weight = 1_000;
MockA!.Error = "moving";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
}
[Test]
public void Test_04_Overloaded() {
MockA!.Weight = 10_000;
MockA!.Error = "overloaded";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
}
[Test]
public void Test_05_InvalidResponse() {
MockA!.Weight = 1_000;
MockA!.Error = "invalid";
Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
}
[Test]
public void Test_06_InvalidCrc() {
MockA!.Weight = 1_000;
MockA!.Error = "crc";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
}
[Test]
public void Test_07_InvalidUnit() {
MockA!.Weight = 1_000;
MockA!.Error = "unit";
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
}
}
}

View File

@ -1 +1 @@
curl -s -L "https://www.necronda.net/elwig/files/create.sql?v=18" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"
curl -s -L "https://www.necronda.net/elwig/files/create.sql?v=19" -u "elwig:ganzGeheim123!" -o "Resources\Sql\Create.sql"