//#define wdhRechnungenAnKarley // Gibt an, das neu erstellte wiederkehrende Rechnungen an info@ geschickt werden, anstatt zum Kunden
using KarleyLibrary;
using KarleyLibrary.Erweiterungen;
using KarleyLibrary.Serialization;
using Serilog;
using Serilog.Core;
using Serilog.Sinks.Firebird;
using System;
using System.Collections.Generic;
using System.Data;
using System.DirectoryServices;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WK5.Core;
using WK5.Core.Basis.Erweiterungen;
using WK5.Core.Basis.Filter;
using WK5.Core.Drucken.Rechnungen;
using WK5.Core.Email;
using WK5.Core.Models;
using WK5.Core.Models.Lager;
using WK5.Core.Models.VatChecker;
using WK5.Core.OpenCart;
using WK5.Core.OpenCart.Karley;
using WK5.Core.Services;

namespace KarleyUpdate
{
    /// <summary>
    /// KarleyUpdate hält Karley am laufen. Es ist der nachfolger von ArtikelUpdateOpencart.
    /// </summary>
    internal class Program
    {

        private static readonly Logger logger;
        private static readonly EmailController emailController;
        private static readonly HolidayDays _holidayDays = new HolidayDays();
        private static string LoggingPrefix = String.Empty;
        static Program()
        {
            logger = new LoggerConfiguration()
                            .MinimumLevel.Debug()
                            .WriteTo.Console()
#if DEBUG
                            .WriteTo.Firebird(GlobalConfig.W4LocalDebugConnectionString, prefix: () => LoggingPrefix)
#else
                            .WriteTo.Firebird(GlobalConfig.W4ConnectionString, prefix: () => LoggingPrefix)
#endif
                            .CreateLogger();

            emailController = new EmailController()
            {
                Html = true
            };
        }

        /// <summary>
        /// Prüft ob die Funktion ausgeführt werden soll.
        /// <para>
        /// Die Funktion wird nicht ausgeführt, wenn das Program Samstags, Sonntags, oder an Feiertagen ausgeführt wird.
        /// </para>
        /// </summary>
        /// <returns></returns>
        private static bool Ausführen()
        {
            if (_holidayDays.IsHoliday(DateTime.Today, State.NordrheinWestfalen) || DateTime.Today.DayOfWeek is DayOfWeek.Saturday || DateTime.Today.DayOfWeek is DayOfWeek.Sunday)
            {
#if DEBUG
                return true;
#else
                return false;
#endif
            }

            return true;

        }
        private static async Task Main(string[] args)
        {
            try
            {
                if (args.Length > 0)
                {
                    /*
                     * Es ist Good-Practice eine eigene Klasse für jeden Case anzulegen.
                     * Ein gutes Beispiel hier ist der Preiskalkulation.
                     * 
                     * Es gibt noch manche Cases wo nur eine Funktion aufgerufen wird, dass sollte der Übersicht halber vermieden werden.
                     * Alle Cases die das tun, sind noch alte Cases die nicht umgestellt worden sind.
                     */
                    switch (args[0])
                    {
                        case "fehlendeBestellungenPrüfen":
                            if (Ausführen())
                            {
                                LoggingPrefix = "KarleyUpdate fehlende Bestellungen prüfen ";
                                logger.Information($"{LoggingPrefix} startet.");
                                await BestellPrüferAufträge();
                            }
                            break;
                        case "auftragsfreigabe":
                            if (Ausführen())
                            {
                                LoggingPrefix = "KarleyUpdate Auftragsfreigabe ";
                                logger.Information($"{LoggingPrefix} startet.");
                                Auftragsfreigabe freigabe = new Auftragsfreigabe();
                                await freigabe.RunAsync();
                            }
                            break;
                        case "wiederkehrendeRechnungen":
                            LoggingPrefix = "KarleyUpdate Wiederkehrende Rechnungen ";
                            logger.Information($"{LoggingPrefix} startet.");
                            await WiederkehrendeRechnungenAsync();
                            break;
                        case "bereinigung":
                            LoggingPrefix = "KarleyUpdate bereinigung ";
                            logger.Information($"{LoggingPrefix} startet.");
                            Bereinigung bereinigung = new Bereinigung(logger);
                            await bereinigung.RunAsync();
                            break;
                        case "inaktiveOnlineArtikel":
                            LoggingPrefix = "KarleyUpdate PrüfeAufInaktiveOnlineArtikel ";
                            logger.Information($"{LoggingPrefix} startet.");
                            await PrüfeAufInaktiveOnlineArtikelAsync();
                            break;
                        case "checkMindestbestand":
                            LoggingPrefix = "KarleyUpdate CheckMindestbestand ";
                            logger.Information($"{LoggingPrefix} startet.");
                            await CheckMindestbestand();
                            break;
                        case "alteArtikel":
                            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                            {
                                LoggingPrefix = "KarleyUpdate - Alte Artikel ";
                                logger.Information($"{LoggingPrefix} startet.");
                                await AlteArtikel();
                            }
                            break;
                        case "emailMarks":
                            LoggingPrefix = "KarleyUpdate MailMarkierungenZurücksetzen ";
                            logger.Information($"{LoggingPrefix} startet.");
                            await EmailMarkierungen();
                            break;
                        case "teillieferungPrüfung":
                            if (Ausführen())
                            {
                                LoggingPrefix = "KarleyUpate Teillieferungs Prüfung ";
                                logger.Information($"{LoggingPrefix} startet.");
                                await TeillieferungPrüfung();
                            }
                            break;
                        case "offeneEUAuftraege":
                            if (Ausführen())
                            {
                                LoggingPrefix = "KarleyUpdate UstID Prüfung Offene EU Ausland Aufträge ";
                                logger.Information($"{LoggingPrefix} startet.");
                                await OffeneEUAuftraegeUstIdPruefung();
                            }
                            break;
                        case "opencartBilderDownload":
                            if (Ausführen())
                            {
                                LoggingPrefix = "KarleyUpdate OpencartBildSynchronisation ";
                                logger.Information($"{LoggingPrefix} startet.");
                                OpencartBild opencart = new OpencartBild();
                                await opencart.RunAsync();
                            }
                            break;
                        case "divStanzePrüfung":

                            if (Ausführen())
                            {
                                LoggingPrefix = "KarleyUpdate DiversStanzePrüfung ";
                                logger.Information($"{LoggingPrefix} startet.");
                                await StanzeDivPrüfung();
                            }

                            break;
                        case "checkAbverkaufsArtikel":
                            if (Ausführen())
                            {
                                LoggingPrefix = "KarleyUpdate AbverkaufsArtikelCheck ";
                                logger.Information($"{LoggingPrefix} startet.");
                                await CheckAbverkaufsArtikel();
                            }
                            break;
                        case "wartungscheck":
                            LoggingPrefix = "KarleyUpdate Wartungscheck";
                            logger.Information($"{LoggingPrefix} startet.");
                            await Wartungscheck.RunAsync(logger);
                            break;
                        case "mieten-import":
                            LoggingPrefix = "KarleyUpdate Mieten-Import";
                            logger.Information($"{LoggingPrefix} startet.");
                            MietenImport import = new MietenImport();
                            await import.RunAsync(logger);
                            break;
                        case "bundle-prüfung":
                            LoggingPrefix = "KarleyUpdate Bundle-Prüfung";
                            logger.Information($"{LoggingPrefix} startet.");
                            await BundlePrüfung.RunAsync(logger);
                            break;
                        case "verbrauchsdaten-cachen":
                            LoggingPrefix = "KarleyUpdate VerbrauchsdatenCachen";
                            logger.Information($"{LoggingPrefix} startet.");
                            await VerbrauchsdatenCachen();
                            break;
                        case "preiskalkulation":
                            LoggingPrefix = "KarleyUpdate Preiskalkulation";
                            logger.Information($"{LoggingPrefix} startet.");
                            Preiskalkulation preiskalkulation = new Preiskalkulation(logger);
                            await preiskalkulation.RunAsync();
                            break;
                        case "bestellungen-ohne-au":
                            LoggingPrefix = "Bestellungen Ohne AU";
                            logger.Information($"{LoggingPrefix} startet.");
                            await BestellungOhneAUBenachrichtigung();
                            break;
                        case "lange-nicht-upgedatet":
                            LoggingPrefix = "Lange Nicht Upgedatet";
                            logger.Information($"{LoggingPrefix} startet.");
                            await LangeNichtUpgedatet.RunAsync(logger);
                            break;
                        case "statistik":
                            LoggingPrefix = "Statistik";
                            logger.Information($"{LoggingPrefix} startet.");
                            await Statistik.RunAsync(logger);
                            break;
                        case "abverkaufscheck":
                            LoggingPrefix = "Abverkaufscheck";
                            logger.Information($"{LoggingPrefix} startet.");
                            await AbverkaufsCheck.RunAsync(logger);
                            break;
                        default:
                            break;
                    }
                    logger.Information($"{LoggingPrefix} wurde beendet.");
                }
            }
            catch (Exception e)
            {
                logger.Error("Fehler bei der Ausführung von KarleyUpdate. Fehler: {e}", e);
                throw;
            }
        }

        /// <summary>
        /// Prüft den letzten Zugang von allen Artikeln, die noch einen Bestand haben. Wenn der letzte Zugang älter als 2 Jahre ist, dann wird eine E-Mail an jeden Mitarbeiter des Vertriebs gesendet.
        /// <para>
        /// Diese Funktion kann nur unter Windows ausgeführt werden, da Sie auf MS Active Directory zugreift.
        /// </para>
        /// </summary>
        /// <returns></returns>
        [SupportedOSPlatform("windows")]
        private static async Task AlteArtikel()
        {
            logger.Information("Alte Artikel Prüfung gestartet");
            using FbController2 fbController2 = new FbController2();

            fbController2.AddParameter("@DATUM", DateTime.Now.Date.AddYears(-2));
            var data = await fbController2.SelectDataAsync(@"SELECT ARWE_A_NR, ARTI_A_BEZ1, WK5_WAGR_N_PRODUKTMANAGER
                        FROM ARTIKELWERTE 
                        LEFT JOIN ARTIKELBEWEGUNG ON ARBE_A_ARTINR = ARWE_A_NR AND ARBE_A_ART = 'ZUGA'
                        LEFT JOIN ARTIKEL ON ARTI_A_NR = ARWE_A_NR
                        LEFT JOIN WARENGRUPPE ON WAGR_A_NR = ARTI_A_WARENGRUPPE
                        WHERE ARWE_N_BESTAND > 0
                        GROUP BY ARWE_A_NR, ARTI_A_BEZ1, WK5_WAGR_N_PRODUKTMANAGER
                        HAVING MAX(ARBE_TIMESTAMP) < @DATUM");

            List<AlterArtikel> artikel = new();
            foreach (DataRow row in data.Rows)
            {
                artikel.Add(ObjectErweiterung.DataRowZuObjekt(new AlterArtikel(), row));
            }

            if (artikel.Any())
            {
                var produktManager = artikel.Select(x => x.ProduktManager).Distinct();
                StringBuilder mailBuilder = new StringBuilder();
                foreach (int personalNummer in produktManager.Where(x => x != 0))
                {
                    mailBuilder.Clear();
                    var (firstName, email) = GetUserByEmployeeId(personalNummer);
                    if (String.IsNullOrWhiteSpace(firstName) || String.IsNullOrWhiteSpace(email))
                    {
                        continue;
                    }

                    mailBuilder.AppendLine($"Hallo {firstName},<br /><br />");
                    mailBuilder.AppendLine("Es wurden alte Artikel gefunden, bei denen der letzte Zugang länger als 2 Jahre her ist, von denen wir aber immer noch einen Bestand haben.<br />");
                    mailBuilder.AppendLine("Bitte diese Produkte bevorzugt Kunden anbieten, sodass wir die verkaufen können und nicht entsorgen müssen.<br /><br />");
                    mailBuilder.AppendLine(@"Ich habe es für Dich mal bereinigt. Alles was da drin ist, Deine Baustelle. RFID und Petzi Zeugs habe ich schon gelöscht.<br /><br />
 
- a) Abverkaufspreise finden und Miriam nennen. Sie kann das dann online einstellen<br />
- b) Gibt es Kunden die Dir einfallen denen man das persönlich ggf. so anbieten kann - dann soll Niklas oder Lukas die mal anrufen oder anschreiben.<br />
- c) Vielleicht haben wir die Artikel gar nicht mehr an Lager? Einiges kommt mir komisch vor und sollte überprüft werden<br /><br />");
                    mailBuilder.AppendLine("<strong>Folgende Artikel sind betroffen:</strong><br />");

                    foreach (var item in artikel.Where(x => x.ProduktManager == personalNummer))
                    {
                        mailBuilder.AppendLine($"<strong>{item.Artikelnummer}</strong> - {item.Bezeichnung}<br />");
                    }
                    mailBuilder.AppendLine("<br />");
                    mailBuilder.AppendLine("Viele Grüße<br />");
                    mailBuilder.AppendLine("Karli - Die freundliche KI der WK5");

                    await emailController.SendenAsync(
                        empfängerEmail: email,
                        betreff: "Alte Artikel gefunden",
                        mailBuilder.ToString()
                    );
                }
            }
        }

        private static async Task CheckMindestbestand()
        {
            logger.Information("Mindestbestandprüfung gestartet.");
            StringBuilder sbMindestbestandNichtVerkauft = new StringBuilder();
            StringBuilder sbVerkauftOhneMindestbestand = new StringBuilder();
            using FbController2 fbController = new FbController2();

            var blockedArtikel = await File.ReadAllLinesAsync(@"\\srv01\daten\W4\tmp\MindestbestandBlock.txt");

            // Wir holen alle Artikel, die wir in den letzten 5 Monaten nicht verkauft haben, aber einen Mindestbestand haben
            fbController.AddParameter("@START", DateTime.Now.AddMonths(-12).Date);
            fbController.AddParameter("@ENDE", DateTime.Now.Date);
            var dataMitBestandNichtBestellt = await fbController.SelectDataAsync(@$"SELECT ARTI_A_NR, ARTI_A_BEZ1, A.ARTI_N_MINBESTAND, 
CASE WHEN SUM(BP.BPOS_N_MENGE) IS NULL THEN 0 ELSE SUM(BP.BPOS_N_MENGE) END AS VERKAUFT,
CASE WHEN COUNT(*) IS NULL THEN 0 ELSE COUNT(*) END AS ANZAHL_BESTELLUNGEN,
(SELECT COUNT(*) FROM (SELECT DISTINCT BELE_A_KUNDENNR FROM BELEGPOS LEFT JOIN BELEGE ON BELE_A_TYP = 'RE' AND BELE_N_NR = BPOS_N_NR WHERE BPOS_A_TYP = 'RE' AND BPOS_A_ARTIKELNR = ARTI_A_NR AND BELE_D_DATE BETWEEN @START AND @ENDE)) AS ANZAHL_KUNDEN
FROM ARTIKEL A
LEFT JOIN BELEGPOS BP ON(BP.BPOS_A_TYP = 'RE' AND BP.BPOS_A_ARTIKELNR = A.ARTI_A_NR)
LEFT JOIN BELEGE B ON(B.BELE_A_TYP = 'RE' AND BELE_N_NR = BPOS_N_NR)
WHERE A.ARTI_L_INAKTIV = 'N' AND
BELE_D_DATE BETWEEN @START AND @ENDE
AND A.ARTI_L_LAGERFUEHR = 'Y' AND A.ARTI_N_MINBESTAND > 0
AND {(blockedArtikel.Length > 0 ? $"ARTI_A_NR NOT IN('{String.Join("','", blockedArtikel)}')" : "1 = 1")}
GROUP BY ARTI_A_NR, ARTI_A_BEZ1, A.ARTI_N_MINBESTAND 
HAVING SUM(BP.BPOS_N_MENGE) = 0");

            foreach (DataRow row in dataMitBestandNichtBestellt.Rows)
            {
                var artikelBestelldaten = ObjectErweiterung.DataRowZuObjekt(new CheckMindestbestandArtikel(), row);
                sbMindestbestandNichtVerkauft.AppendLine($"{artikelBestelldaten}<br />");
            }

            fbController.AddParameter("@START", DateTime.Now.AddMonths(-12).Date);
            fbController.AddParameter("@ENDE", DateTime.Now.Date);

            // Jetzt holen wir alle Artikel, die keinen Mindestbestand haben, aber bestellt worden sind
            var dataOhneMinBestand = await fbController.SelectDataAsync(@$"SELECT ARTI_A_NR, ARTI_A_BEZ1, A.ARTI_N_MINBESTAND, 
CASE WHEN SUM(BP.BPOS_N_MENGE) IS NULL THEN 0 ELSE SUM(BP.BPOS_N_MENGE) END AS VERKAUFT,
CASE WHEN COUNT(*) IS NULL THEN 0 ELSE COUNT(*) END AS ANZAHL_BESTELLUNGEN,
(SELECT COUNT(*) FROM (SELECT DISTINCT BELE_A_KUNDENNR FROM BELEGPOS LEFT JOIN BELEGE ON BELE_A_TYP = 'RE' AND BELE_N_NR = BPOS_N_NR WHERE BPOS_A_TYP = 'RE' AND BPOS_A_ARTIKELNR = ARTI_A_NR AND BELE_D_DATE BETWEEN @START AND @ENDE)) AS ANZAHL_KUNDEN
FROM ARTIKEL A
LEFT JOIN BELEGPOS BP ON(BP.BPOS_A_TYP = 'RE' AND BP.BPOS_A_ARTIKELNR = A.ARTI_A_NR)
LEFT JOIN BELEGE B ON(B.BELE_A_TYP = 'RE' AND BELE_N_NR = BPOS_N_NR)
WHERE A.ARTI_L_INAKTIV = 'N' 
AND A.ARTI_A_NR NOT LIKE 'SO-%' 
AND BELE_D_DATE BETWEEN @START AND @ENDE
AND A.ARTI_L_LAGERFUEHR = 'Y' AND A.ARTI_N_MINBESTAND = 0
AND A.ARTI_L_DIENSTLEIST = 'N'
AND A.ARTI_L_ABVERKAUF = 'N'
AND {(blockedArtikel.Length > 0 ? $"ARTI_A_NR NOT IN('{String.Join("','", blockedArtikel)}')" : "1 = 1")}
GROUP BY ARTI_A_NR, ARTI_A_BEZ1, A.ARTI_N_MINBESTAND
HAVING CASE WHEN COUNT(*) IS NULL THEN 0 ELSE COUNT(*) END > 3");

            // Jetzt den Rythmus bestimmen, in dem die Artikel bestellt worden sind
            foreach (DataRow row in dataOhneMinBestand.Rows)
            {
                var artikelBestelldaten = ObjectErweiterung.DataRowZuObjekt(new CheckMindestbestandArtikel(), row);
                sbVerkauftOhneMindestbestand.AppendLine($"{artikelBestelldaten}<br />");
            }



            if (sbMindestbestandNichtVerkauft.Length > 0 || sbVerkauftOhneMindestbestand.Length > 0)
            {
                StringBuilder emailBuilder = new StringBuilder();
                emailBuilder.AppendLine("Die Prüfung des Mindestbestands bei Artikeln ist erfolgreich durchgelaufen. <br /><br />");
                emailBuilder.AppendLine("<strong>Was ist zu tun?</strong><br />");
                emailBuilder.AppendLine(@"Prüfen, ob es Sinn macht, dass wir einen Mindestbestand haben wollen, oder nicht. Wenn nicht, dann die Artikelnummer in die Blockliste mit aufnehmen. \\srv01\daten\W4\tmp\MindestbestandBlock.txt");

                if (sbMindestbestandNichtVerkauft.Length > 0)
                {
                    emailBuilder.AppendLine("Die folgenden Artikel besitzen einen Mindestbestand, wurden aber innerhalb der letzten 12 Monate nicht verkauft:<br />");
                    emailBuilder.AppendLine(sbMindestbestandNichtVerkauft.ToString());
                }

                emailBuilder.AppendLine("<br /><br />");

                if (sbVerkauftOhneMindestbestand.Length > 0)
                {
                    emailBuilder.AppendLine("Die folgenden Artikel wurden in den letzten 12 Monaten zwar bestellt, verfügen dennoch über keinen Mindestbestand:<br />");
                    emailBuilder.AppendLine(sbVerkauftOhneMindestbestand.ToString().Replace("Mindestbestand: 0;", ""));
                }

                EmailController emailController = new EmailController();
                await emailController.SendenAsync(
                    empfängerEmail: GlobalConfig.EmailShopverwalter,
                    betreff: "Prüfung des Mindestbestandes für Artikel",
                    body: emailBuilder.ToString()
                );
            }

        }

        /// <summary>
        /// Prüft, ob es im Onlineshop aktive Artikel gibt, die in der W4 entweder auf inaktiv stehen, oder gar nicht mehr existieren.
        /// <para>
        /// Wenn solche Artikel gefunden werden, dann wird eine Mail zur Überprüfung an die ShopVerwaltung gesendet.
        /// </para>
        /// </summary>
        private static async Task PrüfeAufInaktiveOnlineArtikelAsync()
        {
            logger.Information("Artikelbereinigung gestartet.");
            using MySqlController2 mySqlController = new MySqlController2();
            var onlineArtikelData = await mySqlController.SelectDataAsync("SELECT DISTINCT `model` FROM `product` WHERE `status` = 1");

            using FbController2 fbController = new FbController2();
            var w4ArtikelData = await fbController.SelectDataAsync("SELECT DISTINCT ARTI_A_NR FROM ARTIKEL WHERE ARTI_L_INAKTIV = 'N'");
            var artikelW4 = new HashSet<string>();
            var artikelOnline = new HashSet<string>();
            foreach (DataRow row in w4ArtikelData.Rows)
            {
                string? artikelnummer = row.Field<string>("ARTI_A_NR");

                if (artikelnummer != null)
                {
                    artikelW4.Add(artikelnummer.ToUpper());
                }
            }

            foreach (DataRow row in onlineArtikelData.Rows)
            {
                string? artikelnummer = row.Field<string>("model");

                if (artikelnummer != null)
                {
                    artikelOnline.Add(artikelnummer.ToUpper());
                }
            }

            // Wir erstellen uns eine Kopie des HashSets, da sich dieses bei ExceptWith verändert. So haben wir beim Debuggen noch Vergleichswerte
            var onlineArtikelKopie = new HashSet<string>(artikelOnline);
            onlineArtikelKopie.ExceptWith(artikelW4);

            if (onlineArtikelKopie.Any())
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("Es wurden online aktive Artikel gefunden, die in der WK5 entweder auf inaktiv stehen, oder nicht länger existieren. Bitte die Artikel prüfen und ggf. online löschen oder korrigieren.<br /><br />");
                foreach (var artikelnummer in onlineArtikelKopie)
                {
                    sb.AppendLine($"{artikelnummer}<br />");
                }

                await emailController.SendenAsync(
                    empfängerEmail: GlobalConfig.EmailShopverwalter,
                    betreff: "Online wurden inaktive oder gelöschte Artikel gefunden",
                    body: sb.ToString()
                );
            }

            logger.Information($"Es {(onlineArtikelKopie.Count == 1 ? "wurde" : "wurden")} {onlineArtikelKopie.Count} Artikel gefunden, die inaktiv sind, oder nicht mehr existieren");
            logger.Information("Artikelbereinigung beendet.");
        }

        /// <summary>
        /// Soll einmal in der Woche ausgeführt werden und alle Aufträge prüfen die bezahlt sind (Vorkasse/Rechnung) aber die Artikel noch nicht bestellt wurden.
        /// <para>Diese Aufträge sollen dann in einer Mail zusammengefasst an den Vertrieb gesendet werden.</para>
        /// </summary>
        private static async Task BestellPrüferAufträge()
        {
            logger.Information($"{nameof(BestellPrüferAufträge)} gestartet");
            AuftragsfreigabeService auftragsfreigabeService = new AuftragsfreigabeService();
            Dictionary<int, string> aufträgeOhneBestellung = new();
            Dictionary<int, string> aufträgeOhneBestellungDirektlieferung = new();
            using FbController2 fbController = new FbController2();
            ZahlungsbedingungCollection zahlungsbedingungen = await Zahlungsbedingung.GetZahlungsbedingungenAsync(fbController);
            await foreach (var auftrag in auftragsfreigabeService.GetOffeneAufträgeAsync(null, CancellationToken.None))
            {
                // Wenn wir eine Projektnummer haben, dann handelt es sich um einen Abrufauftrag.
                if (auftrag.BELE_L_ABRUF)
                {
                    continue;
                }
                // Pausierte Aufträge werden nicht bestellt.
                if (auftrag.WK5_BELE_L_PAUSIERT)
                {
                    continue;
                }

                // Wenn der Auftrag noch nicht bezahlt wurde und nicht per Rechnung beliefert wird, dann ist uns dieser auch egal
                if (!auftrag.BELE_L_BEZAHLT && auftrag.BELE_A_STATUSFELD != "ZAHLUNGERHALTEN" && !zahlungsbedingungen.IstRechnung(auftrag.ZahlungsbedingungId))
                {
                    continue;
                }

                // Erledigte, oder stornierte sind auch egal - der Fall sollte hier aber eigentlich nicht auftreten
                if (auftrag.BELE_L_ERLEDIGT || auftrag.BELE_L_STORNO)
                {
                    continue;
                }

                Dictionary<string, decimal> artikelMengen = new Dictionary<string, decimal>();
                Dictionary<string, decimal> artikelBestand = new Dictionary<string, decimal>();

                // 07.09.2021 MK: Ley sagt, ist schnuppe. Wir verrechnen den Bestand mit dem bestellten

                // Wir gehen nur die Positionen durch, die wir auch bestellen müssen
                // Als erstes berechnen wir die gesamt Mengen
                foreach (var position in auftrag.GetEndPositionen().Where(pos =>
                pos.ARTI_L_LAGERFUEHR &&
                (pos.Parent is not null || (pos.Parent is null && pos.Menge - pos.BPOS_N_MENGEGELIEF > 0)) &&
                !pos.ARTI_L_DIENSTLEIST))
                {

                    if (!artikelMengen.ContainsKey(position.Artikelnummer))
                    {
                        // Wenn es einen Parent gibt, dann wäre die Berechnung falsch, da die abzufragene Position Teil einer Stückliste ist.
                        // Da eine Stückliste nicht in Teilen geliefert werden kann, ist hier die gelieferte Menge irrelevant.
                        if (position.Parent is null)
                        {
                            artikelMengen.Add(position.Artikelnummer, position.Menge - position.BPOS_N_MENGEGELIEF);
                        }
                        else
                        {
                            artikelMengen.Add(position.Artikelnummer, position.Menge);
                        }
                        // Wenn wir für den Artikel noch keine Menge haben, dann haben wir auch den Bestand noch nicht gespeichert
                        // Da es hier um Bestellungen geht, zählen wir bereits bestellte Mengen zum Bestand hinzu.
                        artikelBestand.Add(position.Artikelnummer, position.Bestand + position.Bestellt);
                    }
                    else
                    {
                        // Der Bestand ändert sich ja nicht, wir zählen also nur die Mengen
                        artikelMengen[position.Artikelnummer] += position.Menge - position.BPOS_N_MENGEGELIEF;
                    }
                }

                // Jetzt wo wir die Mengen haben, können wir die Aufträge durchgehen und entsprechend abziehen
                foreach (var position in auftrag.GetEndPositionen().Where(pos =>
                    pos.ARTI_L_LAGERFUEHR &&
                    (pos.Parent is not null || (pos.Parent is null && pos.Menge - pos.BPOS_N_MENGEGELIEF > 0))
                    && !pos.ARTI_L_DIENSTLEIST))
                {
                    decimal menge = position.Menge - position.BPOS_N_MENGEGELIEF;
                    decimal aktuellerBestand = artikelBestand[position.Artikelnummer];

                    // Haben wir noch restbestand?
                    if (aktuellerBestand < 0 || aktuellerBestand - menge < 0)
                    {

                        if (!aufträgeOhneBestellung.ContainsKey(position.BPOS_N_NR))
                        {
                            aufträgeOhneBestellung.Add(position.BPOS_N_NR, auftrag.Kundenname);
                            logger.Information($"Bestellung fehlt. Auftrag: {position.BPOS_N_NR}");
                        }
                    }


                    artikelBestand[position.Artikelnummer] -= menge;
                }




            }

            // MailText generieren
            StringBuilder mailBuilder = new StringBuilder();
            if (aufträgeOhneBestellung.Any())
            {
                mailBuilder.AppendLine("Folgende Aufträge müssen noch bestellt werden:<br />");
                foreach (var kvp in aufträgeOhneBestellung)
                {
                    mailBuilder.AppendLine($"<a href=\"http://wk5.local/Auftraege/{kvp.Key}\">{kvp.Key}</a> - {kvp.Value}<br />");
                }
                mailBuilder.AppendLine("<br />");
            }

            if (aufträgeOhneBestellungDirektlieferung.Any())
            {
                mailBuilder.AppendLine("Folgende Aufträge mit Direktlieferungen wurden noch nicht bestellt:<br />");
                foreach (var kvp in aufträgeOhneBestellungDirektlieferung)
                {
                    mailBuilder.AppendLine($"<a href=\"http://wk5.local/Auftraege/{kvp.Key}\">{kvp.Key}</a> - {kvp.Value}<br />");
                }
            }

            if (mailBuilder.Length > 0)
            {
                await emailController.SendenAsync(
                    empfängerEmail: GlobalConfig.EmailInfoEU,
                    betreff: "Aufträge müssen noch bestellt werden",
                    body: $"Die automatische Prüfung nach offenen Aufträgen, bei denen noch nicht alle Artikel bestellt worden sind ist durchgelaufen.<br /><br />{mailBuilder}"
                );
            }

            logger.Information($"{nameof(BestellPrüferAufträge)} beendet. Es wurden {aufträgeOhneBestellung.Count} Aufträge ohne Bestellung gefunden.");

        }

        private static async Task WiederkehrendeRechnungenAsync()
        {
            logger.Information($"{nameof(WiederkehrendeRechnungenAsync)} gestartet.");
            WiederkehrendeRechnungenService service = new WiederkehrendeRechnungenService();
            StringBuilder fehlendeRechnungenBuilder = new StringBuilder(); // Enthält Texte zu allen Rechnungen, bei denen mindestens eine Rechnung nicht geschrieben worden ist
            StringBuilder preisÄnderungBuilder = new StringBuilder(); // Enthält Texte zu allen Rechnungen, bei denen eine Preisänderung vorliegt und die Rechnung manuell durch den Vertrieb erstellt werden muss.
            StringBuilder rechnungAufInaktivBuilder = new StringBuilder(); // Enthält Texte zu allen Rechnungen, bei denen die letzte Rechnung geschrieben wurde und somit auf inaktiv gesetzt wurde
            StringBuilder rechnungErstelltBuilder = new StringBuilder();
            BelegService belegService = new BelegService();

            // Scholz 07.09.21 - Klein hat den Filter reingenommen ohne hier einzubinden, Aussage: "Mach einfach Limit 100 rein oder so"
            WiederkehrendeRechnungenFilter filter = new WiederkehrendeRechnungenFilter
            {
                Limit = 100
            };

            using FbController2 fbController2 = new FbController2();
            await foreach (var wdhRechnung in service.GetWiederkehrendeRechnungenAsync(filter, fbController2)
                .Where(x =>
                x.WIED_L_STATUS
                && (x.WIED_D_KUENDIGUNGSDATUM == default || x.WIED_D_KUENDIGUNGSDATUM.Date >= DateTime.Now.Date))
            )
            {
                string message = "";
                // Prüfung auf Anzahl der Rechnungen
                var anzahlRechnungen = await wdhRechnung.GetWiederkehrendeRechnungenAnzahlAsync(fbController2);

                // Wir müssen ja nicht immer eine neue Rechnung schreiben, sondern nur dann, wenn eine neue Rechnung anfällt
                if (wdhRechnung.AnzahlRechnungenShouldExists - anzahlRechnungen == 0)
                {
                    logger.Information($"Keine Rechnung Notwendig für RE-{wdhRechnung.WIED_N_NR}");
                    continue;
                }

                // Eine Rechnung kann ja fehlen, da wir die hier ja automatisch generieren würden.
                if (wdhRechnung.AnzahlRechnungenShouldExists - anzahlRechnungen > 1)
                {
                    message = $"RE: {wdhRechnung.WIED_N_NR}; AU: {wdhRechnung.BELE_N_NR_AU}; Anzahl (müsste es geben): {wdhRechnung.AnzahlRechnungenShouldExists}; Anzahl: {anzahlRechnungen}";
                    fehlendeRechnungenBuilder.AppendLine($"{message}<br />");
                    logger.Error(message);
                    // Wir wissen nicht, warum die Rechnung fehlt, daher legen wir auch erstmal automatisch keine neue an.
                    // Die neue müsste automatisch angelegt werden, sobald die fehlende Rechnung manuell erstellt worden ist.
                    continue;
                }

                if (wdhRechnung.WIED_D_PREISAENDERUNG_ZUM <= DateTime.Now && wdhRechnung.WIED_D_PREISAENDERUNG_ZUM != default)
                {
                    message = $"RE: {wdhRechnung.WIED_N_NR}; AU: {wdhRechnung.BELE_N_NR_AU}; Preisänderung zum: {wdhRechnung.WIED_D_PREISAENDERUNG_ZUM.ToShortDateString()}";
                    preisÄnderungBuilder.AppendLine($"{message}<br />");
                    logger.Error(message);
                    // Bei einer Preisänderung können wir die Rechnung auch nicht automatisch erstellen, da die Rechnungsvorlage sich verändert.
                    continue;
                }


                // Wenn wir bis hier kommen, dann dürfen wir auch eine Rechnung generieren
                logger.Information($"Erstelle Wiederkehrende Rechnung für {wdhRechnung.WIED_N_NR}");
                try
                {
                    await fbController2.StartTransactionAsync();
                    var neuerBeleg = await belegService.KopiereBelegAsync(fbController2, wdhRechnung.WIED_N_NR, BelegTyp.Rechnung);
                    if (neuerBeleg != null)
                    {
                        rechnungErstelltBuilder.AppendLine($"Neu: RE-{neuerBeleg.Belegnummer} Basis: {wdhRechnung.WIED_N_NR}");

                        Kunde kunde = await Kunde.GetKundeAsync(neuerBeleg.Kundennummer) ?? throw new NullReferenceException(nameof(kunde));

                        fbController2.AddParameter("@BELE_N_NR", neuerBeleg.Belegnummer);
                        fbController2.AddParameter("@BELE_N_NR_AU", wdhRechnung.BELE_N_NR_AU);
                        fbController2.AddParameter("@BELE_A_NAME1", kunde.KUND_A_NAME1);
                        fbController2.AddParameter("@BELE_A_NAME2", kunde.KUND_A_NAME2);
                        fbController2.AddParameter("@BELE_A_LAND", kunde.KUND_A_LAND);
                        fbController2.AddParameter("@BELE_A_ORT", kunde.KUND_A_ORT);
                        fbController2.AddParameter("@BELE_A_PLZ", kunde.KUND_A_PLZ);
                        fbController2.AddParameter("@BELE_A_STRASSE", kunde.KUND_A_STRASSE);

                        await fbController2.QueryAsync(@"UPDATE BELEGE SET 
WK5_BELE_L_WDH_RE = 'Y', BELE_N_NR_AU = @BELE_N_NR_AU,
BELE_A_NAME1 = @BELE_A_NAME1, BELE_A_NAME2 = @BELE_A_NAME2,
BELE_A_LAND = @BELE_A_LAND, BELE_A_ORT = @BELE_A_ORT, BELE_A_PLZ = @BELE_A_PLZ, BELE_A_STRASSE = @BELE_A_STRASSE
WHERE BELE_A_TYP = 'RE' AND BELE_N_NR = @BELE_N_NR");
                        // Wir brauchen noch Informationen zur Laufzeit in der eigentlichen Position
                        foreach (var pos in neuerBeleg.Positionen)
                        {
                            pos.Bezeichnung4 = $"Abrechnungszeitaum: {wdhRechnung.AktuellesIntervall.Von.ToShortDateString()} bis {wdhRechnung.AktuellesIntervall.Bis.ToShortDateString()}";
                            pos.Bezeichnung5 = $"Vertragslaufzeit: {wdhRechnung.AktuelleLaufzeit.Von.ToShortDateString()} bis {wdhRechnung.AktuelleLaufzeit.Bis.ToShortDateString()}";
                            fbController2.AddParameter("@BPOS_A_BEZ4", pos.Bezeichnung4);
                            fbController2.AddParameter("@BPOS_A_BEZ5", pos.Bezeichnung5);
                            fbController2.AddParameter("@WK5_BPOS_N_EK", pos.ArtikelEinkaufspreis);
                            fbController2.AddParameter("@BPOS_N_POSID", pos.PosId);
                            await fbController2.QueryAsync("UPDATE BELEGPOS SET BPOS_A_BEZ4 = @BPOS_A_BEZ4, BPOS_A_BEZ5 = @BPOS_A_BEZ5, WK5_BPOS_N_EK = @WK5_BPOS_N_EK WHERE BPOS_N_POSID = @BPOS_N_POSID");
                        }

                        neuerBeleg.Liefertermin = wdhRechnung.AktuellesIntervall.Bis;
                        fbController2.AddParameter("@BELE_D_LIEFERDATE", neuerBeleg.Liefertermin);
                        fbController2.AddParameter("@BELE_N_NR", neuerBeleg.Belegnummer);
                        await fbController2.QueryAsync("UPDATE BELEGE SET BELE_A_ZEICHEN = 'WDH', BELE_D_LIEFERDATE = @BELE_D_LIEFERDATE WHERE BELE_A_TYP = 'RE' AND BELE_N_NR = @BELE_N_NR");

                        await wdhRechnung.UpdateLastActionAsync(fbController2);
                        // Wenn erfolgreich die neue Rechnung erstellt wurde, dann auf Kündigungsdatum prüfen.
                        // <= da es ja vorkommen kann, dass eine Rechnung erst später gestellt worden ist.
                        if (wdhRechnung.WIED_D_KUENDIGUNGSDATUM.Date <= DateTime.Now.Date && wdhRechnung.WIED_D_KUENDIGUNGSDATUM != default)
                        {
                            message = $"ID: {wdhRechnung.WIED_N_NR} Rechnung: {wdhRechnung.WIED_N_NR}<br />";
                            logger.Information($"Auf Inaktiv gesetzt: {message}");
                            rechnungAufInaktivBuilder.AppendLine($"{message}<br />");
                            await wdhRechnung.AufInaktivAsync(fbController2);
                        }
                        await fbController2.CommitChangesAsync();

                        Ansprechpartner? partner = await Ansprechpartner.GetAnsprechpartnerByNameAsync(neuerBeleg.Kundennummer, neuerBeleg.Ansprechpartner);
                        string anrede = partner is null || String.IsNullOrEmpty(partner.PART_A_BRIEFANREDE) || String.IsNullOrEmpty(partner.PART_A_NAME) ? "Sehr geehrte Damen und Herren" : $"{partner.PART_A_BRIEFANREDE} {partner.PART_A_NAME}";

                        string? empfänger = await neuerBeleg.GetRechnungsEmailAdresseAsync(fbController2);

                        string template = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, @"EmailTemplates/WiederkehrendeRechnungEmail.html"));
                        template = template.Replace("{%IMPRESSUM%}", GlobalConfig.Configuration.FirmenDaten.ImpressumHTML());
                        template = template.Replace("{%ANREDE%}", anrede);

                        if (String.IsNullOrWhiteSpace(empfänger))
                        {
                            empfänger = await neuerBeleg.GetVersandEmailAdresseAsync(fbController2);
                        }
#if wdhRechnungenAnKarley
                        string betreff = $"Ihre Rechnung {neuerBeleg.BELE_N_NR} <{empfänger}>";
                        empfänger = GlobalConfig.EmailInfoEU;
#else
                        string betreff = $"Ihre Rechnung {neuerBeleg.Belegnummer}";
#endif

                        Rechnung rechnung = await Rechnung.GetRechnungAsync(neuerBeleg.Belegnummer, fbController2) ?? throw new NullReferenceException(nameof(rechnung));

                        PrintRechnung printRechnung = await PrintRechnung.CreateAsync(rechnung, new PrintRechnungRegelsatz { ShowHeader = true, ShowFooter = true }, fbController2);

                        string archivPfad = await GlobalConfig.GetConfigAsync(GlobalConfig.WK5_ARCHIVPFAD_CONFIG_NAME);
#if DEBUG
                        archivPfad = @"F:\";
#endif
                        string filenameRechnungPdf = printRechnung.Print(archivPfad);

                        var anhang = new EmailAnhang(filenameRechnungPdf, await neuerBeleg.GetBelegDateinameAsync(fbController2));

                        if (String.IsNullOrWhiteSpace(anhang.Pfad) || !File.Exists(anhang.Pfad))
                        {
                            string fehlermeldung = $"Die neue Rechnung {neuerBeleg.Belegnummer} konnte nicht verschickt werden, da der Anhang nicht generiert werden konnte.";
                            logger.Error(fehlermeldung);
                            await EmailController.FehlerMailSendenAsync(
                                betreff: "Fehler beim Mailversand für Wiederkehrende Rechnungen",
                                body: fehlermeldung
                            );
                        }
                        else
                        {
                            await emailController.SendenAsync(empfänger, betreff, template, new List<EmailAnhang>() { anhang });
                        }
                    }
                    else
                    {
                        string fehlermeldung = $"Für die Basisrechnung {wdhRechnung.WIED_N_NR} konnte keine Rechnung erstellt werden, da die Kopiermethode keine Belegnummer zurückgegeben hat.";
                        logger.Error(fehlermeldung);
                        await EmailController.FehlerMailSendenAsync(
                            betreff: "Fehler bei der Erstellung einer Wiederkehrenden Rechnung",
                            body: fehlermeldung
                        );

                        await fbController2.RollbackChangesAsync();
                    }

                }
                catch (Exception ex)
                {
                    logger.Error(ex.Message);
                    await fbController2.RollbackChangesAsync();
                }
            }

            // Report Senden
            StringBuilder reportBuilder = new StringBuilder();

            if (fehlendeRechnungenBuilder.Length > 0)
            {
                reportBuilder.AppendLine("Folgende Wiederkehrende Rechnungen konnten nicht automatisch generiert werden, da mindestens eine Vorrechnung fehlt:<br />");
                reportBuilder.AppendLine(fehlendeRechnungenBuilder.ToString());
                reportBuilder.AppendLine("<br />");
                reportBuilder.AppendLine("<strong>Was ist zu tun?</strong><br />");
                reportBuilder.AppendLine("Fehlende Rechnungen müssen manuell angelegt werden, eine pro Berechnungsmonat. Diese erstellten Rechnungsnummern sind der Programmierung zu nennen, sodass diese die Rechnung als Wiederkehrende Rechnung kennzeichnen kann und danach wieder automatisch Rechnungen erstellt werden können.<br /><br />");
            }

            if (preisÄnderungBuilder.Length > 0)
            {
                reportBuilder.AppendLine("Folgende Wiederkehrende Rechnungen konnten nicht automatisch generiert werden, da eine Preisänderung vorliegt:<br />");
                reportBuilder.AppendLine(preisÄnderungBuilder.ToString());
                reportBuilder.AppendLine("<br />");
                reportBuilder.AppendLine("<strong>Was ist zu tun?</strong><br />");
                reportBuilder.AppendLine("Die Rechnung muss manuell -1x mit dem neuen Preis erstellt werden, und die neue Rechnungsnummer muss als neue Basisrechnung in der WK5 hinterlegt werden.Das könnt Ihr als Vertrieb selbst machen.Diese neue Rechnung ist damit die neue Vorlage(Basisrechnung) für die Zukunft.Die Rechnung muss nicht durch die Programmierung nachgteragen werden.<br /><br />");

            }

            if (rechnungAufInaktivBuilder.Length > 0)
            {
                reportBuilder.AppendLine("Folgende Wiederkehrende Rechnungen wurden auf inaktiv gesetzt, da das Kündigungsdatum erreicht wurde:<br />");
                reportBuilder.AppendLine(rechnungAufInaktivBuilder.ToString());
                reportBuilder.AppendLine("<br />");
            }

            if (rechnungErstelltBuilder.Length > 0)
            {
                reportBuilder.AppendLine("Folgende Wiederkehrende Rechnungen wurden automatisch erstellt:<br />");
                reportBuilder.AppendLine(rechnungErstelltBuilder.ToString());
                reportBuilder.AppendLine("<br />");
            }

            if (reportBuilder.Length > 0)
            {
                reportBuilder.Insert(0, "Die automatische Prüfung und Erstellung der Wiederkehrenden Rechnungen ist durchgelaufen.<br /><br />");
                reportBuilder.AppendLine("<br /><br />");
                reportBuilder.AppendLine($"Diese E-Mail wurde generiert automatisch generiert durch {nameof(WiederkehrendeRechnungenAsync)} von KarleyUpdate aus der WK5.");
                await emailController.SendenAsync(
                    empfängerEmail: "software@karley.eu",
                    betreff: "Prüfung der Wiederkehrenden Rechnungen abgeschlossen",
                    body: reportBuilder.ToString()
                );
            }

            logger.Information($"{nameof(WiederkehrendeRechnungenAsync)} beendet.");
        }

        [SupportedOSPlatform("windows")]
        public static (string? firstName, string? email) GetUserByEmployeeId(int employeeId)
        {
            var search = new DirectorySearcher(new DirectoryEntry())
            {
                Filter = $"(&(objectClass=user)(employeeid={employeeId}))"
            };
            search.PropertiesToLoad.Add("givenName");
            search.PropertiesToLoad.Add("mail");

            using (var results = search.FindAll())
            {
                foreach (SearchResult result in results)
                {
                    return new(result.Properties["givenName"][0].ToString(), result.Properties["mail"][0].ToString());
                }
            }
            return (null, null);
        }

        private static Task EmailMarkierungen()
        {
            //logger.Information("Email Markierungen gestartet");
            //Account oDavidAccount = EmailController.GetDavidAccount();
            //Archive oArchive = oDavidAccount.GetArchive(@"\\srv01\david\archive\common");

            //List<MessageItem2> items = oArchive.GetArchiveEntries($"OnlyEMail").Cast<MessageItem2>().ToList();



            //foreach (MessageItem2 item in items)
            //{
            //    var fields = (Fields)item.Fields;

            //    if ((SByte)fields.Item("SymNumber").Value == 44)
            //    {
            //        fields.Item("SymNumber").Value = (SByte)0;
            //    }
            //    item.Save();
            //}

            return Task.CompletedTask;
        }

        private static async Task TeillieferungPrüfung()
        {
            logger.Information("Teillieferungsprüfung gestartet");
            using FbController2 fbController = new FbController2();
            LagerService service = new LagerService();
            BelegService belegService = new BelegService();
            CancellationTokenSource source = new CancellationTokenSource();
            List<Zugang> zugänge = await service.GetZugängeAsync(new ZugangFilter { Von = DateTime.Today, Bis = DateTime.Today.AddDays(1), Limit = 1500, ZeitraumFiltern = true, Option = ZugangFilterOption.Alle }, source.Token).ToListAsync();

            var res = zugänge.SelectMany(x => x.Positionen).GroupBy(x => x.CHAR_A_ARTINR).Select(x => new { Artikelnummer = x.Key, Menge = x.Sum(s => s.CHAR_N_MENGEOFFEN) }).ToList();

            List<Auftrag> teilliefAufträge = new List<Auftrag>();
            ZahlungsbedingungCollection zahlungsbedingungen = await Zahlungsbedingung.GetZahlungsbedingungenAsync(fbController);
            foreach (var arti in res.Where(x => x.Menge > 0))
            {
                List<int> belege = await belegService.GetAufträgeZuArtikel(arti.Artikelnummer, zahlungsbedingungen).ToListAsync();

                foreach (int aunr in belege)
                {
                    if (!teilliefAufträge.Where(x => x.Belegnummer == aunr).Any())
                    {
                        Auftrag? auftrag = await Auftrag.GetAuftragAsync(aunr, fbController);
                        if (auftrag is not null && !auftrag.Lieferbar && !auftrag.WK5_BELE_L_PAUSIERT)
                        {
                            bool wirdDurchBestellungGeliefert = await auftrag.WirdKomplettDurchDirektlieferungGeliefert();
                            if (!wirdDurchBestellungGeliefert)
                            {
                                teilliefAufträge.Add(auftrag);
                            }
                        }
                    }
                }
            }

            string body = @"
<style>
    table{
        border-collapse: collapse;        
    }

    table td, table th{
        border: 1px solid #ddd;
        padding: 8px;
    }

    table th{
        padding-top: 12px;
        padding-bottom: 12px;
        text-align: left;
        font-weight: bolder;
    }

</style>";
            body += $@"<h3>Teillieferungen prüfen</h3>
<p>Ihr habt unten eine Liste mit Aufträgen bei denen heute ein Zugang gebucht wurde.
<br/>
Prüft nach ob für diese Aufträge eine Teillieferung vorgenommen werden kann!
</p>
<br/>";

            //Link zu kl browserinterface mit "Jetzt freigeben" dann neue Text pos rein mit nichtdrucken 
            // Und text wie "Freigegeben von XXXX" Environment.Username Join auf Personal Nachname
            // Und dann freigeben
            foreach (Auftrag au in teilliefAufträge)
            {
                if (!au.Lieferbar)
                {
                    body += $"<span><a href='http://wk5.local/Auftraege/{au.Belegnummer}'>AU-{au.Belegnummer} ({au.Name1})</a></span><br/>";
                    body += @"
<table>
    <thead>
        <tr>
            <th>Artikel</th>
            <th>Bezeichnung</th>
            <th>Zu liefern</th>
            <th>Bestand</th>
            <th>Zugang heute</th>
        </tr>
    </thead>
    <tbody>";
                    foreach (Belegposition pos in au.Positionen)
                    {
                        var tmpRes = res.Where(x => x.Artikelnummer == pos.Artikelnummer).FirstOrDefault();
                        body += $@"<tr>
    <td><a href='http://wk5.local/Artikel/{pos.Artikelnummer}'>{pos.Artikelnummer}</a></td>
    <td>{pos.GetBezeichnung()}</td>
    <td>{pos.GetZuLieferndeMenge()}</td>
    <td>{pos.Bestand}</td>
    <td>{tmpRes?.Menge ?? 0}</td>
</tr>";
                    }
                    body += @$"</tbody></table><br/>
<a href='http://wk5.local/Auftraege/Auftragsfreigabe/Teillieferung/{au.Belegnummer}'>Jetzt freigeben!</a><br/><br/>";
                }
            }

            if (teilliefAufträge.Any())
            {
                await EmailController.FehlerMailSendenAsync("Teillieferungen prüfen", body, "info@karley.eu");
            }
        }

        private static async Task OffeneEUAuftraegeUstIdPruefung()
        {
            logger.Information("Offene Eu Auftragsprüfung gestartet");
            string sql = @"SELECT BELE_A_TYP,BELE_N_NR,BELE_A_KUNDENNR, BELE_A_NAME1 FROM BELEGE 
LEFT JOIN KUNDEN ON BELE_A_KUNDENNR = KUND_A_NR
WHERE BELE_A_TYP = 'AU' 
AND iif(COALESCE(BELE_N_LIEFADRESSE,0) > 0, (SELECT KULA_A_LAND FROM LIEFERANSCHRIFTEN WHERE KULA_N_NR = BELE_N_LIEFADRESSE AND KULA_A_KUNDNR = BELE_A_KUNDENNR ) ,BELE_A_LAND) != 'DE' 
AND iif(COALESCE(BELE_N_LIEFADRESSE,0) > 0, (SELECT KULA_A_LAND FROM LIEFERANSCHRIFTEN WHERE KULA_N_NR = BELE_N_LIEFADRESSE AND KULA_A_KUNDNR = BELE_A_KUNDENNR ) ,BELE_A_LAND) IN (SELECT LAND_A_ID FROM LANDESKENNZEICHEN WHERE WK5_LAND_L_ISTEULAND = 'Y') 
AND ( BELE_L_MWST = 'Y' OR KUND_L_MWST = 'Y' )
AND BELE_L_ERLEDIGT = 'N'";

            using FbController2 fbController = new FbController2();

            DataTable data = await fbController.SelectDataAsync(sql);
            List<(int auftragsnummer, string? kundenname)> aufträgeMitInvaliderVat = new();
            List<int> geprüfteAufträge = new List<int>();
            using VatService vatService = new VatService(new HttpClient());
            foreach (DataRow row in data.Rows)
            {
                string? kundnr = row.Field<string>("BELE_A_KUNDENNR");
                string? kundenname = row.Field<string>("BELE_A_NAME1");
                string belegtyp = row.Field<string>("BELE_A_TYP") ?? String.Empty;
                int belenr = row.Field<int>("BELE_N_NR");

                if (!String.IsNullOrWhiteSpace(kundnr))
                {
                    Kunde? kund = await Kunde.GetKundeAsync(kundnr);
                    if (kund is not null)
                    {
                        VatRequest request = new VatRequest
                        (
                            kund.KUND_A_USTIDNR ?? String.Empty,
                            kund.KUND_WK5_A_UST_NAME ?? kund.KUND_A_NAME1 ?? String.Empty,
                            kund.KUND_WK5_A_UST_ORT ?? kund.KUND_A_ORT ?? String.Empty,
                            kund.KUND_WK5_A_UST_PLZ ?? kund.KUND_A_PLZ ?? String.Empty,
                            kund.KUND_WK5_A_UST_STRASSE ?? kund.KUND_A_STRASSE ?? String.Empty,
                            kund.KUND_A_NR,
                            false,
                            VatZuordnung.Kunde,
                            0
                        );

                        VatCheck check = await vatService.CheckVatAsync(request, fbController, false);
                        if (vatService.IsVatCheckValid(check))
                        {


                            kund.KUND_L_MWST = false;

                            await Kunde.UpdateKundeAsync(kund, 2);

                            Auftrag? auftrag = await Auftrag.GetAuftragAsync(belenr, fbController);
                            if (auftrag is not null)
                            {
                                BelegService belegService = new BelegService();
                                auftrag.MwstBerechnen = false;
                                await belegService.UpdateAsync<Auftrag>(auftrag, kund, await Option.GetOptionenAsync(fbController), fbController);
                            }

                            await vatService.UpdateZuordnungAsync(request, true, fbController);
                            geprüfteAufträge.Add(belenr);
                        }
                        else
                        {
                            aufträgeMitInvaliderVat.Add((belenr, kundenname));
                        }
                    }
                }

            }

            StringBuilder mailBody = new StringBuilder();
            if (aufträgeMitInvaliderVat.Count > 0)
            {
                mailBody.AppendLine("<p>Es wurden offene Aufträge ins EU-Ausland gefunden bei denen keine erfolgreiche UstID Prüfung durchgeführt werden konnte</p>");
                mailBody.AppendLine("<p>Bitte die UstId einmal manuell prüfen und sonst den Kunden nach einer UstId fragen.</p>");
                mailBody.AppendLine("<ul>");
                foreach ((int auftragsnummer, string? kundenname) tuple in aufträgeMitInvaliderVat)
                {
                    mailBody.AppendLine($"<li><a href=\"https://wk5.local/Auftraege/{tuple.auftragsnummer}\">Auftrag-{tuple.auftragsnummer}</a> - {tuple.kundenname}</li>");
                }
                mailBody.AppendLine("</ul>");
                await EmailController.FehlerMailSendenAsync("Prüfung offener Aufträge ins EU Ausland abgeschlossen", mailBody.ToString(), "info@karley.eu");
            }

        }

        private static async Task StanzeDivPrüfung()
        {
            logger.Information("Stanze Divers PRüfung gestartet");
            using FbController2 fbController = new FbController2();
            ArtikelService artikelService = new ArtikelService();
            Artikel stanze = await artikelService.GetAsync("STANZE", fbController) ?? throw new ArgumentNullException(nameof(stanze));
            Artikel divers = await artikelService.GetAsync("DIVERS", fbController) ?? throw new ArgumentNullException(nameof(divers));

            if (divers.Bestand > 0 || stanze.Bestand > 0)
            {
                string body = @$"
<p>
Lieber Vertrieb,<br/>
<br/>
folgende Artikel haben einen Bestand:<br/>
DIVERS -> Menge: {divers.Bestand}<br/>
STANZE -> Menge: {stanze.Bestand}<br/>
<br/>
Diese Produkte dürfen keinen Bestand haben. Wenn diese Bestand haben, habt <strong>Ihr was falsch</strong> gemacht. Die gehen nämlich rein und direkt wieder raus.<br/>
Also überprüfen ob die ggf. noch zu Buchen sind. Falls nicht, dann den Fehler finden, wo Ihr vergessen habt einem Kunden etwas zu berechnen.<br/>
Kommt das in Folge vor wird der Wert dem Vertrieb im Jahresbonus abgezogen.<br/>
<br/>
Die Geschäftsführung</p>";

                await EmailController.FehlerMailSendenAsync("Bestand von DIV oder STANZE", body, "info@karley.eu");
            }

        }

        private static async Task CheckAbverkaufsArtikel()
        {
            logger.Information("Abverkaufsartikel Check gestartet");
            string sql = @"SELECT ARTI_A_NR FROM ARTIKEL
LEFT JOIN ARTIKELWERTE ON ARWE_A_NR = ARTI_A_NR 
WHERE ARTI_L_INAKT_ABVER = 'Y'
AND ARWE_N_BESTAND <= 0
AND ARTI_L_INAKTIV = 'N'";

            using FbController2 fbController = new FbController2();
            ArtikelService artikelService = new ArtikelService();
            DataTable data = await fbController.SelectDataAsync(sql);

            List<string> updatedArtikel = new List<string>();

            foreach (DataRow row in data.Rows)
            {
                string? artikelnummer = row.Field<string>("ARTI_A_NR");

                if (!String.IsNullOrWhiteSpace(artikelnummer))
                {
                    Artikel? artikel = await artikelService.GetAsync(artikelnummer, fbController);
                    if (artikel is not null)
                    {
                        if (artikel.Bestand <= 0 && artikel.ARTI_L_INAKT_ABVER && !artikel.ARTI_L_INAKTIV)
                        {

                            artikel.ARTI_L_INAKTIV = true;
                            await artikelService.UpdateAsync(artikel, fbController);
                            KarleyProduct? ocProduct = await KarleyProduct.GetProductAsync(artikel.Artikelnummer);

                            if (ocProduct is not null)
                            {
                                ocProduct.Status = false;
                                ocProduct.Quantity = (int)artikel.Bestand;
                                await ocProduct.UpdateProductAsync();
                            }
                            updatedArtikel.Add(artikel.Artikelnummer);
                        }
                    }
                }
            }

            if (updatedArtikel.Count > 0)
            {
                string body = @"<p>Es wurden Artikel ohne Bestand gefunden, die als Flag ""Inaktiv nach Abverkauf hatten""</p>
<p>Die Artikel wurden automatisch in der W4, und sofern vorhanden im Online Shop, auf inaktiv gestellt.</p>
<p>Bitte prüfen ob diese Artikel gelöscht werden können:</p>
<ul>
";
                foreach (string artikel in updatedArtikel)
                {
                    body += @$"<li><a href = ""https://wk5.local/Artikel/{artikel}"">{artikel}</a></li>";
                }

                body += @"</ul>";

                await EmailController.FehlerMailSendenAsync("Abverkaufte Artikel gefunden", body, "shopverwaltung@karley.eu");
            }
        }

        private static async Task VerbrauchsdatenCachen()
        {
            string sql = "SELECT ARTI_A_NR FROM ARTIKEL";

            using FbController2 fbController = new FbController2();
            var data = await fbController.SelectDataAsync(sql);
            int count = 0;
            foreach (DataRow row in data.Rows)
            {
                Console.Write($"\r[{count}\\{data.Rows.Count}]");
                string? artikelnummer = row.Field<string>("ARTI_A_NR");
                if (!String.IsNullOrWhiteSpace(artikelnummer))
                {
                    await foreach (Verbrauchsdaten cache in Verbrauchsdaten.GetCache(artikelnummer))
                    {
                        await cache.Save();
                    }
                }
                count++;
            }
        }

        private static async Task BestellungOhneAUBenachrichtigung()
        {
            using FbController2 fbController = new FbController2();
            BestellService bestellService = new BestellService();
            ArtikelService artikelService = new ArtikelService();

            StringBuilder bestellungenBuilder = new StringBuilder();

            string bestellSql = "SELECT BEST_N_NR FROM BESTELLUNGEN WHERE BEST_L_ERLEDIGT = 'N' AND COALESCE(BEST_N_AB_NR,0) = 0";

            DataTable bestellData = await fbController.SelectDataAsync(bestellSql);

            foreach (DataRow bestellRow in bestellData.Rows)
            {
                Bestellung? bestellung = await Bestellung.GetBestellungAsync(bestellRow.Field<int>("BEST_N_NR"), fbController);
                if (bestellung is not null)
                {
                    string sql = @"SELECT * FROM BELEG_CHANGE 
WHERE BCNG_A_BELEGTYP = @TYP 
AND BCNG_N_BELEGNR = @NR 
AND BCNG_N_ART = @ART 
AND BCNG_TIMESTAMP >= @FROM 
AND BCNG_TIMESTAMP < @TO
ORDER BY BCNG_TIMESTAMP DESC";

                    // Nur die Änderungen vom letzten Tag
                    DateTime from = DateTime.Now.Date.AddDays(-1);
                    DateTime to = from.AddDays(1);

                    fbController.AddParameter("@TYP", "BE");
                    fbController.AddParameter("@NR", bestellung.Bestellnummer);
                    fbController.AddParameter("@ART", 120);
                    fbController.AddParameter("@FROM", from);
                    fbController.AddParameter("@TO", to);

                    DataTable data = await fbController.SelectDataAsync(sql);

                    if (data.Rows.Count >= 1)
                    {
                        string s_Alt = data.Rows[0].Field<string>("BCNG_A_WERTALT");
                        DateTime alt = DateTime.Parse(s_Alt);

                        string s_Neu = data.Rows[0].Field<string>("BCNG_A_WERTNEU");
                        DateTime neu = DateTime.Parse(s_Neu);

                        TimeSpan diff = neu - alt;

                        if (diff.TotalDays >= 3)
                        {
                            StringBuilder bestellungBody = new StringBuilder();

                            List<Beleg> belege = new List<Beleg>();

                            await foreach (Beleg beleg in bestellService.GetOffeneAufträgeDieArtikelDerBestellungEnthalten(bestellung.Bestellnummer, fbController))
                            {
                                if (beleg.Liefertermin > neu)
                                {
                                    continue;
                                }

                                belege.Add(beleg);
                            }

                            if (belege.Count > 0)
                            {
                                foreach(Beleg beleg in belege)
                                {
                                    bestellungBody.AppendLine($"<li><a href='http://wk5.local/Auftraege/{beleg.Belegnummer}'>{beleg.Belegtyp}-{beleg.Belegnummer}</a></li>");
                                }


                                if (!String.IsNullOrWhiteSpace(bestellungBody.ToString()))
                                {
                                    StringBuilder internalBuilder = new StringBuilder();

                                    internalBuilder.AppendLine("<div class=\"box\">");
                                    internalBuilder.AppendLine($"<h2>Bestellung <a href='http://wk5.local/Bestellungen/{bestellung.Bestellnummer}'>{bestellung.Bestellnummer}</a></h2>");
                                    internalBuilder.AppendLine("<hr/>");
                                    internalBuilder.AppendLine($"<p>Der Termin der <a href='http://wk5.local/Bestellungen/{bestellung.Bestellnummer}'>Bestellung {bestellung.Bestellnummer}</a> hat sich vom {alt:dd.MM.yyyy} auf den {neu:dd.MM.yyyy} verschoben.</p>");
                                    internalBuilder.AppendLine($"<p>Dadurch könnten sich die Lieferzeiten der folgenden Aufträge verschieben:</p>");
                                    internalBuilder.AppendLine($"<ul>");
                                    internalBuilder.Append(bestellungBody.ToString());
                                    internalBuilder.AppendLine($"</ul>");
                                    internalBuilder.AppendLine($"<p>Bitte prüft das und benachrichtigt ggf. die Kunden</p>");
                                    internalBuilder.AppendLine($"<br/>");
                                    internalBuilder.AppendLine($"</div>");

                                    bestellungenBuilder.AppendLine(internalBuilder.ToString());
                                }
                            }
                        }
                    }
                }
            }

            if (!String.IsNullOrWhiteSpace(bestellungenBuilder.ToString()))
            {
                StringBuilder emailBuilder = new StringBuilder();
                emailBuilder.AppendLine("<style>");
                emailBuilder.AppendLine(".box {");
                emailBuilder.AppendLine("\tborder-style: solid;");
                emailBuilder.AppendLine("\tdisplay: inline-block;");
                emailBuilder.AppendLine("}");
                emailBuilder.AppendLine("</style>");
                emailBuilder.AppendLine("<h1>Es wurden Bestellungen gefunden bei denen sich die Lieferzeiten geändert haben.</h1>");
                emailBuilder.AppendLine("<strong>Bitte prüft ob Ihr den Kunden bescheidgeben müssst!</strong>");
                emailBuilder.AppendLine("<br/>");
                emailBuilder.AppendLine(bestellungenBuilder.ToString());

                EmailController emailController = new EmailController();
                await emailController.SendenAsync("info@karley.eu", "Bestellungen mit geänderten Lieferterminen", emailBuilder.ToString());
            }


        }




    }
}
