diff --git a/bsmd.Tool/App.config b/bsmd.Tool/App.config index ec50e784..9a2087dd 100644 --- a/bsmd.Tool/App.config +++ b/bsmd.Tool/App.config @@ -1,36 +1,72 @@ - + -
- -
+
+ +
- - + + - - - - - - + + + + + + - + - + - - - - replace me! - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + replace me! + + + + + + 4 + + + C:\temp\hisnord + + + C:\temp\dbh + + + diff --git a/bsmd.Tool/Echolot.cs b/bsmd.Tool/Echolot.cs new file mode 100644 index 00000000..1e3dcf88 --- /dev/null +++ b/bsmd.Tool/Echolot.cs @@ -0,0 +1,224 @@ +// Copyright (c) 2020- schick Informatik +// Description: The purpose of this tool is to evaluate files sent both through HIS-Nord and dbh +// to evaluate how many classes were sent at what time and by whom to the purpose of improved employee +// time planning + + +using ClosedXML.Excel; +using log4net; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace bsmd.Tool +{ + internal static class Echolot + { + + private static readonly ILog _log = LogManager.GetLogger(typeof(Echolot)); + + static readonly HashSet ValidGroupingKeys = new HashSet + { + "VISIT", + "TRANSIT", + "NOA_NOD", + "NOANOD", + "ATA", + "ATD", + "SEC", + "POBA", + "POBD", + "NAME", + "TIEFA", + "TIEFD", + "BKRA", + "BKRD", + "STAT", + "LADG", + "INFO", + "SERV", + "PRE72H", + "MDH", + "WAS", + "CREWA", + "PASA", + "BPOL", + "TOWA", + "TOWD", + "HAZA", + "HAZD", + "AGNT", + "STO", + "CREWD", + "PASD", + "WAS_RCPT" + }; + + static readonly HashSet IgnoreGroupingKeys = new HashSet + { + "VISIT", + "TRANSIT", + "ATA", + "ATD" + }; + + internal static void Evaluate(string outputFolder, int maxThreads) + { + DateTime executionTime = DateTime.Now; + + #region first scan: dbh files + + string inputFolder = Properties.Settings.Default.DBH_Folder; + var files = Directory.GetFiles(inputFolder, "*.xml"); + var results = new ConcurrentBag(); + + Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = maxThreads }, file => + { + try + { + var doc = XDocument.Load(file); + + // Look for a valid grouping key at the root level + var groupingElem = doc.Root.Elements() + .FirstOrDefault(x => ValidGroupingKeys.Contains(x.Name.LocalName) && !IgnoreGroupingKeys.Contains(x.Name.LocalName)); + + if (groupingElem == null) + { + _log.InfoFormat("skipping {0}", file); + return; // Skip file + } + + var lastName = doc.Descendants("RPLastName").FirstOrDefault()?.Value?.Trim(); + var firstName = doc.Descendants("RPFirstName").FirstOrDefault()?.Value?.Trim(); + var timestampStr = doc.Descendants("Timestamp").FirstOrDefault()?.Value?.Trim(); + + DateTime timestamp = DateTime.Parse(timestampStr); + + results.Add(new ResultRow + { + FirstName = firstName, + LastName = lastName, + Timestamp = timestamp, + WeekStart = GetWeekStart(timestamp, executionTime.DayOfWeek), + Provider = "DBH" + }); + } + catch (Exception ex) + { + _log.Error(ex.ToString()); + } + }); + + #endregion + + #region second scan: his-nord files + + var inputFolder2 = Properties.Settings.Default.HISNORD_Folder; + var files2 = Directory.GetFiles(inputFolder2, "*.xml"); + + Parallel.ForEach(files2, new ParallelOptions { MaxDegreeOfParallelism = maxThreads }, file => + { + try + { + var doc = XDocument.Load(file); + + var match = Regex.Match(file, @"-([A-Z0-9_]+)\.xml$", RegexOptions.None); + + string key = ""; + if (match.Success) + { + key = match.Groups[1].Value; + } + + if((key.Length == 0) || IgnoreGroupingKeys.Contains(key)) + { + _log.InfoFormat("skipping {0}", file); + return; // Skip file + } + + var username = doc.Descendants("firstname").FirstOrDefault()?.Value?.Trim(); + if(username == null) + { + _log.WarnFormat("Username not found in file {0}", file); + return; + } + var splitname = username.Split(' '); + var lastName = splitname[1].Trim(); + var firstName = splitname[0].Trim(); + + DateTime timestamp = File.GetCreationTime(file); + + results.Add(new ResultRow + { + FirstName = firstName, + LastName = lastName, + Timestamp = timestamp, + WeekStart = GetWeekStart(timestamp, executionTime.DayOfWeek), + Provider = "HIS-NORD" + }); + } + catch (Exception ex) + { + _log.Error(ex.ToString()); + } + }); + + + #endregion + + var grouped = results + .GroupBy(r => r.WeekStart) + .OrderBy(g => g.Key); + + + // Write Excel + string excelFile = Path.Combine(outputFolder, $"echolot_{executionTime:yyyyMMdd_HHmmss}.xlsx"); + using (var workbook = new XLWorkbook()) + { + foreach (var weekGroup in grouped) + { + var ws = workbook.Worksheets.Add(weekGroup.Key.ToString("yyyy-MM-dd")); + ws.Cell(1, 1).Value = "Firstname"; + ws.Cell(1, 2).Value = "Lastname"; + ws.Cell(1, 3).Value = "Count"; + int row = 2; + + var orderedGroups = weekGroup + .GroupBy(x => new { x.FirstName, x.LastName }) + .OrderByDescending(g => g.Count()); // Use OrderBy for ascending + + + foreach (var nameGroup in orderedGroups) + { + ws.Cell(row, 1).Value = nameGroup.Key.FirstName; + ws.Cell(row, 2).Value = nameGroup.Key.LastName; + ws.Cell(row, 3).Value = nameGroup.Count(); + row++; + } + } + workbook.SaveAs(excelFile); + } + } + + static DateTime GetWeekStart(DateTime date, DayOfWeek weekStart) + { + int diff = (7 + (date.DayOfWeek - weekStart)) % 7; + return date.Date.AddDays(-1 * diff); + } + + class ResultRow + { + public string FirstName { get; set; } + public string LastName { get; set; } + public DateTime Timestamp { get; set; } + public DateTime WeekStart { get; set; } + public string Provider { get; set; } + } + + } +} diff --git a/bsmd.Tool/Options.cs b/bsmd.Tool/Options.cs index 026e703c..ae1a507d 100644 --- a/bsmd.Tool/Options.cs +++ b/bsmd.Tool/Options.cs @@ -25,6 +25,18 @@ namespace bsmd.Tool [Option("locodes", HelpText = "use this flag if you want to import locodes")] public bool ImportLocodes { get; set; } + [Option("echolot", HelpText = "use this flag to run the echolot output file evaluation")] + public bool Echolot { get; set; } + + [Option('i', "input_folder", HelpText = "Input folder")] + public string InputFolder { get; set; } + + [Option('o', "output_folder", HelpText = "Output folder")] + public string OutputFolder { get; set; } + + [Option("max_threads", HelpText = "Maximum amount of parallelism for folder parsing")] + public int? MaxThreads { get; set; } + [Option('s', "staledays", Default = 30, HelpText ="Delete files older than X days")] public int StaleDays { get; set; } diff --git a/bsmd.Tool/Program.cs b/bsmd.Tool/Program.cs index 54909cb8..82e2b3ac 100644 --- a/bsmd.Tool/Program.cs +++ b/bsmd.Tool/Program.cs @@ -1,4 +1,5 @@ -using CommandLine; +using bsmd.database; +using CommandLine; using log4net; using System; @@ -40,15 +41,24 @@ namespace bsmd.Tool LocodeSQliteImport.Import(o.LocodeDB, o.LocodeCSV); } } + if(o.Echolot) + { + string outputFolder = Properties.Settings.Default.EcholotOutputFolder; + if (!o.OutputFolder.IsNullOrEmpty()) + outputFolder = o.OutputFolder; + int maxThreads = 2; + if (o.MaxThreads.HasValue) maxThreads = o.MaxThreads.Value; + Echolot.Evaluate(outputFolder, maxThreads); + } }); } catch (Exception ex) { log.Fatal(ex.ToString()); + Console.WriteLine(ex.Message); result = 1; - } - Console.Read(); + } return result; } } diff --git a/bsmd.Tool/Properties/Settings.Designer.cs b/bsmd.Tool/Properties/Settings.Designer.cs index 4d40f00d..a26903d7 100644 --- a/bsmd.Tool/Properties/Settings.Designer.cs +++ b/bsmd.Tool/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace bsmd.Tool.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -23,15 +23,48 @@ namespace bsmd.Tool.Properties { } } - [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("replace me!")] public string ConnectionString { get { return ((string)(this["ConnectionString"])); } - set { - this["ConnectionString"] = value; + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string EcholotOutputFolder { + get { + return ((string)(this["EcholotOutputFolder"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("4")] + public int EcholotMaxThreads { + get { + return ((int)(this["EcholotMaxThreads"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("C:\\temp\\hisnord")] + public string HISNORD_Folder { + get { + return ((string)(this["HISNORD_Folder"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("C:\\temp\\dbh")] + public string DBH_Folder { + get { + return ((string)(this["DBH_Folder"])); } } } diff --git a/bsmd.Tool/Properties/Settings.settings b/bsmd.Tool/Properties/Settings.settings index 07cf4d90..3785f743 100644 --- a/bsmd.Tool/Properties/Settings.settings +++ b/bsmd.Tool/Properties/Settings.settings @@ -2,8 +2,20 @@ - + replace me! + + + + + 4 + + + C:\temp\hisnord + + + C:\temp\dbh + \ No newline at end of file diff --git a/bsmd.Tool/bsmd.Tool.csproj b/bsmd.Tool/bsmd.Tool.csproj index 92d23e99..877acf69 100644 --- a/bsmd.Tool/bsmd.Tool.csproj +++ b/bsmd.Tool/bsmd.Tool.csproj @@ -44,25 +44,62 @@ ..\bsmd.database\bin\Debug\bsmd.database.dll + + ..\packages\ClosedXML.0.105.0\lib\netstandard2.0\ClosedXML.dll + + + ..\packages\ClosedXML.Parser.2.0.0\lib\netstandard2.0\ClosedXML.Parser.dll + ..\packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll - - ..\packages\log4net.2.0.15\lib\net45\log4net.dll + + ..\packages\DocumentFormat.OpenXml.3.1.1\lib\net46\DocumentFormat.OpenXml.dll + + + ..\packages\DocumentFormat.OpenXml.Framework.3.1.1\lib\net46\DocumentFormat.OpenXml.Framework.dll + + + ..\packages\ExcelNumberFormat.1.1.0\lib\net20\ExcelNumberFormat.dll + + + ..\packages\log4net.3.1.0\lib\net462\log4net.dll + + + ..\packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll + + + ..\packages\RBush.Signed.4.0.0\lib\net47\RBush.dll + + + ..\packages\SixLabors.Fonts.1.0.0\lib\netstandard2.0\SixLabors.Fonts.dll + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + - - ..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.117.0\lib\net46\System.Data.SQLite.dll + + ..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.119.0\lib\net46\System.Data.SQLite.dll + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + @@ -76,6 +113,7 @@ + @@ -89,6 +127,7 @@ + SettingsSingleFileGenerator @@ -96,12 +135,12 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/bsmd.Tool/packages.config b/bsmd.Tool/packages.config index 31f24d70..6fb49e25 100644 --- a/bsmd.Tool/packages.config +++ b/bsmd.Tool/packages.config @@ -1,7 +1,19 @@  + + - - - + + + + + + + + + + + + + \ No newline at end of file