78 Commits

Author SHA1 Message Date
fierke b3ab21ae38 [fix?:] windows paths (ai-based since windows is boring) 2026-05-18 11:42:32 +02:00
fierke 67d007bb31 [fix:] inconsistency with config.json 2026-05-18 11:37:10 +02:00
fierke bf28ba1914 [chore:] logging for StableFontResolver.cs 2026-05-16 18:19:50 +02:00
fierke 40630dee35 [chore:] logging for PdfBuilder.cs 2026-05-16 18:19:36 +02:00
fierke c6baa6a187 [chore:] logging for MarkdownRenderer.cs 2026-05-16 18:19:27 +02:00
fierke 557052bbc7 [chore:] logging for EditorWindow.axaml.cs 2026-05-16 18:19:19 +02:00
fierke 696d0f8fcb [chore:] logging for DataImport.cs 2026-05-16 18:19:09 +02:00
fierke 201b19cbb5 [chore:] logging for CsvBuilder.cs 2026-05-16 18:18:58 +02:00
fierke d7d4b3b31b [file:] removing docs.tex since we're using a markdown wiki 2026-05-16 16:14:31 +00:00
fierke fa10b5c7d0 [chore:] logging for ResultWindow.axaml.cs 2026-05-16 15:10:55 +02:00
fierke 85685d95bb [chore:] logging for NamingWindow.axaml.cs 2026-05-16 15:10:49 +02:00
fierke 8a42f8dc7d [chore:] logging for CombineAddresses.cs 2026-05-16 15:09:55 +02:00
fierke cdc4fb70cd [chore:] logging for AddressShortener.cs 2026-05-16 15:09:50 +02:00
fierke 10dfd1e080 [chore:] logging for AddressRepair.cs 2026-05-16 15:09:45 +02:00
fierke 4b98f53881 [chore:] logging for AddressPatch.cs 2026-05-16 15:09:38 +02:00
fierke 5f79df55d2 [chore:] logging for AddressCreation.cs 2026-05-16 15:09:32 +02:00
fierke 2d33326bab [chore:] logging for AddressCheck.cs 2026-05-16 15:09:23 +02:00
fierke b48652910e [fix:] format errors 2026-05-16 14:44:38 +02:00
fierke a74a97559b [fix:] typo 2026-05-16 14:42:28 +02:00
fierke b5b6dc8de7 [chore:] basic saving logging 2026-05-16 14:41:28 +02:00
fierke 6dfab5e73a [chore:] basic logging 2026-05-16 14:41:16 +02:00
fierke 6f03a26c29 [fix:] console logging printed an array 👀 2026-05-16 14:41:06 +02:00
fierke 7c81920e84 [init:] logging system 2026-05-16 14:12:16 +02:00
fierke d337f94851 [fix?:] trying 2026-05-15 18:56:55 +02:00
fierke 1922b30ada [fix?:] will this work? 2026-05-15 18:41:45 +02:00
fierke 68541621d9 [fix?:] windows file path for label export? 2026-05-15 18:34:25 +02:00
fierke 474b628f0b [chore:] renaming address sets now gives the current name as a base 2026-05-15 17:51:35 +02:00
fierke 7fe45ce4e3 [feat:] csv-divider-buttons 2026-05-06 07:07:41 +02:00
fierke a46539c3cc [fix:] wrong sorting of imported address sets 2026-05-06 06:35:23 +02:00
fierke 72a5630db1 [chore:] enable shortener usage 2026-05-06 06:34:48 +02:00
fierke 7697edac5e [fix:] shorten-button was enabled too early 2026-05-06 06:33:55 +02:00
fierke 028b9793db [chore:] basic address shortener 2026-05-06 06:33:37 +02:00
fierke 2c5f2ed48b [feat:] (opt) delete original sets after merging 2026-05-03 09:21:57 +02:00
fierke f0598a39ec [chore:] listbox item type is no string anymore (hopefully I tested everything?) 2026-05-02 16:27:43 +02:00
fierke f9e419d573 [chore:] cleanup 2026-05-02 16:20:18 +02:00
fierke 56233e6f5c [chore:] tiny improvements for address check 2026-05-02 15:39:42 +02:00
fierke 488830cdad [fix:] multiple little fixed for the combining methods 2026-05-02 12:56:27 +02:00
fierke 52fbefb803 [chore:] added missing help- and about-links 2026-04-29 14:58:53 +02:00
fierke c5aabc2a02 [fix:] window focus 2026-04-28 11:00:51 +02:00
fierke 4a9f9a1ff0 [fix:] startup location 2026-04-28 11:00:40 +02:00
fierke fab14eb107 [fix:] naming window for rest-set not visible if not necessary 2026-04-28 11:00:26 +02:00
fierke 2dcc1bd657 Merge remote-tracking branch 'origin/main' 2026-04-28 10:54:09 +02:00
fierke 3767fece48 [fix:] make customer delete button working 2026-04-28 10:53:53 +02:00
fierke 842608e96f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	MainWindow.axaml
#	MainWindow.axaml.cs
2026-04-27 17:39:19 +02:00
fierke d9ee3e2fc9 [chore:] deleting address sets 2026-04-27 17:39:08 +02:00
fierke 80d1498cc7 [chore:] if it happens it happens 2026-04-27 13:07:22 +02:00
fierke de2f453553 [chore:] editor window margins 2026-04-27 13:07:01 +02:00
fierke 7e168c4d0f [fix:] line breaks in editor window now possible 2026-04-27 13:06:29 +02:00
fierke 9becedbd97 [chore:] better file- and folder creating 2026-04-27 09:04:16 +02:00
fierke 16982c3d95 [fix:] adding ".md" if not added by user (wiki) 2026-04-27 08:49:53 +02:00
fierke 93771dd110 [fix:] trying to fix one-item-too-much-issue 2026-04-27 08:49:27 +02:00
fierke 3e14731429 Merge pull request 'export-margin-options implementation' (#33) from export-margin-options into main
Reviewed-on: #33
2026-04-23 14:22:58 +00:00
fierke df6c187a00 [chore:] combining export margin options gui with settings and PdfBuilder.cs 2026-04-23 12:30:35 +02:00
fierke 98b5198f6f [chore:] added smallFontSize and fixed usage of fontSize and smallFontSize 2026-04-23 09:03:25 +02:00
fierke 1a6459d60b [chore:] grid visibility (calc man :D) 2026-04-23 09:03:00 +02:00
fierke 43e6c35beb [gui:] export margin options gui 2026-04-23 09:02:45 +02:00
fierke 9777c6b5a2 [fix:] naming window showed nothing 2026-04-22 08:09:28 +02:00
fierke 9ad378c800 [fix:] temporarily fiex application crash if no customer is selected when start combining (more than union, which was fixed before) 2026-04-16 13:29:59 +02:00
fierke 5ccd4a4e99 [fix:] csv-import now recognizes quotation marks 2026-04-16 12:49:45 +02:00
fierke ac7b23cc28 [fix:] temporarily fixed application crash if no customer is seleceted when starting 2026-04-16 12:49:10 +02:00
fierke 16ed64dbf4 [chore:] there was a space missing that triggered my monk 2026-04-16 11:44:10 +02:00
fierke cc48c0ae2c [chore:] separator option (it's better like that :D) 2026-03-19 14:39:54 +01:00
fierke ba640c602a [file:] better folder structuring 2026-03-19 14:34:36 +01:00
fierke e8738a2eab [fix:] now, they get unique id's when importing 2026-03-19 14:20:59 +01:00
Elias Fierke afbb4626a0 [feat:] implemented renaming functionality 2026-01-19 16:25:33 +01:00
Elias Fierke ee83aca490 [feat:] included context menu for Address-SetListBox 2026-01-19 16:25:20 +01:00
Elias Fierke b40ddfbf2e [fix:] NamingWindow.axaml had a wrong size 2026-01-19 16:17:06 +01:00
Elias Fierke 5172de332c [feat:] added "new file" button for wiki and therefore
implemented the usage of NamingWindow
2026-01-18 16:15:25 +01:00
Elias Fierke d478fd5129 [feat:] simple MessageBox-lik naming window 2026-01-18 16:14:37 +01:00
Elias Fierke 5dcb44aa2a [fix:] added default config- and wiki-paths 2026-01-18 16:13:40 +01:00
Elias Fierke ea31637bdb [feat:] implemented a basic editor for the wiki 2026-01-18 14:36:52 +01:00
Elias Fierke 2c909820d3 Merge remote-tracking branch 'origin/main' 2026-01-18 14:36:07 +01:00
Elias Fierke 96eb122ff8 [fix:] fixed an issue where new lines were rendered as "MarkDig.Blah.Sülz.LineBreakInline" 2026-01-18 14:35:59 +01:00
fierke fed817a6dc [fix:] corrected account information 2026-01-18 13:03:42 +00:00
fierke d5073465b2 [file:] added README 2026-01-18 11:39:49 +00:00
Elias Fierke 10b0eb5bcd [chore:] font resolver included + various changes 2026-01-18 11:38:16 +01:00
Elias Fierke 013bd4a070 [file:] added font-asset with example font 2026-01-18 11:36:41 +01:00
Elias Fierke 2c4eb1fcef [proj:] including assets/fonts to build directory 2026-01-18 11:35:15 +01:00
35 changed files with 3059 additions and 1971 deletions
-118
View File
@@ -1,118 +0,0 @@
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();
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Collections.Generic;
using System.Text;
namespace Logof_Client;
public class CsvBuilder
{
private readonly string Header;
private readonly List<object> Instances;
private readonly KasAddressList KasAddressList;
public CsvBuilder(string header, List<object> instances)
{
Header = header;
Instances = instances;
}
public CsvBuilder(string header, KasAddressList instances)
{
Header = header;
KasAddressList = instances;
}
public string? BuildKas()
{
var result = new StringBuilder();
result.AppendLine(Header);
foreach (var l in KasAddressList.KasPersons)
result.AppendLine(
l.refsid + "," +
l.anrede + "," +
l.titel + "," +
l.vorname + "," +
l.adel + "," +
l.name + "," +
l.namezus + "," +
l.anredzus + "," +
l.strasse + "," +
l.strasse2 + "," +
l.plz + "," +
l.ort + "," +
l.land + "," +
l.pplz + "," +
l.postfach + "," +
l.name1 + "," +
l.name2 + "," +
l.name3 + "," +
l.name4 + "," +
l.name5 + "," +
l.funktion + "," +
l.funktion2 + "," +
l.abteilung + "," +
l.funktionad);
// weitere Cases
return result.ToString();
}
}
-244
View File
@@ -1,244 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Logof_Client;
public class DataImport
{
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))
{
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 imported =
new KasAddressList(KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = line.Split(separator).Select(p => p.Trim()).ToArray();
if (parts.Length < 24)
{
Console.WriteLine($"Not enough columns in line: {line}");
continue;
}
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count),
ParseInt(parts[0]),
parts[1],
parts[2],
parts[3],
parts[4],
parts[5],
parts[6],
parts[7],
parts[8],
parts[9],
parts[10],
parts[11],
parts[12],
parts[13],
parts[14],
parts[15],
parts[16],
parts[17],
parts[18],
parts[19],
parts[20],
parts[21],
parts[22],
parts[23]
);
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);
}
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 refsid = 0;
if (patch.has_refsid)
refsid = ParseInt(GetField("refsid"));
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count), 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()
// {
// var biggest = last_refsid;
// foreach (var set in Settings._instance.addressSets.addresses)
// foreach (var address in set.KasPersons)
// if (biggest < address.id)
// biggest = address.id + 1;
//
// last_refsid = biggest + 1;
// return last_refsid;
// }
}
private static int ParseInt(string input)
{
return int.TryParse(input, out var result) ? result : 0;
}
}
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
namespace Logof_Client; namespace Logof_Client;
@@ -32,11 +33,16 @@ public class KasAddressList //Address-Set
this.owner_id = owner_id; this.owner_id = owner_id;
} }
public static string GenerateName(string basic_type, bool? is_rest = false) public static async Task<string> GenerateName(string basic_type, bool? is_rest = false)
{ {
string pre = "";
if (is_rest == true) if (is_rest == true)
return basic_type + " - " + DateTime.Now.ToShortDateString() + " - Rest"; return basic_type + " - " + DateTime.Now.ToShortDateString() + " - Rest";
return basic_type + " - " + DateTime.Now.ToShortDateString(); pre = basic_type + " - " + DateTime.Now.ToShortDateString();
var result = await NamingWindow.Show(MainWindow._instance, pre);
return string.IsNullOrWhiteSpace(result) ? pre : result;
} }
// public void UpdateErrorList(List<KasPersonError> errorList) // public void UpdateErrorList(List<KasPersonError> errorList)
@@ -54,6 +60,11 @@ public class KasAddressList //Address-Set
var id = listItemName.Split(" - ")[0]; var id = listItemName.Split(" - ")[0];
return int.Parse(id); return int.Parse(id);
} }
public override string ToString()
{
return Name + " (" + KasPersons.Count + " Einträge)";
}
} }
public class KasPerson public class KasPerson
@@ -169,13 +180,30 @@ public class KasPerson
public static int GenerateNewID(int base_id) public static int GenerateNewID(int base_id)
{ {
var newid = 100000 + base_id; //var newid = 100000 + base_id;
int highest = 0;
foreach (var set in Settings._instance.addressSets.addresses) foreach (var set in Settings._instance.addressSets.addresses)
{
foreach (var add in set.KasPersons) foreach (var add in set.KasPersons)
if (add.id >= newid) {
newid = add.id + 1; if(add.id >= highest) highest = add.id+1;
}
}
return newid;
return highest + base_id + 1;
}
public override string ToString()
{
if (refsid != null && refsid != 0)
{
return refsid + " - " + name;
}
else
{
return id + " - " + name;
}
} }
} }
@@ -201,4 +229,9 @@ public class KasPersonError
return output; return output;
} }
public string ToString(KasPerson person)
{
return "ID:"+person.id + "; Name: " +person.name +"; Errors: " + GetString();
}
} }
+66 -25
View File
@@ -10,9 +10,10 @@ public class Settings
public static Settings _instance = new(); public static Settings _instance = new();
public AddressSets addressSets = new(); public AddressSets addressSets = new();
public Customers customers = new(); public Customers customers = new();
public PdfExportSettings pdfExport { get; set; } = new();
public string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), // public string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient", "config.json"); // "logofclient", "config.json");
public Settings() public Settings()
{ {
@@ -22,36 +23,52 @@ public class Settings
public static void Save() public static void Save()
{ {
if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), if (!Directory.Exists(Global._instance.config_path))
"logofclient"))) Directory.CreateDirectory(Global._instance.config_path);
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), // if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
"logofclient"));
if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
var json = JsonConvert.SerializeObject(_instance); var json = JsonConvert.SerializeObject(_instance);
File.WriteAllText(_instance.settingsPath, json); File.WriteAllText(Path.Combine(Global._instance.config_path,"config.json"), json);
} }
public static void Load() public static void Load()
{ {
if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path; //if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
try try
{ {
var contents = File.ReadAllText(_instance.settingsPath); var contents = File.ReadAllText(Path.Combine(Global._instance.config_path, "config.json"));
_instance = JsonConvert.DeserializeObject<Settings>(contents); _instance = JsonConvert.DeserializeObject<Settings>(contents);
MainWindow._instance.RefreshCustomerItems(); MainWindow._instance.RefreshCustomerItems();
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Logger.Log($"Error while reading settings. Generating new... {ex.Message}", Logger.LogType.Warning);
Console.WriteLine(ex.StackTrace);
Console.WriteLine("Error while reading settings. Generating new...");
_instance = new Settings(); _instance = new Settings();
} }
} }
} }
public class PdfExportSettings
{
public double cellPaddingTopMm { get; set; } = 5;
public double cellPaddingBottomMm { get; set; } = 5;
public double cellPaddingLeftMm { get; set; } = 5;
public double cellPaddingRightMm { get; set; } = 5;
public double pageMarginTopMm { get; set; } = 0;
public double pageMarginBottomMm { get; set; } = 0;
public double pageMarginLeftMm { get; set; } = 0;
public double pageMarginRightMm { get; set; } = 0;
public int rowsPerPage { get; set; } = 7;
public int columnsPerPage { get; set; } = 3;
public double fontSize { get; set; } = 9;
public double smallFontSize { get; set; } = 6;
}
public class Global public class Global
{ {
public static Global _instance; public static Global _instance;
@@ -61,9 +78,22 @@ public class Global
_instance = this; _instance = this;
} }
public string config_path { get; set; } = ""; public string config_path { get; set; } = Path.Combine(
public string wiki_storage_path { get; set; } = ""; Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient");
public void SetConfigPath(string path)
{
if (!string.IsNullOrWhiteSpace(path))
config_path = PathUtilities.NormalizeFileSystemPath(path);
}
public string wiki_storage_path { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient",
"wiki");
public List<Country> countries { get; set; } = new(); public List<Country> countries { get; set; } = new();
public string font_path { get; set; } = Path.Combine(AppContext.BaseDirectory, "assets", "fonts");
public static void Save() public static void Save()
{ {
@@ -90,23 +120,29 @@ public class Global
var contents = File.ReadAllText(Path.Combine( var contents = File.ReadAllText(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
"global.config")); "global.config"));
_instance = JsonConvert.DeserializeObject<Global>(contents); _instance = JsonConvert.DeserializeObject<Global>(contents) ?? new Global();
_instance.NormalizePaths();
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Logger.Log($"Error while reading global settings. Generating new... {ex.Message}", Logger.LogType.Warning);
Console.WriteLine(ex.StackTrace);
Console.WriteLine("Error while reading global settings. Generating new...");
_instance = new Global(); _instance = new Global();
Save(); Save();
} }
} }
private void NormalizePaths()
{
config_path = PathUtilities.NormalizeFileSystemPath(config_path);
wiki_storage_path = PathUtilities.NormalizeFileSystemPath(wiki_storage_path);
font_path = PathUtilities.NormalizeFileSystemPath(font_path);
}
} }
public class Customers public class Customers
{ {
public List<Customer> customers = new(); public List<Customer> customers = new();
public int current { get; set; } = 0; public Customer current { get; set; } = null;
} }
public class Customer public class Customer
@@ -121,6 +157,11 @@ public class Customer
ID = highestID + 1; ID = highestID + 1;
} }
public override string ToString()
{
return name;
}
public string name { get; set; } = ""; public string name { get; set; } = "";
public string description { get; set; } = ""; public string description { get; set; } = "";
public string sender_address { get; set; } = ""; public string sender_address { get; set; } = "";
@@ -129,11 +170,11 @@ public class Customer
public int ID { get; } public int ID { get; }
public static int GetIDByCustomerListItem(string item_content) // public static int GetIDByCustomerListItem(string item_content)
{ // {
var id = item_content.Split(" - ")[0]; // var id = item_content.Split(" - ")[0];
return int.Parse(id); // return int.Parse(id);
} // }
} }
public class AddressSets public class AddressSets
+40
View File
@@ -0,0 +1,40 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace Logof_Client;
public static class Logger
{
public static void Log(string text, LogType logType = LogType.Info)
{
try
{
string config_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient");
if (!Directory.Exists(config_path))
Directory.CreateDirectory(config_path);
string log_path = Path.Combine(config_path, $"log-{DateTime.Now:dd-MM-yy}.log");
if(!File.Exists(log_path))
File.Create(log_path).Close();
string line = $"[{DateTime.Now:dd.MM.yyyy - hh:mm:ss}]: ({logType.ToString()}) {text}";
Console.WriteLine(line);
File.AppendAllLines(log_path, [line]);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
public enum LogType
{
Error,
Warning,
Info
}
}
+22 -14
View File
@@ -9,25 +9,25 @@
</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="ISO3166" Version="1.0.4" /> <PackageReference Include="ISO3166" Version="1.0.4"/>
<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="Newtonsoft.Json" Version="13.0.4"/>
<PackageReference Include="PdfSharp" Version="6.1.1" /> <PackageReference Include="PdfSharp" Version="6.1.1"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Markdig" Version="0.30.3" /> <PackageReference Include="Markdig" Version="0.30.3"/>
<PackageReference Include="AvaloniaEdit" Version="0.10.12" /> <PackageReference Include="AvaloniaEdit" Version="0.10.12"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -37,17 +37,25 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="assets\icon.ico" /> <None Remove="assets\icon.ico"/>
<AvaloniaResource Include="assets\icon.ico"> <AvaloniaResource Include="assets\icon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource> </AvaloniaResource>
<None Remove="assets\calc_man.png" /> <None Remove="assets\calc_man.png"/>
<AvaloniaResource Include="assets\calc_man.png"> <AvaloniaResource Include="assets\calc_man.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource> </AvaloniaResource>
<None Remove="assets\loading.mp4" /> <None Remove="assets\loading.mp4"/>
<AvaloniaResource Include="assets\loading.mp4"> <AvaloniaResource Include="assets\loading.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource> </AvaloniaResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="assets\fonts\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Folder Include="assets\fonts\"/>
</ItemGroup>
</Project> </Project>
+152 -44
View File
@@ -28,7 +28,7 @@
<Label FontSize="20" Content="Addressverwaltung" VerticalContentAlignment="Center" /> <Label FontSize="20" Content="Addressverwaltung" VerticalContentAlignment="Center" />
</StackPanel> </StackPanel>
</TabItem.Header> </TabItem.Header>
<Grid RowDefinitions="2*,*,*"> <Grid RowDefinitions="3*,80,2*">
<Grid ColumnDefinitions="*,*" Grid.Row="0"> <Grid ColumnDefinitions="*,*" Grid.Row="0">
<!-- Kunden --> <!-- Kunden -->
<Grid Margin="30,30,10,30"> <Grid Margin="30,30,10,30">
@@ -67,7 +67,28 @@
SelectionChanged="LstCustomerAdressSets_OnSelectionChanged" SelectionChanged="LstCustomerAdressSets_OnSelectionChanged"
Background="AliceBlue" Background="AliceBlue"
Margin="0,5,0,5" Margin="0,5,0,5"
SelectionMode="Multiple,Toggle" /> SelectionMode="Multiple,Toggle">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="MnIAdSetRename" Click="MnIAdSetRename_OnClick">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Pencil" Width="12" Height="12" Size="12" />
<Label Content="Umbenennen" VerticalContentAlignment="Center" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="MnIAdSetDelete" Click="MnIAdSetRename_OnClick" IsEnabled="False">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Trash" Width="12" Height="12" Size="12" />
<Label Content="Löschen" VerticalContentAlignment="Center" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Button Grid.Row="2" HorizontalAlignment="Stretch" x:Name="BtnCustomerAddressSetImport" <Button Grid.Row="2" HorizontalAlignment="Stretch" x:Name="BtnCustomerAddressSetImport"
Click="BtnCustomerAddressSetImport_OnClick"> Click="BtnCustomerAddressSetImport_OnClick">
@@ -78,23 +99,10 @@
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
<!-- <Grid Grid.Row="0"> --> <!-- <Grid ColumnDefinitions="*,*,*" Grid.Row="1"> -->
<!-- <StackPanel Orientation="Horizontal" VerticalAlignment="Center" --> <StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="10" HorizontalAlignment="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"
Margin="0,0,5,0"> Margin="0,0,5,0">
<Button HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" <Button Width="250" HorizontalContentAlignment="Center"
Margin="0,0,0,10" IsEnabled="False" 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">
@@ -103,7 +111,7 @@
FontWeight="Bold" /> FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False" <Button Width="250" IsEnabled="False"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
Click="BtnCombine_OnClick" x:Name="BtnCombine" Click="BtnCombine_OnClick" x:Name="BtnCombine"
Margin="0,0,0,10"> Margin="0,0,0,10">
@@ -113,19 +121,7 @@
FontWeight="Bold" /> FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False" <Button Width="250" IsEnabled="False" Click="BtnShorten_OnClick"
HorizontalContentAlignment="Center" x:Name="BtnRepair"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Hammer" Width="36" Height="36" />
<Label Content="Reparieren" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
</StackPanel>
<StackPanel Grid.Column="1" Width="250" Orientation="Vertical" HorizontalAlignment="Left"
Margin="5,0,0,0">
<Button HorizontalAlignment="Stretch" IsEnabled="False"
HorizontalContentAlignment="Center" x:Name="BtnShorten" HorizontalContentAlignment="Center" x:Name="BtnShorten"
Margin="0,0,0,10"> Margin="0,0,0,10">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
@@ -134,7 +130,7 @@
FontWeight="Bold" /> FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False" <Button Width="250" IsEnabled="False"
Click="BtnGenerateLabels_OnClick" Click="BtnGenerateLabels_OnClick"
HorizontalContentAlignment="Center" x:Name="BtnGenerateLabels" HorizontalContentAlignment="Center" x:Name="BtnGenerateLabels"
Margin="0,0,0,10"> Margin="0,0,0,10">
@@ -145,8 +141,17 @@
FontWeight="Bold" /> FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button Width="250" IsEnabled="False"
HorizontalContentAlignment="Center" x:Name="BtnRepair"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Hammer" Width="36" Height="36" />
<Label Content="Reparieren" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel> </StackPanel>
</Grid> </Button>
</StackPanel>
<!-- </Grid> -->
<Grid Grid.Row="2" Margin="20" IsVisible="True" x:Name="GrdCalcMan"> <Grid Grid.Row="2" Margin="20" IsVisible="True" x:Name="GrdCalcMan">
<Image Source="assets/calc_man.png" VerticalAlignment="Center" HorizontalAlignment="Center" /> <Image Source="assets/calc_man.png" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid> </Grid>
@@ -215,10 +220,83 @@
</StackPanel> </StackPanel>
<Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind" /> <Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind" />
</StackPanel> </StackPanel>
</Button> </Button>
</StackPanel> </StackPanel>
<CheckBox HorizontalAlignment="Center" x:Name="CbMergeExportUnmerged" IsChecked="False">Speichere Unverarbeitete in neuem Verteiler</CheckBox> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="50">
<StackPanel Orientation="Vertical" >
<RadioButton Content="Vergleiche nach refsid" IsChecked="True" x:Name="RbComprefsid"></RadioButton>
<RadioButton Content="Vergleiche nach finaler Adresse" IsChecked="False" x:Name="RbCompfinAd"></RadioButton>
</StackPanel>
<StackPanel Orientation="Vertical">
<CheckBox HorizontalAlignment="Left" x:Name="CbMergeExportUnmerged" IsChecked="False">Speichere Unverarbeitete in neuem Verteiler</CheckBox>
<CheckBox HorizontalAlignment="Left" x:Name="CbMergeDeleteOld" IsChecked="False">Lösche ursprüngliche Sets</CheckBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
<Grid Grid.Row="2" Margin="20" ColumnDefinitions="*,5*,*" IsVisible="False" x:Name="GrdExportMarginOptions">
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="20">
<Grid ColumnDefinitions="*,*,*" ColumnSpacing="20">
<StackPanel Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand oben (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingTop" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand unten (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingBot" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand links (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingLeft" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand rechts (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingRight" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand oben"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginTop" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand unten"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginBottom" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand rechts"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginRight" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand links"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginLeft" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="*,*">
<Label Content="Zeilen pro Seite"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargRowsPerPage" Minimum="1" Maximum="10" Value="7"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Spalten pro Seite"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargColumnsPerPage" Minimum="1" Maximum="8" Value="3"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Schriftgröße (groß)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargFontSize" Minimum="5" Maximum="30" Value="9"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Schriftgröße (klein)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargSmallFontSize" Minimum="3" Maximum="30" Value="6"></NumericUpDown>
</Grid>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Start" Click="BtnStartGenerateLabels_OnClick"></Button>
</StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Grid>
@@ -257,9 +335,14 @@
<Grid ColumnDefinitions="300,*"> <Grid ColumnDefinitions="300,*">
<Border Grid.Column="0" Background="#FFF" BorderBrush="#DDD" BorderThickness="0,0,1,0"> <Border Grid.Column="0" Background="#FFF" BorderBrush="#DDD" BorderThickness="0,0,1,0">
<StackPanel> <StackPanel>
<TextBlock Text="Wiki" FontSize="18" Margin="12" /> <StackPanel Spacing="10" Orientation="Horizontal" Margin="10">
<Button Content="+ Datei" x:Name="BtnWikiAddFile"
Click="BtnWikiAddFile_OnClick" />
<Button Content="+ Ordner" x:Name="BtnWikiAddFolder"
Click="BtnWikiAddFolder_OnClick" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TreeView Name="NavTree" Margin="8" /> <TreeView Name="NavTree" Margin="10" />
</ScrollViewer> </ScrollViewer>
</StackPanel> </StackPanel>
</Border> </Border>
@@ -306,8 +389,8 @@
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5"> <StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5"> <StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbConfigPath" HorizontalAlignment="Stretch" <TextBox x:Name="TbConfigPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/config.json" /> Watermark="/home/username/.config/logofclient/" />
<Button IsEnabled="False"> <Button x:Name="BtnConfigPath" HorizontalAlignment="Right">
<Button.Content> <Button.Content>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="File" Width="16" Height="16" Size="16" /> <LucideIcon Kind="File" Width="16" Height="16" Size="16" />
@@ -324,7 +407,24 @@
<StackPanel Orientation="Horizontal" Spacing="5"> <StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbWikiPath" HorizontalAlignment="Stretch" <TextBox x:Name="TbWikiPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/wiki" /> Watermark="/home/username/.config/logofclient/wiki" />
<Button IsEnabled="True" x:Name="BtnWikiPath"> <Button IsEnabled="True" x:Name="BtnWikiPath" HorizontalAlignment="Right">
<Button.Content>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Folder" Width="16" Height="16" Size="16" />
<Label Content="Öffnen..." VerticalContentAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Grid ColumnDefinitions="400,*">
<Label Grid.Column="0">Font-Pfad</Label>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbFontPath" HorizontalAlignment="Stretch"
Watermark="[App-Direcotry]/assets/fonts/" />
<Button IsEnabled="True" x:Name="BtnFontPath" HorizontalAlignment="Right">
<Button.Content> <Button.Content>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="Folder" Width="16" Height="16" Size="16" /> <LucideIcon Kind="Folder" Width="16" Height="16" Size="16" />
@@ -385,12 +485,20 @@
TextChanged="TbSettingsCustomerSenderAddress_OnTextChanged" TextChanged="TbSettingsCustomerSenderAddress_OnTextChanged"
x:Name="TbSettingsCustomerSenderAddress" /> x:Name="TbSettingsCustomerSenderAddress" />
</Grid> </Grid>
<Grid ColumnDefinitions="150,*"> <Grid ColumnDefinitions="150,*,Auto">
<Label Content="CSV-Trennzeichen" /> <Label Content="CSV-Trennzeichen" />
<TextBox Grid.Column="1" Watermark="," <TextBox Grid.Column="1" Watermark=","
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0,0,5,0"
TextChanged="TbSettingsCustomerCsvSeparator_OnTextChanged" TextChanged="TbSettingsCustomerCsvSeparator_OnTextChanged"
x:Name="TbSettingsCustomerCsvSeparator" /> x:Name="TbSettingsCustomerCsvSeparator" />
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="5">
<Button Click="ChangeCSVDivider" Content=","></Button>
<Button Click="ChangeCSVDivider" Content=";"></Button>
<Button Click="ChangeCSVDivider" Content="TAB"></Button>
<Button Click="ChangeCSVDivider" Content="|"></Button>
<Button Click="ChangeCSVDivider" Content="SPACE"></Button>
<Button Click="ChangeCSVDivider" Content=":"></Button>
</StackPanel>
</Grid> </Grid>
<Grid ColumnDefinitions="150,*"> <Grid ColumnDefinitions="150,*">
<Label Content="Address-Patch-Info" /> <Label Content="Address-Patch-Info" />
@@ -417,7 +525,7 @@
VerticalContentAlignment="Center" /> VerticalContentAlignment="Center" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button Background="#99963434" HorizontalAlignment="Stretch" <Button Background="#99963434" HorizontalAlignment="Stretch" x:Name="BtnDeleteCustomer" Click="BtnDeleteCustomer_OnClick"
HorizontalContentAlignment="Center"> HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<LucideIcon Kind="Trash" Width="16" Height="16" Size="16" /> <LucideIcon Kind="Trash" Width="16" Height="16" Size="16" />
+574 -157
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
<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" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Topmost="True"
x:Class="Logof_Client.NamingWindow"
Title="NamingWindow">
<StackPanel Orientation="Vertical">
<TextBlock Name="Text" Margin="10" TextWrapping="Wrap" />
<TextBox Name="Input" Margin="10" />
<StackPanel HorizontalAlignment="Right" Margin="5" Orientation="Horizontal" Name="Buttons">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Margin" Value="5" />
</Style>
</StackPanel.Styles>
</StackPanel>
</StackPanel>
</Window>
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Logof_Client;
public partial class NamingWindow : Window
{
public NamingWindow()
{
InitializeComponent();
}
public static Task<string> Show(Window parent, string input = "", string info = "Bitte geben Sie einen Namen ein:")
{
try
{
var wind = new NamingWindow
{
Title = "Name eingeben"
};
wind.FindControl<TextBlock>("Text").Text = info;
var buttonPanel = wind.FindControl<StackPanel>("Buttons");
var inputBox = wind.FindControl<TextBox>("Input");
inputBox.Text = input;
string res = null;
void AddButton(string caption)
{
var btn = new Button { Content = caption };
btn.Click += (_, __) =>
{
res = inputBox.Text;
wind.Close();
};
buttonPanel.Children.Add(btn);
}
AddButton("Ok");
var tcs = new TaskCompletionSource<string>();
wind.Closed += delegate { tcs.TrySetResult(res); };
if (parent != null)
wind.ShowDialog(parent);
else wind.Show();
wind.Focus();
return tcs.Task;
}
catch (Exception ex)
{
Logger.Log("Error while showing naming window: " + ex.Message, Logger.LogType.Warning);
return Task.FromResult<string>(null!);
}
}
}
+1 -1
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" Icon="assets/icon.ico" Height="150" MinHeight="150" MaxHeight="150" Icon="assets/icon.ico" WindowStartupLocation="CenterScreen" Topmost="True"
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"> -->
+2 -2
View File
@@ -16,9 +16,9 @@ public partial class ProgressWindow : Window
PbProgress.Value = percentage; PbProgress.Value = percentage;
} }
public void AddToLog(string message) public void AddToLog(string message, string percent)
{ {
TbLog.Text = message; TbLog.Text = message + $"\n{percent}%";
//ScvLog.ScrollToEnd(); //ScvLog.ScrollToEnd();
} }
} }
+8
View File
@@ -0,0 +1,8 @@
# Logofclient
Free and open source software suite to manage customers, prices, employees, etc.
## Bugs
Please report any bugs you find to fierke@mypapertown.de, thank you!
## Contributing
Feel free to contribute to this project using your MyPaperCloud-Account (request it via fierke@mypapertown.de) or a local git.mypapercloud.de-Account you are able to create.
+3 -1
View File
@@ -20,7 +20,9 @@
Margin="10,10,10,10" /> Margin="10,10,10,10" />
</Grid> </Grid>
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="StkResults" Orientation="Vertical" Margin="10" /> <!-- <TextBlock x:Name="TbResults"></TextBlock> -->
<ListBox x:Name="LbResults"></ListBox>
<!-- <StackPanel x:Name="StkResults" Orientation="Vertical" Margin="10" /> -->
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
+36 -4
View File
@@ -28,12 +28,25 @@ public partial class ResultWindow : Window
private void GenerateView(List<KasPerson> result) private void GenerateView(List<KasPerson> result)
{ {
try
{
// Filter to only show persons with errors // Filter to only show persons with errors
var result_with_errors = result.Where(p => p.PersonError != null).ToList(); var result_with_errors = result.Where(p => p.PersonError != null).ToList();
LblResultCount.Content = $"{result_with_errors.Count}/{ur_result.Count} Ergebnisse"; LblResultCount.Content = $"{result_with_errors.Count}/{ur_result.Count} Ergebnisse";
StkResults.Children.Clear(); // TbResults.Text = "";
foreach (var person in result_with_errors) StkResults.Children.Add(CreatePersonGrid(person)); // foreach (var person in result_with_errors) TbResults.Text += person.PersonError.GetString()+"\n";
LbResults.Items.Clear();
foreach (var person in result_with_errors) LbResults.Items.Add(person.PersonError.ToString(person));
// StkResults.Children.Clear();
// foreach (var person in result_with_errors) StkResults.Children.Add(CreatePersonGrid(person));
}
catch (Exception ex)
{
Logger.Log("Error while generating result view: " + ex.Message, Logger.LogType.Warning);
}
} }
private Grid CreatePersonGrid(KasPerson person) private Grid CreatePersonGrid(KasPerson person)
@@ -101,6 +114,8 @@ public partial class ResultWindow : Window
} }
private void Load(List<KasPerson> result) private void Load(List<KasPerson> result)
{
try
{ {
var knownErrors = new List<AddressCheck.ErrorTypes>(); var knownErrors = new List<AddressCheck.ErrorTypes>();
var knownWarnings = new List<AddressCheck.WarningTypes>(); var knownWarnings = new List<AddressCheck.WarningTypes>();
@@ -141,12 +156,20 @@ public partial class ResultWindow : Window
GenerateView(result); GenerateView(result);
} }
catch (Exception ex)
{
Logger.Log("Error while showing naming window: " + ex.Message, Logger.LogType.Warning);
}
}
private void BtnUpdateFilter_OnClick(object? sender, RoutedEventArgs e) private void BtnUpdateFilter_OnClick(object? sender, RoutedEventArgs e)
{ {
} }
private void UpdateFilter() private void UpdateFilter()
{
try
{ {
var temp_result = new List<KasPerson>(); var temp_result = new List<KasPerson>();
@@ -201,8 +224,17 @@ public partial class ResultWindow : Window
LblResultCount.Content = $"{temp_result.Count}/{ur_result.Count} Ergebnisse"; LblResultCount.Content = $"{temp_result.Count}/{ur_result.Count} Ergebnisse";
StkResults.Children.Clear(); LbResults.Items.Clear();
foreach (var person in temp_result) StkResults.Children.Add(CreatePersonGrid(person)); foreach (var person in temp_result) LbResults.Items.Add(person.PersonError.ToString(person));
// TbResults.Text = "";
// foreach (var person in temp_result) TbResults.Text += person.PersonError.GetString() +"\n";
// StkResults.Children.Clear();
// foreach (var person in temp_result) StkResults.Children.Add(CreatePersonGrid(person));
} catch (Exception ex)
{
Logger.Log("Error while updating filter: " + ex.Message, Logger.LogType.Warning);
}
} }
+16 -2
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -37,6 +38,8 @@ public class AddressCheck
} }
public async Task<List<KasPerson>> Perform(int id) public async Task<List<KasPerson>> Perform(int id)
{
try
{ {
// Find the index of the address set with the given id // Find the index of the address set with the given id
var adset_index = -1; var adset_index = -1;
@@ -237,7 +240,8 @@ public class AddressCheck
// Directly set PersonError in the address set // Directly set PersonError in the address set
var person_index = adset.KasPersons.IndexOf(person); var person_index = adset.KasPersons.IndexOf(person);
if (person_index >= 0) if (person_index >= 0)
Settings._instance.addressSets.addresses[adset_index].KasPersons[person_index].PersonError = Settings._instance.addressSets.addresses[adset_index].KasPersons[person_index]
.PersonError =
new KasPersonError((errors, warnings)); new KasPersonError((errors, warnings));
} }
@@ -247,7 +251,8 @@ public class AddressCheck
await Dispatcher.UIThread.InvokeAsync(() => await Dispatcher.UIThread.InvokeAsync(() =>
{ {
if (hasFaults) if (hasFaults)
_progress.AddToLog($"Person mit id {person.id} ist fehlerhaft"); _progress.AddToLog($"Person mit id {person.id} ist fehlerhaft",
Convert.ToInt32(percent).ToString());
_progress.ChangePercentage(percent); _progress.ChangePercentage(percent);
}); });
@@ -260,5 +265,14 @@ public class AddressCheck
return Settings._instance.addressSets.addresses[adset_index].KasPersons return Settings._instance.addressSets.addresses[adset_index].KasPersons
.Where(p => p.PersonError != null) .Where(p => p.PersonError != null)
.ToList(); .ToList();
} }
catch (Exception ex)
{
Logger.Log($"Error while performing address check: {ex.Message}", Logger.LogType.Error);
}
return null;
}
} }
+29 -1
View File
@@ -43,7 +43,8 @@ public static class AddressCreator
public static string? CreateFinalMarkdownString(int id) public static string? CreateFinalMarkdownString(int id)
{ {
// Maximum seven lines of information // Maximum seven lines of information
try
{
// find the address // find the address
KasPerson? address = null; KasPerson? address = null;
var string_address = ""; var string_address = "";
@@ -197,9 +198,18 @@ public static class AddressCreator
if (address_line_count > 1) return string_address; if (address_line_count > 1) return string_address;
return null; return null;
} }
catch (Exception ex)
{
Logger.Log($"Error while creating markdown string: {ex.Message}", Logger.LogType.Error);
}
return null;
}
public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel, public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel,
string name, string namezus) string name, string namezus)
{
try
{ {
if (!string.IsNullOrWhiteSpace(anredezus)) if (!string.IsNullOrWhiteSpace(anredezus))
return string.Join(" ", return string.Join(" ",
@@ -215,6 +225,14 @@ public static class AddressCreator
) )
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})"); + (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
} }
catch (Exception ex)
{
Logger.Log($"Error while performing address name line creation: {ex.Message}", Logger.LogType.Error);
}
return null;
}
/// <summary> /// <summary>
/// Returns true if a plz (or pplz) is valid /// Returns true if a plz (or pplz) is valid
@@ -223,6 +241,8 @@ public static class AddressCreator
/// <param name="land">country, to check the plz</param> /// <param name="land">country, to check the plz</param>
/// <returns>true or faslse, depending on result :+1:</returns> /// <returns>true or faslse, depending on result :+1:</returns>
public static bool CheckPLZ(string plz, string land) public static bool CheckPLZ(string plz, string land)
{
try
{ {
if (string.IsNullOrWhiteSpace(plz)) return false; if (string.IsNullOrWhiteSpace(plz)) return false;
@@ -249,4 +269,12 @@ public static class AddressCreator
// For non-German countries, accept any non-empty postal code // For non-German countries, accept any non-empty postal code
return true; return true;
} }
catch (Exception ex)
{
Logger.Log($"Error while performing address plz check: {ex.Message}", Logger.LogType.Error);
}
return false;
}
} }
+140
View File
@@ -0,0 +1,140 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Logof_Client;
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)
{
try
{
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;
}
catch (Exception ex)
{
Logger.Log($"Error while importing address patch: {ex.Message}", Logger.LogType.Error);
}
return null;
}
public override string ToString()
{
try
{
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();
}
catch (Exception ex)
{
Logger.Log($"Error while parsing: {ex.Message}", Logger.LogType.Error);
}
return "Error while parsing";
}
}
+11 -9
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Logof_Client; namespace Logof_Client;
@@ -9,15 +10,16 @@ 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) try
// foreach (var p in failed_addresses) {
// {
// if (k.refsid != p.Item1) continue; }
// catch (Exception ex)
// if (p.Item1.Contains(AddressCheck.WarningTypes.DoubledRefsid)) {
// { Logger.Log($"Error while performing address repair: {ex.Message}", Logger.LogType.Error);
// } }
// }
return null;
return null; return null;
} }
+84
View File
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace Logof_Client;
public class AddressShortener(ProgressWindow progressWindow)
{
private readonly ProgressWindow _progress = progressWindow;
public async Task Perform(KasAddressList list)
{
try
{
List<int> doubled_ids = new List<int>();
for (int i = 0; i < list.KasPersons.Count; i++)
{
var address = list.KasPersons[i];
for (int j = 0; j < list.KasPersons.Count; j++)
{
if (i == j) continue;
var sec_address = list.KasPersons[j];
if (address.refsid == sec_address.refsid && !doubled_ids.Contains(address.refsid) && address.refsid != 0)
{
doubled_ids.Add(address.refsid);
}
}
}
// delete doubled addresses by refsid
foreach (int id in doubled_ids)
{
// does this remove both of the doubled addresses?
list.KasPersons.Remove(list.KasPersons.FirstOrDefault(x => x.refsid == id));
}
List<int> toRemove = new List<int>();
foreach (var address in list.KasPersons)
{
try
{
if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.NoPLZorPPLZ))
{
toRemove.Add(address.id);
}
else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PlzNotUsable) &&
address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PPlzNotUsable))
{
toRemove.Add(address.id);
} else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PlzNotUsable) &&
address.PersonError.warnings.Contains(AddressCheck.WarningTypes.NoPPLZ))
{
toRemove.Add(address.id);
}
else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PPlzNotUsable) &&
address.PersonError.warnings.Contains(AddressCheck.WarningTypes.NoPLZ))
{
toRemove.Add(address.id);
}
}
catch
{
Console.WriteLine("PersonError not accessible: " + address.id);
}
}
// delete doubled addresses by refsid
foreach (int id in toRemove)
{
// does this remove both of the doubled addresses?
list.KasPersons.Remove(list.KasPersons.Find(x => x.id == id));
}
}
catch (Exception ex)
{
Logger.Log($"Error while performing address shortener: {ex.Message}", Logger.LogType.Error);
}
}
}
+116 -108
View File
@@ -16,41 +16,59 @@ public class CombineAddresses
_progress = progressWindow; _progress = progressWindow;
} }
public async Task<(KasAddressList, KasAddressList)> Perform(List<KasAddressList> address_lists, string type, public enum CombineType
{
refsid,
final_adress,
none
}
public async Task<(KasAddressList, KasAddressList)> Perform(List<KasAddressList> address_lists, string type, CombineType comb_type,
bool? exportUnused, bool? deleteOld)
{
try
{
var result = await Execute(address_lists,type,comb_type,exportUnused);
if (deleteOld == true)
{
foreach (var list in address_lists)
{
Settings._instance.addressSets.addresses.Remove(list);
}
}
return result;
}
catch (Exception ex)
{
Logger.Log($"Error while performing address combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
private async Task<(KasAddressList, KasAddressList)> Execute(List<KasAddressList> address_lists, string type, CombineType comb_type,
bool? exportUnused) bool? exportUnused)
{ {
var res = await Task.Run(async () => try
{ {
if (type == "difference") return Difference(address_lists, exportUnused); if (type == "difference") return await Difference(address_lists, comb_type, exportUnused);
if (type == "union") return Union(address_lists, exportUnused); if (type == "union") return await Union(address_lists, comb_type, exportUnused);
if (type == "intersection") return Intersection(address_lists, exportUnused); if (type == "intersection") return await Intersection(address_lists, comb_type, exportUnused);
if (type == "symdiff") return SymmetricDifference(address_lists, exportUnused); if (type == "symdiff") return await SymmetricDifference(address_lists, comb_type, 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); return (null, null);
} }
catch (Exception ex)
{
Logger.Log($"Error while executing address combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
/// <summary> /// <summary>
@@ -60,10 +78,13 @@ public class CombineAddresses
/// <param name="second">Second 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> /// <param name="only_refsid">If true, only a refsid-check will be done</param>
/// <returns></returns> /// <returns></returns>
public bool CompareAddresses(KasPerson first, KasPerson second, bool only_refsid = false) public bool CompareAddresses(KasPerson first, KasPerson second, CombineType comb_type)
{ {
if (first.refsid == second.refsid) return true;
if (!only_refsid) // A refsid of 0 means "missing", so it must not collapse unrelated entries.
if(comb_type == CombineType.refsid)
if (first.refsid != 0 && second.refsid != 0 && first.refsid == second.refsid) return true;
if (comb_type == CombineType.final_adress)
if (first.name == second.name && if (first.name == second.name &&
first.anrede == second.anrede && first.anrede == second.anrede &&
first.anredzus == second.anredzus && first.anredzus == second.anredzus &&
@@ -92,12 +113,14 @@ public class CombineAddresses
return false; return false;
} }
public async Task<(KasAddressList, KasAddressList)> Difference(List<KasAddressList> address_lists, public async Task<(KasAddressList, KasAddressList)> Difference(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, bool? return_unused,
Progress? progress = null) Progress? progress = null)
{
try
{ {
if (address_lists == null || address_lists.Count == 0) if (address_lists == null || address_lists.Count == 0)
return (new KasAddressList(KasAddressList.GenerateName("difference")), null); return (new KasAddressList(await KasAddressList.GenerateName("difference")), null);
progress ??= new Progress progress ??= new Progress
{ {
@@ -109,12 +132,14 @@ public class CombineAddresses
var restUnion = new List<KasPerson>(); var restUnion = new List<KasPerson>();
for (var i = 1; i < address_lists.Count; i++) for (var i = 1; i < address_lists.Count; i++)
restUnion.AddRange(address_lists[i].KasPersons); restUnion.AddRange(address_lists[i].KasPersons);
var result = new KasAddressList(KasAddressList.GenerateName("difference")); var result = new KasAddressList(await KasAddressList.GenerateName("difference"));
var second_result = new KasAddressList(KasAddressList.GenerateName("difference_rest")); var second_result = new KasAddressList("none");
if(return_unused == true)
second_result = new KasAddressList(await KasAddressList.GenerateName("difference_rest", false));
foreach (var person in address_lists[0].KasPersons) foreach (var person in address_lists[0].KasPersons)
{ {
var isDouble = restUnion.Any(p => CompareAddresses(person, p)); var isDouble = restUnion.Any(p => CompareAddresses(person, p, comb_type));
if (!isDouble) if (!isDouble)
result.KasPersons.Add(person); result.KasPersons.Add(person);
else else
@@ -131,13 +156,25 @@ public class CombineAddresses
if (return_unused == true) return (result, second_result); if (return_unused == true) return (result, second_result);
return (result, null); return (result, null);
} }
catch (Exception ex)
{
Logger.Log($"Error while performing difference-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
public async Task<(KasAddressList, KasAddressList)> Union(List<KasAddressList> address_lists, bool? return_unused, public async Task<(KasAddressList, KasAddressList)> Union(List<KasAddressList> address_lists, CombineType comb_type, bool? return_unused,
Progress progress = null) Progress progress = null)
{ {
var result = new KasAddressList(KasAddressList.GenerateName("union")); try
var second_result = new KasAddressList(KasAddressList.GenerateName("union_rest")); {
var result = new KasAddressList(await KasAddressList.GenerateName("union"));
var second_result = new KasAddressList("none");
if(return_unused == true)
second_result = new KasAddressList(await KasAddressList.GenerateName("union_rest", false));
if (address_lists == null || address_lists.Count == 0) if (address_lists == null || address_lists.Count == 0)
return (result, null); return (result, null);
@@ -148,7 +185,7 @@ public class CombineAddresses
foreach (var list in address_lists) foreach (var list in address_lists)
foreach (var person in list.KasPersons) foreach (var person in list.KasPersons)
{ {
if (!result.KasPersons.Any(existing => CompareAddresses(existing, person))) if (!result.KasPersons.Any(existing => CompareAddresses(existing, person, comb_type)))
result.KasPersons.Add(person); result.KasPersons.Add(person);
else else
second_result.KasPersons.Add(person); second_result.KasPersons.Add(person);
@@ -168,6 +205,14 @@ public class CombineAddresses
if (return_unused == true) return (result, second_result); if (return_unused == true) return (result, second_result);
return (result, null); return (result, null);
} }
catch (Exception ex)
{
Logger.Log($"Error while performing union-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
public async Task<KasAddressList> MoveDuplicatesToNew() public async Task<KasAddressList> MoveDuplicatesToNew()
@@ -175,11 +220,14 @@ public class CombineAddresses
return null; return null;
} }
public async Task<(KasAddressList, KasAddressList)> Intersection(List<KasAddressList> address_lists, public async Task<(KasAddressList, KasAddressList)> Intersection(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, Progress progress = null) bool? return_unused, Progress progress = null)
{ {
var result = new KasAddressList(KasAddressList.GenerateName("intersection")); try
var second_result = new KasAddressList(KasAddressList.GenerateName("intersection_rest")); {
var result = new KasAddressList(await KasAddressList.GenerateName("intersection"));
var second_result = new KasAddressList("none");
if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("intersection_rest", false));
if (address_lists == null || address_lists.Count == 0) if (address_lists == null || address_lists.Count == 0)
return (result, null); return (result, null);
@@ -195,7 +243,7 @@ public class CombineAddresses
{ {
// Prüfen, ob Person in *allen* anderen Listen vorkommt // Prüfen, ob Person in *allen* anderen Listen vorkommt
var isInAll = otherLists.All(list => var isInAll = otherLists.All(list =>
list.KasPersons.Any(existing => CompareAddresses(existing, person))); list.KasPersons.Any(existing => CompareAddresses(existing, person, comb_type)));
if (isInAll) if (isInAll)
result.KasPersons.Add(person); result.KasPersons.Add(person);
@@ -220,13 +268,24 @@ public class CombineAddresses
if (return_unused == true) return (result, second_result); if (return_unused == true) return (result, second_result);
return (result, null); return (result, null);
} }
catch (Exception ex)
{
Logger.Log($"Error while performing intersection-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
public async Task<(KasAddressList, KasAddressList)> SymmetricDifference(List<KasAddressList> address_lists, public async Task<(KasAddressList, KasAddressList)> SymmetricDifference(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, Progress progress = null) bool? return_unused, Progress progress = null)
{ {
var result = new KasAddressList(KasAddressList.GenerateName("symmetric_difference")); try
var second_result = new KasAddressList(KasAddressList.GenerateName("symmetric_rest")); {
var result = new KasAddressList(await KasAddressList.GenerateName("symmetric_difference"));
var second_result = new KasAddressList("none");
if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("symmetric_rest", false));
if (address_lists == null || address_lists.Count == 0) if (address_lists == null || address_lists.Count == 0)
return (result, null); return (result, null);
@@ -241,7 +300,7 @@ public class CombineAddresses
foreach (var person in list.KasPersons) foreach (var person in list.KasPersons)
{ {
// Prüfen, ob schon vorhanden // Prüfen, ob schon vorhanden
var existing = allPersons.FirstOrDefault(p => CompareAddresses(p.person, person)); var existing = allPersons.FirstOrDefault(p => CompareAddresses(p.person, person, comb_type));
if (existing.person != null) if (existing.person != null)
{ {
// Falls schon drin → Vorkommen erhöhen // Falls schon drin → Vorkommen erhöhen
@@ -278,66 +337,15 @@ public class CombineAddresses
if (return_unused == true) return (result, second_result); if (return_unused == true) return (result, second_result);
return (result, null); return (result, null);
} }
catch (Exception ex)
{
Logger.Log($"Error while performing symdiff-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,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 class Progress
+86
View File
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Logof_Client;
public class CsvBuilder
{
private readonly string Header;
private readonly List<object> Instances;
private readonly KasAddressList KasAddressList;
private readonly char Separator;
public CsvBuilder(string header, List<object> instances, char separator = ',')
{
Header = header;
Instances = instances;
Separator = separator;
}
public CsvBuilder(string header, KasAddressList instances, char separator = ',')
{
Header = header;
KasAddressList = instances;
Separator = separator;
}
public string? BuildKas()
{
var result = new StringBuilder();
result.AppendLine(Header);
foreach (var l in KasAddressList.KasPersons)
result.AppendLine(string.Join(Separator, new[]
{
EscapeCsvField(l.refsid.ToString()),
EscapeCsvField(l.anrede),
EscapeCsvField(l.titel),
EscapeCsvField(l.vorname),
EscapeCsvField(l.adel),
EscapeCsvField(l.name),
EscapeCsvField(l.namezus),
EscapeCsvField(l.anredzus),
EscapeCsvField(l.strasse),
EscapeCsvField(l.strasse2),
EscapeCsvField(l.plz),
EscapeCsvField(l.ort),
EscapeCsvField(l.land),
EscapeCsvField(l.pplz),
EscapeCsvField(l.postfach),
EscapeCsvField(l.name1),
EscapeCsvField(l.name2),
EscapeCsvField(l.name3),
EscapeCsvField(l.name4),
EscapeCsvField(l.name5),
EscapeCsvField(l.funktion),
EscapeCsvField(l.funktion2),
EscapeCsvField(l.abteilung),
EscapeCsvField(l.funktionad)
}));
// weitere Cases
return result.ToString();
}
private string EscapeCsvField(string? value)
{
try
{
var field = value ?? string.Empty;
var mustQuote = field.Contains(Separator) || field.Contains('"') || field.Contains('\r') || field.Contains('\n');
if (!mustQuote)
return field;
return "\"" + field.Replace("\"", "\"\"") + "\"";
}
catch (Exception ex)
{
Logger.Log($"Error while escapting csv field: {ex.Message}",Logger.LogType.Warning);
}
return "";
}
}
+310
View File
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Logof_Client;
public class DataImport
{
public static async Task<(bool, KasAddressList)> ImportKasAddressList(Uri pathToCsv, AddressPatch patch = null,
char separator = ',')
{
if (patch == null)
return await ImportKasAddressListWithoutPatch(pathToCsv, separator);
return await ImportKasAddressListWithPatch(pathToCsv, patch, separator);
}
private static async Task<(bool, KasAddressList)> ImportKasAddressListWithoutPatch(Uri pathToCsv, char separator)
{
try
{
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 imported = new KasAddressList(
await KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = ParseCsvLine(line, separator);
if (parts.Length < 24)
{
Console.WriteLine($"Not enough columns in line: {line}");
continue;
}
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count),
ParseInt(parts[0]),
parts[1],
parts[2],
parts[3],
parts[4],
parts[5],
parts[6],
parts[7],
parts[8],
parts[9],
parts[10],
parts[11],
parts[12],
parts[13],
parts[14],
parts[15],
parts[16],
parts[17],
parts[18],
parts[19],
parts[20],
parts[21],
parts[22],
parts[23]
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Logger.Log($"Error while creating new kas person (import): {ex.Message}",Logger.LogType.Error);
Console.WriteLine(ex.StackTrace);
return (false, null);
}
}
return (true, imported);
}
catch (Exception ex)
{
Logger.Log($"Error while importing kas address list without patch: {ex.Message}",Logger.LogType.Error);
}
return (false, null);
}
private static async Task<(bool, KasAddressList)> ImportKasAddressListWithPatch(Uri pathToCsv, AddressPatch patch,
char separator)
{
try
{
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 = ParseCsvLine(headerLine, separator);
var imported = new KasAddressList(
await 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 = ParseCsvLine(line, separator);
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 refsid = 0;
if (patch.has_refsid)
refsid = ParseInt(GetField("refsid"));
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count), 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)
{
Logger.Log($"Error while creating kas person (import, patch): {ex.Message}",Logger.LogType.Error);
Console.WriteLine(ex.StackTrace);
return (false, null);
}
}
return (true, imported);
}
catch (Exception ex)
{
Logger.Log($"Error while importing kas address list with patch: {ex.Message}",Logger.LogType.Error);
}
// int GenerateNewRefsid()
// {
// var biggest = last_refsid;
// foreach (var set in Settings._instance.addressSets.addresses)
// foreach (var address in set.KasPersons)
// if (biggest < address.id)
// biggest = address.id + 1;
//
// last_refsid = biggest + 1;
// return last_refsid;
// }
return (false, null);
}
private static int ParseInt(string input)
{
return int.TryParse(input, out var result) ? result : 0;
}
private static string[] ParseCsvLine(string line, char separator)
{
try
{
var fields = new List<string>();
var current = new StringBuilder();
var inQuotes = false;
for (var i = 0; i < line.Length; i++)
{
var c = line[i];
if (c == '"')
{
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
{
current.Append('"');
i++;
continue;
}
inQuotes = !inQuotes;
continue;
}
if (c == separator && !inQuotes)
{
fields.Add(current.ToString().Trim());
current.Clear();
continue;
}
current.Append(c);
}
fields.Add(current.ToString().Trim());
return fields.ToArray();
}
catch (Exception ex)
{
Logger.Log($"Error while persing csv line: {ex.Message}",Logger.LogType.Error);
}
return [];
}
}
+166 -43
View File
@@ -1,39 +1,91 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using PdfSharp; using PdfSharp;
using PdfSharp.Drawing; using PdfSharp.Drawing;
using PdfSharp.Fonts;
using PdfSharp.Pdf; using PdfSharp.Pdf;
namespace Logof_Client; namespace Logof_Client;
public class PdfBuilder public class PdfBuilder
{ {
// Table layout private readonly PdfExportSettings _settings;
private const int CellsPerRow = 3; private readonly XFont _boldFont;
private const int CellsPerPage = 21; // 3 columns × 7 rows private readonly XFont _regularFont;
private readonly XFont _boldFont = new("Arial", 9, XFontStyleEx.Bold); private readonly XFont _smallFont;
private readonly double _cellHeight = 42.43; // mm
private readonly double _cellPaddingBottom = 5; // mm
// Padding inside cells public PdfBuilder(PdfExportSettings? settings = null)
private readonly double _cellPaddingLeft = 5; // mm {
private readonly double _cellPaddingTop = 5; // mm try
{
EnsureFontResolverRegistered();
_settings = settings ?? new PdfExportSettings();
// Cell dimensions (in mm) // Select first font from build output fonts folder (AppContext.BaseDirectory/fonts)
private readonly double _cellWidth = 70; // mm var chosenFamily = "Arial";
try
{
if (Directory.Exists(Global._instance.font_path))
{
var first = Directory.EnumerateFiles(Global._instance.font_path, "*.ttf").FirstOrDefault();
if (!string.IsNullOrEmpty(first))
chosenFamily = StripStyleSuffix(Path.GetFileNameWithoutExtension(first)) ?? chosenFamily;
}
}
catch (Exception ex)
{
Logger.Log($"Error while searching for fonts: {ex.Message}",Logger.LogType.Error);
chosenFamily = "Arial";
}
// Font settings _boldFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Bold);
private readonly double _fontSize = 9; _regularFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Regular);
_smallFont = new XFont(chosenFamily, _settings.smallFontSize, XFontStyleEx.Regular);
}
catch (Exception ex)
{
Logger.Log($"Error while font resolving: {ex.Message}",Logger.LogType.Error);
}
private readonly double _marginBottom = 1; // mm
// Paper and layout settings }
private readonly double _marginLeft = 0; // mm
private readonly double _marginRight = 0; // mm private static void EnsureFontResolverRegistered()
private readonly double _marginTop = 0; // mm {
private readonly XFont _regularFont = new("Arial", 9, XFontStyleEx.Regular); try
private readonly XFont _smallFont = new("Arial", 6, XFontStyleEx.Regular); {
if (GlobalFontSettings.FontResolver != null) return;
//var fontsDir = Path.Combine(AppContext.BaseDirectory, "fonts");
GlobalFontSettings.FontResolver = new StableFontResolver(Global._instance.font_path);
}
catch (Exception ex)
{
Logger.Log($"Error while ensuring font resolver register state: {ex.Message}",Logger.LogType.Error);
}
}
private static string StripStyleSuffix(string name)
{
try
{
if (string.IsNullOrEmpty(name)) return name;
var idx = name.IndexOf('-');
if (idx < 0) idx = name.IndexOf('_');
if (idx > 0)
return name.Substring(0, idx);
return name;
}
catch (Exception ex)
{
Logger.Log($"Error while stripping style suffix: {ex.Message}",Logger.LogType.Error);
}
return null;
}
/// <summary> /// <summary>
@@ -44,6 +96,8 @@ public class PdfBuilder
/// <param name="outputPath">Path where the PDF should be saved</param> /// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText, public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText,
string outputPath) string outputPath)
{
try
{ {
// Find the AddressSet by ID // Find the AddressSet by ID
var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId); var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
@@ -82,6 +136,12 @@ public class PdfBuilder
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath); CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath);
} }
catch (Exception ex)
{
Logger.Log($"Error while creating address label pdf from address set with placeholder: {ex.Message}",Logger.LogType.Error);
}
}
/// <summary> /// <summary>
/// Creates a PDF document with a single placeholder cell for other information. /// Creates a PDF document with a single placeholder cell for other information.
@@ -90,6 +150,8 @@ public class PdfBuilder
/// <param name="placeholderText">Text for the first cell (top-left)</param> /// <param name="placeholderText">Text for the first cell (top-left)</param>
/// <param name="outputPath">Path where the PDF should be saved</param> /// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfWithPlaceholder(List<string> addresses, string placeholderText, string outputPath) public void CreateAddressLabelPdfWithPlaceholder(List<string> addresses, string placeholderText, string outputPath)
{
try
{ {
if (addresses == null || addresses.Count == 0) if (addresses == null || addresses.Count == 0)
throw new ArgumentException("Addresses array cannot be null or empty"); throw new ArgumentException("Addresses array cannot be null or empty");
@@ -112,23 +174,26 @@ public class PdfBuilder
document.Save(outputPath); document.Save(outputPath);
} }
catch (Exception ex)
{
Logger.Log($"Error while creating address label pdf with placeholder: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawPage(XGraphics gfx, List<string> addresses, ref int addressIndex) private void DrawPage(XGraphics gfx, List<string> addresses, ref int addressIndex)
{ {
var cellCount = 0; for (var row = 0; row < _settings.rowsPerPage; row++)
for (var row = 0; row < 7; row++)
{ {
for (var col = 0; col < 3; col++) for (var col = 0; col < _settings.columnsPerPage; col++)
{ {
if (addressIndex >= addresses.Count) break; if (addressIndex >= addresses.Count) break;
var x = MmToPoints(_marginLeft + col * _cellWidth); var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm());
var y = MmToPoints(_marginTop + row * _cellHeight); var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm());
DrawCell(gfx, x, y, addresses[addressIndex]); DrawCell(gfx, x, y, addresses[addressIndex]);
addressIndex++; addressIndex++;
cellCount++;
} }
if (addressIndex >= addresses.Count) break; if (addressIndex >= addresses.Count) break;
@@ -138,13 +203,13 @@ public class PdfBuilder
private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex, private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex,
ref bool isFirstCell, string placeholderText) ref bool isFirstCell, string placeholderText)
{ {
var cellCount = 0; try
for (var row = 0; row < 7; row++)
for (var col = 0; col < 3; col++)
{ {
var x = MmToPoints(_marginLeft + col * _cellWidth); for (var row = 0; row < _settings.rowsPerPage; row++)
var y = MmToPoints(_marginTop + row * _cellHeight); for (var col = 0; col < _settings.columnsPerPage; col++)
{
var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm());
var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm());
// First cell: placeholder // First cell: placeholder
if (isFirstCell) if (isFirstCell)
@@ -161,15 +226,21 @@ public class PdfBuilder
{ {
DrawEmptyCell(gfx, x, y); DrawEmptyCell(gfx, x, y);
} }
cellCount++;
} }
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing page with placholder: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawCell(XGraphics gfx, double x, double y, string? address) private void DrawCell(XGraphics gfx, double x, double y, string? address)
{ {
var cellWidthPoints = MmToPoints(_cellWidth); try
var cellHeightPoints = MmToPoints(_cellHeight); {
var cellWidthPoints = MmToPoints(GetCellWidthMm());
var cellHeightPoints = MmToPoints(GetCellHeightMm());
// Draw cell border // Draw cell border
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints); var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
@@ -178,23 +249,40 @@ public class PdfBuilder
// Draw address content if available // Draw address content if available
if (!string.IsNullOrEmpty(address)) DrawMarkdownText(gfx, address, x, y, cellWidthPoints, cellHeightPoints); if (!string.IsNullOrEmpty(address)) DrawMarkdownText(gfx, address, x, y, cellWidthPoints, cellHeightPoints);
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing cell: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawEmptyCell(XGraphics gfx, double x, double y) private void DrawEmptyCell(XGraphics gfx, double x, double y)
{ {
var cellWidthPoints = MmToPoints(_cellWidth); try
var cellHeightPoints = MmToPoints(_cellHeight); {
var cellWidthPoints = MmToPoints(GetCellWidthMm());
var cellHeightPoints = MmToPoints(GetCellHeightMm());
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints); var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect); gfx.DrawRectangle(XPens.Black, rect);
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing empty cell: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight) private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight)
{ {
var paddingLeftPoints = MmToPoints(_cellPaddingLeft); try
var paddingTopPoints = MmToPoints(_cellPaddingTop); {
var paddingBottomPoints = MmToPoints(_cellPaddingBottom); var paddingLeftPoints = MmToPoints(_settings.cellPaddingLeftMm);
var paddingRightPoints = MmToPoints(_settings.cellPaddingRightMm);
var paddingTopPoints = MmToPoints(_settings.cellPaddingTopMm);
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
var maxWidth = cellWidth - paddingLeftPoints * 2; var maxWidth = Math.Max(0, cellWidth - paddingLeftPoints - paddingRightPoints);
// Split text by newlines and remove empty trailing lines // Split text by newlines and remove empty trailing lines
var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None); var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None);
@@ -222,8 +310,16 @@ public class PdfBuilder
currentY += lineHeight; currentY += lineHeight;
} }
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown text: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth) private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
{
try
{ {
if (string.IsNullOrWhiteSpace(line)) return; if (string.IsNullOrWhiteSpace(line)) return;
var currentX = x; var currentX = x;
@@ -313,8 +409,16 @@ public class PdfBuilder
i = textEnd; i = textEnd;
} }
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown line: {ex.Message}",Logger.LogType.Error);
}
}
private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth) private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
{
try
{ {
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
return text; return text;
@@ -329,6 +433,13 @@ public class PdfBuilder
return string.Empty; return string.Empty;
} }
catch (Exception ex)
{
Logger.Log($"Error while truncating text to width: {ex.Message}",Logger.LogType.Error);
}
return null;
}
/// <summary> /// <summary>
/// Converts millimeters to points (1 mm = 2.834645669 points) /// Converts millimeters to points (1 mm = 2.834645669 points)
@@ -338,6 +449,18 @@ public class PdfBuilder
return mm * 2.834645669; return mm * 2.834645669;
} }
private double GetCellWidthMm()
{
var availableWidthMm = 210d - _settings.pageMarginLeftMm - _settings.pageMarginRightMm;
return availableWidthMm / _settings.columnsPerPage;
}
private double GetCellHeightMm()
{
var availableHeightMm = 297d - _settings.pageMarginTopMm - _settings.pageMarginBottomMm;
return availableHeightMm / _settings.rowsPerPage;
}
// Configuration methods to allow customization // Configuration methods to allow customization
/// <summary> /// <summary>
+29 -2
View File
@@ -62,13 +62,15 @@ public class StableFontResolver : IFontResolver
} }
} }
} }
catch catch (Exception ex)
{ {
// ignore resolver init errors Logger.Log($"Error while initializing FontResolver: {ex.Message}",Logger.LogType.Error);
} }
} }
private static string NormalizeStyle(string s) private static string NormalizeStyle(string s)
{
try
{ {
if (string.IsNullOrEmpty(s)) return "Regular"; if (string.IsNullOrEmpty(s)) return "Regular";
s = s.ToLowerInvariant(); s = s.ToLowerInvariant();
@@ -78,8 +80,17 @@ public class StableFontResolver : IFontResolver
if (s.Contains("regular") || s == "r") return "Regular"; if (s.Contains("regular") || s == "r") return "Regular";
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s); return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s);
} }
catch (Exception ex)
{
Logger.Log($"Error while normalizing style: {ex.Message}",Logger.LogType.Error);
}
return null;
}
public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic) public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
try
{ {
// If requested family exists, pick corresponding style if available // If requested family exists, pick corresponding style if available
string familyToUse = null; string familyToUse = null;
@@ -100,8 +111,17 @@ public class StableFontResolver : IFontResolver
// Face name format: Family#Style // Face name format: Family#Style
return new FontResolverInfo($"{familyToUse}#{style}"); return new FontResolverInfo($"{familyToUse}#{style}");
} }
catch (Exception ex)
{
Logger.Log($"Error while resolving typeface: {ex.Message}",Logger.LogType.Error);
}
return null;
}
public byte[] GetFont(string faceName) public byte[] GetFont(string faceName)
{
try
{ {
if (string.IsNullOrEmpty(faceName)) return null; if (string.IsNullOrEmpty(faceName)) return null;
@@ -133,4 +153,11 @@ public class StableFontResolver : IFontResolver
return null; return null;
} }
catch (Exception ex)
{
Logger.Log($"Error while getting font: {ex.Message}",Logger.LogType.Error);
}
return [];
}
} }
+12 -3
View File
@@ -1,8 +1,17 @@
<Window xmlns="https://github.com/avaloniaui" <Window 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.Wiki.EditorWindow" x:Class="Logof_Client.Wiki.EditorWindow"
Title="Wiki Editor (disabled)" Width="400" Height="150"> Title="Wiki Editor" MinWidth="600" MinHeight="350" WindowState="Maximized">
<Grid> <Grid Margin="10,0,10,10">
<TextBlock Margin="12" TextWrapping="Wrap">Coming soon.</TextBlock> <Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Spacing="10" Orientation="Horizontal">
<Button x:Name="BtnSave" Content="Speichern" Click="BtnSave_OnClick" />
<Button x:Name="BtnSaveAs" Content="Speichern unter..." Click="BtnSaveAs_OnClick" />
<Button x:Name="BtnDelete" Content="Löschen" Click="BtnDelete_OnClick" />
</StackPanel>
<TextBox AcceptsTab="True" AcceptsReturn="True" Grid.Row="1" x:Name="TbContent" />
</Grid> </Grid>
</Window> </Window>
+57 -5
View File
@@ -1,17 +1,69 @@
using System; using System;
using System.IO;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
namespace Logof_Client.Wiki; namespace Logof_Client.Wiki;
public partial class EditorWindow : Window public partial class EditorWindow : Window
{ {
private void InitializeComponent() public string filename = "";
{
Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(this);
}
public EditorWindow() public EditorWindow(string filename = "")
{
try
{ {
InitializeComponent(); InitializeComponent();
this.filename = PathUtilities.NormalizeFileSystemPath(filename);
if (!string.IsNullOrWhiteSpace(this.filename) && File.Exists(this.filename))
{
var content = TbContent;
if (content != null) content.Text = File.ReadAllText(this.filename);
Title = "Wiki Editor - " + filename;
}
else if (!string.IsNullOrWhiteSpace(this.filename))
{
MessageBox.Show(null, "Die Datei existiert nicht", "Fehler");
Close();
}
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
}
private void BtnSave_OnClick(object? sender, RoutedEventArgs e)
{
try
{
File.WriteAllText(filename, TbContent.Text);
MainWindow._instance.PopulateNavTree();
}
catch (Exception ex)
{
MessageBox.Show(null,
"Es ist ein Fehler aufgetreten. Bitte senden Sie ihn über git.mypapercloud.de/fierke/logofclient ein:\n" +
ex.StackTrace, "Fehler");
Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);
}
}
private void BtnSaveAs_OnClick(object? sender, RoutedEventArgs e)
{
MessageBox.Show(null,
"Feature noch nicht implemetiert.\nErstelle neue Dateien unter " +
PathUtilities.NormalizeFileSystemPath(Global._instance.wiki_storage_path),
"Fehler");
}
private async void BtnDelete_OnClick(object? sender, RoutedEventArgs e)
{
var result = await MessageBox.Show(null, "Sicher?", "Sicher?", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No) return;
try
{
File.Delete(filename);
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
MainWindow._instance.PopulateNavTree();
Close();
} }
} }
+14
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
@@ -11,6 +12,8 @@ namespace Logof_Client.Wiki;
public static class MarkdownRenderer public static class MarkdownRenderer
{ {
public static Control Render(string markdown) public static Control Render(string markdown)
{
try
{ {
var panel = new StackPanel { Spacing = 6 }; var panel = new StackPanel { Spacing = 6 };
if (string.IsNullOrWhiteSpace(markdown)) return panel; if (string.IsNullOrWhiteSpace(markdown)) return panel;
@@ -93,9 +96,14 @@ public static class MarkdownRenderer
} }
return panel; return panel;
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
return new Panel();
} }
private static string GetInlineText(ContainerInline? container) private static string GetInlineText(ContainerInline? container)
{
try
{ {
if (container == null) return string.Empty; if (container == null) return string.Empty;
var sb = new StringBuilder(); var sb = new StringBuilder();
@@ -115,6 +123,9 @@ public static class MarkdownRenderer
case LinkInline li: case LinkInline li:
sb.Append(GetInlineText(li)); sb.Append(GetInlineText(li));
break; break;
case LineBreakInline:
sb.Append("\n");
break;
default: default:
sb.Append(inline.ToString()); sb.Append(inline.ToString());
break; break;
@@ -122,5 +133,8 @@ public static class MarkdownRenderer
} }
return sb.ToString(); return sb.ToString();
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
return null;
} }
} }
+5 -2
View File
@@ -13,7 +13,7 @@ public class WikiService
// prefer global wiki storage path if configured // prefer global wiki storage path if configured
if (Global._instance != null && !string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path)) if (Global._instance != null && !string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path))
{ {
var cfg = Global._instance.wiki_storage_path; var cfg = PathUtilities.NormalizeFileSystemPath(Global._instance.wiki_storage_path);
WikiRootFullPath = Path.IsPathRooted(cfg) WikiRootFullPath = Path.IsPathRooted(cfg)
? cfg ? cfg
: Path.Combine(Directory.GetCurrentDirectory(), cfg); : Path.Combine(Directory.GetCurrentDirectory(), cfg);
@@ -25,8 +25,11 @@ public class WikiService
wikiRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), wikiRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient", "logofclient",
"wiki"); "wiki");
wikiRoot = PathUtilities.NormalizeFileSystemPath(wikiRoot);
WikiRoot = wikiRoot; WikiRoot = wikiRoot;
WikiRootFullPath = Path.Combine(Directory.GetCurrentDirectory(), wikiRoot); WikiRootFullPath = Path.IsPathRooted(wikiRoot)
? wikiRoot
: Path.Combine(Directory.GetCurrentDirectory(), wikiRoot);
} }
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-157
View File
@@ -1,157 +0,0 @@
\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}