diff --git a/Shared/Core/Rule/GkvRules.cs b/Shared/Core/Rule/GkvRules.cs
index d5a8d996e..baaceb2dd 100644
--- a/Shared/Core/Rule/GkvRules.cs
+++ b/Shared/Core/Rule/GkvRules.cs
@@ -91,6 +91,8 @@ namespace BS.Shared.Core.Rule
public static bool IsUnitDescriptionValid(IInvoiceItem invoiceItem)
{
+ if(invoiceItem == null) return false;
+
// GPos. 123456
// 0123456
return invoiceItem.UnitDescription.Length >= 8;
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner.sln b/Tools/BeWoLogCleaner/BeWoLogCleaner.sln
new file mode 100644
index 000000000..0718f5308
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35707.178 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeWoLogCleaner", "BeWoLogCleaner\BeWoLogCleaner.csproj", "{319D754B-02E6-46B9-8C3B-EEA6613584A6}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {319D754B-02E6-46B9-8C3B-EEA6613584A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {319D754B-02E6-46B9-8C3B-EEA6613584A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {319D754B-02E6-46B9-8C3B-EEA6613584A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {319D754B-02E6-46B9-8C3B-EEA6613584A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/App.config b/Tools/BeWoLogCleaner/BeWoLogCleaner/App.config
new file mode 100644
index 000000000..f2211e327
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/App.config
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/BeWoLogCleaner.csproj b/Tools/BeWoLogCleaner/BeWoLogCleaner/BeWoLogCleaner.csproj
new file mode 100644
index 000000000..af836ae57
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/BeWoLogCleaner.csproj
@@ -0,0 +1,101 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {319D754B-02E6-46B9-8C3B-EEA6613584A6}
+ Exe
+ BeWoLogCleaner
+ BeWoLogCleaner
+ v4.7.2
+ 512
+ true
+ true
+
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\..\Lib\log4net.dll
+
+
+ ..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.5\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll
+
+
+
+ ..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll
+
+
+
+ ..\packages\System.IO.Pipelines.10.0.5\lib\net462\System.IO.Pipelines.dll
+
+
+ ..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll
+
+
+
+ ..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll
+
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll
+
+
+ ..\packages\System.Text.Encodings.Web.10.0.5\lib\net462\System.Text.Encodings.Web.dll
+
+
+ ..\packages\System.Text.Json.10.0.5\lib\net462\System.Text.Json.dll
+
+
+ ..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
+
+
+ Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".
+
+
+
+
\ No newline at end of file
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/CleanRule.cs b/Tools/BeWoLogCleaner/BeWoLogCleaner/CleanRule.cs
new file mode 100644
index 000000000..894d014d0
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/CleanRule.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace BeWoLogCleaner
+{
+ public class CleanRule
+ {
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = "";
+ [JsonPropertyName("directory")]
+ public string Directory { get; set; } = "";
+ [JsonPropertyName("pattern")]
+ public string Pattern { get; set; } = "*.log";
+ [JsonPropertyName("recursive")]
+ public bool Recursive { get; set; } = false;
+ [JsonPropertyName("retentiondays")]
+ public int RetentionDays { get; set; } = 7;
+ [JsonPropertyName("action")]
+ public CleanAction Action { get; set; } = CleanAction.Delete;
+ }
+}
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/CleanerConfig.cs b/Tools/BeWoLogCleaner/BeWoLogCleaner/CleanerConfig.cs
new file mode 100644
index 000000000..f9a41fdf8
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/CleanerConfig.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace BeWoLogCleaner
+{
+ public class CleanerConfig
+ {
+ [JsonPropertyName("rules")]
+ public List Rules { get; set; } = new List();
+
+ [JsonPropertyName("dryrun")]
+ public bool DryRun { get; set; } = false;
+ }
+}
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/Enums.cs b/Tools/BeWoLogCleaner/BeWoLogCleaner/Enums.cs
new file mode 100644
index 000000000..7a06bdf5c
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/Enums.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BeWoLogCleaner
+{
+ public enum CleanAction
+ { Delete, Compress }
+}
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/LoggerUtils.cs b/Tools/BeWoLogCleaner/BeWoLogCleaner/LoggerUtils.cs
new file mode 100644
index 000000000..ccf76c34a
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/LoggerUtils.cs
@@ -0,0 +1,30 @@
+using log4net;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BeWoLogCleaner
+{
+ public static class LoggerUtils
+ {
+ private static bool _LoggerInit = false;
+
+ public static void InitLogger()
+ {
+ if (_LoggerInit) return;
+
+ log4net.Config.XmlConfigurator.Configure();
+
+ _LoggerInit = true;
+ }
+
+ public static ILog GetLogger(Type declaringType)
+ {
+ InitLogger();
+
+ return log4net.LogManager.GetLogger(declaringType);
+ }
+ }
+}
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/Program.cs b/Tools/BeWoLogCleaner/BeWoLogCleaner/Program.cs
new file mode 100644
index 000000000..96952346f
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/Program.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Compression;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Text.Json;
+using log4net;
+
+namespace BeWoLogCleaner
+{
+ internal class Program
+ {
+ static ILog Logger { get; set; }
+ static CleanerConfig Config { get; set; }
+
+ static void Main(string[] args)
+ {
+ Logger = LoggerUtils.GetLogger(typeof(Program));
+ ReadConfig();
+ Run();
+ }
+
+ static void ReadConfig()
+ {
+ var path = "clean_config.json";
+ var json = File.ReadAllText(path);
+ var json_obj = JsonSerializer.Deserialize(json);
+ Config = json_obj;
+ }
+
+ static void Run()
+ {
+ WriteLine("=== Log File Cleaner ===");
+ if (Config.DryRun)
+ WriteLine("*** DRY-RUN – es werden keine Dateien verändert ***");
+ WriteLine();
+
+ int totalFiles = 0;
+ long totalBytes = 0;
+
+ foreach (var rule in Config.Rules)
+ {
+ WriteLine($"[{rule.Name}]");
+ WriteLine($" Verzeichnis : {rule.Directory}");
+ WriteLine($" Muster : {rule.Pattern}");
+ WriteLine($" Alter : > {rule.RetentionDays} Tage");
+ WriteLine($" Aktion : {rule.Action}");
+ WriteLine();
+
+ if (!Directory.Exists(rule.Directory))
+ {
+ WriteLine($" ⚠ Verzeichnis nicht gefunden, übersprungen.");
+ WriteLine();
+ continue;
+ }
+
+ var searchOption = rule.Recursive
+ ? SearchOption.AllDirectories
+ : SearchOption.TopDirectoryOnly;
+
+ var files = Directory
+ .EnumerateFiles(rule.Directory, rule.Pattern, searchOption)
+ .Select(p => new FileInfo(p))
+ .Where(f => (DateTime.Now - f.LastWriteTime).TotalDays > rule.RetentionDays)
+ .ToList();
+
+ if (files.Count == 0)
+ {
+ WriteLine(" Keine Dateien gefunden.");
+ WriteLine();
+ continue;
+ }
+
+ foreach (var file in files)
+ {
+ long size = file.Length;
+
+ switch (rule.Action)
+ {
+ case CleanAction.Delete:
+ var str = $" 🗑 {file.FullName} ({FormatSize(size)})";
+ if (!Config.DryRun)
+ {
+ file.Delete();
+ totalBytes += size;
+ WriteLine(str + " → gelöscht");
+ }
+ else
+ {
+ WriteLine(str + " → [DRY-RUN]");
+ }
+ break;
+
+ case CleanAction.Compress:
+ var gzPath = file.FullName + ".gz";
+ var str1 = $" 📦 {file.FullName} ({FormatSize(size)})";
+ if (!Config.DryRun)
+ {
+ CompressFile(file, gzPath);
+ long saved = size - new FileInfo(gzPath).Length;
+ totalBytes += saved;
+ WriteLine($"{str1} → komprimiert (gespart: {FormatSize(saved)})");
+ }
+ else
+ {
+ WriteLine($"{str1} → [DRY-RUN]");
+ }
+ break;
+ }
+
+ totalFiles++;
+ }
+
+ WriteLine();
+ }
+
+ WriteLine("=== Zusammenfassung ===");
+ WriteLine($" Dateien verarbeitet : {totalFiles}");
+ WriteLine($" Speicher freigegeben: {FormatSize(totalBytes)}");
+ if (Config.DryRun)
+ WriteLine(" (DRY-RUN – nichts wurde wirklich verändert)");
+ }
+
+ static void WriteLine(string line = "")
+ {
+ Console.WriteLine(line);
+
+ Logger.Info(line);
+ }
+
+ private static void CompressFile(FileInfo source, string destPath)
+ {
+ using (var input = source.OpenRead())
+ {
+ using (var output = File.Create(destPath))
+ {
+ using (var gz = new GZipStream(output, CompressionLevel.Optimal))
+ {
+ input.CopyTo(gz);
+ source.Delete();
+ }
+ }
+ }
+ }
+
+ private static string FormatSize(long bytes)
+ {
+ if (bytes < 1_024)
+ return $"{bytes} B";
+ if (bytes < 1_048_576)
+ return $"{bytes / 1024.0:F1} KB";
+ if (bytes < 1_073_741_824)
+ return $"{bytes / 1_048_576.0:F1} MB";
+
+ return $"{bytes / 1_073_741_824.0:F2} GB";
+ }
+ }
+}
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/Properties/AssemblyInfo.cs b/Tools/BeWoLogCleaner/BeWoLogCleaner/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..2afaf1d01
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Allgemeine Informationen über eine Assembly werden über die folgenden
+// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
+// die einer Assembly zugeordnet sind.
+[assembly: AssemblyTitle("BeWoLogCleaner")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BeWoLogCleaner")]
+[assembly: AssemblyCopyright("Copyright © 2026")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
+// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
+// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
+[assembly: ComVisible(false)]
+
+// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
+[assembly: Guid("319d754b-02e6-46b9-8c3b-eea6613584a6")]
+
+// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
+//
+// Hauptversion
+// Nebenversion
+// Buildnummer
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/clean_config.json b/Tools/BeWoLogCleaner/BeWoLogCleaner/clean_config.json
new file mode 100644
index 000000000..3b8363216
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/clean_config.json
@@ -0,0 +1,12 @@
+{
+ "dryrun": true,
+ "rules": [
+ {
+ "name": "ai_requests",
+ "directory": "C:\\Projects\\Bewo\\BeWo - Main\\Host\\logs\\LogFiles\\ai\\requests",
+ "recursive": false,
+ "retentiondays": 7,
+ "action": 0
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Tools/BeWoLogCleaner/BeWoLogCleaner/packages.config b/Tools/BeWoLogCleaner/BeWoLogCleaner/packages.config
new file mode 100644
index 000000000..7be4a0e9f
--- /dev/null
+++ b/Tools/BeWoLogCleaner/BeWoLogCleaner/packages.config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file