using KarleyLibrary.Attributes;
using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace WK5.Core.Models
{
    public class Belegposition : ICloneable
    {
        #region Konstanten
        public const string ARTIKELNR_TEXT = "TEXT";
        public const string POSITIONEN_SELECT_SQL = @"SELECT BP.*, 
ARTI_A_LAGER, ARTI_L_SN, ARTI_N_GEWICHT, ARTI_N_ZOLLTARIF, ARTI_L_LAGERFUEHR, ARTI_A_WARENGRUPPE, ARTI_L_DIENSTLEIST, ARTI_B_NOTIZ, ARTI_A_URSPRUNGSLAND, ARTI_L_MIETE,
ARTI_A_EAN, ARTI_A_HERST_NR, ARTI_N_COLLI, ARTI_A_UNTERWARENG, ARTI_N_COLLI, WK5_ARTI_L_ANFRAGEARTIKEL, WK5_ARTI_N_GEBINDE, ARTI_N_EK, ARTI_L_ABVERKAUF, ARTI_B_PACKTEXT,
(SELECT CASE WHEN COUNT(*) > 0 THEN 'Y' ELSE 'N' END FROM BELEGSTUECKLISTE WHERE BSTU_N_BELEPOSID = BP.BPOS_N_POSID) AS IST_L_STUECKLISTE, 
(CASE WHEN BPOS_A_TYP = 'LS' OR BPOS_A_TYP = 'GU' THEN
    (SELECT CASE WHEN SUM(BCHA_N_MENGE) IS NULL THEN 0 ELSE SUM(BCHA_N_MENGE) END FROM BELEGCHARGEN WHERE BCHA_N_POSID = BPOS_N_POSID) 
ELSE 0 END) AS GECHARGT,
ARWE_N_BESTAND AS Bestand, ARWE_N_BEDARF AS Bedarf, ARWE_N_OFFBESTELL AS Bestellt, ARWE_N_BEDARF_RUEST AS Reserviert,
(SELECT COALESCE(SUM(CHAR_N_EKPREIS * CHAR_N_MENGEOFFEN) / SUM(CHAR_N_MENGEOFFEN),0) FROM CHARGEN WHERE CHAR_A_ARTINR = BPOS_A_ARTIKELNR AND CHAR_N_MENGEOFFEN > 0) AS DURCHSCHNITTSEINKAUFSPREIS
FROM BELEGPOS BP 
LEFT JOIN ARTIKEL A ON BP.BPOS_A_ARTIKELNR = A.ARTI_A_NR
LEFT JOIN ARTIKELWERTE AW ON AW.ARWE_A_NR = A.ARTI_A_NR
WHERE";
        #endregion
        #region Private Felder
        public decimal _bestand;
        private int _vpe = 1;
        private decimal _gebinde = 1;
        private string _artikelnummer = String.Empty;
        private string _bezeichnung1 = String.Empty;
        private string _bezeichnung2 = String.Empty;
        private string _bezeichnung3 = String.Empty;
        private string _bezeichnung4 = String.Empty;
        private string _bezeichnung5 = String.Empty;
        private string _langtext = String.Empty;
        private string _option = String.Empty;
        private string _bild = String.Empty;
        private string _kundensachnummer = String.Empty;
        private string _rabattbezeichnung = String.Empty;
        private decimal _gechargt;
        private bool _positionBenötigtSeriennummer;
        private decimal _posEinkaufspreis;
        private decimal _menge;
        #endregion
        #region Datenbankfelder
        #region Alphanumerische Werte
        [CompareField("BPOS_A_ARTIKELNR")]
        public string Artikelnummer { get => _artikelnummer; set => _artikelnummer = (value ?? String.Empty).Trim(); }
        [CompareField("BPOS_A_BEZ1")]
        public string Bezeichnung1 { get => _bezeichnung1; set => _bezeichnung1 = (value ?? String.Empty).Trim(); }
        [CompareField("BPOS_A_BEZ2")]
        public string Bezeichnung2 { get => _bezeichnung2; set => _bezeichnung2 = (value ?? String.Empty).Trim(); }
        [CompareField("BPOS_A_BEZ3")]
        public string Bezeichnung3 { get => _bezeichnung3; set => _bezeichnung3 = (value ?? String.Empty).Trim(); }
        [CompareField("BPOS_A_BEZ4")]
        public string Bezeichnung4 { get => _bezeichnung4; set => _bezeichnung4 = (value ?? String.Empty).Trim(); }
        [CompareField("BPOS_A_BEZ5")]
        public string Bezeichnung5 { get => _bezeichnung5; set => _bezeichnung5 = (value ?? String.Empty).Trim(); }
        public string? BPOS_A_BILDADRESSE { get; set; }
        [CompareField("BPOS_A_KUNDENARTIKELNR")]
        public string Kundensachnummer { get => _kundensachnummer; set => _kundensachnummer = (value ?? String.Empty).Trim(); }
        public string BPOS_A_KUNDENR { get; set; } = String.Empty;
        public string? BPOS_A_MASSEINHEIT { get; set; }
        [CompareField("BPOS_A_OPTION")]
        public string Option { get => _option; set => _option = (value ?? String.Empty).Trim(); }
        [CompareField("BPOS_A_RABATTBEZ1")]
        public string Rabattbezeichnung { get => _rabattbezeichnung; set => _rabattbezeichnung = (value ?? String.Empty).Trim(); }
        public string? BPOS_A_RABATTBEZ2 { get; set; }
        public string? BPOS_A_TERMINTEXT { get; set; }
        public string BPOS_A_TYP { get; set; } = String.Empty;
        public string? BPOS_A_ZWSUMMETEXT { get; set; }
        public string? ARTI_A_EAN { get; set; }
        #endregion
        #region Blob
        public string? BPOS_B_DRUCKZUSATZ { get; set; }
        [CompareField("BPOS_B_TEXT")]
        public string Langtext { get => _langtext; set => _langtext = (value ?? String.Empty).Trim(); }
        #endregion
        #region Datum
        public DateTime BPOS_D_BESTAETIGT { get; set; }
        public DateTime BPOS_D_LIEFERTERMIN { get; set; }
        public DateTime BPOS_TIMESTAMP { get; set; }
        public DateTime BPOS_D_MIETE_START { get; set; }
        public DateTime BPOS_D_MIETE_ENDE { get; set; }
        #endregion
        #region Logische Werte
        [CompareField("BPOS_L_DRUCKLANGTEXT")]
        public bool LangtextDrucken { get; set; } = true;
        public bool BPOS_L_GERUESTET { get; set; }
        public bool BPOS_L_NETTOPREIS { get; set; }
        [CompareField("BPOS_L_NICHT_DRUCKEN")]
        public bool PosOhneDruck { get; set; }
        public bool BPOS_L_PRUEFEN { get; set; }
        public bool BPOS_L_RUESTFREI { get; set; }
        [CompareField("WK5_BPOS_L_BILD_VOLLE_BREITE")]
        public bool DruckBildInVollerBreite { get; set; } = false;
        #endregion
        #region Numerische Werte
        public decimal BPOS_N_BODEN { get; set; }
        public decimal BPOS_N_BREITE { get; set; }
        public decimal BPOS_N_BREITE2 { get; set; }
        public decimal BPOS_N_BRUTTO { get; set; }
        public decimal BPOS_N_EK_GES { get; set; }
        public int BPOS_N_GERUESTET { get; set; }
        public int BPOS_N_GUGRUND { get; set; }
        public decimal BPOS_N_HOEHE { get; set; }
        public decimal BPOS_N_HOEHE2 { get; set; }
        public decimal BPOS_N_LAENGE { get; set; }
        public decimal BPOS_N_LAENGE2 { get; set; }
        public int BPOS_N_LASTUSER { get; set; }
        [CompareField("BPOS_N_MENGE")]
        public decimal Menge
        {
            get => _menge;
            set
            {
                if (IstBundle)
                {
                    foreach (var bundleArtikel in BundleArtikel)
                    {
                        if (value is 0)
                        {
                            bundleArtikel.Menge = bundleArtikel.Menge / (_menge > 0 ? _menge : 1);
                        }
                        else
                        {
                            bundleArtikel.Menge = bundleArtikel.Menge / (_menge > 0 ? _menge : 1) * value;
                        }

                    }
                }

                _menge = value;
            }
        }
        public decimal BPOS_N_MENGEGELIEF { get; set; }
        public decimal BPOS_N_MENGENFAKTOR { get; set; }
        [CompareField("BPOS_N_MWSTPROZ")]
        public decimal MwstSatz { get; set; }
        public decimal BPOS_N_NETTO { get; set; }
        public int BPOS_N_NR { get; set; }
        public int BPOS_N_OLDPOSID { get; set; }
        [CompareField("BPOS_N_POS")]
        public int PosNr { get; set; }
        public int BPOS_N_POSDRUCK { get; set; }
        [CompareField("BPOS_N_POSID")]
        public int PosId { get; set; }
        public int BPOS_N_POSID_AU { get; set; }
        [CompareField("BPOS_N_PREIS")]
        public decimal Preis { get; set; }
        [CompareField("BPOS_N_RABATTPROZ")]
        public decimal Rabatt1 { get; set; }
        [CompareField("BPOS_N_RABATTPROZ2")]
        public decimal Rabatt2 { get; set; }
        public decimal BPOS_N_RABATTWERT1 { get; set; }
        public decimal BPOS_N_RABATTWERT2 { get; set; }
        public decimal BPOS_N_RUESTFREIMENGE { get; set; }
        public decimal BPOS_N_STAERKE { get; set; }
        public decimal BPOS_N_TMP_LIEFERMENGE { get; set; }
        [CompareField("WK5_BPOS_N_EK")]
        public decimal PosEinkaufspreis
        {
            get
            {
                if (_posEinkaufspreis is 0 && BundleParent is null)
                {
                    return ArtikelEinkaufspreis;
                }

                return _posEinkaufspreis;
            }
            set => _posEinkaufspreis = value;
        }

        [CompareField("ARTI_N_EK")]
        public decimal ArtikelEinkaufspreis { get; set; }

        [CompareField("WK5_BPOS_N_BILD")]
        public int BildId { get; set; }

        public decimal Durchschnittseinkaufspreis { get; set; }

        #endregion
        #endregion

        #region Stückliste
        // Der Code für Stücklisten ist nur noch aus Kompatibilitätsgründen vorhanden
        // Stücklsiten werden durch Bundles ersetzt.
        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob die Position eine Stückliste ist, oder nicht.
        /// </summary>
        [CompareField("IST_L_STUECKLISTE")]
        public bool PositionIstStückliste { get; set; }

        /// <summary>
        /// Ruft alle Artikel der Stückliste ab.
        /// </summary>
        public IList<Belegposition> StücklistenArtikel { get; set; } = new List<Belegposition>();

        /// <summary>
        /// Ruft die vorhergehende Position einer Stückliste ab.
        /// </summary>
        /// <value>Eine Position der ersten Stufe liefert hier null zurück.</value>
        public Belegposition? Parent { get; set; }

        /// <summary>
        /// Ruft die eindeutige Stücklistenpositions ID ab, anhand dessen sich ein Artikel einer Stückliste eindeutig identifizieren lässt.
        /// </summary>
        /// <value>Dieser Wert ist 0, wenn der Artikel sich nicht in einer Stückliste befindet.</value>
        [CompareField("BSTU_N_POSID")]
        public int StücklistenPositionsId { get; set; }
        /// <summary>
        /// Ruft die Position eines Artikels innerhalb einer Stückliste ab.
        /// <para>
        /// Dieser Wert wird für die Anzeigereihenfolge verwendet.
        /// </para>
        /// </summary>
        public int ARST_N_POSITION { get; set; }
        #endregion
        #region Positionen abrufen
        /// <summary>
        /// Liefert ausschließlich die <see cref="Belegposition"/>en des Beleges, die keine Stückliste sind.
        /// </summary>
        /// <returns></returns>
        public virtual IEnumerable<Belegposition> GetEndPositionen()
        {
            if (StücklistenArtikel != null)
            {
                foreach (var artikel in StücklistenArtikel)
                {
                    if (artikel.PositionIstStückliste)
                    {
                        foreach (var subArtikel in GetStücklistenPositionen(artikel))
                        {
                            yield return subArtikel;
                        }
                    }
                    else
                    {
                        yield return artikel;
                    }
                }
            }

            if (IstBundle)
            {
                foreach (var artikel in BundleArtikel)
                {
                    yield return artikel;
                }
            }
        }

        /// <summary>
        /// Liefert alle Belegpositionen einer Stückliste
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        protected virtual IEnumerable<Belegposition> GetStücklistenPositionen(Belegposition pos)
        {
            foreach (var artikel in pos.StücklistenArtikel)
            {
                // Es kann ggf. vorkommen, dass eine Stückliste eine Stückliste enthält. Wir müssen also rekursiv an das ganze herangehen.
                if (artikel.PositionIstStückliste)
                {
                    foreach (var subArtikel in GetStücklistenPositionen(artikel))
                    {
                        yield return subArtikel;
                    }
                }
                else
                {
                    yield return artikel;
                }
            }
        }
        /// <summary>
        /// Liefert eine Belegposition aus der Stückliste
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="stücklistenPositionsId"></param>
        /// <returns></returns>
        public virtual Belegposition? GetPosition(Belegposition pos, int stücklistenPositionsId)
        {
            foreach (var stklPosition in pos.StücklistenArtikel)
            {
                if (stklPosition.StücklistenPositionsId == stücklistenPositionsId)
                {
                    return stklPosition;
                }

                if (stklPosition.PositionIstStückliste)
                {
                    return GetPosition(stklPosition, stücklistenPositionsId);
                }
            }

            return null;
        }

        #endregion
        #region Weiteres 
        [CompareField("WK5_BPOS_A_BILD")]
        public string Bild { get => _bild; set => _bild = (value ?? String.Empty).Trim(); }
        /// <summary>
        /// Ruft die Position des Artikels im Lager ab
        /// </summary>
        public string? ARTI_A_LAGER { get; set; } = String.Empty;
        /// <summary>
        /// Gibt an, ob es sich bei der Position um eine Miete handelt oder nicht.
        /// </summary>
        public bool ARTI_L_MIETE { get; set; }

        /// <summary>
        /// Ruft das Gewicht des Artikels ab
        /// </summary>
        [CompareField("ARTI_N_GEWICHT")]
        public decimal Gewicht { get; set; }

        public string? ARTI_A_URSPRUNGSLAND { get; set; }
        /// <summary>
        /// Ruft einen Wert ab, ob der Artikel gechargt werden muss, oder nicht.
        /// </summary>
        public bool ARTI_L_LAGERFUEHR { get; set; }

        public bool ARTI_L_DIENSTLEIST { get; set; }

        public string? ARTI_B_NOTIZ { get; set; }

        [CompareField("BESTAND")]
        public decimal Bestand
        {
            get
            {

                if (!PositionIstStückliste && !IstBundle)
                {
                    return _bestand;
                }
                else
                {
                    Dictionary<string, decimal> stklMengen = new Dictionary<string, decimal>();

                    foreach (Belegposition pos in StücklistenArtikel)
                    {
                        if (stklMengen.ContainsKey(pos.Artikelnummer))
                        {
                            stklMengen[pos.Artikelnummer] += Menge / pos.Menge;
                        }
                        else
                        {
                            stklMengen.Add(pos.Artikelnummer, Menge / pos.Menge);
                        }
                    }

                    foreach (Belegposition pos in BundleArtikel)
                    {
                        if (stklMengen.ContainsKey(pos.Artikelnummer))
                        {
                            stklMengen[pos.Artikelnummer] += pos.Menge / Menge;
                        }
                        else
                        {
                            stklMengen.Add(pos.Artikelnummer, pos.Menge / (Menge is 0 ? 1 : Menge));
                        }
                    }

                    decimal minAmount = Int32.MaxValue;

                    foreach (Belegposition pos in StücklistenArtikel)
                    {
                        decimal teiler = stklMengen[pos.Artikelnummer];

                        // Es kann vorkommen, dass jemand für eine Stückliste vergessen hat eine Menge zu hinterlegen
                        // Dann würden wir durch 0 teilen und eine DivisionByZeroException auslösen
                        // Deshalb geben wir -1 zurück, wenn keine Menge vorhanden ist
                        // Das hat den Vorteil, dass der Auftrag nicht automatisch freigegeben wird, da der Bestand nicht ausreichend ist
                        if (teiler == 0)
                        {
                            return -1;
                        }

                        decimal bestand = pos.Bestand / teiler;
                        if (bestand >= 0 && bestand < minAmount)
                        {
                            minAmount = bestand;
                        }
                    }


                    foreach (Belegposition pos in BundleArtikel)
                    {
                        decimal teiler = stklMengen[pos.Artikelnummer];

                        // Es kann vorkommen, dass jemand für eine Stückliste vergessen hat eine Menge zu hinterlegen
                        // Dann würden wir durch 0 teilen und eine DivisionByZeroException auslösen
                        // Deshalb geben wir -1 zurück, wenn keine Menge vorhanden ist
                        // Das hat den Vorteil, dass der Auftrag nicht automatisch freigegeben wird, da der Bestand nicht ausreichend ist
                        if (teiler == 0)
                        {
                            return -1;
                        }

                        decimal bestand = pos.Bestand / teiler;
                        if (bestand >= 0 && bestand < minAmount)
                        {
                            minAmount = bestand;
                        }
                    }

                    return (int)Math.Floor(minAmount);
                }
            }
            protected set
            {
                _bestand = value;
            }
        }
        [CompareField("BEDARF")]
        public decimal Bedarf { get; set; }
        [CompareField("BESTELLT")]
        public decimal Bestellt { get; set; }
        [CompareField("RESERVIERT")]
        public decimal Reserviert { get; set; }

        public int ARTI_N_ZOLLTARIF { get; set; }

        public string ARTI_A_WARENGRUPPE { get; set; } = String.Empty;
        public string ARTI_A_UNTERWARENG { get; set; } = String.Empty;

        [CompareField("ARTI_N_COLLI")]
        public int Vpe { get => _vpe; set => _vpe = value <= 0 ? 1 : value; }
        [CompareField("WK5_ARTI_L_ANFRAGEARTIKEL")]
        public bool AnfrageArtikel { get; set; }

        [CompareField("WK5_ARTI_N_GEBINDE")]
        public decimal Gebinde { get => _gebinde; set => _gebinde = value <= 0 ? 1 : value; }


        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob als <see cref="BPOS_N_MENGE"/> eine Kommazahl ist, oder nicht.
        /// </summary>
        public bool MengeHatKommaStelle => Menge % 1 != 0;

        public decimal Gechargt
        {
            get
            {
                if (PositionIstStückliste)
                {
                    decimal gechargt = 0;
                    foreach (var pos in StücklistenArtikel)
                    {
                        gechargt += pos.Gechargt;
                    }

                    return gechargt;
                }
                else if (IstBundle)
                {
                    decimal gechargt = 0;
                    foreach (var pos in BundleArtikel)
                    {
                        gechargt += pos.Gechargt;
                    }

                    return gechargt;
                }
                else
                {
                    if (ARTI_L_LAGERFUEHR)
                    {
                        return _gechargt;
                    }
                    else
                    {
                        return Menge;
                    }
                }
            }
            set => _gechargt = value;
        }

        public bool BenötigtWeitereSeriennummern
        {
            get
            {
                if (!PositionBenötigtSeriennummer)
                {
                    return false;
                }

                if (PositionBenötigtSeriennummer && (PositionIstStückliste || IstBundle))
                {

                    foreach (var pos in GetEndPositionen().Where(x => x.PositionBenötigtSeriennummer))
                    {
                        if (pos.BenötigtWeitereChargen)
                        {
                            return true;
                        }
                    }

                    return false;
                }

                return Gechargt != Menge;

            }
        }
        public bool BenötigtWeitereChargen
        {
            get
            {
                if (EnumHelper.GetBelegTyp(BPOS_A_TYP) is not BelegTyp.Lieferschein and not BelegTyp.Gutschrift)
                {
                    return false;
                }



                if (PositionIstStückliste || IstBundle)
                {
                    if (PosId is 0)
                    {
                        return true;
                    }

                    foreach (var pos in GetEndPositionen().Where(x => !x.PositionBenötigtSeriennummer))
                    {
                        if (pos.BenötigtWeitereChargen)
                        {
                            return true;
                        }
                    }



                    return false;
                }
                else
                {
                    if (ARTI_L_LAGERFUEHR)
                    {
                        return Menge - Gechargt > 0;
                    }
                    else
                    {
                        return false;
                    }

                }
            }
        }
        [CompareField("ARTI_L_ABVERKAUF")]
        public bool IstAbverkaufsartikel { get; set; }
        public bool PosIstBereitsInAnderenAuftragEnthalten { get; set; }
        #endregion

        #region Bundles
        [CompareField("BPOS_N_BUNDLEID")]
        public int BundleId { get; set; }
        public List<Belegposition> BundleArtikel { get; set; } = new();
        public Belegposition? BundleParent { get; set; }
        public bool IstBundle => BundleArtikel.Any();
        #endregion
        public bool BPOS_L_FERTIGUNG { get; set; } = false;

        #region Preisberechnung
        public decimal PreisMitRabatt
        {
            get
            {
                decimal preis = Preis;

                if (Rabatt1 > 0)
                {
                    preis *= (100 - Rabatt1) / 100;
                }

                if (Rabatt2 > 0)
                {
                    preis *= (100 - Rabatt2) / 100;
                }

                return preis;
            }
        }
        public decimal EinkaufsPreisGesamt
        {
            get
            {
                return PosEinkaufspreis * Menge;
            }
        }
        public decimal Netto => PreisMitRabatt * Menge;
        public decimal Brutto => Netto + (Netto * MwstSatz / 100);

        public decimal Rabatt => (Preis - PreisMitRabatt) * Menge;
        public decimal Mwst => Brutto - Netto;

        public decimal Roherlös => Netto - EinkaufsPreisGesamt;
        public decimal RoherlösProzent => EinkaufsPreisGesamt is 0 ? 0 : Roherlös / (Netto > 0 ? Netto : 1) * 100;

        #endregion
        #region Seriennummern
        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob die Position eine Seriennummer benötigt, oder nicht.
        /// </summary>
        [CompareField("ARTI_L_SN")]
        public bool PositionBenötigtSeriennummer
        {
            get
            {
                if (PositionIstStückliste || IstBundle)
                {
                    foreach (var pos in GetEndPositionen())
                    {
                        if (pos.PositionBenötigtSeriennummer)
                        {
                            return true;
                        }
                    }

                    // Stücklisten können keine Seriennummern verwaltung haben
                    return false;
                }


                return _positionBenötigtSeriennummer;
            }
            set => _positionBenötigtSeriennummer = value;
        }

        /// <summary>
        /// Ruft eine Liste mit bereits gebuchten Seriennummern für diese Position ab.
        /// </summary>
        public IList<Seriennummer> Seriennummern { get; protected set; } = new List<Seriennummer>();

        /// <summary>
        /// Ruft für die Position alle freien Seriennummern ab.
        /// <para>
        /// Es werden nur Seriennummern zurückgegeben, die noch nicht ausgeliefert wurden und sich nicht in der Tabelle WK5_BUCHUNG_SN befinden.
        /// </para>
        /// </summary>
        /// <returns></returns>
        public async IAsyncEnumerable<string> GetFreieSeriennummernAsync(FbController2 fbController)
        {

            fbController.AddParameter("@ARTIKELNUMMER", Artikelnummer);

            var data = await fbController.SelectDataAsync("SELECT * FROM VIEW_SN WHERE CHAR_A_ARTINR = @ARTIKELNUMMER AND SNNR_L_AUSGELIEFERT = 'N' AND SNNR_A_SN NOT IN(SELECT DISTINCT BUCHUNGSN_A_SN FROM WK5_BUCHUNG_SN) ORDER BY SNNR_A_SN");

            foreach (DataRow row in data.Rows)
            {
                yield return row["SNNR_A_SN"].ToString()!;
            }
        }

        #endregion

        public Belegposition()
        {

        }
        protected Belegposition(int BPOS_N_POSID)
        {
            this.PosId = BPOS_N_POSID;
        }
        #region Position Laden


        protected virtual async Task<Belegposition?> LadePositionAsync(FbController2 fbController)
        {
            fbController.AddParameter("@POSID", PosId);
            DataRow? posRow = await fbController.SelectRowAsync($"{POSITIONEN_SELECT_SQL} BPOS_N_POSID = @POSID");

            if (posRow is null)
            {
                return null;
            }

            // Werte auf das aktuelle Objekt übernehmen
            Belegposition position = ObjectErweiterung.DataRowZuObjekt(this, posRow);

            if (position.PositionIstStückliste)
            {
                await position.LadeStücklisteAsync(position, 0, fbController);
            }

            if (position.PositionBenötigtSeriennummer)
            {
                await position.LadeSeriennummernAsync(position, fbController);
            }
            return position;
        }

        internal virtual async Task LadeStücklisteAsync(Belegposition pos, int ebene, FbController2 fbController)
        {
            if (!pos.PositionIstStückliste)
            {
                return;
            }

            // Alle es müssen die Daten für alle Artikel geladen werden, die in der Stückliste sind
            fbController.AddParameter("@BSTU_N_BELEPOSID", pos.PosId);
            fbController.AddParameter("@BSTU_N_EBENE", ebene);
            fbController.AddParameter("@BSTU_N_POSID_UEBER", pos.StücklistenPositionsId);

            string sql = @"SELECT 
BSTU_A_UNTERARTI AS BPOS_A_ARTIKELNR, 
BSTU_N_MENGE AS BPOS_N_MENGE, 
BSTU_N_POS AS ARST_N_POSITION, 
BSTU_N_POSID,
ARTI_L_SN, 
ARTI_A_LAGER, 
ARTI_A_BEZ1 AS BPOS_A_BEZ1,
ARTI_A_BEZ2 AS BPOS_A_BEZ2,
ARTI_A_BEZ3 AS BPOS_A_BEZ3,
ARTI_A_BEZ4 AS BPOS_A_BEZ4,
ARTI_A_BEZ5 AS BPOS_A_BEZ5,
ARTI_L_DIENSTLEIST,
ARTI_B_NOTIZ,
ARTI_N_GEWICHT, ARTI_L_LAGERFUEHR,
ARTI_N_EK, ARTI_A_URSPRUNGSLAND,
ARWE_N_OFFBESTELL AS Bestellt,
ARWE_N_BESTAND as Bestand,
(SELECT CASE WHEN COUNT(*) > 0 THEN 'Y' ELSE 'N' END FROM BELEGSTUECKLISTE WHERE BSTU_N_POSID_UEBER = BST.BSTU_N_POSID) AS IST_L_STUECKLISTE,
(SELECT CASE WHEN SUM(BCHA_N_MENGE) IS NULL THEN 0 ELSE SUM(BCHA_N_MENGE) END FROM BELEGCHARGEN WHERE BCHA_N_POSID = BSTU_N_BELEPOSID AND BCHA_N_STCKLPOSID = BSTU_N_POSID)  AS GECHARGT
FROM BELEGSTUECKLISTE BST 
INNER JOIN ARTIKEL A ON (A.ARTI_A_NR = BST.BSTU_A_UNTERARTI)
INNER JOIN ARTIKELWERTE AW ON AW.ARWE_A_NR = A.ARTI_A_NR
WHERE BSTU_N_BELEPOSID = @BSTU_N_BELEPOSID AND BSTU_N_EBENE = @BSTU_N_EBENE";

            if (ebene > 0)
            {
                sql += " AND BSTU_N_POSID_UEBER = @BSTU_N_POSID_UEBER";
            }

            // Die Spalten werden extra als AS gekennzeichnet, damit eine automatische Zuordnung über ObjectErweiterung möglich ist.
            var stücklistenArtikel = await fbController.SelectDataAsync(sql);

            foreach (DataRow artikel in stücklistenArtikel.Rows)
            {
                // Es muss eine tiefe Kopie der Belegposition angelegt werden
                Belegposition stücklistenPosition = (Belegposition)pos.Clone();
                stücklistenPosition.Parent = pos; // Als ersten Schritt, da andere Funktionen auf den Parent aufbauen. 
                // Werte auf das aktuelle Objekt übernehmen
                ObjectErweiterung.DataRowZuObjekt(stücklistenPosition, artikel);


                if (stücklistenPosition.PositionBenötigtSeriennummer)
                {
                    await LadeSeriennummernAsync(stücklistenPosition, fbController);
                }

                pos.StücklistenArtikel.Add(stücklistenPosition);

                // Irgendein Genie kann in der W4 die Stückliste selbst in der Stückliste hinterlegen. Das ist natürlich schwachsinn und würde an dieser Stelle zu einer StackOverflowException führen, da die Stückliste endlos rekursiv eingelesen wird.
                // Daher prüfen noch zusätzlich, dass der Subartikel der Stückliste nicht die Stückliste selbst ist. 
                // Wenn es kein Parent gibt, befinden wir uns auf der obersten ebene der Stückliste, daher müssen wir diese Positionen auch einlesen.
                if (stücklistenPosition.PositionIstStückliste
                    && (stücklistenPosition.Parent is null || stücklistenPosition.Parent.Artikelnummer != stücklistenPosition.Artikelnummer))
                {
                    await LadeStücklisteAsync(stücklistenPosition, ebene + 1, fbController);
                }
            }
        }

        internal virtual async Task LadeSeriennummernAsync(Belegposition pos, FbController2 fbController)
        {

            fbController.AddParameter("@BSNR_N_POSID", pos.PosId);
            fbController.AddParameter("@BSNR_N_STLPOSID", pos.StücklistenPositionsId);


            var data = await fbController.SelectDataAsync("SELECT * FROM BELEGSN LEFT JOIN SN ON BSNR_A_SN = SNNR_A_SN AND BSNR_N_CHARGE = SNNR_N_CHARGE WHERE BSNR_N_POSID = @BSNR_N_POSID AND BSNR_N_STLPOSID = @BSNR_N_STLPOSID");

            foreach (DataRow row in data.Rows)
            {
                Seriennummer sn = ObjectErweiterung.DataRowZuObjekt(new Seriennummer(), row);                
                Seriennummern.Add(sn);                
            }
        }


        #endregion

        #region Schnittstelle ICloneable
        public virtual object Clone()
        {
            Belegposition copy = (Belegposition)this.MemberwiseClone();
            copy.StücklistenArtikel = new List<Belegposition>();
            copy.Seriennummern = new List<Seriennummer>();
            return copy;
        }

        #endregion

        #region Preise


        /// <summary>
        /// Berechnet den wahren Verkaufspreis einer Belegposition einbezogen der Rabatte
        /// </summary>
        /// <returns>Gibt den wahren Verkaufspreis dieser Belegposition zurück</returns>
        public decimal GetTrueVk()
        {
            decimal total = 0.0m;
            decimal subtotal = Preis * Menge;
            decimal rabatt1 = subtotal * (Rabatt1 / 100);
            decimal rabatt2 = subtotal * (Rabatt2 / 100);

            total += subtotal - (rabatt1 + rabatt2);

            return total;
        }

        public decimal GetGesamtNetto() => Preis * Menge;
        public decimal GetGesamtNettoMitRabatt() => GetGesamtNetto() - GetRabatt1() - GetRabatt2();
        public decimal GetRabatt1()
        {
            if (Rabatt1 > 0)
            {
                return Menge * Preis * Rabatt1 / 100;
            }

            return 0;
        }
        public decimal GetRabatt2()
        {
            if (Rabatt2 > 0)
            {
                return (Menge * Preis - GetRabatt1()) * Rabatt2 / 100;
            }

            return 0;
        }

        public decimal GetMwstSumme() => GetGesamtNettoMitRabatt() * MwstSatz / 100;
        #endregion
        public string GetBezeichnung() => Regex.Replace($"{Bezeichnung1} {Bezeichnung2} {Bezeichnung3} {Bezeichnung4} {Bezeichnung5}", "  +/g", " ").Trim();
        public decimal GetStücklistenMenge()
        {
            if (this.Parent == null)
            {
                return this.Menge;
            }

            Belegposition? parent = this.Parent;
            decimal menge = this.Menge;
            while (parent != null)
            {
                menge *= parent.Menge;
                parent = parent.Parent;
            }
            return menge;
        }


        public decimal GetZuLieferndeMenge()
        {
            return GetStücklistenMenge() - BPOS_N_MENGEGELIEF;
        }
    }

}