Compare commits
37 Commits
8e5709c215
...
v0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
313cd58fc7 | ||
|
|
723722ba47 | ||
|
|
58964896ad | ||
|
|
af1c3ff8cc | ||
|
|
48852e4505 | ||
|
|
60cde86efe | ||
|
|
67cfe10f5f | ||
|
|
e186070f05 | ||
|
|
d36314b724 | ||
|
|
beed5decbf | ||
|
|
71859fa978 | ||
|
|
f05470249a | ||
|
|
f4918aa8ee | ||
|
|
4e1f08883a | ||
| 99b35c0aaf | |||
| aeb4092f28 | |||
| c760ef0936 | |||
| e750b4c757 | |||
|
|
3877d91af4 | ||
|
|
7af6444da2 | ||
|
|
4cfbcd0ab4 | ||
|
|
b82473ada2 | ||
|
|
1cba67253a | ||
|
|
63c1559110 | ||
|
|
b670ba11fa | ||
|
|
1ad57543d1 | ||
|
|
6cd4ea2df6 | ||
|
|
30e42afe35 | ||
|
|
70e127b2f0 | ||
|
|
7c73170b46 | ||
|
|
290f69e976 | ||
|
|
6ce08d7d4a | ||
|
|
86df6f6a63 | ||
|
|
eae0568ae0 | ||
|
|
4ebd6bc407 | ||
|
|
2c22306fef | ||
|
|
174223ba9e |
@@ -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;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ public class KasPerson
|
||||
|
||||
public KasPerson()
|
||||
{
|
||||
id = GenerateNewID(0);
|
||||
refsid = 0;
|
||||
anrede = "";
|
||||
titel = "";
|
||||
@@ -88,7 +89,7 @@ public class KasPerson
|
||||
funktionad = "";
|
||||
}
|
||||
|
||||
public KasPerson(int refsid,
|
||||
public KasPerson(int id, int refsid,
|
||||
string anrede,
|
||||
string titel,
|
||||
string vorname,
|
||||
@@ -113,6 +114,7 @@ public class KasPerson
|
||||
string abteilung,
|
||||
string funktionad)
|
||||
{
|
||||
this.id = id;
|
||||
this.refsid = refsid;
|
||||
this.anrede = anrede;
|
||||
this.titel = titel;
|
||||
@@ -139,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; }
|
||||
@@ -163,6 +166,17 @@ 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
|
||||
@@ -177,4 +191,14 @@ public class KasPersonError
|
||||
//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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
199
MainWindow.axaml
199
MainWindow.axaml
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -5,6 +5,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Logof_Client;
|
||||
|
||||
@@ -30,13 +31,49 @@ public partial class ResultWindow : Window
|
||||
// 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";
|
||||
DgResult.ItemsSource = result_with_errors;
|
||||
|
||||
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();
|
||||
@@ -45,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" +
|
||||
@@ -87,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);
|
||||
}
|
||||
@@ -97,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);
|
||||
}
|
||||
@@ -112,46 +149,80 @@ public partial class ResultWindow : Window
|
||||
private void UpdateFilter()
|
||||
{
|
||||
var temp_result = new List<KasPerson>();
|
||||
var checked_types = new List<AddressCheck.ErrorTypes>();
|
||||
var checked_types_war = new List<AddressCheck.WarningTypes>();
|
||||
|
||||
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 person in ur_result)
|
||||
{
|
||||
if (person.PersonError == null) continue;
|
||||
// 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 err in person.PersonError.errors)
|
||||
if (checked_types.Contains(err) && !temp_result.Contains(person))
|
||||
var personErrors = person.PersonError.errors ?? Enumerable.Empty<AddressCheck.ErrorTypes>();
|
||||
var personWarnings = person.PersonError.warnings ?? Enumerable.Empty<AddressCheck.WarningTypes>();
|
||||
|
||||
var matchesError = false;
|
||||
var matchesWarning = false;
|
||||
|
||||
// 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);
|
||||
|
||||
foreach (var war in person.PersonError.warnings)
|
||||
if (checked_types_war.Contains(war) && !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 = (KasPerson)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();
|
||||
}
|
||||
}
|
||||
38
Settings.cs
38
Settings.cs
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -63,199 +63,199 @@ public class AddressCheck
|
||||
|
||||
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 refsid {person.refsid} ist fehlerhaft");
|
||||
|
||||
_progress.ChangePercentage(percent);
|
||||
});
|
||||
// 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)
|
||||
|
||||
@@ -5,52 +5,6 @@ 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
|
||||
var address = new KasPerson();
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -86,31 +40,64 @@ public static class AddressCreator
|
||||
/// </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)
|
||||
public static string? CreateFinalMarkdownString(int id)
|
||||
{
|
||||
// Maximum seven lines of information
|
||||
|
||||
// find the address
|
||||
var address = new KasPerson();
|
||||
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;
|
||||
|
||||
// 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++;
|
||||
}
|
||||
|
||||
@@ -121,10 +108,10 @@ 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++;
|
||||
@@ -133,28 +120,33 @@ public static class AddressCreator
|
||||
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
|
||||
address.adel, address.name, address.namezus);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(nameline))
|
||||
if (!string.IsNullOrWhiteSpace(nameline))
|
||||
{
|
||||
string_address = nameline + string_address;
|
||||
string_address = nameline + "\n" + string_address;
|
||||
address_line_count++;
|
||||
}
|
||||
|
||||
// REIHENFOLGE
|
||||
var nameattribs = new[]
|
||||
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
|
||||
|
||||
for (var 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
|
||||
catch
|
||||
{
|
||||
break;
|
||||
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))
|
||||
{
|
||||
@@ -165,41 +157,45 @@ public static class AddressCreator
|
||||
string_address = address.strasse.Trim() + "\n" + string_address;
|
||||
address_line_count++;
|
||||
}
|
||||
else
|
||||
else if (!string.IsNullOrWhiteSpace(address.postfach.Trim()))
|
||||
{
|
||||
string_address = address.postfach.Trim() + "\n" + string_address;
|
||||
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))
|
||||
if (!string.IsNullOrWhiteSpace(nameline))
|
||||
{
|
||||
string_address = nameline + string_address;
|
||||
string_address = nameline + "\n" + string_address;
|
||||
address_line_count++;
|
||||
}
|
||||
|
||||
var nameattribs = new[]
|
||||
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
|
||||
|
||||
for (var 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
|
||||
catch
|
||||
{
|
||||
break;
|
||||
Console.WriteLine("ERROR 15821");
|
||||
}
|
||||
|
||||
string_address = names + "\n" + string_address;
|
||||
} // Error-Handling?
|
||||
|
||||
return string_address;
|
||||
return "Hier könnte eine\nAdresse stehen";
|
||||
if (address_line_count > 1) return string_address;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel,
|
||||
@@ -210,16 +206,14 @@ public static class AddressCreator
|
||||
new[] { anredezus, titel, vorname, adel, name }
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
)
|
||||
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})")
|
||||
+ "\n";
|
||||
+ (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()})")
|
||||
+ "\n";
|
||||
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,20 +225,28 @@ public static class AddressCreator
|
||||
public static bool CheckPLZ(string plz, string land)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(plz)) return false;
|
||||
|
||||
// For non-german countries, accept any non-empty postal code
|
||||
if (land.ToLower().Trim() != "germany" && land.ToLower().Trim() != "ger" && land.ToLower().Trim() != "" &&
|
||||
land.ToLower().Trim() != "de" && land.ToLower().Trim() != "deutschland")
|
||||
return true;
|
||||
|
||||
// For Germany, check if it's a valid numeric postal code (5 digits)
|
||||
if (int.TryParse(plz, out var iplz))
|
||||
{
|
||||
if (iplz > 99999 || iplz < 10000) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it can't be parsed as int, it's not a valid German postal code
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
// For non-German countries, accept any non-empty postal code
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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
360
Tasks/PdfBuilder.cs
Normal 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
8
Wiki/EditorWindow.axaml
Normal 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>
|
||||
17
Wiki/EditorWindow.axaml.cs
Normal file
17
Wiki/EditorWindow.axaml.cs
Normal 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
126
Wiki/MarkdownRenderer.cs
Normal 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
13
Wiki/WikiItem.cs
Normal 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
71
Wiki/WikiService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user