using DatenDB; using Deckungsbeitrag.AA_Forms; using Deckungsbeitrag.Properties; using Microsoft.Win32; using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Management; using System.Net; using System.Reflection; using System.Runtime; using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Timers; using System.Web.SessionState; using System.Windows.Forms; namespace Deckungsbeitrag { static class Program { public static Color Wirlblau = Settings.Default.Wirlblau; public static ImageList imagelist = new ImageList(); public static string userid; public static Benutzer benutzer = new Benutzer(); public static bool Artikelliste_Importiert = false; public static bool Kundenliste_Importiert = false; public static bool Software_Updated = false; private static DateTime zielZeit = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day, 22, 0, 0); private static System.Timers.Timer timer; //public static string vaultPath = @"N:\TECHNIK\Software\Wirl-Verwaltung\Feedback"; private static string savePath = ConfigurationManager.AppSettings["DateiSavePfad"]; private static readonly string login = "login"; private static bool istest = false; /// /// Der Haupteinstiegspunkt für die Anwendung. /// [STAThread] static void Main() { userid = ConfigurationManager.AppSettings["ConnectionString"].Split(';')[0]; userid = userid.Split('=')[1]; timer = new System.Timers.Timer(); timer.Interval = 1000; timer.Elapsed += Timer_Elapsed; timer.Start(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Screen[] screens = Screen.AllScreens; Cursor.Current = Cursors.WaitCursor; // Check, Install und Update wenn Update vorhanden. if (CheckAndInstallUpdate()) { MessageBox.Show("Update wurde erfolgreich durchgeführt.\nStarte das Programm über den Desktop neu.", "UPDATE INFO", MessageBoxButtons.OK, MessageBoxIcon.Information); Software_Updated = true; Settings.Default.LastSoftwareUpdate = DateTime.Now; //return; } // Laden der Image-Liste mit Icons für ObjectListView in Export etc. ImageList_Laden(); // Importieren und Updaten der Kundenliste wenn Datei vorhanden. KundenList_Laden(); // Importieren und Updaten der Artikelliste wenn Datei vorhanden. ArtikelList_Laden(); Settings.Default.Save(); Cursor.Current = Cursors.Default; if (istest) { Application.Run(new FormAufleger()); return; } if (userid.StartsWith("ms") | userid.StartsWith("ps")) { switch (userid) { case var s when s.Contains("wstrasse"): benutzer = Benutzer.GetBenutzer(null, 4); Application.Run(new FormAufleger(userid)); break; case var s when s.Contains("frottee"): Application.Run(new FormFehlmengeCount(userid)); break; case var s when s.Contains("kleinteile"): Application.Run(new FormFehlmengeCount(userid)); break; case var s when s.Contains("grossteile"): Application.Run(new FormFehlmengeCount(userid)); break; case var s when s.Contains("spannleintuch"): Application.Run(new FormFehlmengeCount(userid)); break; default: break; } } else { // Öffnen der ReadMe wenn noch nicht geöffnet wurde. //Open_ReadMe(); FormLogin loginForm = new FormLogin(); if (loginForm.ShowDialog() == DialogResult.OK) { benutzer = loginForm.Person; // Switch on BenutzerRolle für Weiterleitung zum richtigen Screen switch (loginForm.Person.Rolle) { case BenutzerRolle.Verwaltung: //Büropersonal ohne aktive Hallenbeschäftigung Application.Run(new FormMain(loginForm.Person)); break; case BenutzerRolle.Fahrer: //Zustellfahrer Application.Run(new FormMain(loginForm.Person)); break; case BenutzerRolle.Admin: //Höheres Büropersonal oder Claudia und Kevin Application.Run(new FormMain(loginForm.Person)); break; case BenutzerRolle.Waschstrasse: //Bediener einer Waschstraße Application.Run(new FormNeuerAuftrag(loginForm.Person, AuftragTyp.Standart)); break; case BenutzerRolle.Master: //Entwickler oder Claudia und Kevin Application.Run(new FormMain(loginForm.Person)); break; case BenutzerRolle.Expedit: //Endkontrolle Lieferungen und Lieferscheine (Mirka) Application.Run(new FormExpedit(loginForm.Person, screens)); break; case BenutzerRolle.Frottee: //Containerbestückung Frotteewäsche //BenutzerRolle Frottee = Expedit Frottee da die Maschine keine Anmeldung erfordert. Application.Run(new FormExpedit(loginForm.Person, screens)); break; case BenutzerRolle.Flach: //Containerbestückung Flachwäsche //BenutzerRolle Flach = Expedit Frottee da die Maschine keine Anmeldung erfordert. Application.Run(new FormExpedit(loginForm.Person, screens)); break; default: break; } } } } #region private-Funktionen private static void Open_ReadMe() { string readmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Verwaltung_ReadMe.txt"); // Prüfe, ob ReadMe schon einmal geöffnet wurde using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\KilianWirlGmbH\Verwaltung")) { if (key == null || key.GetValue("ReadMeShown") == null) { // Erstes Mal nach Installation if (File.Exists(readmePath)) { Process.Start(new ProcessStartInfo { FileName = readmePath, UseShellExecute = true }); // WICHTIG: Warte 2 Sekunden (ReadMe öffnen) System.Threading.Thread.Sleep(2000); // Abbruch-Dialog DialogResult result = MessageBox.Show( "ReadMe wurde geöffnet.\n\n" + "Falls INSTALL-REMINDER noch Änderungen enthält,\n" + "soll die Anwendung jetzt ABGEBROCHEN werden?\n\n" + "[JA = Abbruch | NEIN = Fortfahren]", "Nach Installation - Update prüfen", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1 // Standard: Abbruch ); if (result == DialogResult.Yes) { MessageBox.Show("Installation wurde abgebrochen.\nBitte Änderungen vornehmen und neu installieren.", "Abbruch", MessageBoxButtons.OK, MessageBoxIcon.Information); return; // App beenden } // Markiere als gezeigt Registry.CurrentUser.CreateSubKey(@"Software\KilianWirlGmbH\Verwaltung") .SetValue("ReadMeShown", 1); } } } } private static void Timer_Elapsed(object sender, ElapsedEventArgs e) { if (DateTime.Now >= zielZeit) { timer.Stop(); timer.Dispose(); Application.Exit(); // Schließt alle Forms und beendet die App } } private static void ArtikelList_Laden() { string updatePath = Path.Combine(savePath, "Listen"); updatePath = @"N:\TECHNIK\Software\Wirl-Verwaltung\Listen"; //Pfad zu Listen string dateiName = "Artikelkurzliste.csv"; string row; string[] rows; try { // Prüfen ob File Artikelkurzliste existiert. if (File.Exists(Path.Combine(updatePath, dateiName))) { string filename = Path.Combine(updatePath, dateiName); StreamReader streamReader = new StreamReader(filename, Encoding.GetEncoding("iso-8859-1")); row = string.Empty; rows = new string[0]; int saved = 0; int tosave = 0; int idx = 1; while (!streamReader.EndOfStream) { Array.Resize(ref rows, idx); row = streamReader.ReadLine(); if (row.StartsWith("60") || row.StartsWith("20")) { rows[idx - 1] = row; idx++; } } streamReader.Close(); // Zeilen der Datei durchiterieren. foreach (string line in rows) { string[] linedata = line.Split(';'); // ArtikelGruppen 60 und 20 werden weiterverarbeitet. if (linedata[0] == "60" || linedata[0] == "20") { tosave++; // Artikel wird in DB gesucht und geholt. Artikel art = Artikel.GetArtikel(Convert.ToInt32(linedata[1])); if (art == null) art = new Artikel(); art.Nummer = Convert.ToInt32(linedata[1]); art.Bezeichnung = linedata[3]; // Kategorie wird nach Artikelnummer zugeordnet. switch (art.Nummer) { case var nr when nr.ToString()[1] == '3': // 2.Stelle wird auf 3 überprüft art.Kategorie = ArtikelKategorie.Frottee; break; case var nr when nr.ToString()[1] == '1' & nr.ToString()[2] == '1': // 2. und 3. Stelle wird auf 1 überprüft if (nr.ToString()[4] == '5' || nr.ToString()[4] == '6') art.Kategorie = ArtikelKategorie.Spannleintuch; else art.Kategorie = ArtikelKategorie.Grossteile; break; case var nr when nr.ToString()[1] == '1' & nr.ToString()[2] == '2': // 2. und 3. Stelle wird auf 2 überprüft art.Kategorie = ArtikelKategorie.Kleinteile; break; default: break; } // Wenn Artikel Shortbezeichnung ist vorhanden dann leeren damit neue Bezeichnung erstellt werden kann. if (!string.IsNullOrWhiteSpace(art.Short)) art.Short = string.Empty; // Erstellen der Shortbezeichnung für Buttons etc. if (art.Bezeichnung.Contains("DOPPEL") | art.Bezeichnung.Contains("D-")) art.Short = art.Short + "D-"; if (art.Bezeichnung.Contains("SPANN")) art.Short = art.Short + "SP"; if (art.Bezeichnung.Contains("LEINTUCH")) art.Short = art.Short + "LT"; if (art.Bezeichnung.Contains("SPANN")) { string[] s = art.Bezeichnung.Split(' '); if (s.Length == 3) art.Short = art.Short + " " + s[2]; } //Farben zuordnen und zu Short hinzufügen if (art.Bezeichnung.Contains("HANDTUCH")) art.Short = art.Short + "HT"; if (art.Bezeichnung.Contains("DUSCHTUCH")) art.Short = art.Short + "DT"; if (art.Bezeichnung.Contains("BADETUCH")) art.Short = art.Short + "BT"; if (art.Bezeichnung.Contains("BADEVORLEGER")) art.Short = art.Short + "BV"; if (art.Bezeichnung.Contains("BADEMANTEL")) art.Short = art.Short + "BM"; if (art.Bezeichnung.Contains("SAUNATUCH")) art.Short = art.Short + "ST"; if (art.Bezeichnung.Contains("GESICHTSTUCH")) art.Short = art.Short + "GT"; if (art.Bezeichnung.Contains("POLSTERBEZUG")) { string extra; int index = art.Bezeichnung.IndexOf(" ") + 1; int length = art.Bezeichnung.IndexOf(" ", index + 1) - index; art.Short = art.Short + "PB"; if (index > 0 & length > 0) { extra = art.Bezeichnung.Substring(index, length); art.Short += " " + extra; } switch (art.Bezeichnung) { case var bez when bez.Contains("60x80"): art.Short = art.Short + " 60x80"; break; case var bez when bez.Contains("40x60"): art.Short = art.Short + " 40x60"; break; case var bez when bez.Contains("40x40"): art.Short = art.Short + " 40x40"; break; case var bez when bez.Contains("40x50"): art.Short = art.Short + " 40x50"; break; case var bez when bez.Contains("40x70"): art.Short = art.Short + " 40x70"; break; default: break; } } //Größen zuordnen und zu Short hinzufügen if (art.Bezeichnung.Contains("DECKENBEZUG")) { string extra; int index = art.Bezeichnung.IndexOf(" ") + 1; int length = art.Bezeichnung.IndexOf(" ", index + 1) - index; art.Short = art.Short + "DB"; if (index > 0 & length > 0) { extra = art.Bezeichnung.Substring(index, length); art.Short += " " + extra; } } if (art.Bezeichnung.Contains("TISCHTUCH")) { art.Short = art.Short + "TT"; int index = art.Bezeichnung.IndexOf("x"); if (index > 0) art.Short = art.Short + art.Bezeichnung.Substring(index - 4, 8); } //Tischtücher und Größen zuordnen und zu Short hinzufügen if (art.Bezeichnung.Contains("MUNDSERVIETTE")) art.Short = art.Short + "MS"; if (art.Bezeichnung.Contains("DECKSERVIETTE")) art.Short = art.Short + "DS"; if (art.Save() == 1) saved++; } } if (saved == tosave) { Artikelliste_Importiert = true; Settings.Default.LastArtikelUpdate = DateTime.Now; } } } catch (Exception ex) { MessageBox.Show($"Import fehlgeschlagen: {ex.Message}\nPfad: {updatePath}\nDatei: {dateiName}", "IMPORT FEHLER", MessageBoxButtons.OK, MessageBoxIcon.Warning); } finally { // Wenn alles funktioniert hat wird die Datei gelöscht. if (Artikelliste_Importiert) File.Delete(Path.Combine(updatePath, dateiName)); } } private static void KundenList_Laden() { string updatePath = Path.Combine(savePath, "Listen"); updatePath = @"N:\TECHNIK\Software\Wirl-Verwaltung\Listen"; //Pfad zu Listen string dateiName = "Kundenstammblatt.csv"; string row; string[] rows; try { // Prüfen ob File Kundenstammblatt existiert. if (File.Exists(Path.Combine(updatePath, dateiName))) { string filename = Path.Combine(updatePath, dateiName); StreamReader sr = new StreamReader(filename, Encoding.GetEncoding("iso-8859-1")); row = string.Empty; rows = new string[0]; int saved = 0; int tosave = 0; int idx = 0; while (!sr.EndOfStream) { Array.Resize(ref rows, ++idx); row = sr.ReadLine(); rows[idx - 1] = row; } sr.Close(); // Jede Zeile der Datei wird durchiteriert. foreach (string line in rows) { string[] linedata = line.Split(';'); // Wenn 1. Datensatz von linedata keine Zahlen sind oder 5. Datensatz Preisgruppe enthält wird Zeile ignoriert. if (!int.TryParse(linedata[0], out _)) continue; if (linedata[4].Contains("Preisgruppe")) continue; tosave++; // Kunde wird gesucht und aus DB geholt. Kunde kunde = Kunde.GetKunde(linedata[0], null, null); if (kunde == null) kunde = new Kunde(); kunde.KundeNummer = linedata[0]; kunde.KundeGruppe = linedata[3]; kunde.PLZ = int.TryParse(linedata[4], out int tmp2) ? tmp2 : 00; kunde.KundeName = linedata[5]; kunde.KundeName2 = linedata[6]; kunde.Strasse = linedata[8]; kunde.Land = linedata[9]; kunde.Ort = linedata[10]; kunde.Bettenanzahl = int.TryParse(linedata[45], out int bett) ? bett : 0; kunde.Waescheart = linedata[46]; kunde.Service = linedata[47]; kunde.Region = linedata[48]; kunde.Suchtext = linedata[55]; kunde.Aktiv = linedata[56] == "1" ? true : false; if (kunde.Save() == 1) saved++; } if (saved == tosave) { Kundenliste_Importiert = true; Settings.Default.LastKundeUpdate = DateTime.Now; } } } catch (Exception ex) { MessageBox.Show($"Import fehlgeschlagen: {ex.Message}\nPfad: {updatePath}\nDatei: {dateiName}", "IMPORT FEHLER", MessageBoxButtons.OK, MessageBoxIcon.Warning); } finally { // Wenn alles funktioniert hat wird die Datei gelöscht. if (Kundenliste_Importiert) File.Delete(Path.Combine(updatePath, dateiName)); } } private static bool CheckAndInstallUpdate() { string updatePath = Path.Combine(savePath, "Setup"); string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); updatePath = @"N:\TECHNIK\Software\Wirl-Verwaltung\Setup"; // Ihr interner UNC-Pfad try { string newVersion = File.ReadAllText(Path.Combine(updatePath, "Version.txt")).Trim(); if (string.IsNullOrWhiteSpace(newVersion)) { // Alle msi Files auflisten. string[] msiFiles = Directory.GetFiles(updatePath, "SetupVerwaltung*.msi"); if (msiFiles.Length == 0) return false; // Neueste Version aus Dateiname finden string validMsis = msiFiles .Select(f => new { Path = f, Version = GetVersionFromMsiFilename(f) }) .Where(x => IsValidVersion(x.Version)) .OrderByDescending(x => new Version(x.Version)) .FirstOrDefault()?.Version; // Wenn Version ist valide wird sie als neue Version gführt. if (validMsis != null) newVersion = validMsis; } // Prüfen ob neue Version ist größer als aktuelle Version. if (new Version(newVersion).CompareTo(new Version(currentVersion)) > 0) { DialogResult result = MessageBox.Show( $"Update verfügbar: {newVersion}\nInstallieren?", "UPDATE VERFÜGBAR", MessageBoxButtons.YesNo, MessageBoxIcon.Question); string msiPath = Path.Combine(updatePath, $"SetupVerwaltung{newVersion}.msi"); if (result == DialogResult.Yes) { try { // 1. MSI lokal kopieren (umgeht UNC-Fehler 2203) string localMsi = Path.Combine(@"C:\tmp", $"SetupVerwaltung{newVersion}.msi"); File.Copy(msiPath, localMsi, true); // 2. MSI starten var psi = new ProcessStartInfo { FileName = "msiexec.exe", Arguments = $"/i \"{localMsi}\" /qn /norestart ALLUSERS=1 /L*v \"C:\\tmp\\update.log\"", Verb = "runas", UseShellExecute = true }; // Prozesse und Programm schließen. // 1. SELBST schließen (wichtig!) Application.Exit(); // 2. ALLE Prozesse killen foreach (Process p in Process.GetProcessesByName("Deckungsbeitrag")) if (p.Id != Process.GetCurrentProcess().Id) p.Kill(); foreach (Process p in Process.GetProcessesByName("Wirl-Verwaltung")) p.Kill(); foreach (Process p in Process.GetProcessesByName("SetupVerwaltung")) p.Kill(); // 3. 2 Sekunden warten (Restart Manager) Thread.Sleep(2000); Process.Start(psi)?.Dispose(); return true; } catch (Exception ex) { MessageBox.Show($"Start fehlgeschlagen: {ex.Message}", "UPDATE FEHLER", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } } } catch (Exception ex) { MessageBox.Show($"Update-Prüfung fehlgeschlagen: {ex.Message}\nPfad: {updatePath}", "UPDATE FEHLER", MessageBoxButtons.OK, MessageBoxIcon.Warning); AddFehler(null, ex.Message, ex.StackTrace, null); } return false; } private static string GetVersionFromMsiFilename(string msiFilePath) { string filename = Path.GetFileNameWithoutExtension(msiFilePath); // Pattern: SetupVerwaltung1.0.44.msi → 1.0.44 // Oder: SetupVerwaltung_v1_0_44.msi → 1.0.44 Match match = Regex.Match(filename, @"(\d+(?:\.\d+){2,3})"); if (match.Success) return match.Groups[1].Value; return "0.0.0"; // Fallback } private static bool IsValidVersion(string version) { return Version.TryParse(version, out _); } private static void ImageList_Laden() { imagelist.Images.Add(Resources.Exclamation_Red); imagelist.Images.Add(Resources.InformationSymbol_16x); imagelist.Images.Add("print", Resources.PrintTag1); imagelist.Images.Add("abschließen", Resources.checkmark); imagelist.Images.Add("dirt", Resources.dirt); imagelist.Images.Add("clean", Resources.clean); imagelist.Images.Add("close", Resources.Close_red_16x); } #endregion #region public-Funktion public static void AddFeedback(Benutzer user, string feedbackText, string bildDateiname, string timeStamp) { string vaultPath = Path.Combine(savePath, "Feedback\\VerwaltungFeedback.md"); string feedback = string.Empty; Benutzer ben = benutzer; // Wenn User existiert wird dieser Verwendet. Sonst der bei Anmeldung registrierte Benutzer. if (user != null) ben = user; // Wenn der Bilddateiname leer ist wird feedback ohne Bilddateiname erstellt. if (string.IsNullOrWhiteSpace(bildDateiname)) { feedback = $@"- [ ] **Zeitpunkt:** {timeStamp} **| User:** {ben.Vorname} {ben.Nachname} **| Text:** {feedbackText}{Environment.NewLine}"; } else feedback = $@" - [ ] **Zeitpunkt:** {timeStamp} **| User:** {ben.Vorname} {ben.Nachname} **| Text:** {feedbackText} **| Bild:** ![[{bildDateiname}.png]]{Environment.NewLine}"; // Test wenn Image mitgegeben wird wird es hier gespeichert. (Pfadprobleme) File.AppendAllText(vaultPath, feedback); } public static DialogResult ShowOnBoarding(string userid, OnboardingSchritt onBoardingArt) { FormOnBoarding formOnBoarding = new FormOnBoarding(userid.ToLower(), onBoardingArt); if (formOnBoarding.ShowDialog() == DialogResult.Cancel) return DialogResult.Cancel; else return DialogResult.OK; } /// /// Fehler werden in Obsidian Fehlerliste Importiert. /// /// wenn null dann in Program registrierter User /// Fehlertext aus Exception oder individuell erstellen /// wenn null wird StackTrace nicht eingefügt. Kann auch individuell verwendet werden (Ort des Fehlers) /// wenn null wird TimeStamp hier erstellt public static void AddFehler(Benutzer user, string fehlerText, string fehlerStackTrace, string timeStamp) { string path = Path.Combine(savePath, "Feedback"); string vaultPath = Path.Combine(path, "VerwaltungFehler.md"); //string bildPath = Path.Combine(savePath, bildDateiname); string fehler = string.Empty; string benutzerKennung = string.Empty; Benutzer ben = benutzer; // Wenn User existiert wird dieser Verwendet. Sonst der bei Anmeldung registrierte Benutzer. if (user != null) ben = user; if (ben.BenutzerID == null) benutzerKennung = userid; else benutzerKennung = ben.Vorname + " " + ben.Nachname; if (timeStamp == null) timeStamp = DateTime.Now.ToString("dd.MM.yyyy-HH-mm"); // Wenn der Bilddateiname leer ist wird feedback ohne Bilddateiname erstellt. if (string.IsNullOrWhiteSpace(fehlerStackTrace)) { fehler = $@"- [ ] **Zeitpunkt:** {timeStamp} **| User:** {benutzerKennung} **| Fehler:** {fehlerText}{Environment.NewLine}"; } else { string filename = $"fehler-{timeStamp}.txt"; string txtfile = Path.Combine(path, filename); File.WriteAllText(txtfile, fehlerStackTrace); fehler = $@" - [ ] **Zeitpunkt:** {timeStamp} **| User:** {benutzerKennung} **| Fehler:** {fehlerText} **| StackTrace:** [[{filename}]]{Environment.NewLine}"; } // Test wenn Image mitgegeben wird wird es hier gespeichert. (Pfadprobleme) File.AppendAllText(vaultPath, fehler); } #endregion } } //TODO: Mit AddFehler in Obsidian eine Fehlerliste betreiben. (Wichtige Infos feststellen und bereitstellen) //DONE: Mit AddFehler können Fehler aus try/chatch in Obsidian eingefügt werden. (Timestamp, PC-Name, Ex-Message)