Compare commits

..

74 Commits

Author SHA1 Message Date
Elias Fierke
4cfbcd0ab4 [feat:] added Country implementation for country code editing (unused) 2026-01-01 14:45:57 +01:00
Elias Fierke
b82473ada2 [chore:] PdfBuilder.cs skips empty addresses 2025-12-21 11:43:41 +01:00
Elias Fierke
1cba67253a [chore:] selectable pdf file path for label creation 2025-12-21 11:32:38 +01:00
Elias Fierke
63c1559110 [chore:] added "Postfach" to address creation 2025-12-21 11:25:48 +01:00
Elias Fierke
b670ba11fa [chore:] sender address usage in PdfBuilder.cs 2025-12-15 10:17:10 +01:00
Elias Fierke
1ad57543d1 [chore:] sender address editing 2025-12-15 10:04:33 +01:00
Elias Fierke
6cd4ea2df6 [chore:] added sender address field 2025-12-15 10:01:07 +01:00
Elias Fierke
30e42afe35 [chore:] rearrangend some result window features for debugging purposes (unfinal) 2025-12-14 14:33:55 +01:00
Elias Fierke
70e127b2f0 [init:] Initialized PDF Creation 2025-12-14 14:33:25 +01:00
Elias Fierke
7c73170b46 [chore:] implemented label generation button handler 2025-12-14 14:33:07 +01:00
Elias Fierke
290f69e976 [chore:] the IDE wanted to add some spaces 2025-12-14 14:32:45 +01:00
Elias Fierke
6ce08d7d4a [gui:] added a few tabs that will be used later 2025-12-14 14:32:02 +01:00
Elias Fierke
86df6f6a63 [file:] imported PdfSharp 2025-12-14 14:31:33 +01:00
Elias Fierke
eae0568ae0 [feat:] added KasPersonError.GetString() 2025-12-14 14:31:20 +01:00
Elias Fierke
4ebd6bc407 [fix:] fixed some logical errors in address creation 2025-12-14 14:30:53 +01:00
Elias Fierke
2c22306fef [fix:] plz check was bad 2025-12-14 14:30:33 +01:00
Elias Fierke
174223ba9e [fix:] Address Creation used an empty address instead of the one from the address set 2025-12-14 14:30:07 +01:00
Elias Fierke
8e5709c215 [feat:] initial fully implemented address creation 2025-12-07 13:30:52 +01:00
Elias Fierke
b70bd5e324 [struc:] moved KasPersonError-Instance from addressset to KasPerson-Instance 2025-12-07 13:30:25 +01:00
8c56717b9c [fix:] plz's and pplz's CAN be alphanumeric 2025-11-27 14:48:24 +01:00
7cd02456bc [init:] AddressCreator with first string address alternative 2025-11-27 14:47:48 +01:00
88bbc9644c [chore:] applied improved set naming 2025-11-17 13:28:40 +01:00
743c3aacd1 [chore:] added basic name-generation-function for address-sets 2025-11-17 13:14:26 +01:00
ac8362d9ba [feat:] added unprocessed-item-to-new-set-Option to address-combining 2025-11-17 13:13:30 +01:00
0c11cb2819 [chore:] added "-new"-part to names of AddressSets if the name already exists (will be improved further) 2025-11-11 11:16:06 +01:00
f057226a5f [fix:] bad refsid creation on address-patch-import 2025-11-11 10:50:36 +01:00
Elias Fierke
a70948599f [fix:] fixed new customer issue (new customer creation was terrible) 2025-11-04 14:43:11 +01:00
Elias Fierke
fb671e3526 [docs:] removed unrelated comment from another document which provided the template 2025-11-04 14:20:47 +01:00
Elias Fierke
a2af60a808 [chore:] implemented symmetric difference (combiner) and its usage 2025-10-23 20:49:19 +02:00
Elias Fierke
34c4dd8df1 [init:] initialized logof client documentation (tex) 2025-10-23 16:49:54 +02:00
Elias Fierke
bed0ebaf16 [chore:] implemented intersection (combiner) and its usage 2025-10-23 16:43:23 +02:00
Elias Fierke
d0ecfda404 [chore:] implemented union (combiner) and its usage 2025-10-23 13:54:48 +02:00
Elias Fierke
f28ac1a4b9 [chore:] implemented difference (combiner) and its usage 2025-10-23 13:32:26 +02:00
Elias Fierke
09bbdfdfde [feat:] added address-getter 2025-10-23 13:31:31 +02:00
Elias Fierke
6efafb7cc2 [init:] initialized new combine-functions 2025-10-23 10:40:25 +02:00
Elias Fierke
f93d9aada8 [fix:] refsid creation for non-refsid address-sets 2025-10-23 10:40:01 +02:00
Elias Fierke
f27adffe41 [chore:] added loading.mp4 to project resources 2025-10-23 10:39:27 +02:00
Elias Fierke
48b0500bfe [chore:] initial splash preparation, which will not be used for now 2025-10-23 10:39:04 +02:00
Elias Fierke
c6da9ac416 [file:] some ui layout for a not-shown window yeah 2025-10-23 10:38:32 +02:00
Elias Fierke
d02d5199c3 [file:] added new window for splash screen 2025-10-09 11:22:17 +02:00
Elias Fierke
b4430ae424 [file:] added loading animation 2025-10-09 11:19:15 +02:00
Elias Fierke
52c028cc8a [chore:] added link to git 2025-10-09 10:26:28 +02:00
Elias Fierke
af8a74c2c3 [chore:] added ID to addres-sets (yes, there was no -_-) 2025-10-09 10:22:22 +02:00
Elias Fierke
63d430dd72 [chore:] combine split into four possible variants of merging 2025-10-09 10:21:42 +02:00
Elias Fierke
c4663e489e [fix:] changed Shwo to Show 👍 2025-10-09 10:19:27 +02:00
Elias Fierke
f3ec633c53 [file:] deleted /obj 2025-10-09 08:58:39 +02:00
Elias Fierke
6d3ea7fd2d [fix:] added /obj/* to .gitignore 2025-10-09 08:53:58 +02:00
Elias Fierke
fc69c8cd6d [fix:] added /obj/ to .gitignore 2025-10-09 08:53:18 +02:00
Elias Fierke
69e9b84812 [fix:] forgot to use the separator in last commit 2025-10-09 08:51:25 +02:00
Elias Fierke
c7740a48dc stuff 2025-10-09 08:40:24 +02:00
Elias Fierke
3cf1fb3012 [chore:] remove unused code for now 2025-10-09 08:40:09 +02:00
Elias Fierke
316c9511d3 [chore:] added CSV-Separator Settings.cs 2025-10-09 08:39:49 +02:00
Elias Fierke
4ee852b37c [chore:] icon path changed 2025-10-09 08:39:14 +02:00
Elias Fierke
cd78eea228 [file:] moved icons 2025-10-09 08:37:52 +02:00
Elias Fierke
df7b653218 [file:] imported calc man 2025-10-09 08:37:42 +02:00
Elias Fierke
842cb3f0f7 [file:] imported icon 2025-10-07 18:33:04 +02:00
Elias Fierke
1c54fa8937 [chore:] minor consistency improvements to AddressCheck.cs 2025-10-07 13:04:32 +02:00
Elias Fierke
f33c69afcb [fix:] integrated refsid to data import 2025-10-06 18:16:51 +02:00
Elias Fierke
69c4957b62 stuff 2025-10-04 14:29:11 +02:00
Elias Fierke
8af9856afa [fix:] Import with AddressPatch hasn't been called 2025-10-04 14:29:02 +02:00
Elias Fierke
47942ddd78 [fix:] import using AddressPatch now working 2025-10-04 14:28:23 +02:00
Elias Fierke
081a705d2d [file:] oh, i removed a line break. nice. 2025-10-04 09:48:53 +02:00
Elias Fierke
d77b89a9f2 [file:] better kas address list name 2025-10-03 16:39:18 +02:00
Elias Fierke
d131b90103 [file:] added Netwonsonft.Json package 2025-10-03 16:39:02 +02:00
Elias Fierke
badeafe042 [chore:] kas address list improvement (more information) 2025-10-03 16:38:17 +02:00
Elias Fierke
138ab7e6c7 [chore:] force light theme due to unreadability issues (temp) 2025-10-03 16:37:47 +02:00
Elias Fierke
cdb669d261 [chore:] address list name got prettier 2025-10-03 16:37:17 +02:00
Elias Fierke
bb65df9ea2 [chore:] added address patch 2025-10-03 16:36:22 +02:00
afc3a78358 [chore:] improved gui 2025-09-21 18:09:21 +02:00
6b2eeda29a Update .idea/.idea.Logof Client.dir/.idea/.gitignore 2025-09-21 15:03:51 +00:00
55b091b2cc [chore:] kram 2025-09-21 16:58:32 +02:00
615ef22a55 [chore:] moved files for better structure 2025-09-21 16:10:21 +02:00
a32c96a325 [chore:] massive ui changes 2025-09-21 16:10:08 +02:00
f6952288d7 [feat:] added TabControl for Multi-Feature App-Design 2025-09-18 15:56:35 +02:00
44 changed files with 3368 additions and 2570 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/obj/*

View File

@@ -11,3 +11,5 @@
# Datasource local storage ignored files # Datasource local storage ignored files
/dataSources/ /dataSources/
/dataSources.local.xml /dataSources.local.xml
/obj
/obj/*

View File

@@ -1,235 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Logof_Client;
public class AddressCheck
{
public enum ErrorTypes
{
PlzTooShort,
PlzTooLong,
PPlzTooShort,
PPlzTooLong,
// empty,
FullAddressTooLong,
DoubledRefsid,
MayBeSameAddress,
NoPLZorPPLZ
}
public enum WarningTypes
{
NoCity,
NoStreet,
NoLastName,
NoFirstName,
NoStreetNumber,
NoPLZ,
NoPPLZ
}
private readonly ProgressWindow _progress;
public AddressCheck(ProgressWindow progressWindow)
{
_progress = progressWindow;
}
public async Task<List<(int, List<ErrorTypes>, List<WarningTypes>)>> Perform(KasAddressList addresses)
{
var failed_refsids = new List<(int, List<ErrorTypes>, List<WarningTypes>)>();
var total = addresses.KasPersons.Count;
var current = 0;
await Task.Run(async () =>
{
foreach (var person in addresses.KasPersons)
{
var errors = new List<ErrorTypes>();
var warnings = new List<WarningTypes>();
var hasFaults = false;
var address_component_count = 2; // cause anrede and name are first
// PLZ-Prüfung
if (person.plz == 0 || person.plz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPLZ);
}
else
{
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 == 0 || person.pplz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPPLZ);
}
else
{
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 addresses.KasPersons)
{
if (addresses.KasPersons.IndexOf(person) == addresses.KasPersons.IndexOf(person2)) continue;
if (person.refsid == person2.refsid)
{
hasFaults = true;
errors.Add(ErrorTypes.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;
errors.Add(ErrorTypes.FullAddressTooLong);
}
if (hasFaults)
lock (failed_refsids)
{
failed_refsids.Add((person.refsid, errors, warnings));
}
// Fortschritt aktualisieren
Interlocked.Increment(ref current);
var percent = current / (double)total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (hasFaults)
_progress.AddToLog($"Person mit refsid {person.refsid} ist fehlerhaft");
_progress.ChangePercentage(percent);
});
}
});
return failed_refsids;
}
}

118
AddressPatch.cs Normal file
View File

@@ -0,0 +1,118 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
public class AddressPatch
{
public string refsid_is { get; set; } = "";
public string anrede_is { get; set; } = "";
public string titel_is { get; set; } = "";
public string vorname_is { get; set; } = "";
public string adel_is { get; set; } = "";
public string name_is { get; set; } = "";
public string namezus_is { get; set; } = "";
public string anredzus_is { get; set; } = "";
public string strasse_is { get; set; } = "";
public string strasse2_is { get; set; } = "";
public string plz_is { get; set; } = "";
public string ort_is { get; set; } = "";
public string land_is { get; set; } = "";
public string pplz_is { get; set; } = "";
public string postfach_is { get; set; } = "";
public string name1_is { get; set; } = "";
public string name2_is { get; set; } = "";
public string name3_is { get; set; } = "";
public string name4_is { get; set; } = "";
public string name5_is { get; set; } = "";
public string funktion_is { get; set; } = "";
public string funktion2_is { get; set; } = "";
public string abteilung_is { get; set; } = "";
public string funktionad_is { get; set; } = "";
public bool has_refsid { get; set; }
public bool has_anrede { get; set; }
public bool has_titel { get; set; }
public bool has_vorname { get; set; }
public bool has_adel { get; set; }
public bool has_name { get; set; }
public bool has_namezus { get; set; }
public bool has_anredzus { get; set; }
public bool has_strasse { get; set; }
public bool has_strasse2 { get; set; }
public bool has_plz { get; set; }
public bool has_ort { get; set; }
public bool has_land { get; set; }
public bool has_pplz { get; set; }
public bool has_postfach { get; set; }
public bool has_name1 { get; set; }
public bool has_name2 { get; set; }
public bool has_name3 { get; set; }
public bool has_name4 { get; set; }
public bool has_name5 { get; set; }
public bool has_funktion { get; set; }
public bool has_funktion2 { get; set; }
public bool has_abteilung { get; set; }
public bool has_funktionad { get; set; }
public static AddressPatch Import(Uri filename)
{
var patch = new AddressPatch();
// Alle Zeilen aus der Datei laden
var lines = File.ReadAllLines(filename.LocalPath);
// Alle Properties der Klasse (Strings und bools)
var properties = typeof(AddressPatch).GetProperties(BindingFlags.Public | BindingFlags.Instance);
// Nur die Properties, die mit _is enden (also die String-Werte)
var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
foreach (var prop in stringProps)
{
// Beispiel: prop.Name = "name_is"
var baseName = prop.Name.Substring(0, prop.Name.Length - 3); // "name"
// In der Datei wird nach "name:" gesucht (ohne _is)
var line = lines.FirstOrDefault(l => l.StartsWith(baseName + ":"));
if (line != null)
{
// Wert extrahieren (alles nach dem Doppelpunkt)
var value = line.Substring(line.IndexOf(':') + 1).Trim();
// Wert im Patch-Objekt setzen
prop.SetValue(patch, value);
// Passendes has_ Feld aktivieren, z.B. "has_name"
var hasProp = properties.FirstOrDefault(p => p.Name == "has_" + baseName);
if (hasProp != null && hasProp.PropertyType == typeof(bool)) hasProp.SetValue(patch, true);
}
}
return patch;
}
public override string ToString()
{
var properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
var lines = new StringBuilder();
foreach (var prop in stringProps)
{
// passendes has_ Feld
var hasProp =
properties.FirstOrDefault(p => p.Name == "has_" + prop.Name.Substring(0, prop.Name.Length - 3));
if (hasProp != null && (bool)hasProp.GetValue(this))
{
var value = (string)prop.GetValue(this);
lines.AppendLine($"{prop.Name} => {value}");
}
}
return lines.ToString().TrimEnd();
}
}

View File

@@ -1,11 +1,11 @@
<Application xmlns="https://github.com/avaloniaui" <Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Logof_Client.App" x:Class="Logof_Client.App"
RequestedThemeVariant="Default"> RequestedThemeVariant="Light">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles> <Application.Styles>
<FluentTheme /> <FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/> <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@@ -1,94 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Logof_Client;
public class CombineAddresses
{
private readonly ProgressWindow _progress;
public CombineAddresses(ProgressWindow progressWindow)
{
_progress = progressWindow;
}
public async Task<KasAddressList> Perform(List<KasAddressList> address_lists)
{
KasAddressList result = new();
await Task.Run(async () =>
{
for (var i = 0; i < address_lists.Count; i++)
if (i == 0)
lock (result)
{
result = address_lists[i];
}
else
lock (result)
{
result = Merge(result, address_lists[i], i + 1, address_lists.Count).Result;
}
});
return result;
}
private async Task<KasAddressList> Merge(KasAddressList first, KasAddressList second, int num, int total)
{
foreach (var sec in second.KasPersons)
{
var is_new = true;
foreach (var fi in first.KasPersons)
{
if (fi.refsid == sec.refsid)
{
is_new = false;
break;
}
if (fi.name == sec.name &&
fi.anrede == sec.anrede &&
fi.anredzus == sec.anredzus &&
fi.namezus == sec.namezus &&
fi.titel == sec.titel &&
fi.adel == sec.adel &&
fi.strasse == sec.strasse &&
fi.strasse2 == sec.strasse2 &&
fi.vorname == sec.vorname &&
fi.ort == sec.ort &&
fi.land == sec.land &&
fi.plz == sec.plz &&
fi.pplz == sec.pplz &&
fi.funktion == sec.funktion &&
fi.funktion2 == sec.funktion2 &&
fi.funktionad == sec.funktionad &&
fi.abteilung == sec.abteilung &&
fi.postfach == sec.postfach &&
fi.name1 == sec.name1 &&
fi.name2 == sec.name2 &&
fi.name3 == sec.name3 &&
fi.name4 == sec.name4 &&
fi.name5 == sec.name5)
{
is_new = false;
break;
}
}
if (is_new) first.KasPersons.Add(sec);
var subperc = second.KasPersons.IndexOf(sec) / second.KasPersons.Count;
var percent = (num + (double)subperc) / total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (is_new)
_progress.AddToLog($"Person mit refsid {sec.refsid} ergänzt");
else
_progress.AddToLog($"Person mit refsid {sec.refsid} bereits vorhanden");
_progress.ChangePercentage(percent);
});
}
return first;
}
}

View File

@@ -1,11 +1,22 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection;
namespace Logof_Client; namespace Logof_Client;
public class DataImport public class DataImport
{ {
public static (bool, KasAddressList) ImportKasAddressList(Uri pathToCsv, char separator = ',') public static (bool, KasAddressList) ImportKasAddressList(Uri pathToCsv, AddressPatch patch = null,
char separator = ',')
{
if (patch == null)
return ImportKasAddressListWithoutPatch(pathToCsv, separator);
return ImportKasAddressListWithPatch(pathToCsv, patch, separator);
}
private static (bool, KasAddressList) ImportKasAddressListWithoutPatch(Uri pathToCsv, char separator)
{ {
if (!File.Exists(pathToCsv.LocalPath)) if (!File.Exists(pathToCsv.LocalPath))
{ {
@@ -21,18 +32,19 @@ public class DataImport
return (false, null); return (false, null);
} }
var imported = new KasAddressList(); var imported = new KasAddressList(KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
while (!reader.EndOfStream) while (!reader.EndOfStream)
{ {
var line = reader.ReadLine(); var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line)) if (string.IsNullOrWhiteSpace(line))
continue; continue;
var parts = line.Split(separator); var parts = line.Split(separator).Select(p => p.Trim()).ToArray();
if (parts.Length < 24) if (parts.Length < 24)
{ {
Console.WriteLine($"No enough columns in line: {line}"); Console.WriteLine($"Not enough columns in line: {line}");
continue; continue;
} }
@@ -49,10 +61,10 @@ public class DataImport
parts[7], parts[7],
parts[8], parts[8],
parts[9], parts[9],
ParseInt(parts[10]), parts[10],
parts[11], parts[11],
parts[12], parts[12],
ParseInt(parts[13]), parts[13],
parts[14], parts[14],
parts[15], parts[15],
parts[16], parts[16],
@@ -69,7 +81,7 @@ public class DataImport
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Error while parsing line: {line} - {ex.Message}"); Console.WriteLine($"Error while parsing line: {line} - {ex.Message}");
Console.WriteLine($"{ex.StackTrace}"); Console.WriteLine(ex.StackTrace);
return (false, null); return (false, null);
} }
} }
@@ -77,6 +89,161 @@ public class DataImport
return (true, imported); return (true, imported);
} }
private static (bool, KasAddressList) ImportKasAddressListWithPatch(Uri pathToCsv, AddressPatch patch,
char separator)
{
if (!File.Exists(pathToCsv.LocalPath))
{
Console.WriteLine($"File not found: {pathToCsv}");
return (false, null);
}
using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var headers = headerLine.Split(separator).Select(h => h.Trim()).ToArray();
var imported = new KasAddressList(KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
var patchType = typeof(AddressPatch);
var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
var hasProperties = patchType.GetProperties(binding)
.Where(p => p.PropertyType == typeof(bool) && p.Name.StartsWith("has_", StringComparison.OrdinalIgnoreCase))
.ToArray();
var last_refsid = 1000000;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = line.Split(separator).Select(p => p.Trim()).ToArray();
var fieldValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
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;
var patchProp = patchType.GetProperty(fieldName + "_is", binding);
string desiredHeader = null;
if (has && patchProp != null)
desiredHeader = patchProp.GetValue(patch)?.ToString();
else
desiredHeader = fieldName;
var resolvedValue = "";
if (!string.IsNullOrEmpty(desiredHeader))
{
var idx = Array.FindIndex(headers,
h => string.Equals(h, desiredHeader, StringComparison.OrdinalIgnoreCase));
if (idx >= 0 && idx < parts.Length)
{
resolvedValue = parts[idx];
}
else
{
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
resolvedValue = "";
}
}
fieldValues[fieldName] = resolvedValue ?? "";
}
string GetField(string name)
{
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"));
try
{
var person = new KasPerson(
new_refsid,
GetField("anrede"),
GetField("titel"),
GetField("vorname"),
GetField("adel"),
GetField("name"),
GetField("namezus"),
GetField("anredzus"),
GetField("strasse"),
GetField("strasse2"),
GetField("plz"),
GetField("ort"),
GetField("land"),
GetField("pplz"),
GetField("postfach"),
GetField("name1"),
GetField("name2"),
GetField("name3"),
GetField("name4"),
GetField("name5"),
GetField("funktion"),
GetField("funktion2"),
GetField("abteilung"),
GetField("funktionad")
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Console.WriteLine($"Error while parsing line: {line} - {ex.Message}");
Console.WriteLine(ex.StackTrace);
return (false, null);
}
}
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;
}
}
private static int ParseInt(string input) private static int ParseInt(string input)
{ {
return int.TryParse(input, out var result) ? result : 0; return int.TryParse(input, out var result) ? result : 0;

View File

@@ -3,18 +3,63 @@ using System.Collections.Generic;
namespace Logof_Client; namespace Logof_Client;
public class KasAddressList public class KasAddressList //Address-Set
{ {
//public List<KasPersonError> errors = new();
public List<KasPerson> KasPersons; public List<KasPerson> KasPersons;
public KasAddressList() public KasAddressList(string name)
{ {
KasPersons = new List<KasPerson>(); KasPersons = new List<KasPerson>();
Name = name;
foreach (var set in Settings._instance.addressSets.addresses)
if (Name == set.Name)
Name = name + "-new";
var highest = 0;
foreach (var k in Settings._instance.addressSets.addresses)
if (highest <= k.ID)
highest = k.ID + 1;
ID = highest;
}
public string Name { get; set; } = "Neues Address-Set";
public int owner_id { get; set; }
public int ID { get; }
public void SetOwner(int owner_id)
{
this.owner_id = owner_id;
}
public static string GenerateName(string basic_type, bool? is_rest = false)
{
if (is_rest == true)
return basic_type + " - " + DateTime.Now.ToShortDateString() + " - Rest";
return basic_type + " - " + DateTime.Now.ToShortDateString();
}
// public void UpdateErrorList(List<KasPersonError> errorList)
// {
// //errors.Clear();
// // foreach (KasPersonError err in errorList)
// // {
// //
// // }
// //foreach (var error in errorList) errors.Add(new KasPersonError(error));
// }
public static int GetIDByAddressSetListItem(string listItemName)
{
var id = listItemName.Split(" - ")[0];
return int.Parse(id);
} }
} }
public class KasPerson public class KasPerson
{ {
public KasPersonError PersonError = null;
public KasPerson() public KasPerson()
{ {
refsid = 0; refsid = 0;
@@ -27,10 +72,10 @@ public class KasPerson
anredzus = ""; anredzus = "";
strasse = ""; strasse = "";
strasse2 = ""; strasse2 = "";
plz = 0; plz = "";
ort = ""; ort = "";
land = ""; land = "";
pplz = 0; pplz = "";
postfach = ""; postfach = "";
name1 = ""; name1 = "";
name2 = ""; name2 = "";
@@ -53,10 +98,10 @@ public class KasPerson
string anredzus, string anredzus,
string strasse, string strasse,
string strasse2, string strasse2,
int plz, string plz,
string ort, string ort,
string land, string land,
int pplz, string pplz,
string postfach, string postfach,
string name1, string name1,
string name2, string name2,
@@ -104,10 +149,10 @@ public class KasPerson
public string anredzus { get; set; } public string anredzus { get; set; }
public string strasse { get; set; } public string strasse { get; set; }
public string strasse2 { get; set; } public string strasse2 { get; set; }
public int plz { get; set; } public string plz { get; set; }
public string ort { get; set; } public string ort { get; set; }
public string land { get; set; } public string land { get; set; }
public int pplz { get; set; } public string pplz { get; set; }
public string postfach { get; set; } public string postfach { get; set; }
public string name1 { get; set; } public string name1 { get; set; }
public string name2 { get; set; } public string name2 { get; set; }
@@ -122,35 +167,24 @@ public class KasPerson
public class KasPersonError public class KasPersonError
{ {
public KasPersonError((int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>) single_result) public KasPersonError((List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>) single_result)
{
refsid = single_result.Item1;
try
{
foreach (var err in single_result.Item2) errors += err + ", ";
errors = errors.Trim();
errors = errors.TrimEnd(',');
}
catch
{ {
//refsid = single_result.Item1;
errors = single_result.Item1;
warnings = single_result.Item2;
} }
try //public int refsid { get; set; }
{ public List<AddressCheck.ErrorTypes> errors { get; set; } = new();
if (single_result.Item3 != null) public List<AddressCheck.WarningTypes> warnings { get; set; } = new();
{
foreach (var err in single_result.Item3) warnings += err + ", ";
warnings = warnings.Trim();
warnings = warnings.TrimEnd(',');
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public int refsid { get; set; } public string GetString()
public string errors { get; set; } = ""; {
public string warnings { get; set; } = ""; var output = "";
foreach (var error in errors) output += error + ", ";
foreach (var warning in warnings) output += warning + ", ";
return output;
}
} }

View File

@@ -9,16 +9,33 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.2" /> <PackageReference Include="Avalonia" Version="11.3.2"/>
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.2" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.2"/>
<PackageReference Include="Avalonia.Desktop" Version="11.3.2" /> <PackageReference Include="Avalonia.Desktop" Version="11.3.2"/>
<PackageReference Include="Avalonia.Themes.Fluent" 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.Fonts.Inter" Version="11.3.2"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.2"> <PackageReference Include="Avalonia.Diagnostics" Version="11.3.2">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets> <IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets> <PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Lucide.Avalonia" Version="0.1.35" /> <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"/>
<AvaloniaResource Include="assets\icon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
<None Remove="assets\calc_man.png"/>
<AvaloniaResource Include="assets\calc_man.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
<None Remove="assets\loading.mp4"/>
<AvaloniaResource Include="assets\loading.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -3,44 +3,108 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Logof_Client.MainWindow" MinWidth="1000" MinHeight="600" IsVisible="False"
x:Class="Logof_Client.MainWindow" WindowState="Maximized" Icon="assets/icon.ico"
Title="Logof Client"> Title="Logof Client">
<Border> <Border>
<Grid RowDefinitions="30,*,*"> <Grid RowDefinitions="30,*">
<Menu Background="#50888888"> <Menu Background="#50888888">
<MenuItem Header="Datei"> <MenuItem Header="Datei">
<MenuItem Click="MnuSettings_OnClick" x:Name="MnuSettings" Header="Einstellungen" /> <!-- <MenuItem Click="MnuSettings_OnClick" x:Name="MnuSettings" Header="Einstellungen" /> -->
<Separator /> <!-- <Separator /> -->
<MenuItem Click="MnuExit_OnClick" x:Name="MnuExit" Header="Beenden" /> <MenuItem Click="MnuExit_OnClick" x:Name="MnuExit" Header="Beenden" />
</MenuItem> </MenuItem>
<MenuItem Header="Hilfe"> <MenuItem Header="Hilfe">
<MenuItem Header="Onlinehilfe" x:Name="MnuHelp" Click="MnuHelp_OnClick" /> <MenuItem Header="Onlinehilfe" x:Name="MnuHelp" Click="MnuHelp_OnClick" />
<MenuItem Header="Github" x:Name="MnuGithub" Click="MnuGithub_OnClick" /> <MenuItem Header="Git" x:Name="MnuGit" Click="MnuGit_OnClick" />
<MenuItem Header="Über" x:Name="MnuAbout" Click="MnuAbout_OnClick" /> <MenuItem Header="Über" x:Name="MnuAbout" Click="MnuAbout_OnClick" />
</MenuItem> </MenuItem>
</Menu> </Menu>
<Grid Grid.Row="1"> <TabControl Grid.Row="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"> <TabItem>
<TextBox x:Name="TbFilename" Watermark="Dateipfad" Width="400" VerticalContentAlignment="Center" /> <TabItem.Header>
<Button x:Name="BtnChooseFile" Margin="10,0,0,0" Click="BtnChooseFile_OnClick">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="FolderOpen" Width="16" Height="16" Size="16" /> <LucideIcon Kind="MapPinHouse" Width="32" Height="32" Size="32" />
<Label Content="Öffnen" VerticalContentAlignment="Center" /> <Label FontSize="20" Content="Addressverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid RowDefinitions="2*,*,*">
<Grid ColumnDefinitions="*,*" Grid.Row="0">
<!-- Kunden -->
<Grid Margin="30,30,10,30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<LucideIcon Kind="CircleUser" Width="16" Height="16" Size="16" />
<Label Content="Kunden" VerticalContentAlignment="Center" />
</StackPanel>
<ListBox Grid.Row="1"
Margin="0,5,0,0"
x:Name="LstCustomers"
Background="AliceBlue" SelectionChanged="LstCustomers_OnSelectionChanged"
SelectionMode="Single" />
</Grid>
<!-- Address-Sets -->
<Grid Margin="10,30,30,30" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<LucideIcon Kind="NotebookTabs" Width="16" Height="16" Size="16" />
<Label Content="Address-Sets" VerticalContentAlignment="Center" />
</StackPanel>
<ListBox Grid.Row="1"
x:Name="LstCustomerAdressSets"
SelectionChanged="LstCustomerAdressSets_OnSelectionChanged"
Background="AliceBlue"
Margin="0,5,0,5"
SelectionMode="Multiple,Toggle" />
<Button Grid.Row="2" HorizontalAlignment="Stretch" x:Name="BtnCustomerAddressSetImport"
Click="BtnCustomerAddressSetImport_OnClick">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Plus" Width="16" Height="16" Size="16" />
<Label Content="Importieren" VerticalContentAlignment="Center" />
</StackPanel> </StackPanel>
</Button> </Button>
</StackPanel>
</Grid> </Grid>
<Grid Grid.ColumnDefinitions="*,*" Grid.Row="2"> </Grid>
<!-- <Grid Grid.Row="0"> -->
<!-- <StackPanel Orientation="Horizontal" VerticalAlignment="Center" -->
<!-- HorizontalAlignment="Center"> -->
<!-- <TextBox x:Name="TbFilename" Watermark="Dateipfad" Width="400" -->
<!-- VerticalContentAlignment="Center" /> -->
<!-- <Button x:Name="BtnChooseFile" Margin="10,0,0,0" Click="BtnChooseFile_OnClick"> -->
<!-- <StackPanel Orientation="Horizontal"> -->
<!-- <LucideIcon Kind="FolderOpen" Width="16" Height="16" Size="16" /> -->
<!-- <Label Content="Öffnen" VerticalContentAlignment="Center" /> -->
<!-- </StackPanel> -->
<!-- </Button> -->
<!-- </StackPanel> -->
<!-- </Grid> -->
<Grid Grid.ColumnDefinitions="*,*" Grid.Row="1">
<StackPanel Grid.Column="0" Width="250" Orientation="Vertical" HorizontalAlignment="Right" <StackPanel Grid.Column="0" Width="250" Orientation="Vertical" HorizontalAlignment="Right"
Margin="0,0,5,0"> Margin="0,0,5,0">
<Button HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Margin="0,0,0,10" <Button HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
Margin="0,0,0,10" IsEnabled="False"
x:Name="BtnCheck" Click="BtnCheck_OnClick"> x:Name="BtnCheck" Click="BtnCheck_OnClick">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="SpellCheck" Width="36" Height="36" /> <LucideIcon Kind="SpellCheck" Width="36" Height="36" />
<Label Content="Prüfen" VerticalContentAlignment="Center" FontSize="15" FontWeight="Bold" /> <Label Content="Prüfen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button HorizontalAlignment="Stretch" IsEnabled="True" HorizontalContentAlignment="Center" <Button HorizontalAlignment="Stretch" IsEnabled="False"
HorizontalContentAlignment="Center"
Click="BtnCombine_OnClick" x:Name="BtnCombine" Click="BtnCombine_OnClick" x:Name="BtnCombine"
Margin="0,0,0,10"> Margin="0,0,0,10">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
@@ -49,7 +113,8 @@
FontWeight="Bold" /> FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False" HorizontalContentAlignment="Center" <Button HorizontalAlignment="Stretch" IsEnabled="False"
HorizontalContentAlignment="Center" x:Name="BtnRepair"
Margin="0,0,0,10"> Margin="0,0,0,10">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="Hammer" Width="36" Height="36" /> <LucideIcon Kind="Hammer" Width="36" Height="36" />
@@ -60,23 +125,344 @@
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Width="250" Orientation="Vertical" HorizontalAlignment="Left" <StackPanel Grid.Column="1" Width="250" Orientation="Vertical" HorizontalAlignment="Left"
Margin="5,0,0,0"> Margin="5,0,0,0">
<Button HorizontalAlignment="Stretch" IsEnabled="False" HorizontalContentAlignment="Center" <Button HorizontalAlignment="Stretch" IsEnabled="False"
HorizontalContentAlignment="Center" x:Name="BtnShorten"
Margin="0,0,0,10"> Margin="0,0,0,10">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="ListX" Width="36" Height="36" /> <LucideIcon Kind="ListX" Width="36" Height="36" />
<Label Content="Kürzen" VerticalContentAlignment="Center" FontSize="15" FontWeight="Bold" /> <Label Content="Kürzen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False" HorizontalContentAlignment="Center" <Button HorizontalAlignment="Stretch" IsEnabled="False"
Click="BtnGenerateLabels_OnClick"
HorizontalContentAlignment="Center" x:Name="BtnGenerateLabels"
Margin="0,0,0,10"> Margin="0,0,0,10">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="Tags" Width="36" Height="36" /> <LucideIcon Kind="Tags" Width="36" Height="36" />
<Label Content="Etiketten generieren" VerticalContentAlignment="Center" FontSize="15" <Label Content="Etiketten generieren" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" /> FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid Grid.Row="2" Margin="20" IsVisible="True" x:Name="GrdCalcMan">
<Image Source="assets/calc_man.png" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid> </Grid>
<Grid Grid.Row="2" Margin="20" IsVisible="False" x:Name="GrdCombineTypes">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10"
Margin="5,0,0,0">
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineUnion"
Click="BtnCombineUnion_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresUnite" Width="36" Height="36" />
<Label Content="Vereinigung" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9"
Content="Fügt Elemente beider Mengen in eine Menge zusammen" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineIntersect"
Click="BtnCombineIntersection_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresIntersect" Width="36" Height="36" />
<Label Content="Schnittmenge" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<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"
Click="BtnCombineDifference_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresSubtract" Width="36" Height="36" />
<Label Content="Differenz" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9"
Content="Elemente der ersten Menge ohne Elemente der zweiten Menge" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineSymmetric"
Click="BtnCombineSymmetricDifference_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresExclude" Width="36" Height="36" />
<Label Content="Symmetrische Differenz"
VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind" />
</StackPanel>
</Button>
</StackPanel>
<CheckBox HorizontalAlignment="Center" x:Name="CbMergeExportUnmerged" IsChecked="False">Speichere Unverarbeitete in neuem Verteiler</CheckBox>
</StackPanel>
</Grid>
</Grid>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Users" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Personalverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="BadgeEuro" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Finanzverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="ReceiptEuro" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Preisverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="LibraryBig" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Wiki" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="True">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Settings" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Einstellungen" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<TabControl>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="FileCog" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Global" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<StackPanel Orientation="Vertical">
<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>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Contact" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Kunden" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid ColumnDefinitions="300,*">
<Grid RowDefinitions="*,60">
<ListBox x:Name="LstSettingsCustomers"
SelectionChanged="LstSettingsCustomers_OnSelectionChanged" />
<Button Grid.Row="1" HorizontalAlignment="Stretch" x:Name="BtnSettingsAddCustomer"
Click="BtnSettingsAddCustomer_OnClick"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Plus" Width="32" Height="32" />
<Label Content="Hinzufügen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
</Grid>
<StackPanel Margin="10,0,0,0" Grid.Column="1" Spacing="10" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="TableProperties" Width="24" Height="24" />
<Label Content="Eigenschaften" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Grid ColumnDefinitions="150,*">
<Label Content="Name" />
<TextBox Grid.Column="1" Watermark="Kundenbezeichnung"
TextChanged="TbSettingsCustomerName_OnTextChanged"
HorizontalAlignment="Stretch"
x:Name="TbSettingsCustomerName" />
</Grid>
<Grid ColumnDefinitions="150,*">
<Label Content="Beschreibung" />
<TextBox Grid.Column="1" Watermark="Kundenbeschreibung"
HorizontalAlignment="Stretch"
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=","
HorizontalAlignment="Stretch"
TextChanged="TbSettingsCustomerCsvSeparator_OnTextChanged"
x:Name="TbSettingsCustomerCsvSeparator" />
</Grid>
<Grid ColumnDefinitions="150,*">
<Label Content="Address-Patch-Info" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Stretch"
x:Name="TbSettingsCustomerPatchInfo" />
</Grid>
<Button HorizontalAlignment="Stretch"
x:Name="BtnSettingsImportCustomerAddressPatch"
Click="BtnSettingsImportCustomerAddressPatch_OnClick"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Route" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Address-Patch importieren..."
VerticalContentAlignment="Center" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" x:Name="BtnSettingsSaveCustomerData"
Click="BtnSettingsSaveCustomerData_OnClick"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Save" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Speichern"
VerticalContentAlignment="Center" />
</StackPanel>
</Button>
<Button Background="#99963434" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Trash" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Löschen" VerticalContentAlignment="Center" />
</StackPanel>
</Button>
</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="*" />
</Grid.RowDefinitions>
<Grid 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="1">
<TextBlock Text="Übersetzung:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" x:Name="TbSettingsCountryTranslation"
TextChanged="TbSettingsCountryTranslation_OnTextChanged" />
</Grid>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="LbSettingsAlternatives" Grid.Row="0" 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>
</Grid>
</Border> </Border>
</Window> </Window>

View File

@@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@@ -11,48 +11,69 @@ namespace Logof_Client;
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
public static MainWindow _instance; public static MainWindow _instance;
private Country _selectedCountry;
private bool _suppressCountryRefresh = false;
public Uri filePath; public Uri filePath;
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
//Hide();
var s = new StartupWindow();
//s.Show();
_instance = this; _instance = this;
// try WindowState = WindowState.Maximized;
// { Global.Load();
// var temppath = "kaspersons.csv"; Settings.Load();
// var result = DataImport.ImportKasAddressList(new Uri(temppath));
// if (result.Item1) RefreshCountryView();
// {
// var check_result = new AddressCheck().Perform(result.Item2); //Thread.Sleep(3000);
// foreach (var item in check_result.Result) //Show();
// {
// Console.WriteLine();
// Console.Write(item.Item1 + " ");
// foreach (var error in item.Item2) Console.Write(error + ", ");
// }
// }
// }
// catch
// {
// }
} }
private async void StartAddressCheck(Uri path) private async void StartAddressCheck(int addresSetID)
{
//var addresses = DataImport.ImportKasAddressList(path); // Ihr Code hier
var progressWindow = new ProgressWindow();
progressWindow.Show(_instance);
var processor = new AddressCheck(progressWindow);
var result = await processor.Perform(addresSetID);
// foreach (var item in result)
// {
// }
progressWindow.Close();
new ResultWindow(result, addresSetID).Show();
//await MessageBox.Show(_instance, $"{result.Count} Einträge fehlerhaft.", "Fertig");
}
private async void StartAddressRepair(Uri path)
{ {
var addresses = DataImport.ImportKasAddressList(path); // Ihr Code hier var addresses = DataImport.ImportKasAddressList(path); // Ihr Code hier
var progressWindow = new ProgressWindow(); var progressWindow = new ProgressWindow();
// Fenster anzeigen (nicht blockierend)
progressWindow.Show(_instance); progressWindow.Show(_instance);
var processor = new AddressCheck(progressWindow); var processor = new AddressRepair(progressWindow);
var result = await processor.Perform(addresses.Item2); //var result = await processor.Perform(addresses.Item2, errors);
// Nach Verarbeitung schließen
progressWindow.Close(); progressWindow.Close();
// Ergebnis anzeigen, z.B. als Dialog
new ResultWindow(result, addresses.Item2).Show(); //new ResultWindow(result, addresses.Item2).Show();
//await MessageBox.Show(_instance, $"{result.Count} Einträge fehlerhaft.", "Fertig"); //await MessageBox.Show(_instance, $"{result.Count} Einträge fehlerhaft.", "Fertig");
} }
@@ -66,19 +87,25 @@ public partial class MainWindow : Window
throw new NotImplementedException(); throw new NotImplementedException();
} }
private void MnuSettings_OnClick(object? sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
private void MnuHelp_OnClick(object? sender, RoutedEventArgs e) private void MnuHelp_OnClick(object? sender, RoutedEventArgs e)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
private void MnuGithub_OnClick(object? sender, RoutedEventArgs e) private void MnuGit_OnClick(object? sender, RoutedEventArgs e)
{ {
throw new NotImplementedException(); try
{
Process.Start(new ProcessStartInfo
{
FileName = "https://git.mypapercloud.de/fierke/logofclient",
UseShellExecute = true // Wichtig für Plattformübergreifendes Öffnen
});
}
catch (Exception ex)
{
Console.WriteLine($"Fehler beim Öffnen des Links: {ex.Message}");
}
} }
private async void BtnChooseFile_OnClick(object? sender, RoutedEventArgs e) private async void BtnChooseFile_OnClick(object? sender, RoutedEventArgs e)
@@ -99,19 +126,25 @@ public partial class MainWindow : Window
}); });
if (file == null) return; if (file == null) return;
TbFilename.Text = file[0].Path.ToString(); //TbFilename.Text = file[0].Path.ToString();
filePath = file[0].Path; filePath = file[0].Path;
} }
private void BtnCheck_OnClick(object? sender, RoutedEventArgs e) private void BtnCheck_OnClick(object? sender, RoutedEventArgs e)
{ {
if (filePath == null) MakeCalcManVisible();
if (LstCustomerAdressSets.SelectedIndex == -1)
{ {
MessageBox.Show(null, "Bitte zunächst eine Datei auswählen", "Datei fehlt"); MessageBox.Show(null, "Bitte zunächst ein Adress-Set auswählen", "Kein Adress-Set ausgewählt");
return; return;
} }
StartAddressCheck(filePath); //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); // var result = DataImport.ImportKasAddressList(filePath);
// if (result.Item1) // if (result.Item1)
// { // {
@@ -127,17 +160,19 @@ public partial class MainWindow : Window
private void BtnCombine_OnClick(object? sender, RoutedEventArgs e) private void BtnCombine_OnClick(object? sender, RoutedEventArgs e)
{ {
if (filePath == null) GrdCalcMan.IsVisible = false;
{ GrdCombineTypes.IsVisible = true;
MessageBox.Show(null, "Bitte zunächst eine Datei auswählen", "Datei fehlt");
return;
} }
StartCombine(filePath); private void MakeCalcManVisible()
{
GrdCalcMan.IsVisible = true;
GrdCombineTypes.IsVisible = false;
} }
private async void StartCombine(Uri path) private async void StartCombine(Uri path)
{ {
MakeCalcManVisible();
var addresses = DataImport.ImportKasAddressList(path); var addresses = DataImport.ImportKasAddressList(path);
var progressWindow = new ProgressWindow(); var progressWindow = new ProgressWindow();
var address_list = new List<KasAddressList> { addresses.Item2 }; var address_list = new List<KasAddressList> { addresses.Item2 };
@@ -166,14 +201,14 @@ public partial class MainWindow : Window
progressWindow.Show(_instance); progressWindow.Show(_instance);
var processor = new CombineAddresses(progressWindow); var processor = new CombineAddresses(progressWindow);
var result = await processor.Perform(address_list); //var result = await processor.Perform(address_list);
progressWindow.Close(); progressWindow.Close();
File.WriteAllText(OpenSettingsSaveAsDialog().Result, //File.WriteAllText(OpenSettingsSaveAsDialog().Result,
new CsvBuilder( //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", //"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()); //result).BuildKas());
} }
private async Task<string> OpenSettingsSaveAsDialog() private async Task<string> OpenSettingsSaveAsDialog()
@@ -204,9 +239,484 @@ public partial class MainWindow : Window
} }
catch (Exception ex) catch (Exception ex)
{ {
//Logger.Log("Saving file not successfull: " + ex.Message, Logger.LogType.Error); //Logger.Log("Saving file not successful: " + ex.Message, Logger.LogType.Error);
await MessageBox.Show(this, ex.Message, "Fehler beim Speichern der Datei"); await MessageBox.Show(this, ex.Message, "Fehler beim Speichern der Datei");
return null; return null;
} }
} }
private async void StartCombine(List<KasAddressList> address_lists, int owner_id, string type)
{
var progressWindow = new ProgressWindow();
progressWindow.Show(_instance);
var processor = new CombineAddresses(progressWindow);
var result = await processor.Perform(address_lists, type, CbMergeExportUnmerged.IsChecked);
if (result.Item1 != null)
result.Item1.owner_id = owner_id;
if (result.Item2 != null)
result.Item2.owner_id = owner_id;
if (result.Item1 != null)
Settings._instance.addressSets.addresses.Add(result.Item1);
if (result.Item2 != null)
Settings._instance.addressSets.addresses.Add(result.Item2);
Settings.Save();
progressWindow.Close();
//new ResultWindow(result, addresSetID).Show();
}
private void BtnSettingsAddCustomer_OnClick(object? sender, RoutedEventArgs e)
{
Settings._instance.customers.customers.Add(new Customer());
TbSettingsCustomerDescription.Text = "";
TbSettingsCustomerName.Text = "";
TbSettingsCustomerPatchInfo.Text = "";
Settings.Save();
RefreshCustomerItems();
try
{
LstSettingsCustomers.SelectedIndex = LstSettingsCustomers.Items.Count - 1;
}
catch
{
}
}
private void TbSettingsCustomerName_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.name = TbSettingsCustomerName.Text;
//Settings.Save();
//RefreshCustomerItems();
}
private void LstSettingsCustomers_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (LstSettingsCustomers.SelectedIndex < 0) return;
Settings._instance.customers.current =
Customer.GetIDByCustomerListItem(LstSettingsCustomers.SelectedItems[0].ToString());
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID == Settings._instance.customers.current)
{
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();
else
TbSettingsCustomerPatchInfo.Text = "";
}
}
public void RefreshCustomerItems(int index = 0)
{
if (LstCustomers.Items.Count > 0)
LstCustomers.SelectedIndex = -1;
else
LstCustomers.SelectedItem = null;
LstCustomers.Items.Clear();
if (LstSettingsCustomers.Items.Count > 0)
LstSettingsCustomers.SelectedIndex = -1;
else
LstSettingsCustomers.SelectedItem = null;
LstSettingsCustomers.Items.Clear();
foreach (var customer in Settings._instance.customers.customers)
{
LstCustomers.Items.Add(customer.ID + " - " + customer.name);
LstSettingsCustomers.Items.Add(customer.ID + " - " + customer.name);
}
try
{
LstSettingsCustomers.SelectedIndex = index;
}
catch
{
}
}
private void TbSettingsCustomerDescription_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.description = TbSettingsCustomerDescription.Text;
//Settings.Save();
}
private void BtnSettingsSaveCustomerData_OnClick(object? sender, RoutedEventArgs e)
{
Settings.Save();
RefreshCustomerItems(LstSettingsCustomers.SelectedIndex);
}
private void LstCustomers_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
MakeCalcManVisible();
if (LstCustomers.SelectedItems == null || LstCustomers.SelectedIndex == -1) return;
var customer_id = int.Parse(LstCustomers.SelectedItem.ToString().Split(" - ")[0]);
RefreshAddressSetListItems(customer_id);
}
private async void BtnCustomerAddressSetImport_OnClick(object? sender, RoutedEventArgs e)
{
MakeCalcManVisible();
var opts = new FilePickerOpenOptions();
opts.Title = "Address-Set importieren...";
opts.AllowMultiple = false;
var type = new FilePickerFileType("CSV-Dateien (*.csv)");
type.Patterns = new[] { "*.csv" };
opts.FileTypeFilter = new[] { type };
var paths = await StorageProvider.OpenFilePickerAsync(opts);
if (paths?.Count <= 0) return;
if (LstCustomers.SelectedIndex < 0) return;
var selected_path = paths[0].Path;
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID == Customer.GetIDByCustomerListItem(LstCustomers.SelectedItems[0].ToString()))
{
if (customer.patch == null)
{
var got = DataImport.ImportKasAddressList(selected_path, null, customer.separator);
if (!got.Item1)
{
Console.WriteLine("Error while importing. Please try another file.");
}
else
{
got.Item2.SetOwner(customer.ID);
Settings._instance.addressSets.addresses.Add(got.Item2);
}
//var customer_id = int.Parse(LstCustomers.SelectedItem.ToString().Split(" - ")[0]);
RefreshAddressSetListItems(customer.ID);
}
else
{
var got = DataImport.ImportKasAddressList(selected_path, customer.patch, customer.separator);
if (!got.Item1)
{
Console.WriteLine("Error while importing. Please try another file.");
}
else
{
got.Item2.SetOwner(customer.ID);
Settings._instance.addressSets.addresses.Add(got.Item2);
}
//var customer_id = int.Parse(LstCustomers.SelectedItem.ToString().Split(" - ")[0]);
RefreshAddressSetListItems(customer.ID);
}
}
Settings.Save();
}
public void RefreshAddressSetListItems(int customer_id)
{
BtnCheck.IsEnabled = false;
BtnCombine.IsEnabled = false;
BtnGenerateLabels.IsEnabled = false;
BtnRepair.IsEnabled = false;
BtnShorten.IsEnabled = false;
if (LstCustomers.SelectedIndex < 0) return;
LstCustomerAdressSets.SelectedItems = null;
LstCustomerAdressSets.Items.Clear();
foreach (var k in Settings._instance.addressSets.addresses)
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID == k.owner_id && customer.ID == customer_id)
LstCustomerAdressSets.Items.Add(k.ID + " - " + k.Name + "(" + k.KasPersons.Count + " Einträge)");
}
private void LstCustomerAdressSets_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
MakeCalcManVisible();
if (LstCustomerAdressSets.SelectedItems == null || LstCustomerAdressSets.SelectedIndex == -1)
{
BtnCheck.IsEnabled = false;
BtnCombine.IsEnabled = false;
BtnGenerateLabels.IsEnabled = false;
BtnRepair.IsEnabled = false;
BtnShorten.IsEnabled = false;
}
else
{
BtnCheck.IsEnabled = true;
BtnCombine.IsEnabled = true;
BtnGenerateLabels.IsEnabled = true;
// BtnRepair.IsEnabled = true;
// BtnShorten.IsEnabled = true;
}
}
private async void BtnSettingsImportCustomerAddressPatch_OnClick(object? sender, RoutedEventArgs e)
{
if (LstSettingsCustomers.SelectedIndex == null || LstSettingsCustomers.SelectedIndex == -1) return;
var opts = new FilePickerOpenOptions();
opts.Title = "Address-Patch für " + LstSettingsCustomers.SelectedItems[0] + "importieren...";
opts.AllowMultiple = false;
var type = new FilePickerFileType("ADPAC-Dateien (*.adpac)");
type.Patterns = new[] { "*.adpac" };
opts.FileTypeFilter = new[] { type };
var paths = await StorageProvider.OpenFilePickerAsync(opts);
if (paths?.Count <= 0) return;
//if (LstSettingsCustomers.SelectedIndex < 0) return;
var selected_path = paths[0].Path;
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID == Customer.GetIDByCustomerListItem(LstSettingsCustomers.SelectedItems[0].ToString()))
customer.patch = AddressPatch.Import(selected_path);
Settings.Save();
}
private void TbSettingsCustomerCsvSeparator_OnTextChanged(object? sender, TextChangedEventArgs e)
{
if (LstSettingsCustomers.SelectedIndex == null || LstSettingsCustomers.SelectedIndex == -1) return;
var sep = !string.IsNullOrEmpty(TbSettingsCustomerCsvSeparator.Text)
? TbSettingsCustomerCsvSeparator.Text
: ",";
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID == Settings._instance.customers.current)
try
{
customer.separator = Convert.ToChar(sep);
}
catch (FormatException ex)
{
MessageBox.Show(this, "Error while converting: " + ex.Message, "Could not parse");
}
catch (Exception ex)
{
MessageBox.Show(this, "Unknown Error: " + ex.Message, "Error");
}
}
private void BtnCombineDifference_OnClick(object? sender, RoutedEventArgs e)
{
var list = new List<KasAddressList>();
foreach (var item in LstCustomerAdressSets.SelectedItems)
list.Add(Settings._instance.addressSets.GetAddressSetByID(
Convert.ToInt32(item.ToString().Split(" - ")[0])));
StartCombine(list, Convert.ToInt32(LstCustomers.SelectedItem.ToString().Split(" - ")[0]), "difference");
}
private void BtnCombineUnion_OnClick(object? sender, RoutedEventArgs e)
{
var list = new List<KasAddressList>();
foreach (var item in LstCustomerAdressSets.SelectedItems)
list.Add(Settings._instance.addressSets.GetAddressSetByID(
Convert.ToInt32(item.ToString().Split(" - ")[0])));
StartCombine(list, Convert.ToInt32(LstCustomers.SelectedItem.ToString().Split(" - ")[0]), "union");
}
private void BtnCombineIntersection_OnClick(object? sender, RoutedEventArgs e)
{
var list = new List<KasAddressList>();
foreach (var item in LstCustomerAdressSets.SelectedItems)
list.Add(Settings._instance.addressSets.GetAddressSetByID(
Convert.ToInt32(item.ToString().Split(" - ")[0])));
StartCombine(list, Convert.ToInt32(LstCustomers.SelectedItem.ToString().Split(" - ")[0]), "intersection");
}
private void BtnCombineSymmetricDifference_OnClick(object? sender, RoutedEventArgs e)
{
var list = new List<KasAddressList>();
foreach (var item in LstCustomerAdressSets.SelectedItems)
list.Add(Settings._instance.addressSets.GetAddressSetByID(
Convert.ToInt32(item.ToString().Split(" - ")[0])));
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();
}
} }

View File

@@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" SizeToContent="WidthAndHeight" mc:Ignorable="d" SizeToContent="WidthAndHeight"
x:Class="Logof_Client.MessageBox" x:Class="Logof_Client.MessageBox" Icon="assets/icon.ico"
Title="MessageBox"> Title="MessageBox">
<StackPanel> <StackPanel>
<TextBlock Name="Text" Margin="10" TextWrapping="Wrap" /> <TextBlock Name="Text" Margin="10" TextWrapping="Wrap" />

View File

@@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" Width="800" MinWidth="800" MaxWidth="800" d:DesignHeight="150" mc:Ignorable="d" d:DesignWidth="800" Width="800" MinWidth="800" MaxWidth="800" d:DesignHeight="150"
Height="150" MinHeight="150" MaxHeight="150" Height="150" MinHeight="150" MaxHeight="150" Icon="assets/icon.ico"
x:Class="Logof_Client.ProgressWindow" Title="Verarbeitung läuft..."> x:Class="Logof_Client.ProgressWindow" Title="Verarbeitung läuft...">
<Grid> <Grid>
<!-- <ScrollViewer x:Name="ScvLog"> --> <!-- <ScrollViewer x:Name="ScvLog"> -->

View File

@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" Icon="assets/icon.ico"
x:Class="Logof_Client.ResultWindow" x:Class="Logof_Client.ResultWindow"
Title="Ergebnis"> Title="Ergebnis">
<Grid Grid.ColumnDefinitions="200,*"> <Grid Grid.ColumnDefinitions="200,*">
@@ -15,10 +15,12 @@
<!-- <Button x:Name="BtnUpdateFilter" Content="Aktualisieren" HorizontalAlignment="Stretch" --> <!-- <Button x:Name="BtnUpdateFilter" Content="Aktualisieren" HorizontalAlignment="Stretch" -->
<!-- VerticalAlignment="Bottom" Margin="10,0,10,10" Click="BtnUpdateFilter_OnClick" /> --> <!-- VerticalAlignment="Bottom" Margin="10,0,10,10" Click="BtnUpdateFilter_OnClick" /> -->
<Button Content="Ausgewählte Anzeigen" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" <Button Content="Ausgewählte Anzeigen" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
x:Name="BtnShwoSelected" Click="BtnShwoSelected_OnClick" x:Name="BtnShowSelected" Click="BtnShowSelected_OnClick"
Margin="10,10,10,10" /> Margin="10,10,10,10" />
</Grid> </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> </Grid>
</Window> </Window>

View File

@@ -1,35 +1,73 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media;
namespace Logof_Client; namespace Logof_Client;
public partial class ResultWindow : Window public partial class ResultWindow : Window
{ {
public List<CheckBox> errortypecheckboxes = new(); public List<CheckBox> errortypecheckboxes = new();
public KasAddressList ur_addresses = new(); public KasAddressList ur_addresses = new("Ergebnis_" + DateTime.Now.ToString("ddMMyy_HHmmss"));
public List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> ur_result; public List<KasPerson> ur_result;
public List<CheckBox> warningtypecheckboxes = new(); public List<CheckBox> warningtypecheckboxes = new();
public ResultWindow(List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> result, public ResultWindow(List<KasPerson> result,
KasAddressList ur_addresses) int addressSetID)
{ {
InitializeComponent(); InitializeComponent();
ur_result = result; ur_result = result;
this.ur_addresses = ur_addresses; ur_addresses = ur_addresses;
Load(result); Load(result);
//ViewSingle(200552426); //ViewSingle(200552426);
} }
private void GenerateView(List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> result) private void GenerateView(List<KasPerson> result)
{ {
var errors = new List<KasPersonError>(); // Filter to only show persons with errors
foreach (var single_result in result) errors.Add(new KasPersonError(single_result)); var result_with_errors = result.Where(p => p.PersonError != null).ToList();
LblResultCount.Content = $"{errors.Count}/{ur_result.Count} Ergebnisse"; LblResultCount.Content = $"{result_with_errors.Count}/{ur_result.Count} Ergebnisse";
DgResult.ItemsSource = errors;
StkResults.Children.Clear();
foreach (var person in result_with_errors) StkResults.Children.Add(CreatePersonGrid(person));
}
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"))
};
// Refsid
grid.Children.Add(new TextBlock
{
Text = "refsid: ",
FontWeight = FontWeight.Bold, Margin = new Thickness(5)
});
grid.Children.Add(new TextBlock { Text = person.refsid.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 refsid) private void ViewSingle(int refsid)
@@ -62,18 +100,20 @@ public partial class ResultWindow : Window
} }
} }
private void Load(List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> result) private void Load(List<KasPerson> result)
{ {
var knownErrors = new List<AddressCheck.ErrorTypes>(); var knownErrors = new List<AddressCheck.ErrorTypes>();
var knownWarnings = new List<AddressCheck.WarningTypes>(); var knownWarnings = new List<AddressCheck.WarningTypes>();
foreach (var single_result in result) foreach (var person in result)
{ {
foreach (var errtyp in single_result.Item2) if (person.PersonError == null) continue;
foreach (var errtyp in person.PersonError.errors)
if (!knownErrors.Contains(errtyp)) if (!knownErrors.Contains(errtyp))
knownErrors.Add(errtyp); knownErrors.Add(errtyp);
foreach (var wartyp in single_result.Item3) foreach (var wartyp in person.PersonError.warnings)
if (!knownWarnings.Contains(wartyp)) if (!knownWarnings.Contains(wartyp))
knownWarnings.Add(wartyp); knownWarnings.Add(wartyp);
} }
@@ -108,49 +148,75 @@ public partial class ResultWindow : Window
private void UpdateFilter() private void UpdateFilter()
{ {
var temp_result = new List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)>(); 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) foreach (var cb in errortypecheckboxes)
if (cb.IsChecked == true) 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) foreach (var cb in warningtypecheckboxes)
if (cb.IsChecked == true) if (cb.IsChecked == true)
checked_types_war.Add(
(AddressCheck.WarningTypes)Enum.Parse(typeof(AddressCheck.WarningTypes), cb.Content.ToString()));
foreach (var sres in ur_result)
{ {
foreach (var err in sres.Item2) var s = cb.Content?.ToString()?.Trim();
if (checked_types.Contains(err) && !temp_result.Contains(sres)) if (!string.IsNullOrEmpty(s) &&
temp_result.Add(sres); Enum.TryParse<AddressCheck.WarningTypes>(s, true, out var wt))
checkedWarnings.Add(wt);
}
foreach (var war in sres.Item3) // If no checkboxes are selected, show all persons with errors (default behavior)
if (checked_types_war.Contains(war) && !temp_result.Contains(sres)) if (checkedErrors.Count == 0 && checkedWarnings.Count == 0)
temp_result.Add(sres); temp_result = ur_result.Where(p => p.PersonError != null).ToList();
else
foreach (var person in ur_result)
{
if (person.PersonError == null) continue;
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);
}
LblResultCount.Content = $"{temp_result.Count}/{ur_result.Count} Ergebnisse";
StkResults.Children.Clear();
foreach (var person in temp_result) StkResults.Children.Add(CreatePersonGrid(person));
} }
var errors = new List<KasPersonError>(); private void BtnShowSelected_OnClick(object? sender, RoutedEventArgs e)
foreach (var single_result in temp_result) errors.Add(new KasPersonError(single_result));
LblResultCount.Content = $"{errors.Count}/{ur_result.Count} Ergebnisse";
DgResult.ItemsSource = errors;
}
private void BtnShwoSelected_OnClick(object? sender, RoutedEventArgs e)
{ {
foreach (var selected in DgResult.SelectedItems) // foreach (var selected in DgResult.SelectedItems)
try // try
{ // {
var _asKas = (KasPersonError)selected; // var _asKas = (KasPerson)selected;
ViewSingle(_asKas.refsid); // ViewSingle(_asKas.refsid);
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
Console.WriteLine(ex.Message); // Console.WriteLine(ex.Message);
} // }
} }
} }

184
Settings.cs Normal file
View File

@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace Logof_Client;
public class Settings
{
public static Settings _instance = new();
public AddressSets addressSets = new();
public Customers customers = new();
public string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient", "config.json");
public Settings()
{
_instance = this;
}
public static void Save()
{
if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient")))
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient"));
if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
var json = JsonConvert.SerializeObject(_instance);
File.WriteAllText(_instance.settingsPath, json);
}
public static void Load()
{
if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
try
{
var contents = File.ReadAllText(_instance.settingsPath);
_instance = JsonConvert.DeserializeObject<Settings>(contents);
MainWindow._instance.RefreshCustomerItems();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
Console.WriteLine("Error while reading settings. Generating new...");
_instance = new Settings();
}
}
}
public class Global
{
public static Global _instance;
public Global()
{
_instance = this;
}
public string config_path { get; set; } = "";
public List<Country> countries { get; set; } = new();
public static void Save()
{
if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient")))
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient"));
var json = JsonConvert.SerializeObject(_instance, Formatting.Indented);
File.WriteAllText(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
"global.config"), json);
}
public static void Load()
{
// if (!File.Exists(Path.Combine(
// Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
// "global.config")))
// File.Create(Path.Combine(
// Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
// "global.config"));
try
{
var contents = File.ReadAllText(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
"global.config"));
_instance = JsonConvert.DeserializeObject<Global>(contents);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
Console.WriteLine("Error while reading global settings. Generating new...");
_instance = new Global();
Save();
}
}
}
public class Customers
{
public List<Customer> customers = new();
public int current { get; set; } = 0;
}
public class Customer
{
public Customer()
{
var highestID = 0;
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID > highestID)
highestID = customer.ID;
ID = highestID + 1;
}
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];
return int.Parse(id);
}
}
public class AddressSets
{
public List<KasAddressList> addresses = new();
public KasAddressList GetAddressSetByID(int ID)
{
foreach (var i in addresses)
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;
}
}

12
StartupWindow.axaml Normal file
View File

@@ -0,0 +1,12 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" Width="400" MinWidth="400" MaxWidth="400" d:DesignHeight="400"
Height="400" MinHeight="400" MaxHeight="400" Icon="assets/icon.ico" SystemDecorations="None"
x:Class="Logof_Client.StartupWindow" Title="Verarbeitung läuft...">
<Grid>
<Label Content="Hello, World!" />
<ProgressBar x:Name="PbLoading" Minimum="0" Maximum="100" />
</Grid>
</Window>

13
StartupWindow.axaml.cs Normal file
View File

@@ -0,0 +1,13 @@
using Avalonia.Controls;
namespace Logof_Client;
public partial class StartupWindow : Window
{
public StartupWindow()
{
InitializeComponent();
}
}

264
Tasks/AddressCheck.cs Normal file
View File

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

275
Tasks/AddressCreation.cs Normal file
View File

@@ -0,0 +1,275 @@
using System;
using System.Linq;
namespace Logof_Client;
public static class AddressCreator
{
/// <summary>
/// Creates max-seven-lines-long Markdown address-string. Analyzes the KasPerson-Instance to find the best result.
/// </summary>
/// <param name="refsid">KasPerson-ID</param>
/// <returns>A Markdown string with the address that is maximum seven lines long</returns>
public static string? CreateFinalMarkdownString2(int refsid)
{
// Maximum seven lines of information
// find the address
KasPerson? address = 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)
{
address = temp;
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
//
// + Wenn Land nicht Deutschland Abbildung von Land (Fettgedruckt)
//
// Alternative A (wenn PPLZ + Ort ausgefüllt):
// + Abbildung PPLZ + Ort - Ort ggf. Abschneiden, wenn länger als eine Zeile … das längste was ich finden konnte: „Giugliano in Campania-Lago Patra“ (passt exakt auf eine Zeile)
// + Abbildung Postfach wenn leer, dann Straße
// + Abbildung Wenn Anredezusatz nicht leer, Anredezusatz, sonst Anrede + Titel + Vorname + Adel + Name + Namenszusatz in Klammern
// + Abbildung Name 1 + Name 2 + Name 3 + Name 4 + Name 5 + Abteilung (insgesamt max. 7 Zeilen)
//
// ansonsten
//
// + Abbildung PLZ + Ort - Ort ggf. Abschneiden, wenn länger als eine Zeile … das längste was ich finden konnte: „Giugliano in Campania-Lago Patra“ (passt exakt auf eine Zeile)
// + Abbildung Straße wenn Straße leer, dann Postfach
// + Abbildung Wenn Anredezusatz nicht leer, Anredezusatz, sonst Anrede + Titel + Vorname + Adel + Name + Namenszusatz in Klammern
// + Abbildung Name 1 + Name 2 + Name 3 + Name 4 + Name 5 + Abteilung (insgesamt max. 7 Zeilen)
//
// Auswurf Fehler-Datei
//
// + wenn keine PLZ und/oder kein Ort -> Fehler
// + wenn kein Name 1-5 und/oder Name -> Fehler
//
// Auswurf CSV-Datei (Komma/TAB, UTF-8, ISO…)
//
// Auswurf PDF mit normalen Absender
//
// Auswurf PDF mit PvSt.
/// <summary>
/// Creates max-seven-lines-long Markdown address-string. Analyzes the KasPerson-Instance to find the best result.
/// </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)
{
// Maximum seven lines of information
// find the address
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)
{
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")
{
string_address = "\n**" + address.land.Trim() + "**"; // Needs to be bold
address_line_count++;
}
// Alternative A: pplz valid and city existing
if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.pplz, address.land))
{
string_address = address.pplz + " " + address.ort + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.postfach))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.strasse.Trim()))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + string_address;
address_line_count++;
}
// REIHENFOLGE
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Alternative B: plz valid and city existing
else if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.plz, address.land))
{
string_address = address.plz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.strasse))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.postfach.Trim()))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + string_address;
address_line_count++;
}
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Error-Handling?
return string_address;
return "Hier könnte eine\nAdresse stehen";
}
public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel,
string name, string namezus)
{
if (!string.IsNullOrWhiteSpace(anredezus))
return string.Join(" ",
new[] { anredezus, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})")
+ "\n";
// else
return string.Join(" ",
new[] { anrede, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})")
+ "\n";
}
/// <summary>
/// Returns true if a plz (or pplz) is valid
/// </summary>
/// <param name="plz">the plz itself</param>
/// <param name="land">country, to check the plz</param>
/// <returns>true or faslse, depending on result :+1:</returns>
public static bool CheckPLZ(string plz, string land)
{
if (string.IsNullOrWhiteSpace(plz)) 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;
}
}

View File

@@ -9,15 +9,15 @@ public class AddressRepair(ProgressWindow progressWindow)
public KasAddressList Perform(KasAddressList all_addresses, public KasAddressList Perform(KasAddressList all_addresses,
List<(int, List<AddressCheck.ErrorTypes>)> failed_addresses) List<(int, List<AddressCheck.ErrorTypes>)> failed_addresses)
{ {
foreach (var k in all_addresses.KasPersons) // foreach (var k in all_addresses.KasPersons)
foreach (var p in failed_addresses) // foreach (var p in failed_addresses)
{ // {
if (k.refsid != p.Item1) continue; // if (k.refsid != p.Item1) continue;
//
if (p.Item2.Contains(AddressCheck.ErrorTypes.DoubledRefsid)) // if (p.Item1.Contains(AddressCheck.WarningTypes.DoubledRefsid))
{ // {
} // }
} // }
return null; return null;
} }

351
Tasks/CombineAddresses.cs Normal file
View File

@@ -0,0 +1,351 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Logof_Client;
public class CombineAddresses
{
private readonly ProgressWindow _progress;
public CombineAddresses(ProgressWindow progressWindow)
{
_progress = progressWindow;
}
public async Task<(KasAddressList,KasAddressList)> Perform(List<KasAddressList> address_lists, string type, bool? exportUnused)
{
var res = await Task.Run(async () =>
{
if (type == "difference") return Difference(address_lists, exportUnused);
if (type == "union") return Union(address_lists, exportUnused);
if (type == "intersection") return Intersection(address_lists, exportUnused);
if (type == "symdiff") return SymmetricDifference(address_lists, exportUnused);
return null;
});
return res.Result;
// KasAddressList result = new("Ergebnis_" + DateTime.Now.ToString("ddMMyy_HHmmss"));
// await Task.Run(async () =>
// {
// for (var i = 0; i < address_lists.Count; i++)
// if (i == 0)
// lock (result)
// {
// result = address_lists[i];
// }
// else
// lock (result)
// {
// result = Merge(result, address_lists[i], i + 1, address_lists.Count).Result;
// }
// });
// return result;
return (null,null);
}
/// <summary>
/// Returns true if the addresses are the same.
/// </summary>
/// <param name="first">First address to compare</param>
/// <param name="second">Second address to compare</param>
/// <param name="only_refsid">If true, only a refsid-check will be done</param>
/// <returns></returns>
public bool CompareAddresses(KasPerson first, KasPerson second, bool only_refsid = false)
{
if (first.refsid == second.refsid) return true;
if (!only_refsid)
if (first.name == second.name &&
first.anrede == second.anrede &&
first.anredzus == second.anredzus &&
first.namezus == second.namezus &&
first.titel == second.titel &&
first.adel == second.adel &&
first.strasse == second.strasse &&
first.strasse2 == second.strasse2 &&
first.vorname == second.vorname &&
first.ort == second.ort &&
first.land == second.land &&
first.plz == second.plz &&
first.pplz == second.pplz &&
first.funktion == second.funktion &&
first.funktion2 == second.funktion2 &&
first.funktionad == second.funktionad &&
first.abteilung == second.abteilung &&
first.postfach == second.postfach &&
first.name1 == second.name1 &&
first.name2 == second.name2 &&
first.name3 == second.name3 &&
first.name4 == second.name4 &&
first.name5 == second.name5)
return true;
return false;
}
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);
progress ??= new Progress
{
TotalPersons = address_lists.Sum(l => l.KasPersons.Count),
ComparedPersons = 0
};
// Vereinigung aller Listen außer der ersten
var restUnion = new List<KasPerson>();
for (var i = 1; i < address_lists.Count; i++)
restUnion.AddRange(address_lists[i].KasPersons);
var result = new KasAddressList(KasAddressList.GenerateName("difference"));
var second_result = new KasAddressList(KasAddressList.GenerateName("difference_rest"));
foreach (var person in address_lists[0].KasPersons)
{
var isDouble = restUnion.Any(p => CompareAddresses(person, p));
if (!isDouble)
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.";
await Dispatcher.UIThread.InvokeAsync(() => progress.LogAction?.Invoke(logMessage));
}
if(return_unused == true) return (result,second_result);
else return (result, 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);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
foreach (var list in address_lists)
foreach (var person in list.KasPersons)
{
if (!result.KasPersons.Any(existing => CompareAddresses(existing, person)))
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.refsid} hinzugefügt (aktuell {result.KasPersons.Count} Einträge)";
if (progress == null) continue;
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
if(return_unused == true) return (result,second_result);
else return (result, null);
}
public async Task<KasAddressList> MoveDuplicatesToNew()
{
return 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);
// Nur die erste Liste als Ausgangspunkt verwenden
var baseList = address_lists[0];
var otherLists = address_lists.Skip(1).ToList();
var total = baseList.KasPersons.Count;
var processed = 0;
foreach (var person in baseList.KasPersons)
{
// Prüfen, ob Person in *allen* anderen Listen vorkommt
var isInAll = otherLists.All(list =>
list.KasPersons.Any(existing => CompareAddresses(existing, person)));
if (isInAll)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.refsid} geprüft {(isInAll ? "in allen enthalten" : "nicht überall vorhanden")}";
// Sicher und nicht blockierend loggen
if (progress != null)
{
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
}
if(return_unused == true) return (result,second_result);
else return (result, 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"));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
// Wir sammeln alle Personen + zählen, wie oft sie vorkommen
var allPersons = new List<(KasPerson person, int count)>();
foreach (var list in address_lists)
foreach (var person in list.KasPersons)
{
// Prüfen, ob schon vorhanden
var existing = allPersons.FirstOrDefault(p => CompareAddresses(p.person, person));
if (existing.person != null)
{
// Falls schon drin → Vorkommen erhöhen
var index = allPersons.IndexOf(existing);
allPersons[index] = (existing.person, existing.count + 1);
}
else
{
// Neu hinzufügen
allPersons.Add((person, 1));
}
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.refsid} verarbeitet (Zwischengröße {allPersons.Count})";
if (progress != null)
{
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
}
// Nur diejenigen übernehmen, die *genau einmal* vorkamen
foreach (var (person, count) in allPersons)
if (count == 1)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
if(return_unused == true) return (result,second_result);
else return (result, null);
}
// private async Task<KasAddressList> Merge(KasAddressList first, KasAddressList second, int num, int total)
// {
// foreach (var sec in second.KasPersons)
// {
// var is_new = true;
// foreach (var fi in first.KasPersons)
// {
// if (fi.refsid == sec.refsid)
// {
// is_new = false;
// break;
// }
//
// if (fi.name == sec.name &&
// fi.anrede == sec.anrede &&
// fi.anredzus == sec.anredzus &&
// fi.namezus == sec.namezus &&
// fi.titel == sec.titel &&
// fi.adel == sec.adel &&
// fi.strasse == sec.strasse &&
// fi.strasse2 == sec.strasse2 &&
// fi.vorname == sec.vorname &&
// fi.ort == sec.ort &&
// fi.land == sec.land &&
// fi.plz == sec.plz &&
// fi.pplz == sec.pplz &&
// fi.funktion == sec.funktion &&
// fi.funktion2 == sec.funktion2 &&
// fi.funktionad == sec.funktionad &&
// fi.abteilung == sec.abteilung &&
// fi.postfach == sec.postfach &&
// fi.name1 == sec.name1 &&
// fi.name2 == sec.name2 &&
// fi.name3 == sec.name3 &&
// fi.name4 == sec.name4 &&
// fi.name5 == sec.name5)
// {
// is_new = false;
// break;
// }
// }
//
// if (is_new) first.KasPersons.Add(sec);
// var subperc = second.KasPersons.IndexOf(sec) / second.KasPersons.Count;
// var percent = (num + (double)subperc) / total * 100;
// await Dispatcher.UIThread.InvokeAsync(() =>
// {
// if (is_new)
// _progress.AddToLog($"Person mit refsid {sec.refsid} ergänzt");
// else
// _progress.AddToLog($"Person mit refsid {sec.refsid} bereits vorhanden");
//
// _progress.ChangePercentage(percent);
// });
// }
//
// return first;
// }
}
public class Progress
{
public int TotalPersons { get; set; } // Gesamtzahl der zu prüfenden Personen
public int ComparedPersons { get; set; } // Schon verglichene Personen
public double Percentage => TotalPersons == 0 ? 0 : (double)ComparedPersons / TotalPersons * 100;
public Action<string>? LogAction { get; set; } // z.B. Dispatcher-UI-Callback
public void Increment()
{
var comparedPersons = ComparedPersons;
Interlocked.Increment(ref comparedPersons);
}
}

391
Tasks/PdfBuilder.cs Normal file
View File

@@ -0,0 +1,391 @@
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 in a 3×7 grid layout on A4 pages.
/// </summary>
/// <param name="addressSetId">The ID of the AddressSet to use</param>
/// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfFromAddressSet(int addressSetId, 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))
// ensure single line and wrap in a small-font tag
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].refsid);
if (string.IsNullOrWhiteSpace(addr.Trim())) continue;
if (!string.IsNullOrEmpty(senderLine))
addresses.Add(senderLine + addr);
else
addresses.Add(addr);
}
CreateAddressLabelPdf(addresses, outputPath);
}
/// <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].refsid);
if (string.IsNullOrWhiteSpace(addr.Trim())) continue;
if (!string.IsNullOrEmpty(senderLine))
addresses.Add(senderLine + (addr ?? ""));
else
addresses.Add(addr);
}
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath);
}
/// <summary>
/// Creates a PDF document with address stickers in a 3×7 grid layout on A4 pages.
/// </summary>
/// <param name="addresses">Array of addresses (from CreateFinalMarkdownString)</param>
/// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdf(List<string> addresses, 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;
while (addressIndex < addresses.Count)
{
var page = document.AddPage();
page.Size = PageSize.A4;
using (var gfx = XGraphics.FromPdfPage(page))
{
// Draw the grid and fill cells
DrawPage(gfx, addresses, ref addressIndex);
}
}
// Save the document
document.Save(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)
{
var currentX = x;
var i = 0;
while (i < line.Length)
{
// 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))
{
gfx.DrawString(inner, _smallFont, XBrushes.Black,
new XRect(currentX, y, maxWidth - (currentX - x), _smallFont.Size * 1.2),
XStringFormats.TopLeft);
var measuredSmall = gfx.MeasureString(inner, _smallFont);
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));
// Draw bold text and measure width accurately
gfx.DrawString(boldText, _boldFont, XBrushes.Black,
new XRect(currentX, y, maxWidth - (currentX - x), _boldFont.Size * 1.2),
XStringFormats.TopLeft);
var measured = gfx.MeasureString(boldText, _boldFont);
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))
{
gfx.DrawString(regularText, _regularFont, XBrushes.Black,
new XRect(currentX, y, maxWidth - (currentX - x), _regularFont.Size * 1.2), XStringFormats.TopLeft);
var measured = gfx.MeasureString(regularText, _regularFont);
currentX += measured.Width;
}
i = textEnd;
}
}
/// <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");
}
}

BIN
assets/calc_man.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
assets/loading.mp4 Normal file

Binary file not shown.

157
docs/logof_client_docs.tex Normal file
View File

@@ -0,0 +1,157 @@
\documentclass[a4paper]{article}
\usepackage{graphicx}
\usepackage[left=3cm,right=3cm,top=3cm, bottom=3cm]{geometry}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage[headsepline, footsepline]{scrlayer-scrpage}
\usepackage{enumerate}
\usepackage{dsfont}
\usepackage[]{mathtools}
\usepackage[]{mathbbol}
\usepackage{multicol}
\usepackage{enumitem}
\usepackage[hidelinks]{hyperref}
\usepackage[]{circuitikz}
\usepackage{tcolorbox} % Für schöne Boxen
\usetikzlibrary{circuits.logic.IEC}
\usepackage{tikz}
\usepackage{tkz-graph}
\usepackage{listings}
\usepackage{xcolor}
\setlist{itemsep=0.3em, topsep=0.5em, parsep=0pt}
\newcommand{\bpf}[1]{%
\par\vspace{0.8\baselineskip}% Abstand vor der Überschrift
\noindent\textbf{#1}% Fettgedruckte Überschrift
\par\vspace{0.3\baselineskip}% Abstand nach der Überschrift
}
\renewcommand{\contentsname}{Inhaltsverzeichnis}
\renewcommand{\figurename}{Grafik}
\renewcommand{\partname}{Teil}
\renewcommand{\epsilon}{\varepsilon}
\definecolor{darkgrey}{HTML}{232327}
% Zählerdefinition mit AUTOMATISCHEM SUBSECTION-RESET
\newcounter{commoncounter}[subsection]
\renewcommand{\thecommoncounter}{\thesubsection.\arabic{commoncounter}}
% Zähler direkt bei Dokumentstart initialisieren
\AtBeginDocument{\setcounter{commoncounter}{0}}
% BOX-DEFINITIONEN mit korrigierter Zählerlogik
\newtcolorbox{definitionbox}[1][]{
before title={\refstepcounter{commoncounter}}, % KRITISCH: vor dem Titel!
title={Definition \thecommoncounter: #1},
colback=white,
colframe=white!75!darkgrey,
fonttitle=\bfseries,
boxrule=0.6mm,
coltitle=black,
rounded corners,
before skip=10pt,
after skip=10pt
}
\newtcolorbox{examplebox}[1][]{
before title={\refstepcounter{commoncounter}}, % KRITISCH: vor dem Titel!
title={Beispiel \thecommoncounter: #1},
colback=white,
colframe=white!75!orange,
fonttitle=\bfseries,
boxrule=0.6mm,
coltitle=black,
rounded corners,
before skip=10pt,
after skip=10pt
}
\newtcolorbox{satzbox}[1][]{
before title={\refstepcounter{commoncounter}}, % KRITISCH: vor dem Titel!
title={Satz \thecommoncounter: #1},
colback=white,
colframe=white!75!blue,
fonttitle=\bfseries,
boxrule=0.6mm,
coltitle=black,
rounded corners,
before skip=10pt,
after skip=10pt
}
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codeblue}{rgb}{0,0,0.8}
\definecolor{codered}{rgb}{0.8,0,0}
\definecolor{lightgray}{rgb}{0.95,0.95,0.95}
\lstdefinestyle{CSharpStyle}{
language=Python,
basicstyle=\ttfamily\small, % Monospace-Schrift
keywordstyle=\color{blue}\bfseries, % Schlüsselwörter fett und blau
stringstyle=\color{red}, % Strings rot
commentstyle=\color{codegreen}, % Kommentare grün
backgroundcolor=\color{lightgray}, % Hintergrundfarbe
numbers=left, % Zeilennummern links
numbersep=10px, % Abstand zwischen Zeilennummern und Code
numberstyle=\color{gray}\texttt,
stepnumber=1, % Zeilennummerierung Schrittweite 1
frame=single, % Rahmen um den Code
tabsize=4, % Tabulatorgröße
breaklines=true, % Zeilenumbruch aktivieren
captionpos=none,
showstringspaces=false,
xleftmargin=15pt, % Linker Rand für den Code (verschiebt alles nach rechts)
}
% Umgebung für Listings mit Titel und Zähler
\newenvironment{codeexample}[1][]{
\refstepcounter{commoncounter} % Zähler erhöhen
\lstset{
style=CSharpStyle,
caption={Listing \thecommoncounter: #1}, % Titel mit Zähler
label={listing:\thecommoncounter}
}
}{}
\setlength{\parindent}{0pt}
\title{\includegraphics[width=0.3\textwidth]{../assets/icon.png}\vspace{15pt}\\Logof Client\\
Handbuch}
\author{Elias Fierke}
\pagestyle{scrheadings}
\date{Oktober 2025}
\begin{document}
\ohead{Oktober 2025}
\ofoot{Seite {\pagemark} von \pageref{LastPage}}
\ihead{Logof Client Handbuch}
\maketitle
\tableofcontents
\newpage
\part{Einführung}
\newpage
\part{Installation}
\newpage
\part{Adress-Verwaltung}
\label{LastPage}
\end{document}

View File

@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]

View File

@@ -1 +0,0 @@
7c9a93128a982a1e9b3ed6a98b955291256152e83f2d8c0a81b93bfa11851fbe

Binary file not shown.

View File

@@ -1,22 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Logof Client")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+412de903f5b3dfc96eb50f28565ef826c68c4250")]
[assembly: System.Reflection.AssemblyProductAttribute("Logof Client")]
[assembly: System.Reflection.AssemblyTitleAttribute("Logof Client")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.

View File

@@ -1 +0,0 @@
106552b4e3cc179639dda17b27641bd40928d1e6b4629b5c3baae2909de72cef

View File

@@ -1,37 +0,0 @@
is_global = true
build_property.AvaloniaNameGeneratorIsEnabled = true
build_property.AvaloniaNameGeneratorBehavior = InitializeComponent
build_property.AvaloniaNameGeneratorDefaultFieldModifier = internal
build_property.AvaloniaNameGeneratorFilterByPath = *
build_property.AvaloniaNameGeneratorFilterByNamespace = *
build_property.AvaloniaNameGeneratorViewFileNamingStrategy = NamespaceAndClassName
build_property.AvaloniaNameGeneratorAttachDevTools = true
build_property.TargetFramework = net9.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Logof_Client
build_property.ProjectDir = /home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 9.0
build_property.EnableCodeStyleSeverity =
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/App.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/MainWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/MessageBox.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/ProgressWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/ResultWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml

View File

@@ -1,103 +0,0 @@
{
"format": 1,
"restore": {
"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj": {}
},
"projects": {
"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj",
"projectName": "Logof Client",
"projectPath": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj",
"packagesPath": "/home/fierke/.nuget/packages/",
"outputPath": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/fierke/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net9.0"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.100"
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"dependencies": {
"Avalonia": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Controls.DataGrid": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Desktop": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Diagnostics": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Fonts.Inter": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Themes.Fluent": {
"target": "Package",
"version": "[11.3.2, )"
},
"Lucide.Avalonia": {
"target": "Package",
"version": "[0.1.35, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"downloadDependencies": [
{
"name": "Microsoft.AspNetCore.App.Ref",
"version": "[9.0.8, 9.0.8]"
}
],
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/9.0.109/PortableRuntimeIdentifierGraph.json"
}
}
}
}
}

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/fierke/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/fierke/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/fierke/.nuget/packages/" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.props" Condition="Exists('$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.props')" />
<Import Project="$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.props" Condition="Exists('$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.props')" />
<Import Project="$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.props" Condition="Exists('$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgAvalonia_BuildServices Condition=" '$(PkgAvalonia_BuildServices)' == '' ">/home/fierke/.nuget/packages/avalonia.buildservices/0.0.31</PkgAvalonia_BuildServices>
<PkgAvalonia Condition=" '$(PkgAvalonia)' == '' ">/home/fierke/.nuget/packages/avalonia/11.3.2</PkgAvalonia>
</PropertyGroup>
</Project>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.targets" Condition="Exists('$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.targets')" />
<Import Project="$(NuGetPackageRoot)avalonia.buildservices/0.0.31/buildTransitive/Avalonia.BuildServices.targets" Condition="Exists('$(NuGetPackageRoot)avalonia.buildservices/0.0.31/buildTransitive/Avalonia.BuildServices.targets')" />
<Import Project="$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.targets" Condition="Exists('$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.targets')" />
<Import Project="$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.targets" Condition="Exists('$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.targets')" />
</ImportGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +0,0 @@
{
"version": 2,
"dgSpecHash": "aE0qKetUvS8=",
"success": true,
"projectFilePath": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj",
"expectedPackageFiles": [
"/home/fierke/.nuget/packages/avalonia/11.3.2/avalonia.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.angle.windows.natives/2.1.25547.20250602/avalonia.angle.windows.natives.2.1.25547.20250602.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.buildservices/0.0.31/avalonia.buildservices.0.0.31.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.controls.colorpicker/11.3.2/avalonia.controls.colorpicker.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.controls.datagrid/11.3.2/avalonia.controls.datagrid.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.desktop/11.3.2/avalonia.desktop.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.diagnostics/11.3.2/avalonia.diagnostics.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.fonts.inter/11.3.2/avalonia.fonts.inter.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.freedesktop/11.3.2/avalonia.freedesktop.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.native/11.3.2/avalonia.native.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.remote.protocol/11.3.2/avalonia.remote.protocol.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.skia/11.3.2/avalonia.skia.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.themes.fluent/11.3.2/avalonia.themes.fluent.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.themes.simple/11.3.2/avalonia.themes.simple.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.win32/11.3.2/avalonia.win32.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.x11/11.3.2/avalonia.x11.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp/8.3.1.1/harfbuzzsharp.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.linux/8.3.1.1/harfbuzzsharp.nativeassets.linux.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.macos/8.3.1.1/harfbuzzsharp.nativeassets.macos.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.webassembly/8.3.1.1/harfbuzzsharp.nativeassets.webassembly.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.win32/8.3.1.1/harfbuzzsharp.nativeassets.win32.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/lucide.avalonia/0.1.35/lucide.avalonia.0.1.35.nupkg.sha512",
"/home/fierke/.nuget/packages/microcom.runtime/0.11.0/microcom.runtime.0.11.0.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp/2.88.9/skiasharp.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.linux/2.88.9/skiasharp.nativeassets.linux.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.macos/2.88.9/skiasharp.nativeassets.macos.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.webassembly/2.88.9/skiasharp.nativeassets.webassembly.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.win32/2.88.9/skiasharp.nativeassets.win32.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/system.io.pipelines/8.0.0/system.io.pipelines.8.0.0.nupkg.sha512",
"/home/fierke/.nuget/packages/tmds.dbus.protocol/0.21.2/tmds.dbus.protocol.0.21.2.nupkg.sha512",
"/home/fierke/.nuget/packages/microsoft.aspnetcore.app.ref/9.0.8/microsoft.aspnetcore.app.ref.9.0.8.nupkg.sha512"
],
"logs": []
}

View File

@@ -1 +0,0 @@
"restore":{"projectUniqueName":"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj","projectName":"Logof Client","projectPath":"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj","outputPath":"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/obj/","projectStyle":"PackageReference","originalTargetFrameworks":["net9.0"],"sources":{"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net9.0":{"targetAlias":"net9.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.100"}"frameworks":{"net9.0":{"targetAlias":"net9.0","dependencies":{"Avalonia":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Controls.DataGrid":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Desktop":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Diagnostics":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Fonts.Inter":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Themes.Fluent":{"target":"Package","version":"[11.3.2, )"},"Lucide.Avalonia":{"target":"Package","version":"[0.1.35, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"downloadDependencies":[{"name":"Microsoft.AspNetCore.App.Ref","version":"[9.0.8, 9.0.8]"}],"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/share/dotnet/sdk/9.0.109/PortableRuntimeIdentifierGraph.json"}}

View File

@@ -1 +0,0 @@
17581228084312842

View File

@@ -1 +0,0 @@
17581228084312842