Compare commits

..

39 Commits

Author SHA1 Message Date
Elias Fierke
313cd58fc7 [feat:] filter-button to prevent live reloading 2026-01-15 11:35:08 +01:00
Elias Fierke
723722ba47 [chore:] introducing KasPerson.id (refsid remains but isn't used in the mgmt-backend anymore) 2026-01-15 11:21:08 +01:00
Elias Fierke
58964896ad [fix:] fixed creation of empty lines 2026-01-15 10:12:40 +01:00
Elias Fierke
af1c3ff8cc [fix:] single address lines were too long 2026-01-14 11:47:54 +01:00
Elias Fierke
48852e4505 [chore:] removed unused methods 2026-01-14 11:35:37 +01:00
Elias Fierke
60cde86efe [fix:] one-line addresses were visible in pdf creation 2026-01-14 11:29:21 +01:00
Elias Fierke
67cfe10f5f [chore:] removed unused methods 2026-01-14 11:24:18 +01:00
Elias Fierke
e186070f05 [chore:] ui consistency improvements 2026-01-13 21:05:41 +01:00
Elias Fierke
d36314b724 [chore:] text change in EditorWindow.axaml 2026-01-13 20:48:27 +01:00
Elias Fierke
beed5decbf [fix:] wrong ui implementation of the wiki-path-elements 2026-01-13 20:47:57 +01:00
Elias Fierke
71859fa978 [chore:] code cleanup by IDE 2026-01-13 20:47:19 +01:00
Elias Fierke
f05470249a [chore:] code cleanup 2026-01-13 20:46:22 +01:00
Elias Fierke
f4918aa8ee [chore:] removed ArticleStore (wanted to do it another way) 2026-01-13 20:45:49 +01:00
Elias Fierke
4e1f08883a [chore:] removed NamePromtWindow which was useless 2026-01-13 20:45:23 +01:00
99b35c0aaf [feat:] forgot to git-add the wiki source files... here they are 2026-01-13 18:33:35 +01:00
aeb4092f28 [feat:] initial wiki service implementation (basic, no editing, wrong directory- and saving-logic) 2026-01-13 18:31:41 +01:00
c760ef0936 [chore:] added refsid to pdf-creation 2026-01-13 18:30:58 +01:00
e750b4c757 [chore:] initial article store implementation for Settings.cs 2026-01-13 18:30:12 +01:00
Elias Fierke
3877d91af4 [chore:] usage of Country (and possible import of ISO3166-Countries) 2026-01-02 18:02:08 +01:00
Elias Fierke
7af6444da2 [file:] imported ISO3166-dll from nuget 2026-01-02 16:50:15 +01:00
Elias Fierke
4cfbcd0ab4 [feat:] added Country implementation for country code editing (unused) 2026-01-01 14:45:57 +01:00
Elias Fierke
b82473ada2 [chore:] PdfBuilder.cs skips empty addresses 2025-12-21 11:43:41 +01:00
Elias Fierke
1cba67253a [chore:] selectable pdf file path for label creation 2025-12-21 11:32:38 +01:00
Elias Fierke
63c1559110 [chore:] added "Postfach" to address creation 2025-12-21 11:25:48 +01:00
Elias Fierke
b670ba11fa [chore:] sender address usage in PdfBuilder.cs 2025-12-15 10:17:10 +01:00
Elias Fierke
1ad57543d1 [chore:] sender address editing 2025-12-15 10:04:33 +01:00
Elias Fierke
6cd4ea2df6 [chore:] added sender address field 2025-12-15 10:01:07 +01:00
Elias Fierke
30e42afe35 [chore:] rearrangend some result window features for debugging purposes (unfinal) 2025-12-14 14:33:55 +01:00
Elias Fierke
70e127b2f0 [init:] Initialized PDF Creation 2025-12-14 14:33:25 +01:00
Elias Fierke
7c73170b46 [chore:] implemented label generation button handler 2025-12-14 14:33:07 +01:00
Elias Fierke
290f69e976 [chore:] the IDE wanted to add some spaces 2025-12-14 14:32:45 +01:00
Elias Fierke
6ce08d7d4a [gui:] added a few tabs that will be used later 2025-12-14 14:32:02 +01:00
Elias Fierke
86df6f6a63 [file:] imported PdfSharp 2025-12-14 14:31:33 +01:00
Elias Fierke
eae0568ae0 [feat:] added KasPersonError.GetString() 2025-12-14 14:31:20 +01:00
Elias Fierke
4ebd6bc407 [fix:] fixed some logical errors in address creation 2025-12-14 14:30:53 +01:00
Elias Fierke
2c22306fef [fix:] plz check was bad 2025-12-14 14:30:33 +01:00
Elias Fierke
174223ba9e [fix:] Address Creation used an empty address instead of the one from the address set 2025-12-14 14:30:07 +01:00
Elias Fierke
8e5709c215 [feat:] initial fully implemented address creation 2025-12-07 13:30:52 +01:00
Elias Fierke
b70bd5e324 [struc:] moved KasPersonError-Instance from addressset to KasPerson-Instance 2025-12-07 13:30:25 +01:00
17 changed files with 1729 additions and 522 deletions

View File

@@ -32,7 +32,8 @@ public class DataImport
return (false, null);
}
var imported = new KasAddressList(KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
var imported =
new KasAddressList(KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
while (!reader.EndOfStream)
{
@@ -50,7 +51,7 @@ public class DataImport
try
{
var person = new KasPerson(
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count),
ParseInt(parts[0]),
parts[1],
parts[2],
@@ -108,7 +109,8 @@ public class DataImport
var headers = headerLine.Split(separator).Select(h => h.Trim()).ToArray();
var imported = new KasAddressList(KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
var imported =
new KasAddressList(KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
var patchType = typeof(AddressPatch);
var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
@@ -116,7 +118,7 @@ public class DataImport
.Where(p => p.PropertyType == typeof(bool) && p.Name.StartsWith("has_", StringComparison.OrdinalIgnoreCase))
.ToArray();
var last_refsid = 1000000;
//var last_refsid = 1000000;
while (!reader.EndOfStream)
{
@@ -128,12 +130,12 @@ public class DataImport
var fieldValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var refsid_existing = false;
//var refsid_existing = false;
foreach (var hasProp in hasProperties)
{
var fieldName = hasProp.Name.Substring(4);
var hasObj = hasProp.GetValue(patch);
var has = hasObj is bool b && b;
@@ -161,7 +163,7 @@ public class DataImport
var altIdx = Array.FindIndex(headers, h =>
string.Equals(h, fieldName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(h, fieldName + "_is", StringComparison.OrdinalIgnoreCase));
if (altIdx >= 0 && altIdx < parts.Length)
resolvedValue = parts[altIdx];
else
@@ -177,16 +179,13 @@ public class DataImport
return fieldValues.TryGetValue(name, out var v) ? v : "";
}
var new_refsid = last_refsid;
if (!patch.has_refsid)
new_refsid = GenerateNewRefsid();
else
new_refsid = ParseInt(GetField("refsid"));
var refsid = 0;
if (patch.has_refsid)
refsid = ParseInt(GetField("refsid"));
try
{
var person = new KasPerson(
new_refsid,
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count), refsid,
GetField("anrede"),
GetField("titel"),
GetField("vorname"),
@@ -224,23 +223,17 @@ public class DataImport
return (true, imported);
int GenerateNewRefsid()
{
int biggest = last_refsid;
foreach (var set in Settings._instance.addressSets.addresses)
{
foreach (var address in set.KasPersons)
{
if (biggest < address.refsid)
{
biggest = address.refsid+1;
}
}
}
last_refsid = biggest+1;
return last_refsid;
}
// int GenerateNewRefsid()
// {
// var biggest = last_refsid;
// foreach (var set in Settings._instance.addressSets.addresses)
// foreach (var address in set.KasPersons)
// if (biggest < address.id)
// biggest = address.id + 1;
//
// last_refsid = biggest + 1;
// return last_refsid;
// }
}

View File

@@ -5,7 +5,7 @@ namespace Logof_Client;
public class KasAddressList //Address-Set
{
public List<KasPersonError> errors = new();
//public List<KasPersonError> errors = new();
public List<KasPerson> KasPersons;
public KasAddressList(string name)
@@ -13,12 +13,9 @@ public class KasAddressList //Address-Set
KasPersons = new List<KasPerson>();
Name = name;
foreach (var set in Settings._instance.addressSets.addresses)
{
if (Name == set.Name)
{
Name = name + "-new";
}
}
var highest = 0;
foreach (var k in Settings._instance.addressSets.addresses)
if (highest <= k.ID)
@@ -37,17 +34,20 @@ public class KasAddressList //Address-Set
public static string GenerateName(string basic_type, bool? is_rest = false)
{
if(is_rest == true)
if (is_rest == true)
return basic_type + " - " + DateTime.Now.ToShortDateString() + " - Rest";
else
return basic_type + " - " + DateTime.Now.ToShortDateString();
return basic_type + " - " + DateTime.Now.ToShortDateString();
}
public void UpdateErrorList(List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> errorList)
{
errors.Clear();
foreach (var error in errorList) errors.Add(new KasPersonError(error));
}
// public void UpdateErrorList(List<KasPersonError> errorList)
// {
// //errors.Clear();
// // foreach (KasPersonError err in errorList)
// // {
// //
// // }
// //foreach (var error in errorList) errors.Add(new KasPersonError(error));
// }
public static int GetIDByAddressSetListItem(string listItemName)
{
@@ -58,8 +58,11 @@ public class KasAddressList //Address-Set
public class KasPerson
{
public KasPersonError PersonError = null;
public KasPerson()
{
id = GenerateNewID(0);
refsid = 0;
anrede = "";
titel = "";
@@ -86,7 +89,7 @@ public class KasPerson
funktionad = "";
}
public KasPerson(int refsid,
public KasPerson(int id, int refsid,
string anrede,
string titel,
string vorname,
@@ -111,6 +114,7 @@ public class KasPerson
string abteilung,
string funktionad)
{
this.id = id;
this.refsid = refsid;
this.anrede = anrede;
this.titel = titel;
@@ -137,6 +141,7 @@ public class KasPerson
this.funktionad = funktionad;
}
public int id { get; set; }
public int refsid { get; set; }
public string anrede { get; set; }
public string titel { get; set; }
@@ -161,41 +166,39 @@ public class KasPerson
public string funktion2 { get; set; }
public string abteilung { get; set; }
public string funktionad { get; set; }
public static int GenerateNewID(int base_id)
{
var newid = 100000 + base_id;
foreach (var set in Settings._instance.addressSets.addresses)
foreach (var add in set.KasPersons)
if (add.id >= newid)
newid = add.id + 1;
return newid;
}
}
public class KasPersonError
{
public KasPersonError((int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>) single_result)
public KasPersonError((List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>) single_result)
{
refsid = single_result.Item1;
errors = single_result.Item2;
warnings = single_result.Item3;
// try
// {
// foreach (var err in single_result.Item2) errors += err + ", ";
// errors = errors.Trim();
// errors = errors.TrimEnd(',');
// }
// catch
// {
// }
//
// try
// {
// if (single_result.Item3 != null)
// {
// foreach (var err in single_result.Item3) warnings += err + ", ";
// warnings = warnings.Trim();
// warnings = warnings.TrimEnd(',');
// }
// }
// catch (Exception e)
// {
// Console.WriteLine(e.Message);
// }
//refsid = single_result.Item1;
errors = single_result.Item1;
warnings = single_result.Item2;
}
public int refsid { get; set; }
//public int refsid { get; set; }
public List<AddressCheck.ErrorTypes> errors { get; set; } = new();
public List<AddressCheck.WarningTypes> warnings { get; set; } = new();
public string GetString()
{
var output = "";
foreach (var error in errors) output += error + ", ";
foreach (var warning in warnings) output += warning + ", ";
return output;
}
}

View File

@@ -9,30 +9,43 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.2"/>
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.2"/>
<PackageReference Include="Avalonia.Desktop" Version="11.3.2"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.2"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.2"/>
<PackageReference Include="Avalonia" Version="11.3.2" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.2" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.2" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.2">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Lucide.Avalonia" Version="0.1.35"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
<PackageReference Include="ISO3166" Version="1.0.4" />
<PackageReference Include="Lucide.Avalonia" Version="0.1.35" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="PdfSharp" Version="6.1.1" />
</ItemGroup>
<ItemGroup>
<None Remove="assets\icon.ico"/>
<PackageReference Include="Markdig" Version="0.30.3" />
<PackageReference Include="AvaloniaEdit" Version="0.10.12" />
</ItemGroup>
<ItemGroup>
<None Include="wiki\**\*.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Remove="assets\icon.ico" />
<AvaloniaResource Include="assets\icon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
<None Remove="assets\calc_man.png"/>
<None Remove="assets\calc_man.png" />
<AvaloniaResource Include="assets\calc_man.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
<None Remove="assets\loading.mp4"/>
<None Remove="assets\loading.mp4" />
<AvaloniaResource Include="assets\loading.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>

View File

@@ -135,6 +135,7 @@
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False"
Click="BtnGenerateLabels_OnClick"
HorizontalContentAlignment="Center" x:Name="BtnGenerateLabels"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
@@ -160,10 +161,12 @@
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresUnite" Width="36" Height="36" />
<Label Content="Vereinigung" VerticalContentAlignment="Center" FontSize="15"
<Label Content="Vereinigung" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9" Content="Fügt Elemente beider Mengen in eine Menge zusammen"></Label>
<Label FontSize="9"
Content="Fügt Elemente beider Mengen in eine Menge zusammen" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" MinWidth="240"
@@ -177,9 +180,10 @@
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9" Content="Überträgt nur doppelte Elemente in die neue Menge"></Label>
<Label FontSize="9"
Content="Überträgt nur doppelte Elemente in die neue Menge" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineDifference"
@@ -192,7 +196,8 @@
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9" Content="Elemente der ersten Menge ohne Elemente der zweiten Menge"></Label>
<Label FontSize="9"
Content="Elemente der ersten Menge ohne Elemente der zweiten Menge" />
</StackPanel>
</Button>
@@ -203,11 +208,12 @@
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresExclude" Width="36" Height="36" />
<Label Content="Symmetrische Differenz" VerticalContentAlignment="Center"
<Label Content="Symmetrische Differenz"
VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind"></Label>
<Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind" />
</StackPanel>
</Button>
@@ -241,6 +247,44 @@
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="True">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="LibraryBig" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Wiki" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid ColumnDefinitions="300,*">
<Border Grid.Column="0" Background="#FFF" BorderBrush="#DDD" BorderThickness="0,0,1,0">
<StackPanel>
<TextBlock Text="Wiki" FontSize="18" Margin="12" />
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TreeView Name="NavTree" Margin="8" />
</ScrollViewer>
</StackPanel>
</Border>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="8" Grid.Row="0">
<Button Name="EditButton" Content="Edit" HorizontalContentAlignment="Center"
IsEnabled="False" Width="80" Margin="0,0,8,0" />
<Button Name="OpenFolderButton" HorizontalContentAlignment="Center"
Content="Open Folder" Width="100" />
</StackPanel>
<Border Grid.Row="1" Margin="8" BorderBrush="#DDD" BorderThickness="1" CornerRadius="4">
<ScrollViewer>
<StackPanel Name="PreviewPanel" Margin="8" />
</ScrollViewer>
</Border>
</Grid>
</Grid>
</TabItem>
<TabItem IsEnabled="True">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
@@ -256,20 +300,39 @@
<Label FontSize="16" Content="Global" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="400,*">
<Label Grid.Column="0">config-Datei</Label>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbConfigPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/config.json" />
<Button>
<Button.Content>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="File" Width="16" Height="16" Size="16" />
<Label Content="Öffnen..." VerticalContentAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbConfigPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/config.json" />
<Button IsEnabled="False">
<Button.Content>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="File" Width="16" Height="16" Size="16" />
<Label Content="Öffnen..." VerticalContentAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Grid ColumnDefinitions="400,*">
<Label Grid.Column="0">Wiki-Pfad</Label>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbWikiPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/wiki" />
<Button IsEnabled="True" x:Name="BtnWikiPath">
<Button.Content>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Folder" Width="16" Height="16" Size="16" />
<Label Content="Öffnen..." VerticalContentAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
</Grid>
</StackPanel>
@@ -315,6 +378,13 @@
TextChanged="TbSettingsCustomerDescription_OnTextChanged"
x:Name="TbSettingsCustomerDescription" />
</Grid>
<Grid ColumnDefinitions="150,*">
<Label Content="Absenderadresse" />
<TextBox Grid.Column="1" Watermark="Absenderadresse"
HorizontalAlignment="Stretch"
TextChanged="TbSettingsCustomerSenderAddress_OnTextChanged"
x:Name="TbSettingsCustomerSenderAddress" />
</Grid>
<Grid ColumnDefinitions="150,*">
<Label Content="CSV-Trennzeichen" />
<TextBox Grid.Column="1" Watermark=","
@@ -357,6 +427,97 @@
</StackPanel>
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Globe" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Länder" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid ColumnDefinitions="*,*">
<Grid Grid.Column="0" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="CountryList" Grid.Row="0"
SelectionChanged="CountryList_OnSelectionChanged" />
<Grid Grid.Row="1" ColumnDefinitions="*,250" Margin="0,5,0,0">
<TextBox x:Name="TbSettingsNewCountry" />
<Button Grid.Column="1" x:Name="BtnSettingsNewCountry"
HorizontalAlignment="Stretch" Click="BtnSettingsNewCountry_OnClick"
HorizontalContentAlignment="Center" Content="+ Hinzufügen"
Margin="5,0,0,0" />
</Grid>
</Grid>
<Grid Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,5"
HorizontalAlignment="Right">
<Button x:Name="BtnSettingsInsertDefaultCountries"
Content="Standard-Länder laden (Englisch)"
Click="BtnSettingsInsertDefaultCountries_OnClick" />
</StackPanel>
<Grid Grid.Row="1" ColumnDefinitions="300,*" Margin="0,0,0,5">
<TextBlock Text="Name:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" x:Name="TbSettingsCountryName"
TextChanged="TbSettingsCountryName_OnTextChanged" />
</Grid>
<Grid ColumnDefinitions="300,*" Margin="0,0,0,5" Grid.Row="2">
<TextBlock Text="Übersetzung:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" x:Name="TbSettingsCountryTranslation"
TextChanged="TbSettingsCountryTranslation_OnTextChanged" />
</Grid>
<Grid Grid.Row="3">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="LbSettingsAlternatives" SelectionMode="Multiple" />
<Grid Grid.Row="1" ColumnDefinitions="*,200" Margin="0,5,0,0">
<TextBox x:Name="TbSettingsNewCountryAlternative"
Watermark="Kürzel/Alternative" />
<Button Grid.Column="1" x:Name="BtnSettingsNewCountryAlternative"
Content="+ Hinzufügen" Margin="5 0 0 0"
HorizontalContentAlignment="Center"
Click="BtnSettingsNewCountryAlternative_OnClick"
HorizontalAlignment="Stretch" />
</Grid>
<Button x:Name="BtnSettingsRemoveSelectedAlternatives" Grid.Row="2"
Content="Ausgewählte Entfernen" Background="#99963434"
HorizontalContentAlignment="Center"
HorizontalAlignment="Stretch"
Click="BtnSettingsRemoveSelectedAlternatives_OnClick"
Margin="0,5,0,0" />
<Button Grid.Row="3"
Content="Land Entfernen" Background="#99963434"
HorizontalContentAlignment="Center" x:Name="BtnSettingsRemoveCountry"
HorizontalAlignment="Stretch" Click="BtnSettingsRemoveCountry_OnClick"
Margin="0,5,0,0" />
</Grid>
</Grid>
</Grid>
</TabItem>
</TabControl>
</TabItem>
</TabControl>

View File

@@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Logof_Client.Wiki;
namespace Logof_Client;
public partial class MainWindow : Window
{
public static MainWindow _instance;
private Country _selectedCountry;
private bool _suppressCountryRefresh;
private WikiService _wikiService;
public Uri filePath;
public MainWindow()
@@ -26,6 +33,39 @@ public partial class MainWindow : Window
Global.Load();
Settings.Load();
RefreshCountryView();
// Initialize wiki integration
_wikiService = new WikiService();
try
{
PopulateNavTree();
NavTree.SelectionChanged += NavTree_SelectionChanged;
OpenFolderButton.Click += OpenFolderButton_Click;
EditButton.Click += EditButton_Click;
// populate wiki settings UI if present
try
{
if (!string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path))
TbWikiPath.Text = Global._instance.wiki_storage_path;
}
catch
{
}
try
{
BtnWikiPath.Click += BtnWikiPath_Click;
}
catch
{
}
}
catch
{
}
//Thread.Sleep(3000);
//Show();
}
@@ -115,7 +155,6 @@ public partial class MainWindow : Window
new FilePickerFileType(".csv-Datei")
{
Patterns = new[] { "*.csv" }
//Patterns = new[] { "*" }
}
}
});
@@ -134,23 +173,7 @@ public partial class MainWindow : Window
return;
}
//var set = new KasAddressList("");
//foreach (var adset in Settings._instance.addressSets.addresses)
//if (adset.ID == KasAddressList.GetIDByAddressSetListItem(LstCustomerAdressSets.SelectedItem.ToString()))
StartAddressCheck(KasAddressList.GetIDByAddressSetListItem(LstCustomerAdressSets.SelectedItem.ToString()));
// var result = DataImport.ImportKasAddressList(filePath);
// if (result.Item1)
// {
// var check_result = new AddressCheck().Perform(result.Item2);
// foreach (var item in check_result.Result)
// {
// Console.WriteLine();
// Console.Write(item.Item1 + " ");
// foreach (var error in item.Item2) Console.Write(error + ", ");
// }
// }
}
private void BtnCombine_OnClick(object? sender, RoutedEventArgs e)
@@ -192,7 +215,6 @@ public partial class MainWindow : Window
//filePath = file[0].Path;
foreach (var f in file) address_list.Add(DataImport.ImportKasAddressList(f.Path).Item2);
progressWindow.Show(_instance);
var processor = new CombineAddresses(progressWindow);
@@ -200,10 +222,84 @@ public partial class MainWindow : Window
progressWindow.Close();
//File.WriteAllText(OpenSettingsSaveAsDialog().Result,
//new CsvBuilder(
//"refsid,anrede,titel,vorname,adel,name,namezus,anredzus,strasse,strasse2,plz,ort,land,pplz,postfach,name1,name2,name3,name4,name5,funktion,funktion2,abteilung,funktionad,lastupdate",
//result).BuildKas());
}
private async void NavTree_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (NavTree.SelectedItem is TreeViewItem t && t.Tag is WikiItem item && !item.IsFolder)
{
var text = await _wikiService.LoadFileContentAsync(item.Path);
try
{
PreviewPanel.Children.Clear();
var rendered = MarkdownRenderer.Render(text ?? string.Empty);
PreviewPanel.Children.Add(rendered);
}
catch
{
// fallback: plain text
PreviewPanel.Children.Clear();
PreviewPanel.Children.Add(new TextBlock { Text = text ?? string.Empty });
}
EditButton.IsEnabled = true;
}
}
private void PopulateNavTree()
{
var roots = _wikiService.GetRootItems();
var nodes = new List<TreeViewItem>();
foreach (var r in roots) nodes.Add(BuildNode(r));
NavTree.ItemsSource = nodes;
}
private TreeViewItem BuildNode(WikiItem item)
{
var node = new TreeViewItem { Header = item.Name, Tag = item };
if (item.IsFolder)
foreach (var c in item.Children)
node.Items.Add(BuildNode(c));
return node;
}
private void OpenFolderButton_Click(object? sender, RoutedEventArgs e)
{
var path = Global._instance.wiki_storage_path;
if (!Directory.Exists(path)) return;
try
{
Process.Start(new ProcessStartInfo
{
FileName = path,
UseShellExecute = true
});
}
catch
{
}
}
private async void BtnWikiPath_Click(object? sender, RoutedEventArgs e)
{
var top = GetTopLevel(this);
var folder = await top.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Wiki Pfad wählen",
AllowMultiple = false
});
if (folder == null || folder.Count == 0) return;
var chosen = folder[0].Path;
TbWikiPath.Text = chosen.ToString();
Global._instance.wiki_storage_path = chosen.ToString();
Global.Save();
// reinit wiki service and reload tree
_wikiService = new WikiService();
PopulateNavTree();
}
private async Task<string> OpenSettingsSaveAsDialog()
@@ -249,13 +345,13 @@ public partial class MainWindow : Window
var processor = new CombineAddresses(progressWindow);
var result = await processor.Perform(address_lists, type, CbMergeExportUnmerged.IsChecked);
if(result.Item1 != null)
if (result.Item1 != null)
result.Item1.owner_id = owner_id;
if(result.Item2 != null)
if (result.Item2 != null)
result.Item2.owner_id = owner_id;
if(result.Item1 != null)
if (result.Item1 != null)
Settings._instance.addressSets.addresses.Add(result.Item1);
if(result.Item2 != null)
if (result.Item2 != null)
Settings._instance.addressSets.addresses.Add(result.Item2);
Settings.Save();
progressWindow.Close();
@@ -303,6 +399,7 @@ public partial class MainWindow : Window
{
TbSettingsCustomerDescription.Text = customer.description;
TbSettingsCustomerName.Text = customer.name;
TbSettingsCustomerSenderAddress.Text = customer.sender_address;
TbSettingsCustomerCsvSeparator.Text = customer.separator.ToString();
if (customer.patch != null)
TbSettingsCustomerPatchInfo.Text = customer.patch.ToString();
@@ -311,6 +408,11 @@ public partial class MainWindow : Window
}
}
private async void EditButton_Click(object? sender, RoutedEventArgs e)
{
await MessageBox.Show(this, "Edit feature is currently disabled.", "Edit Disabled");
}
public void RefreshCustomerItems(int index = 0)
{
if (LstCustomers.Items.Count > 0)
@@ -457,8 +559,8 @@ public partial class MainWindow : Window
{
BtnCheck.IsEnabled = true;
BtnCombine.IsEnabled = true;
BtnGenerateLabels.IsEnabled = true;
// BtnGenerateLabels.IsEnabled = true;
// BtnRepair.IsEnabled = true;
// BtnShorten.IsEnabled = true;
}
@@ -554,4 +656,168 @@ public partial class MainWindow : Window
StartCombine(list, Convert.ToInt32(LstCustomers.SelectedItem.ToString().Split(" - ")[0]), "symdiff");
}
private async void BtnGenerateLabels_OnClick(object? sender, RoutedEventArgs e)
{
var saveDialog = new SaveFileDialog
{
DefaultExtension = "pdf",
Filters = { new FileDialogFilter { Name = "PDF-Dateien", Extensions = { "pdf" } } }
};
var filePath = await saveDialog.ShowAsync(this);
if (!string.IsNullOrEmpty(filePath))
{
var builder = new PdfBuilder();
builder.CreateAddressLabelPdfFromAddressSetWithPlaceholder(
Convert.ToInt32(LstCustomerAdressSets.SelectedItems[0].ToString().Split(" - ")[0]),
"Company Logo/Info",
filePath
);
//return true;
}
}
private void TbSettingsCustomerSenderAddress_OnTextChanged(object? sender, TextChangedEventArgs e)
{
if (LstSettingsCustomers.SelectedIndex == null || LstSettingsCustomers.SelectedIndex == -1) return;
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID == Settings._instance.customers.current)
customer.sender_address = TbSettingsCustomerSenderAddress.Text;
}
// Country Section
private void BtnSettingsRemoveCountry_OnClick(object? sender, RoutedEventArgs e)
{
if (_selectedCountry != null)
{
Global._instance.countries.Remove(_selectedCountry);
_selectedCountry = null;
}
RefreshCountryView();
}
public void RefreshCountryView()
{
if (_suppressCountryRefresh) return;
try
{
_suppressCountryRefresh = true;
Global.Save();
var alt_index = LbSettingsAlternatives.SelectedIndex;
var country_index = CountryList.SelectedIndex;
LbSettingsAlternatives.Items.Clear();
try
{
CountryList.Items.Clear();
}
catch
{
}
foreach (var c in Global._instance.countries) CountryList.Items.Add(c.name);
try
{
CountryList.SelectedIndex = country_index;
}
catch
{
}
try
{
_selectedCountry = Country.GetByName(CountryList.SelectedItems[0].ToString());
}
catch
{
_selectedCountry = null;
}
if (_selectedCountry == null) return;
//Console.WriteLine("Refreshing alternatives...");
foreach (var a in _selectedCountry.alternatives) LbSettingsAlternatives.Items.Add(a);
//Console.WriteLine(a);
TbSettingsCountryName.Text = _selectedCountry.name;
TbSettingsCountryTranslation.Text = _selectedCountry.translation;
try
{
LbSettingsAlternatives.SelectedIndex = alt_index;
}
catch
{
}
}
finally
{
_suppressCountryRefresh = false;
}
}
private void BtnSettingsRemoveSelectedAlternatives_OnClick(object? sender, RoutedEventArgs e)
{
foreach (var selected in LbSettingsAlternatives.SelectedItems)
try
{
_selectedCountry.alternatives.Remove(selected.ToString());
}
catch
{
Console.WriteLine("Error while removing country alternative");
}
RefreshCountryView();
}
private void BtnSettingsNewCountryAlternative_OnClick(object? sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(TbSettingsNewCountryAlternative.Text)) return;
_selectedCountry.alternatives.Add(TbSettingsNewCountryAlternative.Text.Trim());
RefreshCountryView();
}
private void TbSettingsCountryTranslation_OnTextChanged(object? sender, TextChangedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(TbSettingsCountryTranslation.Text))
{
_selectedCountry.translation = TbSettingsCountryTranslation.Text.Trim();
RefreshCountryView();
}
}
private void TbSettingsCountryName_OnTextChanged(object? sender, TextChangedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(TbSettingsCountryName.Text))
{
_selectedCountry.name = TbSettingsCountryName.Text.Trim();
RefreshCountryView();
}
}
private void BtnSettingsNewCountry_OnClick(object? sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(TbSettingsNewCountry.Text)) return;
Global._instance.countries.Add(new Country(TbSettingsNewCountry.Text));
RefreshCountryView();
}
private void CountryList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
_selectedCountry = Country.GetByName(CountryList.SelectedItems[0].ToString());
RefreshCountryView();
}
private void BtnSettingsInsertDefaultCountries_OnClick(object? sender, RoutedEventArgs e)
{
ISO3166.Country[] countries = ISO3166.Country.List;
foreach (var countryie in countries)
Global._instance.countries.Add(new Country(countryie.Name, countryie.Name,
new List<string> { countryie.ThreeLetterCode, countryie.TwoLetterCode }));
RefreshCountryView();
}
}

View File

@@ -12,13 +12,16 @@
x:Name="LblResultCount" />
<StackPanel x:Name="StpFilterOptions" Orientation="Vertical" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="10,80,10,0" />
<!-- <Button x:Name="BtnUpdateFilter" Content="Aktualisieren" HorizontalAlignment="Stretch" -->
<!-- VerticalAlignment="Bottom" Margin="10,0,10,10" Click="BtnUpdateFilter_OnClick" /> -->
<Button Content="Filter anwenden" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
x:Name="BtnExecuteFilter" Click="BtnExecuteFilter_OnClick"
Margin="10,10,10,50" />
<Button Content="Ausgewählte Anzeigen" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
x:Name="BtnShowSelected" Click="BtnShowSelected_OnClick"
Margin="10,10,10,10" />
</Grid>
<DataGrid x:Name="DgResult" Grid.Column="1" AutoGenerateColumns="True" />
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="StkResults" Orientation="Vertical" Margin="10" />
</ScrollViewer>
</Grid>
</Window>

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
namespace Logof_Client;
@@ -11,10 +13,10 @@ public partial class ResultWindow : Window
{
public List<CheckBox> errortypecheckboxes = new();
public KasAddressList ur_addresses = new("Ergebnis_" + DateTime.Now.ToString("ddMMyy_HHmmss"));
public List<KasPersonError> ur_result;
public List<KasPerson> ur_result;
public List<CheckBox> warningtypecheckboxes = new();
public ResultWindow(List<KasPersonError> result,
public ResultWindow(List<KasPerson> result,
int addressSetID)
{
InitializeComponent();
@@ -24,18 +26,54 @@ public partial class ResultWindow : Window
//ViewSingle(200552426);
}
private void GenerateView(List<KasPersonError> result)
private void GenerateView(List<KasPerson> result)
{
// var errors = new List<KasPersonError>();
//foreach (var single_result in result) errors.Add(single_result);
LblResultCount.Content = $"{result.Count}/{ur_result.Count} Ergebnisse";
DgResult.ItemsSource = result;
// Filter to only show persons with errors
var result_with_errors = result.Where(p => p.PersonError != null).ToList();
LblResultCount.Content = $"{result_with_errors.Count}/{ur_result.Count} Ergebnisse";
StkResults.Children.Clear();
foreach (var person in result_with_errors) StkResults.Children.Add(CreatePersonGrid(person));
}
private void ViewSingle(int refsid)
private Grid CreatePersonGrid(KasPerson person)
{
var grid = new Grid
{
ColumnDefinitions = ColumnDefinitions.Parse("100,*,100,*,100,*"),
RowDefinitions = RowDefinitions.Parse("Auto"),
Margin = new Thickness(0, 5, 0, 5),
Background = new SolidColorBrush(Color.Parse("#F0F0F0"))
};
// ID
grid.Children.Add(new TextBlock
{
Text = "id: ",
FontWeight = FontWeight.Bold, Margin = new Thickness(5)
});
grid.Children.Add(new TextBlock { Text = person.id.ToString(), Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[1], 1);
// PLZ
grid.Children.Add(new TextBlock { Text = "plz:", FontWeight = FontWeight.Bold, Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[2], 2);
grid.Children.Add(new TextBlock { Text = person.plz, Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[3], 3);
// PPLZ
grid.Children.Add(new TextBlock { Text = "errors:", FontWeight = FontWeight.Bold, Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[4], 4);
grid.Children.Add(new TextBlock { Text = person.PersonError.GetString(), Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[5], 5);
return grid;
}
private void ViewSingle(int id)
{
foreach (var result in ur_addresses.KasPersons)
if (result.refsid == refsid)
if (result.id == id)
{
var wind = new Window();
var stp = new StackPanel();
@@ -44,8 +82,8 @@ public partial class ResultWindow : Window
var tb = new TextBlock();
var tb2 = new TextBlock();
tb.Text =
"refsid:\nanrede:\ntitel:\nvorname:\nadel:\nname:\nnamezus:\nanredzus:\nstrasse:\nstrasse2:\nplz:\nort:\nland:\npplz:\npostfach:\nname1:\nname2:\nname3:\nname4:\nname5:\nfunktion:\nfunktion2:\nabteilung:\nfunktionad:";
tb2.Text = result.refsid + "\n" + result.anrede + "\n" + result.titel + "\n" + result.vorname + "\n" +
"id:\nanrede:\ntitel:\nvorname:\nadel:\nname:\nnamezus:\nanredzus:\nstrasse:\nstrasse2:\nplz:\nort:\nland:\npplz:\npostfach:\nname1:\nname2:\nname3:\nname4:\nname5:\nfunktion:\nfunktion2:\nabteilung:\nfunktionad:";
tb2.Text = result.id + "\n" + result.anrede + "\n" + result.titel + "\n" + result.vorname + "\n" +
result.adel + "\n" + result.name + "\n" + result.namezus + "\n" + result.anredzus + "\n" +
result.strasse + "\n" + result.strasse2 + "\n" + result.plz + "\n" + result.ort + "\n" +
result.land + "\n" + result.pplz + "\n" + result.postfach + "\n" + result.name1 + "\n" +
@@ -62,18 +100,20 @@ public partial class ResultWindow : Window
}
}
private void Load(List<KasPersonError> result)
private void Load(List<KasPerson> result)
{
var knownErrors = new List<AddressCheck.ErrorTypes>();
var knownWarnings = new List<AddressCheck.WarningTypes>();
foreach (var single_result in result)
foreach (var person in result)
{
foreach (var errtyp in single_result.errors)
if (person.PersonError == null) continue;
foreach (var errtyp in person.PersonError.errors)
if (!knownErrors.Contains(errtyp))
knownErrors.Add(errtyp);
foreach (var wartyp in single_result.warnings)
foreach (var wartyp in person.PersonError.warnings)
if (!knownWarnings.Contains(wartyp))
knownWarnings.Add(wartyp);
}
@@ -84,7 +124,7 @@ public partial class ResultWindow : Window
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = errtype.ToString();
cb.Click += (sender, e) => UpdateFilter();
//cb.Click += (sender, e) => UpdateFilter();
errortypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
@@ -94,7 +134,7 @@ public partial class ResultWindow : Window
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = wartype.ToString();
cb.Click += (sender, e) => UpdateFilter();
//cb.Click += (sender, e) => UpdateFilter();
warningtypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
@@ -108,49 +148,81 @@ public partial class ResultWindow : Window
private void UpdateFilter()
{
var temp_result = new List<KasPersonError>();
var checked_types = new List<AddressCheck.ErrorTypes>();
var checked_types_war = new List<AddressCheck.WarningTypes>();
var temp_result = new List<KasPerson>();
var checkedErrors = new HashSet<AddressCheck.ErrorTypes>();
var checkedWarnings = new HashSet<AddressCheck.WarningTypes>();
// safer parsing: use TryParse and trim the Content string
foreach (var cb in errortypecheckboxes)
if (cb.IsChecked == true)
checked_types.Add(
(AddressCheck.ErrorTypes)Enum.Parse(typeof(AddressCheck.ErrorTypes), cb.Content.ToString()));
{
var s = cb.Content?.ToString()?.Trim();
if (!string.IsNullOrEmpty(s) &&
Enum.TryParse<AddressCheck.ErrorTypes>(s, true, out var et))
checkedErrors.Add(et);
}
foreach (var cb in warningtypecheckboxes)
if (cb.IsChecked == true)
checked_types_war.Add(
(AddressCheck.WarningTypes)Enum.Parse(typeof(AddressCheck.WarningTypes), cb.Content.ToString()));
{
var s = cb.Content?.ToString()?.Trim();
if (!string.IsNullOrEmpty(s) &&
Enum.TryParse<AddressCheck.WarningTypes>(s, true, out var wt))
checkedWarnings.Add(wt);
}
foreach (var sres in ur_result)
{
foreach (var err in sres.errors)
if (checked_types.Contains(err) && !temp_result.Contains(sres))
temp_result.Add(sres);
// If no checkboxes are selected, show all persons with errors (default behavior)
if (checkedErrors.Count == 0 && checkedWarnings.Count == 0)
temp_result = ur_result.Where(p => p.PersonError != null).ToList();
else
foreach (var person in ur_result)
{
if (person.PersonError == null) continue;
foreach (var war in sres.warnings)
if (checked_types_war.Contains(war) && !temp_result.Contains(sres))
temp_result.Add(sres);
}
var personErrors = person.PersonError.errors ?? Enumerable.Empty<AddressCheck.ErrorTypes>();
var personWarnings = person.PersonError.warnings ?? Enumerable.Empty<AddressCheck.WarningTypes>();
var matchesError = false;
var matchesWarning = false;
//var errors = new List<KasPersonError>();
//foreach (var single_result in temp_result) errors.Add(new KasPersonError(single_result));
// only test errors if the user selected error-types
if (checkedErrors.Count > 0)
matchesError = personErrors.Any(err => checkedErrors.Contains(err));
// only test warnings if the user selected warning-types
if (checkedWarnings.Count > 0)
matchesWarning = personWarnings.Any(war => checkedWarnings.Contains(war));
// If at least one category matches (OR across categories), include person
if ((matchesError || matchesWarning) && !temp_result.Contains(person))
temp_result.Add(person);
}
LblResultCount.Content = $"{temp_result.Count}/{ur_result.Count} Ergebnisse";
DgResult.ItemsSource = temp_result;
StkResults.Children.Clear();
foreach (var person in temp_result) StkResults.Children.Add(CreatePersonGrid(person));
}
private void BtnShowSelected_OnClick(object? sender, RoutedEventArgs e)
{
foreach (var selected in DgResult.SelectedItems)
try
{
var _asKas = (KasPersonError)selected;
ViewSingle(_asKas.refsid);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// foreach (var selected in DgResult.SelectedItems)
// try
// {
// var _asKas = (KasPerson)selected;
// ViewSingle(_asKas.id);
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex.Message);
// }
}
private void BtnExecuteFilter_OnClick(object? sender, RoutedEventArgs e)
{
Console.WriteLine("Updating filter...");
UpdateFilter();
}
}

View File

@@ -62,6 +62,8 @@ public class Global
}
public string config_path { get; set; } = "";
public string wiki_storage_path { get; set; } = "";
public List<Country> countries { get; set; } = new();
public static void Save()
{
@@ -121,10 +123,12 @@ public class Customer
public string name { get; set; } = "";
public string description { get; set; } = "";
public string sender_address { get; set; } = "";
public AddressPatch patch { get; set; }
public char separator { get; set; } = ',';
public int ID { get; }
public static int GetIDByCustomerListItem(string item_content)
{
var id = item_content.Split(" - ")[0];
@@ -142,6 +146,40 @@ public class AddressSets
if (i.ID == ID)
return i;
return null;
}
}
public class Country
{
public Country(string name, string translation, List<string> alternatives)
{
this.name = name;
this.translation = translation;
this.alternatives = alternatives;
}
public Country(string name)
{
this.name = name;
translation = "";
alternatives = new List<string>();
}
public Country()
{
}
public string? name { get; set; }
public string translation { get; set; }
public List<string> alternatives { get; set; }
public static Country GetByName(string name)
{
foreach (var country in Global._instance.countries)
if (country.name == name)
return country;
return null;
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
@@ -35,217 +36,229 @@ public class AddressCheck
_progress = progressWindow;
}
public async Task<List<KasPersonError>> Perform(int id)
public async Task<List<KasPerson>> Perform(int id)
{
foreach (var adset in Settings._instance.addressSets.addresses)
if (adset.ID == id)
// Find the index of the address set with the given id
var adset_index = -1;
for (var i = 0; i < Settings._instance.addressSets.addresses.Count; i++)
if (Settings._instance.addressSets.addresses[i].ID == id)
{
var failed_refsids = new List<KasPersonError>();
var total = adset.KasPersons.Count;
var current = 0;
await Task.Run(async () =>
{
foreach (var person in adset.KasPersons)
{
var errors = new List<ErrorTypes>();
var warnings = new List<WarningTypes>();
var hasFaults = false;
var address_component_count = 2; // cause anrede and name are first
// PLZ-Prüfung
if (person.plz == "" || person.plz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.plz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PlzNotUsable);
}
// if ((person.plz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz < 10000 && person.land == "GER") ||
// (person.plz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooShort);
// }
// else if ((person.plz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz > 99999 && person.land == "GER") ||
// (person.plz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooLong);
// }
}
// PPLZ-Prüfung
if (person.pplz == "" || person.pplz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.pplz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PPlzNotUsable);
}
// if ((person.pplz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz < 10000 && person.land == "GER") ||
// (person.pplz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooShort);
// }
// else if ((person.pplz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz > 99999 && person.land == "GER") ||
// (person.pplz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooLong);
// }
}
if (warnings.Contains(WarningTypes.NoPLZ) && warnings.Contains(WarningTypes.NoPPLZ))
{
hasFaults = true;
errors.Add(ErrorTypes.NoPLZorPPLZ);
}
// Ort-Prüfung
if (string.IsNullOrWhiteSpace(person.ort))
{
hasFaults = true;
warnings.Add(WarningTypes.NoCity);
}
else
{
address_component_count++;
}
// Street-Number
var street = person.strasse.ToCharArray();
var intcount = 0;
foreach (var c in street)
{
int maybe;
if (int.TryParse(c.ToString(), out maybe)) intcount++;
}
if (intcount == 0)
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreetNumber);
}
// Last-Name
if (string.IsNullOrWhiteSpace(person.name))
{
hasFaults = true;
warnings.Add(WarningTypes.NoLastName);
}
// First-Name
if (string.IsNullOrWhiteSpace(person.vorname))
{
hasFaults = true;
warnings.Add(WarningTypes.NoFirstName);
}
// Street-Check
if (string.IsNullOrWhiteSpace(person.strasse))
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreet);
}
else
{
address_component_count++;
}
// Address-Component-Count
if (!string.IsNullOrWhiteSpace(person.strasse2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.land)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name1)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name3)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name4)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name5)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktionad)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.abteilung)) address_component_count++;
// Double-Refsid or DoubleAddresses
foreach (var person2 in adset.KasPersons)
{
if (adset.KasPersons.IndexOf(person) == adset.KasPersons.IndexOf(person2)) continue;
if (person.refsid == person2.refsid) // trifft auf Patch-Addressen nicht zu
{
hasFaults = true;
warnings.Add(WarningTypes.DoubledRefsid);
}
if (person.name == person2.name &&
person.strasse == person2.strasse &&
person.vorname == person2.vorname &&
person.ort == person2.ort &&
person.funktion == person2.funktion &&
person.funktion2 == person2.funktion2 &&
person.funktionad == person2.funktionad &&
person.abteilung == person2.abteilung &&
person.name1 == person2.name1 &&
person.name2 == person2.name2 &&
person.name3 == person2.name3 &&
person.name4 == person2.name4 &&
person.name5 == person2.name5) //
{
hasFaults = true;
errors.Add(ErrorTypes.MayBeSameAddress);
}
}
// Adressen-Länge
if (address_component_count > 10)
{
hasFaults = true;
warnings.Add(WarningTypes.FullAddressTooLong);
}
if (hasFaults)
lock (failed_refsids)
{
failed_refsids.Add(new KasPersonError((person.refsid, errors, warnings)));
}
// Fortschritt aktualisieren
Interlocked.Increment(ref current);
var percent = current / (double)total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (hasFaults)
_progress.AddToLog($"Person mit refsid {person.refsid} ist fehlerhaft");
_progress.ChangePercentage(percent);
});
}
});
adset.errors = failed_refsids;
Settings.Save();
return failed_refsids;
adset_index = i;
break;
}
return null;
if (adset_index == -1) return new List<KasPerson>();
var adset = Settings._instance.addressSets.addresses[adset_index];
var total = adset.KasPersons.Count;
var current = 0;
await Task.Run(async () =>
{
foreach (var person in adset.KasPersons)
{
var errors = new List<ErrorTypes>();
var warnings = new List<WarningTypes>();
var hasFaults = false;
var address_component_count = 2; // cause anrede and name are first
// PLZ-Prüfung
if (person.plz == "" || person.plz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.plz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PlzNotUsable);
}
// if ((person.plz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz < 10000 && person.land == "GER") ||
// (person.plz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooShort);
// }
// else if ((person.plz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz > 99999 && person.land == "GER") ||
// (person.plz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooLong);
// }
}
// PPLZ-Prüfung
if (person.pplz == "" || person.pplz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.pplz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PPlzNotUsable);
}
// if ((person.pplz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz < 10000 && person.land == "GER") ||
// (person.pplz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooShort);
// }
// else if ((person.pplz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz > 99999 && person.land == "GER") ||
// (person.pplz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooLong);
// }
}
if (warnings.Contains(WarningTypes.NoPLZ) && warnings.Contains(WarningTypes.NoPPLZ))
{
hasFaults = true;
errors.Add(ErrorTypes.NoPLZorPPLZ);
}
// Ort-Prüfung
if (string.IsNullOrWhiteSpace(person.ort))
{
hasFaults = true;
warnings.Add(WarningTypes.NoCity);
}
else
{
address_component_count++;
}
// Street-Number
var street = person.strasse.ToCharArray();
var intcount = 0;
foreach (var c in street)
{
int maybe;
if (int.TryParse(c.ToString(), out maybe)) intcount++;
}
if (intcount == 0)
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreetNumber);
}
// Last-Name
if (string.IsNullOrWhiteSpace(person.name))
{
hasFaults = true;
warnings.Add(WarningTypes.NoLastName);
}
// First-Name
if (string.IsNullOrWhiteSpace(person.vorname))
{
hasFaults = true;
warnings.Add(WarningTypes.NoFirstName);
}
// Street-Check
if (string.IsNullOrWhiteSpace(person.strasse))
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreet);
}
else
{
address_component_count++;
}
// Address-Component-Count
if (!string.IsNullOrWhiteSpace(person.strasse2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.land)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name1)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name3)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name4)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name5)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktionad)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.abteilung)) address_component_count++;
// Double-Refsid or DoubleAddresses
foreach (var person2 in adset.KasPersons)
{
if (adset.KasPersons.IndexOf(person) == adset.KasPersons.IndexOf(person2)) continue;
if (person.refsid == person2.refsid) // trifft auf Patch-Addressen nicht zu
{
hasFaults = true;
warnings.Add(WarningTypes.DoubledRefsid);
}
if (person.name == person2.name &&
person.strasse == person2.strasse &&
person.vorname == person2.vorname &&
person.ort == person2.ort &&
person.funktion == person2.funktion &&
person.funktion2 == person2.funktion2 &&
person.funktionad == person2.funktionad &&
person.abteilung == person2.abteilung &&
person.name1 == person2.name1 &&
person.name2 == person2.name2 &&
person.name3 == person2.name3 &&
person.name4 == person2.name4 &&
person.name5 == person2.name5) //
{
hasFaults = true;
errors.Add(ErrorTypes.MayBeSameAddress);
}
}
// Adressen-Länge
if (address_component_count > 10)
{
hasFaults = true;
warnings.Add(WarningTypes.FullAddressTooLong);
}
if (hasFaults)
lock (Settings._instance.addressSets.addresses)
{
// Directly set PersonError in the address set
var person_index = adset.KasPersons.IndexOf(person);
if (person_index >= 0)
Settings._instance.addressSets.addresses[adset_index].KasPersons[person_index].PersonError =
new KasPersonError((errors, warnings));
}
// Fortschritt aktualisieren
Interlocked.Increment(ref current);
var percent = current / (double)total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (hasFaults)
_progress.AddToLog($"Person mit id {person.id} ist fehlerhaft");
_progress.ChangePercentage(percent);
});
}
});
Settings.Save();
// Return only the persons with errors from the address set
return Settings._instance.addressSets.addresses[adset_index].KasPersons
.Where(p => p.PersonError != null)
.ToList();
}
}

View File

@@ -1,59 +1,10 @@
using System;
using System.Linq;
using System.Runtime.InteropServices.JavaScript;
namespace Logof_Client;
public static class AddressCreator
{
/// <summary>
/// Creates max-seven-lines-long markdown address-string. Analyzes the KasPerson-Instance to find the best result.
/// </summary>
/// <param name="refsid">KasPerson-ID</param>
/// <returns>A markdown string with the address that is maximum seven lines long</returns>
public static string? CreateFinalMarkdownString2(int refsid)
{
// Maximum seven lines of information
// find the address
KasPerson address = new KasPerson();
string string_address = "";
int address_line_count = 0;
foreach (var set in Settings._instance.addressSets.addresses)
{
var temp = set.KasPersons.FirstOrDefault(obj => obj.refsid == refsid);
if (temp != null) break;
}
// no address found
if (address == null)
{
return null;
}
// let's get started: the name (and anrede), block-line-count: 1
if (!string.IsNullOrEmpty(address.anrede))
{
if(address.anrede == "Herr") string_address += "Herrn";
else string_address += address.anrede;
address_line_count++;
}
if (!string.IsNullOrEmpty(address.name) && !string.IsNullOrEmpty(address.vorname))
{
string_address += " " + address.vorname + " " + address.name;
} else if (!string.IsNullOrEmpty(address.name))
{
}
else // Anrede but no name? to the trash!
{
string_address = "";
address_line_count = 0;
}
return "Hier könnte eine\nAdresse stehen";
}
//+++ Aufbau +++
//
// Von unten anfangen, max. 7 Zeilen
@@ -83,41 +34,73 @@ public static class AddressCreator
// Auswurf PDF mit normalen Absender
//
// Auswurf PDF mit PvSt.
/// <summary>
/// Creates max-seven-lines-long markdown address-string. Analyzes the KasPerson-Instance to find the best result.
/// Creates max-seven-lines-long Markdown address-string. Analyzes the KasPerson-Instance to find the best result.
/// </summary>
/// <param name="refsid">KasPerson-ID</param>
/// <returns>A markdown string with the address that is maximum seven lines long</returns>
public static string? CreateFinalMarkdownString(int refsid)
/// <returns>A Markdown string with the address that is maximum seven lines long</returns>
public static string? CreateFinalMarkdownString(int id)
{
// Maximum seven lines of information
// find the address
KasPerson address = new KasPerson();
string string_address = "";
int address_line_count = 0;
KasPerson? address = null;
var string_address = "";
var address_line_count = 0;
foreach (var set in Settings._instance.addressSets.addresses)
{
var temp = set.KasPersons.FirstOrDefault(obj => obj.refsid == refsid);
if (temp != null) break;
var temp = set.KasPersons.FirstOrDefault(obj => obj.id == id);
if (temp != null)
{
address = temp;
break;
}
}
// no address found
if (address == null)
{
return null;
}
if (address == null) return null;
// let's get started: we start from the bottom
// if the country is not germany, set it
// LAND: GER oder DE
if (address.land.ToLower().Trim() != "germany" && address.land.ToLower().Trim() != "ger" && address.land.ToLower().Trim() != "" && address.land.ToLower().Trim() != "de" && address.land.ToLower().Trim() != "deutschland")
// if the country is not Germany, set it; try to map via Global countries alternatives -> translation
var trimmedLand = (address.land ?? "").Trim();
var trimmedLowerLand = trimmedLand.ToLower();
var isGermany = trimmedLowerLand == "germany" || trimmedLowerLand == "ger" ||
trimmedLowerLand == "" || trimmedLowerLand == "de" ||
trimmedLowerLand == "deutschland";
if (!isGermany)
{
string_address = "**" + address.land.Trim() + "**"; // Needs to be bold
var countryToShow = trimmedLand; // default: use raw land value
if (!string.IsNullOrEmpty(trimmedLand))
// search for matching country via alternatives using for-loops
for (var ci = 0; ci < Global._instance.countries.Count; ci++)
{
var country = Global._instance.countries[ci];
for (var ai = 0; ai < country.alternatives.Count; ai++)
try
{
var alt = (country.alternatives[ai] ?? "").Trim();
if (string.Equals(alt, trimmedLand, StringComparison.OrdinalIgnoreCase))
{
countryToShow = string.IsNullOrWhiteSpace(country.translation)
? country.name
: country.translation;
goto CountryFound;
}
}
catch
{
// ignore malformed alternative
}
}
CountryFound:
string_address = "**" + countryToShow + "**"; // Needs to be bold
address_line_count++;
}
// Alternative A: pplz valid and city existing
if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.pplz, address.land))
{
@@ -125,84 +108,145 @@ public static class AddressCreator
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.postfach))
{
string_address = address.postfach.Trim() + "\n" + string_address;
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
else
else if (!string.IsNullOrWhiteSpace(address.strasse.Trim()))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
if (!string.IsNullOrWhiteSpace(address.anredzus))
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = address.anredzus.Trim() + " " + address.titel.Trim()+ " " + address.vorname.Trim()+ " " + address.adel.Trim() + " " + address.name.Trim()+ " (" + address.namezus.Trim() + ")\n" + string_address;
address_line_count++;
}
else
{
string_address = address.anrede.Trim() + " " + address.titel.Trim()+ " " + address.vorname.Trim()+ " " + address.adel.Trim() + " " + address.name.Trim()+ " (" + address.namezus.Trim() + ")\n" + string_address;
string_address = nameline + "\n" + string_address;
address_line_count++;
}
string[] nameattribs = new[] { address.name1, address.name2, address.name3, address.name4, address.name5 };
// REIHENFOLGE
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
for (int i = 0; i < nameattribs.Length-1; i++)
{
if (address_line_count < 7)
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
string_address += nameattribs[i] + "\n" + string_address;
names += "\n" + nameattribs[i];
address_line_count++;
}
}
else break;
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Alternative B: plz valid and city existing
else if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.plz, address.land))
{
}
else // Error-Handling?
{
}
return string_address;
return "Hier könnte eine\nAdresse stehen";
string_address = address.plz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.strasse))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.postfach.Trim()))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + "\n" + string_address;
address_line_count++;
}
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Error-Handling?
if (address_line_count > 1) return string_address;
return null;
}
public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel,
string name, string namezus)
{
if (!string.IsNullOrWhiteSpace(anredezus))
return string.Join(" ",
new[] { anredezus, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
// else
return string.Join(" ",
new[] { anrede, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
}
/// <summary>
/// Returns true if a plz (or pplz) is valid
/// Returns true if a plz (or pplz) is valid
/// </summary>
/// <param name="plz">the plz itself</param>
/// <param name="land">country, to check the plz</param>
/// <returns>true or faslse, depending on result :+1:</returns>
public static bool CheckPLZ(string plz, string land)
{
if (plz == null) return false;
if (land.ToLower().Trim() != "germany" && land.ToLower().Trim() != "ger" && land.ToLower().Trim() != "" && land.ToLower().Trim() != "de" && land.ToLower().Trim() != "deutschland")
{
// if country is not germany... how should we check for a valid plz?
// temporarily accept all of them
return true;
}
if (string.IsNullOrWhiteSpace(plz)) return false;
try
{
int iplz = Convert.ToInt32(plz);
if (iplz > 999999 || iplz < 10000)
var trimmedPlz = plz.Trim();
var trimmedLand = land.ToLower().Trim();
// Check if it's a German country code
var isGermany = trimmedLand == "germany" || trimmedLand == "ger" || trimmedLand == "de" ||
trimmedLand == "deutschland" || trimmedLand == "";
if (isGermany)
// For Germany (including empty land), accept numeric postal codes with 5 digits
try
{
var iplz = Convert.ToInt32(trimmedPlz);
if (trimmedPlz.Length == 5) return true;
return false;
}
catch
{
return false;
}
return true;
} catch
{
return false;
}
// For non-German countries, accept any non-empty postal code
return true;
}
}

View File

@@ -16,7 +16,8 @@ public class CombineAddresses
_progress = progressWindow;
}
public async Task<(KasAddressList,KasAddressList)> Perform(List<KasAddressList> address_lists, string type, bool? exportUnused)
public async Task<(KasAddressList, KasAddressList)> Perform(List<KasAddressList> address_lists, string type,
bool? exportUnused)
{
var res = await Task.Run(async () =>
{
@@ -48,7 +49,7 @@ public class CombineAddresses
// }
// });
// return result;
return (null,null);
return (null, null);
}
@@ -91,11 +92,12 @@ public class CombineAddresses
return false;
}
public async Task<(KasAddressList,KasAddressList)> Difference(List<KasAddressList> address_lists, bool? return_unused,
public async Task<(KasAddressList, KasAddressList)> Difference(List<KasAddressList> address_lists,
bool? return_unused,
Progress? progress = null)
{
if (address_lists == null || address_lists.Count == 0)
return (new KasAddressList(KasAddressList.GenerateName("difference")),null);
return (new KasAddressList(KasAddressList.GenerateName("difference")), null);
progress ??= new Progress
{
@@ -117,26 +119,28 @@ public class CombineAddresses
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
progress.Increment();
if (progress.LogAction == null) continue;
var logMessage =
$"Person mit refsid {person.refsid} verglichen mit {restUnion.Count} Personen des Restes.";
$"Person mit id {person.id} verglichen mit {restUnion.Count} Personen des Restes.";
await Dispatcher.UIThread.InvokeAsync(() => progress.LogAction?.Invoke(logMessage));
}
if(return_unused == true) return (result,second_result);
else return (result, null);
if (return_unused == true) return (result, second_result);
return (result, null);
}
public async Task<(KasAddressList,KasAddressList)> Union(List<KasAddressList> address_lists, bool? return_unused, Progress progress = null)
public async Task<(KasAddressList, KasAddressList)> Union(List<KasAddressList> address_lists, bool? return_unused,
Progress progress = null)
{
var result = new KasAddressList(KasAddressList.GenerateName("union"));
var second_result = new KasAddressList(KasAddressList.GenerateName("union_rest"));
if (address_lists == null || address_lists.Count == 0)
return (result,null);
return (result, null);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
@@ -152,7 +156,7 @@ public class CombineAddresses
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.refsid} hinzugefügt (aktuell {result.KasPersons.Count} Einträge)";
$"{percent:F1}%: Person mit {person.id} hinzugefügt (aktuell {result.KasPersons.Count} Einträge)";
if (progress == null) continue;
if (Dispatcher.UIThread.CheckAccess())
@@ -161,8 +165,8 @@ public class CombineAddresses
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
if(return_unused == true) return (result,second_result);
else return (result, null);
if (return_unused == true) return (result, second_result);
return (result, null);
}
@@ -171,13 +175,14 @@ public class CombineAddresses
return null;
}
public async Task<(KasAddressList,KasAddressList)> Intersection(List<KasAddressList> address_lists, bool? return_unused, Progress progress = null)
public async Task<(KasAddressList, KasAddressList)> Intersection(List<KasAddressList> address_lists,
bool? return_unused, Progress progress = null)
{
var result = new KasAddressList(KasAddressList.GenerateName("intersection"));
var second_result = new KasAddressList(KasAddressList.GenerateName("intersection_rest"));
if (address_lists == null || address_lists.Count == 0)
return (result,null);
return (result, null);
// Nur die erste Liste als Ausgangspunkt verwenden
var baseList = address_lists[0];
@@ -200,7 +205,7 @@ public class CombineAddresses
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.refsid} geprüft {(isInAll ? "in allen enthalten" : "nicht überall vorhanden")}";
$"{percent:F1}%: Person mit {person.id} geprüft {(isInAll ? "in allen enthalten" : "nicht überall vorhanden")}";
// Sicher und nicht blockierend loggen
if (progress != null)
@@ -212,12 +217,13 @@ public class CombineAddresses
}
}
if(return_unused == true) return (result,second_result);
else return (result, null);
if (return_unused == true) return (result, second_result);
return (result, null);
}
public async Task<(KasAddressList,KasAddressList)> SymmetricDifference(List<KasAddressList> address_lists, bool? return_unused, Progress progress = null)
public async Task<(KasAddressList, KasAddressList)> SymmetricDifference(List<KasAddressList> address_lists,
bool? return_unused, Progress progress = null)
{
var result = new KasAddressList(KasAddressList.GenerateName("symmetric_difference"));
var second_result = new KasAddressList(KasAddressList.GenerateName("symmetric_rest"));
@@ -251,7 +257,7 @@ public class CombineAddresses
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.refsid} verarbeitet (Zwischengröße {allPersons.Count})";
$"{percent:F1}%: Person mit {person.id} verarbeitet (Zwischengröße {allPersons.Count})";
if (progress != null)
{
@@ -269,8 +275,8 @@ public class CombineAddresses
else
second_result.KasPersons.Add(person);
if(return_unused == true) return (result,second_result);
else return (result, null);
if (return_unused == true) return (result, second_result);
return (result, null);
}

360
Tasks/PdfBuilder.cs Normal file
View File

@@ -0,0 +1,360 @@
using System;
using System.Collections.Generic;
using System.Linq;
using PdfSharp;
using PdfSharp.Drawing;
using PdfSharp.Pdf;
namespace Logof_Client;
public class PdfBuilder
{
// Table layout
private const int CellsPerRow = 3;
private const int CellsPerPage = 21; // 3 columns × 7 rows
private readonly XFont _boldFont = new("Arial", 9, XFontStyleEx.Bold);
private readonly double _cellHeight = 42.43; // mm
private readonly double _cellPaddingBottom = 5; // mm
// Padding inside cells
private readonly double _cellPaddingLeft = 5; // mm
private readonly double _cellPaddingTop = 5; // mm
// Cell dimensions (in mm)
private readonly double _cellWidth = 70; // mm
// Font settings
private readonly double _fontSize = 9;
private readonly double _marginBottom = 1; // mm
// Paper and layout settings
private readonly double _marginLeft = 0; // mm
private readonly double _marginRight = 0; // mm
private readonly double _marginTop = 0; // mm
private readonly XFont _regularFont = new("Arial", 9, XFontStyleEx.Regular);
private readonly XFont _smallFont = new("Arial", 6, XFontStyleEx.Regular);
/// <summary>
/// Creates a PDF document with address stickers from an AddressSet with a placeholder in the first cell.
/// </summary>
/// <param name="addressSetId">The ID of the AddressSet to use</param>
/// <param name="placeholderText">Text for the first cell (top-left)</param>
/// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText,
string outputPath)
{
// Find the AddressSet by ID
var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
if (addressSet == null)
throw new ArgumentException($"AddressSet with ID {addressSetId} not found");
if (addressSet.KasPersons == null || addressSet.KasPersons.Count == 0)
throw new ArgumentException($"AddressSet with ID {addressSetId} contains no addresses");
// Generate markdown addresses from all KasPersons in the set
//var addresses = new string?[addressSet.KasPersons.Count];
var addresses = new List<string>();
// find customer (owner) to include sender_address
string senderLine = null;
try
{
var owner = Settings._instance.customers.customers.FirstOrDefault(c => c.ID == addressSet.owner_id);
if (owner != null && !string.IsNullOrWhiteSpace(owner.sender_address))
senderLine = "<font6>" + owner.sender_address.Replace("\n", " ").Trim() + "</font6>\n";
}
catch
{
senderLine = null;
}
for (var i = 0; i < addressSet.KasPersons.Count; i++)
{
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
if (string.IsNullOrWhiteSpace(addr)) continue;
if (!string.IsNullOrEmpty(senderLine))
addresses.Add(senderLine + (addr ?? ""));
else
addresses.Add(addr);
}
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath);
}
/// <summary>
/// Creates a PDF document with a single placeholder cell for other information.
/// </summary>
/// <param name="addresses">Array of addresses</param>
/// <param name="placeholderText">Text for the first cell (top-left)</param>
/// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfWithPlaceholder(List<string> addresses, string placeholderText, string outputPath)
{
if (addresses == null || addresses.Count == 0)
throw new ArgumentException("Addresses array cannot be null or empty");
var document = new PdfDocument();
var addressIndex = 0;
var isFirstCell = true;
while (addressIndex < addresses.Count || isFirstCell)
{
var page = document.AddPage();
page.Size = PageSize.A4;
using (var gfx = XGraphics.FromPdfPage(page))
{
DrawPageWithPlaceholder(gfx, addresses, ref addressIndex, ref isFirstCell, placeholderText);
}
}
document.Save(outputPath);
}
private void DrawPage(XGraphics gfx, List<string> addresses, ref int addressIndex)
{
var cellCount = 0;
for (var row = 0; row < 7; row++)
{
for (var col = 0; col < 3; col++)
{
if (addressIndex >= addresses.Count) break;
var x = MmToPoints(_marginLeft + col * _cellWidth);
var y = MmToPoints(_marginTop + row * _cellHeight);
DrawCell(gfx, x, y, addresses[addressIndex]);
addressIndex++;
cellCount++;
}
if (addressIndex >= addresses.Count) break;
}
}
private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex,
ref bool isFirstCell, string placeholderText)
{
var cellCount = 0;
for (var row = 0; row < 7; row++)
for (var col = 0; col < 3; col++)
{
var x = MmToPoints(_marginLeft + col * _cellWidth);
var y = MmToPoints(_marginTop + row * _cellHeight);
// First cell: placeholder
if (isFirstCell)
{
DrawCell(gfx, x, y, placeholderText);
isFirstCell = false;
}
else if (addressIndex < addresses.Count)
{
DrawCell(gfx, x, y, addresses[addressIndex]);
addressIndex++;
}
else
{
DrawEmptyCell(gfx, x, y);
}
cellCount++;
}
}
private void DrawCell(XGraphics gfx, double x, double y, string? address)
{
var cellWidthPoints = MmToPoints(_cellWidth);
var cellHeightPoints = MmToPoints(_cellHeight);
// Draw cell border
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect);
// Draw address content if available
if (!string.IsNullOrEmpty(address)) DrawMarkdownText(gfx, address, x, y, cellWidthPoints, cellHeightPoints);
}
private void DrawEmptyCell(XGraphics gfx, double x, double y)
{
var cellWidthPoints = MmToPoints(_cellWidth);
var cellHeightPoints = MmToPoints(_cellHeight);
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect);
}
private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight)
{
var paddingLeftPoints = MmToPoints(_cellPaddingLeft);
var paddingTopPoints = MmToPoints(_cellPaddingTop);
var paddingBottomPoints = MmToPoints(_cellPaddingBottom);
var maxWidth = cellWidth - paddingLeftPoints * 2;
// Split text by newlines and remove empty trailing lines
var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None);
var lines = rawLines.Where(l => l != null).ToArray();
// Use a conservative line height in points
var lineHeight = _regularFont.Size * 1.2;
// Calculate total height of the text block
var totalHeight = lines.Length * lineHeight;
// Start drawing from the top of the cell (align addresses to top)
var startY = y + paddingTopPoints;
var currentY = startY;
foreach (var line in lines)
{
// Stop if we've reached the top boundary
if (currentY + lineHeight > y + cellHeight - paddingBottomPoints + 0.001)
break;
// Parse and draw the line with markdown support
DrawLineWithMarkdown(gfx, line, x + paddingLeftPoints, currentY, maxWidth);
currentY += lineHeight;
}
}
private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
{
if (string.IsNullOrWhiteSpace(line)) return;
var currentX = x;
var i = 0;
while (i < line.Length)
{
if (currentX - x >= maxWidth)
break;
var remainingWidth = maxWidth - (currentX - x);
// Check for small-font tag <font6> ... </font6>
if (i <= line.Length - 7 && line.Substring(i, 7) == "<font6>")
{
var endTag = line.IndexOf("</font6>", i + 7, StringComparison.Ordinal);
if (endTag != -1)
{
var inner = line.Substring(i + 7, endTag - (i + 7));
if (!string.IsNullOrEmpty(inner))
{
var measuredSmall = gfx.MeasureString(inner, _smallFont);
if (measuredSmall.Width > remainingWidth)
{
inner = TruncateTextToWidth(gfx, inner, _smallFont, remainingWidth);
measuredSmall = gfx.MeasureString(inner, _smallFont);
}
gfx.DrawString(inner, _smallFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _smallFont.Size * 1.2),
XStringFormats.TopLeft);
currentX += measuredSmall.Width;
}
i = endTag + 8; // move past </font6>
continue;
}
}
// Check for bold marker (**)
if (i < line.Length - 1 && line[i] == '*' && line[i + 1] == '*')
{
// Find closing **
var endIndex = line.IndexOf("**", i + 2, StringComparison.Ordinal);
if (endIndex != -1)
{
var boldText = line.Substring(i + 2, endIndex - (i + 2));
var measured = gfx.MeasureString(boldText, _boldFont);
if (measured.Width > remainingWidth)
{
boldText = TruncateTextToWidth(gfx, boldText, _boldFont, remainingWidth);
measured = gfx.MeasureString(boldText, _boldFont);
}
// Draw bold text and measure width accurately
gfx.DrawString(boldText, _boldFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _boldFont.Size * 1.2),
XStringFormats.TopLeft);
currentX += measured.Width;
i = endIndex + 2;
continue;
}
}
// Regular text until next ** or end of line
var nextBoldIndex = line.IndexOf("**", i, StringComparison.Ordinal);
var textEnd = nextBoldIndex == -1 ? line.Length : nextBoldIndex;
var regularText = line.Substring(i, textEnd - i);
if (!string.IsNullOrEmpty(regularText))
{
var measured = gfx.MeasureString(regularText, _regularFont);
if (measured.Width > remainingWidth)
{
regularText = TruncateTextToWidth(gfx, regularText, _regularFont, remainingWidth);
measured = gfx.MeasureString(regularText, _regularFont);
}
gfx.DrawString(regularText, _regularFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _regularFont.Size * 1.2), XStringFormats.TopLeft);
currentX += measured.Width;
}
i = textEnd;
}
}
private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
{
if (string.IsNullOrEmpty(text))
return text;
for (var len = text.Length; len > 0; len--)
{
var truncated = text.Substring(0, len);
var measured = gfx.MeasureString(truncated, font);
if (measured.Width <= maxWidth)
return truncated;
}
return string.Empty;
}
/// <summary>
/// Converts millimeters to points (1 mm = 2.834645669 points)
/// </summary>
private double MmToPoints(double mm)
{
return mm * 2.834645669;
}
// Configuration methods to allow customization
/// <summary>
/// Sets the cell dimensions in millimeters
/// </summary>
public void SetCellDimensions(double width, double height)
{
if (width <= 0 || height <= 0)
throw new ArgumentException("Cell dimensions must be positive");
}
/// <summary>
/// Sets the page margins in millimeters
/// </summary>
public void SetMargins(double left, double top, double right, double bottom)
{
if (left < 0 || top < 0 || right < 0 || bottom < 0)
throw new ArgumentException("Margins cannot be negative");
}
}

8
Wiki/EditorWindow.axaml Normal file
View File

@@ -0,0 +1,8 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Logof_Client.Wiki.EditorWindow"
Title="Wiki Editor (disabled)" Width="400" Height="150">
<Grid>
<TextBlock Margin="12" TextWrapping="Wrap">Coming soon.</TextBlock>
</Grid>
</Window>

View File

@@ -0,0 +1,17 @@
using System;
using Avalonia.Controls;
namespace Logof_Client.Wiki;
public partial class EditorWindow : Window
{
private void InitializeComponent()
{
Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(this);
}
public EditorWindow()
{
InitializeComponent();
}
}

126
Wiki/MarkdownRenderer.cs Normal file
View File

@@ -0,0 +1,126 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Media;
using Markdig;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.Text;
namespace Logof_Client.Wiki;
public static class MarkdownRenderer
{
public static Control Render(string markdown)
{
var panel = new StackPanel { Spacing = 6 };
if (string.IsNullOrWhiteSpace(markdown)) return panel;
var doc = Markdown.Parse(markdown);
foreach (var block in doc)
{
switch (block)
{
case HeadingBlock hb:
{
var text = GetInlineText(hb.Inline);
var tb = new TextBlock
{
Text = text,
FontWeight = FontWeight.Bold,
Margin = new Avalonia.Thickness(0, hb.Level == 1 ? 6 : 2, 0, 2)
};
tb.FontSize = hb.Level switch { 1 => 22, 2 => 18, 3 => 16, _ => 14 };
panel.Children.Add(tb);
break;
}
case ParagraphBlock pb:
{
var text = GetInlineText(pb.Inline);
var tb = new TextBlock { Text = text, TextWrapping = Avalonia.Media.TextWrapping.Wrap };
panel.Children.Add(tb);
break;
}
case FencedCodeBlock cb:
{
var sb = new StringBuilder();
foreach (var line in cb.Lines.Lines)
{
sb.Append(line.ToString());
}
var codeBox = new TextBox
{
Text = sb.ToString(),
FontFamily = "Consolas, monospace",
IsReadOnly = true,
AcceptsReturn = true
};
panel.Children.Add(codeBox);
break;
}
case ListBlock lb:
{
var sp = new StackPanel { Spacing = 2 };
var number = 1;
foreach (var item in lb)
{
if (item is ListItemBlock lib)
{
var itemText = new StringBuilder();
foreach (var sub in lib)
{
if (sub is ParagraphBlock pp)
itemText.Append(GetInlineText(pp.Inline));
}
var tb = new TextBlock { Text = (lb.IsOrdered ? (number++ + ". ") : "• ") + itemText.ToString() };
sp.Children.Add(tb);
}
}
panel.Children.Add(sp);
break;
}
default:
{
// fallback: raw text
panel.Children.Add(new TextBlock { Text = block.ToString() });
break;
}
}
}
return panel;
}
private static string GetInlineText(ContainerInline? container)
{
if (container == null) return string.Empty;
var sb = new StringBuilder();
foreach (var inline in container)
{
switch (inline)
{
case LiteralInline li:
sb.Append(li.Content.ToString());
break;
case EmphasisInline ei:
sb.Append(GetInlineText(ei));
break;
case CodeInline ci:
sb.Append(ci.Content);
break;
case LinkInline li:
sb.Append(GetInlineText(li));
break;
default:
sb.Append(inline.ToString());
break;
}
}
return sb.ToString();
}
}

13
Wiki/WikiItem.cs Normal file
View File

@@ -0,0 +1,13 @@
using System.Collections.ObjectModel;
namespace Logof_Client.Wiki;
public class WikiItem
{
public string Name { get; set; }
public string Path { get; set; }
public bool IsFolder { get; set; }
public ObservableCollection<WikiItem> Children { get; } = new();
public override string ToString() => Name;
}

71
Wiki/WikiService.cs Normal file
View File

@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Logof_Client.Wiki;
public class WikiService
{
public WikiService(string wikiRoot = null)
{
// prefer global wiki storage path if configured
if (Global._instance != null && !string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path))
{
var cfg = Global._instance.wiki_storage_path;
WikiRootFullPath = Path.IsPathRooted(cfg)
? cfg
: Path.Combine(Directory.GetCurrentDirectory(), cfg);
WikiRoot = WikiRootFullPath;
}
else
{
if (wikiRoot == null)
wikiRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient",
"wiki");
WikiRoot = wikiRoot;
WikiRootFullPath = Path.Combine(Directory.GetCurrentDirectory(), wikiRoot);
}
}
public string WikiRoot { get; }
public string WikiRootFullPath { get; }
public List<WikiItem> GetRootItems()
{
var list = new List<WikiItem>();
if (!Directory.Exists(WikiRootFullPath)) return list;
var dirInfo = new DirectoryInfo(WikiRootFullPath);
// Add folders
foreach (var dir in dirInfo.GetDirectories()) list.Add(BuildFolderItem(dir));
// Add files in root
foreach (var file in dirInfo.GetFiles("*.md"))
list.Add(new WikiItem { Name = file.Name, Path = file.FullName, IsFolder = false });
return list.OrderBy(i => i.IsFolder ? 0 : 1).ToList();
}
private WikiItem BuildFolderItem(DirectoryInfo dir)
{
var node = new WikiItem { Name = dir.Name, Path = dir.FullName, IsFolder = true };
foreach (var subdir in dir.GetDirectories()) node.Children.Add(BuildFolderItem(subdir));
foreach (var file in dir.GetFiles("*.md"))
node.Children.Add(new WikiItem { Name = file.Name, Path = file.FullName, IsFolder = false });
return node;
}
public Task<string?> LoadFileContentAsync(string path)
{
if (!File.Exists(path)) return Task.FromResult<string?>(null);
return File.ReadAllTextAsync(path);
}
}