using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WK5.Core.Drucken.InventurRegalErfassungen;
using WK5.Core.Models;
using WK5.Core.Models.Inventuren;

namespace WK5.Core.Services
{
    /// <summary>
    /// Stellt Funktionalitäten zum durchführen einer Inventur zur Verfügung. Diese Klasse kann nicht vererbt werden.
    /// </summary>
    public sealed class InventurService
    {
        //private readonly FbController2 _fbController2 = new FbController2();
        private readonly ArtikelService _artikelService;


        public InventurService(ArtikelService artikelService)
        {
            _artikelService = artikelService;
        }

        #region Lagerorte Laden
        /// <summary>
        /// Liefert alle Hauptlagerorte, die zu einem Unterlagerplatz zugewiesen werden können.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<Hauptlager> GetHauptlagerAsync(int inventurId)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@INVENTUR_ID", inventurId);
            var data = await _fbController2.SelectDataAsync(@"SELECT H.*,
CASE WHEN EXISTS(SELECT * FROM WK5_INVENTUR_REGAL_GESCHLOSSEN WHERE IVRG_N_INVENTUR_NR = @INVENTUR_ID AND IVRG_A_HAUPTREGAL = HALG_A_NR AND IVRG_A_REGAL = HALG_A_NR) THEN
    'Y'
ELSE 
    'N'
END AS GESPERRT,
CASE WHEN EXISTS(SELECT * FROM WK5_INVENTUR_BUCHUNG WHERE IVBE_N_INVENTUR_NR = @INVENTUR_ID AND IVBE_A_REGAL IN (SELECT LAGE_A_NR FROM LAGER WHERE WK5_LAGE_A_HAUPTLAGER = HALG_A_NR)) THEN
    'Y'
ELSE 
    'N'
END AS IN_BEARBEITUNG
FROM WK5_HAUPTLAGER H
ORDER BY HALG_A_NR");
            foreach (DataRow row in data.Rows)
            {
                yield return ObjectErweiterung.DataRowZuObjekt(new Hauptlager(), row);
            }

            yield return new Hauptlager
            {
                HALG_A_NAME = "Korrektur",
                HALG_A_NR = "KORREKTUR",
                InBearbeitung = true,
                IstGesperrt = true
            };
        }
        /// <summary>
        /// Liefert alle Lagerplätze, die einem Hauptlagerplatz zugeordnet sind.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <param name="HALG_A_NR"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<InventurLagerplatz> GetUnterlagerAsync(int inventurId, string HALG_A_NR)
        {
            using FbController2 _fbController2 = new FbController2();
            if (HALG_A_NR.Equals("KORREKTUR"))
            {
                yield return new InventurLagerplatz
                {
                    Hauptregal = HALG_A_NR,
                    Lagername = "Virtuelles Lager zur Korrektur von Buchungen",
                    Lagerplatz = HALG_A_NR,
                    InBearbeitung = true,
                    IstGesperrt = true
                };
            }
            else
            {

                _fbController2.AddParameter("@WK5_LAGE_A_HAUPTLAGER", HALG_A_NR);
                _fbController2.AddParameter("@INVENTUR_ID", inventurId);
                var data = await _fbController2.SelectDataAsync(@"SELECT LAGE_A_NR, LAGE_A_NAME, WK5_LAGE_A_HAUPTLAGER, 
CASE WHEN EXISTS(SELECT * FROM WK5_INVENTUR_REGAL_GESCHLOSSEN WHERE IVRG_N_INVENTUR_NR = @INVENTUR_ID AND IVRG_A_HAUPTREGAL = @WK5_LAGE_A_HAUPTLAGER AND IVRG_A_REGAL = LAGE_A_NR) THEN
    'Y'
ELSE 
    'N'
END AS GESPERRT,
CASE WHEN EXISTS(SELECT * FROM WK5_INVENTUR_BUCHUNG WHERE IVBE_N_INVENTUR_NR = @INVENTUR_ID AND IVBE_A_REGAL = LAGE_A_NR) THEN
    'Y'
ELSE 
    'N'
END AS IN_BEARBEITUNG
FROM LAGER WHERE WK5_LAGE_A_HAUPTLAGER = @WK5_LAGE_A_HAUPTLAGER");
                foreach (DataRow row in data.Rows)
                {
                    yield return ObjectErweiterung.DataRowZuObjekt(new InventurLagerplatz(), row);
                }
            }
        }
        #endregion
        #region Buchungen
        /// <summary>
        /// Führt eine Buchung in der Inventur durch.
        /// </summary>
        /// <param name="anfrage"></param>
        /// <returns></returns>
        public async Task<InventurBuchungsResponse> BuchenAsync(InventurBuchungsAnfrage anfrage)
        {
            using FbController2 _fbController2 = new FbController2();
            #region Überprüfung
            if (String.IsNullOrWhiteSpace(anfrage.Artikelnummer))
            {
                return new InventurBuchungsResponse
                {
                    Message = "Bitte geben Sie eine Artikelnummer an",
                    Status = InventurBuchungStatus.ArtikelNichtGefunden,
                    Success = false
                };
            }

            Artikel? artikel = await _artikelService.GetAsync(anfrage.Artikelnummer, _fbController2);

            if (artikel is null)
            {
                // Wir müssen noch prüfen, ob die Eingabe vielleicht eine Seriennummer eines Artikels ist,
                // da auf manchen Produkten nur die Seriennummer einscannbar ist.
                _fbController2.AddParameter("@SNNR_A_SN", anfrage.Artikelnummer);
                string? artikelnummer = await _fbController2.FetchObjectAsync("SELECT CHAR_A_ARTINR FROM VIEW_SN WHERE SNNR_A_SN = @SNNR_A_SN") as string;

                if (artikelnummer is not null)
                {
                    anfrage = anfrage with { Seriennummer = anfrage.Artikelnummer, Artikelnummer = artikelnummer };
                    artikel = await _artikelService.GetAsync(anfrage.Artikelnummer, _fbController2);
                }

                if (artikel is null)
                {
                    return new InventurBuchungsResponse
                    {
                        Message = $"Artikelnummer {anfrage.Artikelnummer} konnte nicht gefunden werden.",
                        Status = InventurBuchungStatus.ArtikelNichtGefunden,
                        Success = false,
                    };
                }
            }

            // Falls wir eine MPN, EAN oder alternative Artikelnummer eingegeben haben, tauschen wir diese hier an der Stelle durch die echte Artikelnummer aus.
            anfrage = anfrage with { Artikelnummer = artikel.Artikelnummer };

            //if (artikel.ArtikelIstStückliste)
            //{
            //    return new InventurBuchungsResponse
            //    {
            //        Message = "Buchung nicht möglich, da es sich hierbei um eine Stückliste handelt. Bitte die Produkte der Stückliste stattdessen einscannen.",
            //        Status = InventurBuchungStatus.ArtikelIstStückliste,
            //        Success = false
            //    };
            //}

            if (artikel.ARTI_N_COLLI > 1 && !anfrage.IgnoreVpe)
            {
                return new InventurBuchungsResponse
                {
                    Message = $"Für den Artikel {anfrage.Artikelnummer} wurde eine VPE von {artikel.ARTI_N_COLLI} gefunden. Möchten Sie direkt eine vollständige VPE buchen?",
                    Status = InventurBuchungStatus.VpeVorschlagen,
                    Success = false,
                    Value = artikel.ARTI_N_COLLI
                };
            }

            if (artikel.ARTI_L_SN && String.IsNullOrWhiteSpace(anfrage.Seriennummer))
            {
                var seriennummern = await _artikelService.GetSeriennummernAsync(artikel, _fbController2).ToListAsync();
                return new InventurBuchungsResponse
                {
                    Message = $"Zum buchen der Artikelnummer {anfrage.Artikelnummer} wird eine Seriennummer benötigt.",
                    Status = InventurBuchungStatus.SeriennummerBenötigt,
                    Success = false,
                    Value = seriennummern
                };
            }
            else if (artikel.ARTI_L_SN)
            {
                // Seriennummer überprüfen, ob existiert und noch nicht gebucht wurde
                InventurBuchungStatus resultSeriennummer = await PrüfeSeriennummerAsync(anfrage.InventurId, anfrage.Seriennummer);

                // Nur die Zwei Stati können von der Funktion PrüfeSeriennummer zurückgegeben werden, alle anderen sind daher OK.
                if (resultSeriennummer == InventurBuchungStatus.SeriennummerBereitsGebucht)
                {
                    return new InventurBuchungsResponse
                    {
                        Message = $"Buchung fehlgeschlagen, da die Seriennummer {anfrage.Seriennummer} bereits für diese Inventur gebucht worden ist.",
                        Status = resultSeriennummer,
                        Success = false
                    };
                }
                else if (resultSeriennummer == InventurBuchungStatus.SeriennummerNichtVorhanden)
                {
                    return new InventurBuchungsResponse
                    {
                        Message = $"Die Seriennummer {anfrage.Seriennummer} konnte nicht gefunden werden.",
                        Status = resultSeriennummer,
                        Success = false
                    };
                }
            }

            if (await ChargenÜberschritten(anfrage.Artikelnummer, anfrage.Menge, anfrage.InventurId))
            {
                return new InventurBuchungsResponse
                {
                    Message = "Anzahl der maximalen Chargen überschritten.",
                    Status = InventurBuchungStatus.MaxZugangÜberschritten,
                    Success = false
                };
            }

            // Wenn wir eine negative Buchung haben, dann wollen wir nicht in den Minusbereich kommen - das ist physikalisch nicht möglich
            //if(gebuchtTotal + anfrage.Menge < 0)
            //{
            //    anfrage = anfrage with { Menge = gebuchtTotal };
            //}

            var inventur = await Inventur.GetInventurAsync(anfrage.InventurId);

            if (inventur is null)
            {
                return new InventurBuchungsResponse
                {
                    Message = "Die Inventur konnte nicht gefunden werden.",
                    Status = InventurBuchungStatus.InventurNichtGefunden,
                    Success = false
                };
            }
            else
            {
                if (inventur.Erledigt)
                {
                    return new InventurBuchungsResponse
                    {
                        Message = "Die Inventur ist bereits abgeschlossen. Eine weitere Buchung ist nicht möglich.",
                        Status = InventurBuchungStatus.InventurNichtGefunden,
                        Success = false
                    };
                }
            }

            if (!anfrage.Regal.Equals("KORREKTUR", StringComparison.OrdinalIgnoreCase))
            {
                var regal = await Lagerplatz.GetLagerAsync(anfrage.Regal);

                if (regal is null)
                {
                    return new InventurBuchungsResponse
                    {
                        Message = $"Die Buchung ist fehlgeschlagen, da der Lagerplatz {anfrage.Regal} nicht gefunden werden konnte.",
                        Status = InventurBuchungStatus.RegalNichtGefunden,
                        Success = false
                    };
                }
            }
            #endregion
            #region Buchen
            await _fbController2.StartTransactionAsync();
            _fbController2.AddParameter("@IVBE_N_INVENTUR_NR", anfrage.InventurId);
            _fbController2.AddParameter("@IVBE_A_ARTIKELNR", anfrage.Artikelnummer);
            _fbController2.AddParameter("@IVBE_N_MENGE", anfrage.Menge);
            _fbController2.AddParameter("@IVBE_A_REGAL", anfrage.Regal);
            _fbController2.AddParameter("@IVBE_N_USER", anfrage.UserId);
            int buchungsId = Convert.ToInt32(await _fbController2.FetchObjectAsync(@"
UPDATE OR INSERT INTO WK5_INVENTUR_BUCHUNG 
(IVBE_N_INVENTUR_NR, IVBE_A_ARTIKELNR, IVBE_A_REGAL, IVBE_N_USER, IVBE_N_MENGE)
VALUES
(@IVBE_N_INVENTUR_NR, @IVBE_A_ARTIKELNR, @IVBE_A_REGAL, @IVBE_N_USER,
    (SELECT 
        CASE WHEN SUM(IVBE_N_MENGE) IS NULL THEN 
            0 
        ELSE 
            SUM(IVBE_N_MENGE) 
        END  
    FROM WK5_INVENTUR_BUCHUNG 
    WHERE UPPER(IVBE_A_ARTIKELNR) = @IVBE_A_ARTIKELNR 
        AND IVBE_A_REGAL = @IVBE_A_REGAL 
        AND IVBE_N_INVENTUR_NR = @IVBE_N_INVENTUR_NR
    ) + @IVBE_N_MENGE
) 
MATCHING(IVBE_N_INVENTUR_NR, IVBE_A_ARTIKELNR, IVBE_A_REGAL)
RETURNING IVBE_N_NR"));

            // Müssen wir eine Seriennummer hinterlegen?
            if (artikel.ARTI_L_SN)
            {
                _fbController2.AddParameter("@IVSN_N_BUCHUNG_NR", buchungsId);
                _fbController2.AddParameter("@IVSN_A_SN", anfrage.Seriennummer);
                await _fbController2.QueryAsync(@"INSERT INTO WK5_INVENTUR_BUCHUNG_SN (IVSN_N_BUCHUNG_NR, IVSN_A_SN) VALUES (@IVSN_N_BUCHUNG_NR, @IVSN_A_SN)");
            }

            // Kommentar einfügen
            if (!String.IsNullOrWhiteSpace(anfrage.Kommentar))
            {
                _fbController2.AddParameter("@IBUK_N_BUCHUNG_NR", buchungsId);
                _fbController2.AddParameter("@IBUK_B_KOMMENTAR", StringErweiterung.ConvertEncoding(anfrage.Kommentar, Encoding.UTF8, Encoding.GetEncoding("ISO-8859-1")));
                await _fbController2.QueryAsync("INSERT INTO WK5_INVENTUR_BUCHUNG_KOMMENTARE (IBUK_N_BUCHUNG_NR, IBUK_B_KOMMENTAR) VALUES (@IBUK_N_BUCHUNG_NR, @IBUK_B_KOMMENTAR)");
            }

            await _fbController2.CommitChangesAsync();
            #endregion
            // Wir geben die anfrage mit zurück, da wir ggf. eine neue generiert haben, sofern die alte Anfrage sich auf eine Seriennummer als Artikelnummer bezogen hat
            return new InventurBuchungsResponse
            {
                Message = "Buchung erfolgreich",
                Status = InventurBuchungStatus.OK,
                Success = true,
                Value = anfrage
            };
        }
        /// <summary>
        /// Aktualisiert die Daten einer einzelnen Buchung der Inventur
        /// </summary>
        /// <param name="artikel"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<InventurBuchungsResponse> UpdateBuchung(InventurArtikelGebucht artikel, int userId)
        {
            using FbController2 _fbController2 = new FbController2();
            if (await ChargenÜberschritten(artikel))
            {
                return new InventurBuchungsResponse
                {
                    Message = "Anzahl der maximalen Chargen überschritten.",
                    Status = InventurBuchungStatus.MaxZugangÜberschritten,
                    Success = false
                };
            }


            if (artikel.Menge <= 0)
            {
                _fbController2.AddParameter("@BUCHUNGS_ID", artikel.BuchungsId);
                await _fbController2.QueryAsync("DELETE FROM WK5_INVENTUR_BUCHUNG WHERE IVBE_N_NR = @BUCHUNGS_ID");
                _fbController2.AddParameter("@BUCHUNGS_ID", artikel.BuchungsId);
                await _fbController2.QueryAsync("DELETE FROM WK5_INVENTUR_BUCHUNG_KOMMENTARE WHERE IBUK_N_BUCHUNG_NR = @BUCHUNGS_ID");
                _fbController2.AddParameter("@BUCHUNGS_ID", artikel.BuchungsId);
                await _fbController2.QueryAsync("DELETE FROM WK5_INVENTUR_BUCHUNG_SN WHERE IVSN_N_BUCHUNG_NR = @BUCHUNGS_ID");
            }
            else
            {
                _fbController2.AddParameter("@BUCHUNGS_ID", artikel.BuchungsId);
                _fbController2.AddParameter("@MENGE", artikel.Menge);
                _fbController2.AddParameter("@USERID", userId);
                await _fbController2.QueryAsync("UPDATE WK5_INVENTUR_BUCHUNG SET IVBE_N_MENGE = @MENGE, IVBE_N_USER = @USERID WHERE IVBE_N_NR = @BUCHUNGS_ID");

                if (artikel.Seriennummern.Count > 0)
                {
                    _fbController2.AddParameter("@BUCHUNGS_ID", artikel.BuchungsId);
                    await _fbController2.QueryAsync($"DELETE FROM WK5_INVENTUR_BUCHUNG_SN WHERE IVSN_N_BUCHUNG_NR = @BUCHUNGS_ID AND IVSN_A_SN NOT IN('{String.Join("','", artikel.Seriennummern.Select(x => x.Seriennummer))}')");
                }
            }

            return new InventurBuchungsResponse
            {
                Message = "Buchung aktualisiert.",
                Status = InventurBuchungStatus.OK,
                Success = true
            };
        }

        public async Task<decimal> DirektBuchungAsync(string artikelnummer, decimal menge, FbController2 fbController)
        {
            fbController.AddParameter("@INP_ARTIKEL", artikelnummer);
            fbController.AddParameter("@INP_MENGE", menge);
            fbController.AddParameter("@INP_USER", fbController.UserId);
            fbController.AddParameter("@INP_GROESSE", false);
            decimal bestand = Convert.ToDecimal(await fbController.FetchObjectAsync("SELECT INHALT FROM PROZ_INVENTURERFASS(@INP_ARTIKEL, @INP_MENGE, @INP_USER, @INP_GROESSE)"), CultureInfo.InvariantCulture);
            return bestand;
        }

        public async Task DirektBuchungSeriennummerAsync(Seriennummer seriennummer, FbController2 fbController)
        {
            // Erst den ausgeliefert Status korrigieren
            int art = seriennummer.Ausgeliefert ? 2 : 8;
            fbController.AddParameter("@INP_ARTIKEL", seriennummer.Artikelnummer);
            fbController.AddParameter("@INP_ART", art);
            fbController.AddParameter("@INP_SN", seriennummer.Nummer);
            fbController.AddParameter("@INP_USER", fbController.UserId);
            fbController.AddParameter("@INP_CHARGE", seriennummer.Charge);
            await fbController.RunProcedureAsync("PROZ_INVENTURSN");

            // Der Bestand ist noch falsch
            fbController.AddParameter("@INP_ARTIKEL", seriennummer.Artikelnummer);
            fbController.AddParameter("@INP_ART", 3);
            fbController.AddParameter("@INP_SN", string.Empty);
            fbController.AddParameter("@INP_USER", 0);
            fbController.AddParameter("@INP_CHARGE", 0);
            await fbController.RunProcedureAsync("PROZ_INVENTURSN");
        }
        #endregion
        #region Prüfungen
        /// <summary>
        /// Prüft, ob ein Lagerplatz bereits durch einen Mitarbeiter abgeschlossen wurde.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <param name="hauptlager"></param>
        /// <param name="lagerplatz"></param>
        /// <returns></returns>
        public async Task<bool> IstRegalAbgeschlossenAsync(int inventurId, string hauptlager, string lagerplatz)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@INVENTUR_ID", inventurId);
            _fbController2.AddParameter("@HAUPTLAGER", hauptlager);
            _fbController2.AddParameter("@LAGERPLATZ", lagerplatz);
            var obj = await _fbController2.FetchObjectAsync("SELECT IVRG_N_INVENTUR_NR FROM WK5_INVENTUR_REGAL_GESCHLOSSEN WHERE IVRG_N_INVENTUR_NR = @INVENTUR_ID AND IVRG_A_HAUPTREGAL = @HAUPTLAGER AND IVRG_A_REGAL = @LAGERPLATZ");

            return obj is not null;
        }
        /// <summary>
        /// Prüft, ob eine Seriennummer existiert und ob diese bereits in der Inventur gebucht worden ist.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <param name="seriennummer"></param>
        /// <returns>
        /// Liefert im Falle eines Fehlers entweder den Wert <see cref="InventurBuchungStatus.SeriennummerBereitsGebucht"/> oder <see cref="InventurBuchungStatus.SeriennummerNichtVorhanden"/> zurück. Alle anderen Fälle sind OK.
        /// </returns>
        private async Task<InventurBuchungStatus> PrüfeSeriennummerAsync(int inventurId, string seriennummer)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@SERIENNUMMER", seriennummer);
            object? tmp = await _fbController2.FetchObjectAsync("SELECT SNNR_A_SN FROM SN WHERE SNNR_A_SN = @SERIENNUMMER");

            if (tmp is null)
            {
                return InventurBuchungStatus.SeriennummerNichtVorhanden;
            }

            _fbController2.AddParameter("@SERIENNUMMER", seriennummer);
            _fbController2.AddParameter("@INVENTUR_ID", inventurId);
            tmp = await _fbController2.FetchObjectAsync("SELECT IVSN_A_SN FROM WK5_INVENTUR_BUCHUNG IB INNER JOIN WK5_INVENTUR_BUCHUNG_SN IBSN ON IVSN_N_BUCHUNG_NR = IVBE_N_NR WHERE IVSN_A_SN = @SERIENNUMMER AND IVBE_N_INVENTUR_NR = @INVENTUR_ID");

            if (tmp is not null)
            {
                return InventurBuchungStatus.SeriennummerBereitsGebucht;
            }

            return InventurBuchungStatus.OK;
        }
        private async Task<bool> ChargenÜberschritten(InventurArtikelGebucht buchung)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@ARTIKELNUMMER", buchung.Artikelnummer);
            decimal chargenTotal = Convert.ToDecimal(await _fbController2.FetchObjectAsync("SELECT CASE WHEN SUM(CHAR_N_MENGE) IS NULL THEN 0 ELSE SUM(CHAR_N_MENGE) END FROM CHARGEN WHERE UPPER(CHAR_A_ARTINR) = @ARTIKELNUMMER"));
            return chargenTotal < buchung.Menge;
        }
        private async Task<bool> ChargenÜberschritten(string artikelnummer, decimal buchungsMenge, int inventurId)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@ARTIKELNUMMER", artikelnummer);
            decimal chargenTotal = Convert.ToDecimal(await _fbController2.FetchObjectAsync("SELECT CASE WHEN SUM(CHAR_N_MENGE) IS NULL THEN 0 ELSE SUM(CHAR_N_MENGE) END FROM CHARGEN WHERE UPPER(CHAR_A_ARTINR) = @ARTIKELNUMMER"));
            _fbController2.AddParameter("@ARTIKELNUMMER", artikelnummer);
            _fbController2.AddParameter("@INVENTUR_ID", inventurId);
            decimal gebuchtTotal = Convert.ToDecimal(await _fbController2.FetchObjectAsync("SELECT CASE WHEN SUM(IVBE_N_MENGE) IS NULL THEN 0 ELSE SUM(IVBE_N_MENGE) END FROM WK5_INVENTUR_BUCHUNG WHERE UPPER(IVBE_A_ARTIKELNR) = @ARTIKELNUMMER AND IVBE_N_INVENTUR_NR = @INVENTUR_ID"));

            return chargenTotal < gebuchtTotal + buchungsMenge;
        }

        /// <summary>
        /// Prüft, ob eine Inventur verfügbar ist.
        /// </summary>
        /// <returns>Wenn eine Inventur verfügbar ist, dann wird diese zurückgegeben, ansonsten null.</returns>
        public async Task<Inventur?> InventurVerfügbar()
        {
            using FbController2 _fbController2 = new FbController2();
            var row = await _fbController2.SelectRowAsync("SELECT * FROM WK5_INVENTUR WHERE INVE_L_ERLEDIGT = 'N'");
            return row is null ? null : ObjectErweiterung.DataRowZuObjekt(new Inventur(), row);
        }
        #endregion
        #region Daten laden
        /// <summary>
        /// Liefert alle gebuchten Artikel der Inventur.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<InventurArtikelGebucht> GetGebuchteArtikelByInventurAsync(int inventurId)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@IVBE_N_INVENTUR_NR", inventurId);

            var data = await _fbController2.SelectDataAsync(@"SELECT 
IB.*,
(SELECT SUM(IVBE_N_MENGE) FROM WK5_INVENTUR_BUCHUNG IB2 WHERE IB2.IVBE_A_ARTIKELNR = IB.IVBE_A_ARTIKELNR AND IB2.IVBE_N_INVENTUR_NR = IB.IVBE_N_INVENTUR_NR GROUP BY IVBE_A_ARTIKELNR, IVBE_N_INVENTUR_NR) AS GEBUCHT_GESAMT,
ARTI_L_SN, ARWE_N_BESTAND,
ARTI_A_LAGER, ARTI_A_BEZ1, ARTI_A_BEZ2,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'ZUGA') AS LETZTERZUGANG,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'LS') AS LETZTEBEWEGUNG
FROM WK5_INVENTUR_BUCHUNG IB 
LEFT JOIN ARTIKEL A ON A.ARTI_A_NR = IB.IVBE_A_ARTIKELNR
LEFT JOIN ARTIKELWERTE AW ON AW.ARWE_A_NR = A.ARTI_A_NR
WHERE IVBE_N_INVENTUR_NR = @IVBE_N_INVENTUR_NR
ORDER BY IVBE_A_ARTIKELNR");

            foreach (DataRow row in data.Rows)
            {
                InventurArtikelGebucht artikelGebucht = ObjectErweiterung.DataRowZuObjekt(new InventurArtikelGebucht(), row);
                SetArtikelBeschreibung(artikelGebucht, row);
                _fbController2.AddParameter("@IVSN_N_BUCHUNG_NR", artikelGebucht.BuchungsId);
                var dataSeriennummern = await _fbController2.SelectDataAsync("SELECT * FROM WK5_INVENTUR_BUCHUNG_SN WHERE IVSN_N_BUCHUNG_NR = @IVSN_N_BUCHUNG_NR");
                foreach (DataRow seriennummerRow in dataSeriennummern.Rows)
                {
                    artikelGebucht.Seriennummern.Add(ObjectErweiterung.DataRowZuObjekt(new InventurBuchungSeriennummer(), seriennummerRow));
                }

                _fbController2.AddParameter("@IBUK_N_BUCHUNG_NR", artikelGebucht.BuchungsId);
                var dataKommentare = await _fbController2.SelectDataAsync("SELECT * FROM WK5_INVENTUR_BUCHUNG_KOMMENTARE WHERE IBUK_N_BUCHUNG_NR = @IBUK_N_BUCHUNG_NR");
                foreach (DataRow kommentarRow in dataKommentare.Rows)
                {
                    artikelGebucht.Kommentare.Add(ObjectErweiterung.DataRowZuObjekt(new InventurBuchungKommentar(), kommentarRow));
                }



                yield return artikelGebucht;
            }
        }
        /// <summary>
        /// Liefert alle gebuchten Artikel der Inventur für einen bestimmten Lagerplatz.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <param name="lagerplatz"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<InventurArtikelGebucht> GetGebuchteArtikelByUnterlagerAsync(int inventurId, string lagerplatz)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@IVBE_N_INVENTUR_NR", inventurId);
            _fbController2.AddParameter("@IVBE_A_REGAL", lagerplatz);
            var data = await _fbController2.SelectDataAsync(@"SELECT 
IB.*,
(SELECT SUM(IVBE_N_MENGE) FROM WK5_INVENTUR_BUCHUNG IB2 WHERE IB2.IVBE_A_ARTIKELNR = IB.IVBE_A_ARTIKELNR AND IB2.IVBE_N_INVENTUR_NR = IB.IVBE_N_INVENTUR_NR GROUP BY IVBE_A_ARTIKELNR, IVBE_N_INVENTUR_NR) AS GEBUCHT_GESAMT,
ARTI_L_SN, ARWE_N_BESTAND, ARTI_A_LAGER, ARTI_A_BEZ1, ARTI_A_BEZ2,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'ZUGA') AS LETZTERZUGANG,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'LS') AS LETZTEBEWEGUNG
FROM WK5_INVENTUR_BUCHUNG IB 
LEFT JOIN ARTIKEL A ON A.ARTI_A_NR = IB.IVBE_A_ARTIKELNR
LEFT JOIN ARTIKELWERTE AW ON AW.ARWE_A_NR = A.ARTI_A_NR
WHERE IVBE_N_INVENTUR_NR = @IVBE_N_INVENTUR_NR AND IVBE_A_REGAL = @IVBE_A_REGAL");

            foreach (DataRow row in data.Rows)
            {
                InventurArtikelGebucht artikelGebucht = ObjectErweiterung.DataRowZuObjekt(new InventurArtikelGebucht(), row);
                SetArtikelBeschreibung(artikelGebucht, row);
                _fbController2.AddParameter("@IVSN_N_BUCHUNG_NR", artikelGebucht.BuchungsId);
                var dataSeriennummern = await _fbController2.SelectDataAsync("SELECT * FROM WK5_INVENTUR_BUCHUNG_SN WHERE IVSN_N_BUCHUNG_NR = @IVSN_N_BUCHUNG_NR");
                foreach (DataRow seriennummerRow in dataSeriennummern.Rows)
                {
                    artikelGebucht.Seriennummern.Add(ObjectErweiterung.DataRowZuObjekt(new InventurBuchungSeriennummer(), seriennummerRow));
                }

                _fbController2.AddParameter("@IBUK_N_BUCHUNG_NR", artikelGebucht.BuchungsId);
                var dataKommentare = await _fbController2.SelectDataAsync("SELECT * FROM WK5_INVENTUR_BUCHUNG_KOMMENTARE WHERE IBUK_N_BUCHUNG_NR = @IBUK_N_BUCHUNG_NR");
                foreach (DataRow kommentarRow in dataKommentare.Rows)
                {
                    artikelGebucht.Kommentare.Add(ObjectErweiterung.DataRowZuObjekt(new InventurBuchungKommentar(), kommentarRow));
                }



                yield return artikelGebucht;
            }
        }
        /// <summary>
        /// Liefert alle Artikel, die sich in dem entsprechenden Unterlagerplatz befinden sollten.
        /// </summary>
        /// <param name="LAGE_A_NR"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<InventurArtikel> GetArtikelByUnterlagerAsync(string LAGE_A_NR)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@ARTI_A_LAGER", LAGE_A_NR);
            var data = await _fbController2.SelectDataAsync(@"SELECT 
ARTI_A_NR, ARTI_A_LAGER, ARTI_A_BEZ1, ARTI_A_BEZ2, ARWE_N_BESTAND,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'ZUGA') AS LETZTERZUGANG,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'LS') AS LETZTEBEWEGUNG
FROM ARTIKEL A 
LEFT JOIN ARTIKELWERTE AW ON AW.ARWE_A_NR = A.ARTI_A_NR 
WHERE ARTI_A_LAGER = @ARTI_A_LAGER");

            foreach (DataRow row in data.Rows)
            {
                var inventurArtikel = ObjectErweiterung.DataRowZuObjekt(new InventurArtikel(), row);
                SetArtikelBeschreibung(inventurArtikel, row);
                yield return inventurArtikel;
            }
        }
        private void SetArtikelBeschreibung(IArtikel artikel, DataRow row)
        {
            string bezeichnung1 = row.Field<string>("ARTI_A_BEZ1") ?? String.Empty;
            string bezeichnung2 = row.Field<string>("ARTI_A_BEZ2") ?? String.Empty;
            artikel.Bezeichnung = $"{bezeichnung1}{bezeichnung2}";
        }
        /// <summary>
        /// Liefert alle Artikel, die laut W4 einen Lagerbestand haben, aber in der Inventur bislang noch nicht erfasst worden sind.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<InventurArtikel> GetArtikelNichtZugeordnetAsync(int inventurId)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@INVENTUR_ID", inventurId);
            var data = await _fbController2.SelectDataAsync(@"SELECT 
ARTI_A_NR, ARTI_A_LAGER, ARWE_N_BESTAND, ARTI_A_BEZ1, ARTI_A_BEZ2,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'ZUGA') AS LETZTERZUGANG,
(SELECT MAX(ARBE_TIMESTAMP) FROM ARTIKELBEWEGUNG WHERE ARBE_A_ARTINR = ARTI_A_NR AND ARBE_A_ART = 'LS') AS LETZTEBEWEGUNG
FROM ARTIKELWERTE AW
LEFT JOIN ARTIKEL A ON A.ARTI_A_NR = AW.ARWE_A_NR
LEFT JOIN WK5_INVENTUR_BUCHUNG IB ON IB.IVBE_A_ARTIKELNR = A.ARTI_A_NR AND IVBE_N_INVENTUR_NR = @INVENTUR_ID
WHERE ARWE_N_BESTAND > 0 AND IVBE_N_NR IS NULL 
ORDER BY ARTI_A_NR");

            foreach (DataRow row in data.Rows)
            {
                var inventurArtikel = ObjectErweiterung.DataRowZuObjekt(new InventurArtikel(), row);
                SetArtikelBeschreibung(inventurArtikel, row);
                yield return inventurArtikel;
            }
        }
        #endregion
        #region Abschließen
        /// <summary>
        /// Schließt einen Lagerort in der Inventur, sodass keine weiteren Buchungen in der WK5 für das Regal möglich sind.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <param name="hauptlager"></param>
        /// <param name="lagerplatz"></param>
        /// <returns></returns>
        public async Task RegalAbschließenAsync(int inventurId, string hauptlager, string lagerplatz)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@IVRG_N_INVENTUR_NR", inventurId);
            _fbController2.AddParameter("@IVRG_A_HAUPTREGAL", hauptlager);
            _fbController2.AddParameter("@IVRG_A_REGAL", lagerplatz);
            await _fbController2.QueryAsync("INSERT INTO WK5_INVENTUR_REGAL_GESCHLOSSEN (IVRG_N_INVENTUR_NR, IVRG_A_HAUPTREGAL, IVRG_A_REGAL) VALUES (@IVRG_N_INVENTUR_NR, @IVRG_A_HAUPTREGAL, @IVRG_A_REGAL)");
        }

        public async Task<InventurLagerplatzFestlegenResponse> SetLagerplatzDurchInventur(InventurArtikelGebucht artikel, string lagerplatz)
        {
            using FbController2 _fbController2 = new FbController2();
            // Als erstes prüfen wir, ob der Lagerort bereits durch die Inventur gesetzt worden ist
            _fbController2.AddParameter("@WK5_ARTI_N_INVENTUR", artikel.InventurId);
            _fbController2.AddParameter("@ARTI_A_NR", artikel.Artikelnummer);
            var row = await _fbController2.SelectRowAsync("SELECT ARTI_A_NR, ARTI_A_LAGER FROM ARTIKEL WHERE ARTI_A_NR = @ARTI_A_NR AND WK5_ARTI_N_INVENTUR = @WK5_ARTI_N_INVENTUR");

            if (row is not null)
            {
                // Es gibt also einen Eintrag. Nun müssen wir wissen, ob die Lagerorte abweichend sind
                string artikelLagerplatz = row.Field<string>("ARTI_A_LAGER") ?? String.Empty;

                if (!lagerplatz.Equals(artikelLagerplatz))
                {
                    return new InventurLagerplatzFestlegenResponse
                    {
                        Status = InventurLagerplatzFestlegenStatus.BereitsZugewiesen,
                        Value = artikelLagerplatz,
                        Success = false
                    };
                }

            }
            else
            {
                // Der Lagerplatz wurde durch die Inventur noch nicht gesetzt, also dürfen wir diesen nun festlegen
                _fbController2.AddParameter("@ARTI_A_LAGER", lagerplatz);
                _fbController2.AddParameter("@ARTI_A_NR", artikel.Artikelnummer);
                _fbController2.AddParameter("@WK5_ARTI_N_INVENTUR", artikel.InventurId);
                await _fbController2.QueryAsync("UPDATE ARTIKEL SET ARTI_A_LAGER = @ARTI_A_LAGER, WK5_ARTI_N_INVENTUR = @WK5_ARTI_N_INVENTUR WHERE ARTI_A_NR = @ARTI_A_NR");
            }

            return new InventurLagerplatzFestlegenResponse
            {
                Status = InventurLagerplatzFestlegenStatus.OK,
                Success = true
            };
        }

        public async Task RegalÖffnenAsync(int inventurId, string hauptlager, string lagerplatz)
        {
            using FbController2 _fbController2 = new FbController2();
            _fbController2.AddParameter("@IVRG_N_INVENTUR_NR", inventurId);
            _fbController2.AddParameter("@IVRG_A_HAUPTREGAL", hauptlager);
            _fbController2.AddParameter("@IVRG_A_REGAL", lagerplatz);
            await _fbController2.QueryAsync("DELETE FROM WK5_INVENTUR_REGAL_GESCHLOSSEN WHERE IVRG_N_INVENTUR_NR = @IVRG_N_INVENTUR_NR AND IVRG_A_HAUPTREGAL = @IVRG_A_HAUPTREGAL AND IVRG_A_REGAL = @IVRG_A_REGAL");

            // Wenn wir ein Stellfläche eines Regals freigeben, dann muss auch automatisch das Regal der Stellfläche freigegeben werden
            if (!hauptlager.Equals(lagerplatz))
            {
                _fbController2.AddParameter("@IVRG_N_INVENTUR_NR", inventurId);
                _fbController2.AddParameter("@IVRG_A_HAUPTREGAL", hauptlager);
                _fbController2.AddParameter("@IVRG_A_REGAL", hauptlager);
                await _fbController2.QueryAsync("DELETE FROM WK5_INVENTUR_REGAL_GESCHLOSSEN WHERE IVRG_N_INVENTUR_NR = @IVRG_N_INVENTUR_NR AND IVRG_A_HAUPTREGAL = @IVRG_A_HAUPTREGAL AND IVRG_A_REGAL = @IVRG_A_REGAL");
            }

        }
        public async Task<bool> InventurAbschließenAsync(int inventurId, int userId)
        {
            using FbController2 _fbController2 = new FbController2();

            await _fbController2.StartTransactionAsync();
            await _fbController2.QueryAsync("update artikelwerte set ARWE_N_INVENTDIFF = null, arwe_d_inventdatum = null");

            // Bestand auf 0 setzen
            await _fbController2.QueryAsync("UPDATE ARTIKELWERTE SET ARWE_N_BESTAND = 0");

            // Alle Seriennummern sind ausgeliefert.
            await _fbController2.QueryAsync("UPDATE SN SET SNNR_L_AUSGELIEFERT = 'Y'");
            // Dann Bestand für Artikel setzen
            var inventurData = await GetGebuchteArtikelByInventurAsync(inventurId).ToListAsync();
            foreach (var buchung in inventurData.GroupBy(x => x.Artikelnummer).Select(x => new
            {
                Artikelnummer = x.Key,
                Menge = x.Sum(y => y.Menge),
                Kommentare = x.SelectMany(y => y.Kommentare),
                Seriennummern = x.SelectMany(y => y.Seriennummern)
            }))
            {
                _fbController2.AddParameter("@INP_ARTIKEL", buchung.Artikelnummer);
                _fbController2.AddParameter("@INP_MENGE", buchung.Menge);
                _fbController2.AddParameter("@INP_USER", userId);
                _fbController2.AddParameter("@INP_GROESSE", "N");
                await _fbController2.RunProcedureAsync("PROZ_INVENTURERFASS");

                // Jetzt setzen wir alle gescannten Seriennummern auf nicht ausgeliefert
                foreach (var seriennummer in buchung.Seriennummern)
                {
                    _fbController2.AddParameter("@SERIENNUMMER", seriennummer.Seriennummer);
                    var chargeId = Convert.ToInt32(await _fbController2.FetchObjectAsync("SELECT SNNR_N_CHARGE FROM VIEW_SN WHERE SNNR_A_SN = @SERIENNUMMER"));

                    if (chargeId == 0)
                    {
                        throw new ArgumentException(nameof(chargeId));
                    }

                    _fbController2.AddParameter("@INP_ARTIKEL", buchung.Artikelnummer);
                    _fbController2.AddParameter("@INP_ART", 2);
                    _fbController2.AddParameter("@INP_SN", seriennummer.Seriennummer);
                    _fbController2.AddParameter("@INP_USER", userId);
                    _fbController2.AddParameter("@INP_CHARGE", chargeId);
                    await _fbController2.RunProcedureAsync("PROZ_INVENTURSN");
                }

                if (buchung.Seriennummern.Any())
                {
                    _fbController2.AddParameter("@INP_ARTIKEL", buchung.Artikelnummer);
                    _fbController2.AddParameter("@INP_ART", 3);
                    _fbController2.AddParameter("@INP_SN", String.Empty);
                    _fbController2.AddParameter("@INP_USER", userId);
                    _fbController2.AddParameter("@INP_CHARGE", 0);
                    await _fbController2.RunProcedureAsync("PROZ_INVENTURSN");
                }
            }

            _fbController2.AddParameter("@INVENTUR_ID", inventurId);
            await _fbController2.QueryAsync("UPDATE WK5_INVENTUR SET INVE_L_ERLEDIGT = 'Y' WHERE INVE_N_NR = @INVENTUR_ID");

            //await _fbController2.RollbackChangesAsync();
            await _fbController2.CommitChangesAsync();
            return await Task.FromResult(true);
        }
        #endregion
        #region CSV & PDF Generierung
        /// <summary>
        /// Generiert die jeweiligen Zähllisten für die einzelnen Hauptlager
        /// </summary>
        /// <param name="inventurId"></param>
        /// <returns></returns>
        public async IAsyncEnumerable<string> GeneriereZähllistenAsync(int inventurId)
        {
            var inventur = await Inventur.GetInventurAsync(inventurId);
            if (inventur is not null)
            {
                List<Personal> personal = await Personal.GetAllPersonalAsync().ToListAsync();
                await foreach (var hauptlager in GetHauptlagerAsync(inventurId))
                {
                    var tmp = await PrintInventurRegalErfassung.CreateAsync(inventur, hauptlager.HALG_A_NR, this, personal);
                    yield return tmp.Print(GlobalConfig.Configuration.OutputPfad);
                }
            }
        }

        /// <summary>
        /// Generiert die zusammenfassende Liste der Inventur, mit allen Buchungen und Fehlbeständen.
        /// </summary>
        /// <param name="inventurId"></param>
        /// <returns></returns>
        public async Task<string?> GeneriereErfassungslisteAsync(int inventurId)
        {
            using FbController2 _fbController2 = new FbController2();
            Inventur? inventur = await Inventur.GetInventurAsync(inventurId);

            if (inventur is not null)
            {
                string filename = $"INVENTUR_{inventur.Jahr}_{inventurId}_ERFASSUNGSLISTE.CSV";

                UnicodeEncoding uniEncoding = new UnicodeEncoding();

                // You might not want to use the outer using statement that I have
                // I wasn't sure how long you would need the MemoryStream object    
                using MemoryStream ms = new MemoryStream();

                var sw = new StreamWriter(ms, uniEncoding);
                try
                {
                    var inventurData = await GetGebuchteArtikelByInventurAsync(inventurId).ToListAsync();
                    var nichtGebuchtData = await GetArtikelNichtZugeordnetAsync(inventurId).ToListAsync();

                    sw.Write($"Gebuchte Positionen;;;;;{Environment.NewLine}");
                    sw.Write($"ARTIKELNUMMER;BEZEICHNUNG;SOLL;IST;EK;GESAMT_EK_FEHLBESTAND;GESAMT_EK_BESTAND;BEMERKUNG{Environment.NewLine}");
                    decimal fehlbestandInEuro = 0;
                    decimal lagerbestandInEuro = 0;


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

                    using FbController2 fbController = new FbController2();
                    fbController.AddParameter("@DATUM", DateTime.Now.Date.AddYears(-2));
                    var alteArtikelData = await fbController.SelectDataAsync(@"SELECT ARWE_A_NR FROM ARTIKELWERTE 
                        LEFT JOIN ARTIKELBEWEGUNG ON ARBE_A_ARTINR = ARWE_A_NR AND ARBE_A_ART = 'ZUGA'
                        WHERE ARWE_N_BESTAND > 0
                        GROUP BY ARWE_A_NR
                        HAVING MAX(ARBE_TIMESTAMP) < @DATUM");

                    foreach (DataRow row in alteArtikelData.Rows)
                    {
#nullable disable
                        artikelLangeAnLager.Add(row["ARWE_A_NR"] as string);
#nullable enable
                    }

                    alteArtikelData = null;

                    foreach (var item in inventurData.GroupBy(x => x.Artikelnummer).Select(x => new
                    {
                        Artikelnummer = x.Key,
                        Menge = x.Sum(y => y.Menge),
                        Kommentare = x.SelectMany(y => y.Kommentare)
                    }).OrderBy(x => x.Artikelnummer))
                    {
                        Artikel? artikel = await _artikelService.GetAsync(item.Artikelnummer, _fbController2);

                        if (artikel is not null)
                        {
                            string? bezeichnung = $"{artikel.Bezeichnung1} {artikel.Bezeichnung2}".Trim().Replace(";", "").Replace("\"", "");

                            decimal differenz = artikel.Bestand - item.Menge;
                            decimal artikelFehlbestandInEuro = 0;
                            decimal artikelLagerbestandInEuro = item.Menge * artikel.DurchschnittsEinkaufspreis;

                            // Wenn wir eine negative Differenz haben, dann haben wir mehr gebucht, als wir an Bestand da haben sollten
                            if (differenz < 0)
                            {
                                artikelFehlbestandInEuro = -((item.Menge - artikel.Bestand) * artikel.DurchschnittsEinkaufspreis);
                            }
                            else
                            {
                                artikelFehlbestandInEuro = (artikel.Bestand - item.Menge) * artikel.DurchschnittsEinkaufspreis;
                            }

                            fehlbestandInEuro += artikelFehlbestandInEuro;
                            lagerbestandInEuro += artikelLagerbestandInEuro;

                            sw.Write($"\"{item.Artikelnummer}\";{bezeichnung};{artikel.Bestand};{item.Menge};{artikel.DurchschnittsEinkaufspreis};{artikelFehlbestandInEuro};{artikelLagerbestandInEuro};");
                            foreach (var kommentar in item.Kommentare)
                            {
                                sw.Write(kommentar.Kommentar);
                            }

                            if (artikelLangeAnLager.Contains(artikel.Artikelnummer))
                            {
                                sw.Write("Alter Artikel");
                            }

                            sw.Write($"{Environment.NewLine}");
                        }
                    }


                    sw.Write($";;;;;{Environment.NewLine}");
                    sw.Write($";;;;;{Environment.NewLine}");
                    sw.Write($"Nicht gebuchte Positionen;;;;;{Environment.NewLine}");
                    sw.Write($"ARTIKELNUMMER;BEZEICHNUNG;SOLL;IST;EK;GESAMT_EK_FEHLBESTAND;GESAMT_EK_BESTAND;BEMERKUNG{Environment.NewLine}");






                    foreach (var item in nichtGebuchtData.OrderBy(x => x.Artikelnummer))
                    {
                        Artikel? artikel = await _artikelService.GetAsync(item.Artikelnummer, _fbController2);

                        if (artikel is not null)
                        {

                            decimal artikelFehlbestandInEuro = item.Bestand * artikel.DurchschnittsEinkaufspreis;

                            fehlbestandInEuro += artikelFehlbestandInEuro;

                            string? bezeichnung = $"{artikel.Bezeichnung1} {artikel.Bezeichnung2}".Trim().Replace(";", "").Replace("\"", "");
                            string? kommentar = artikelLangeAnLager.Contains(artikel.Artikelnummer) ? "Alter Artikel" : String.Empty;
                            sw.Write($"\"{item.Artikelnummer}\";{bezeichnung};{item.Bestand};0;{artikel.DurchschnittsEinkaufspreis};{artikelFehlbestandInEuro};0;{kommentar}{Environment.NewLine}");
                        }

                    }

                    sw.Write($";;;;;;Gesamter Fehlbestand in EUR;{fehlbestandInEuro}{Environment.NewLine}");
                    sw.Write($";;;;;;Gesamter Warenbestand nach Einkaufspreisen in EUR;{lagerbestandInEuro}{Environment.NewLine}");

                    sw.Flush();//otherwise you are risking empty stream
                    ms.Seek(0, SeekOrigin.Begin);


                    // Test and work with the stream here. 
                    // If you need to start back at the beginning, be sure to Seek again.

                    string fullPath = Path.Combine(GlobalConfig.Configuration.OutputPfad, filename);
                    File.WriteAllBytes(fullPath, ms.ToArray());
                    return fullPath;
                }
                finally
                {
                    sw.Dispose();
                }
            }
            return null;
        }
        #endregion
    }
}
