using KarleyLibrary.Attributes;
using KarleyLibrary.Erweiterungen;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using WK5.Core.Basis.Erweiterungen;
using WK5.Core.Services;

namespace WK5.Core.Models
{

    public abstract class Beleg
    {
        private string _anrede = "Sehr geehrte Damen und Herren";
        private string _name1 = String.Empty;
        private string _name2 = String.Empty;
        private string _name3 = String.Empty;
        private string _bestelltDurch = String.Empty;
        private string _bestellnummer = String.Empty;
        private string _land = String.Empty;
        private string _lieferzeit = String.Empty;
        private string _ort = String.Empty;
        private string _postleitzahl = String.Empty;
        private string _strasse = String.Empty;
        private string _ansprechpartner = String.Empty;
        private string _fußtext = String.Empty;
        private string _kopftext = String.Empty;
        private string _notiz = String.Empty;
        private string _anfrageDurch = String.Empty;
        private string _anfragennummer = String.Empty;
        private string _ohneBegründungGrund = string.Empty;
        private byte[] _neutralerLieferschein = Array.Empty<byte>();
        private string _speditionsAuftragsnummer = String.Empty;
        #region Eigenschaften
        #region Datenbankfelder
        #region Alphanumerisch
        public string? WK5_BELE_A_ZAHL_KOMMENTAR { get; set; }
        [CompareField("BELE_A_ANFRADURCH")]
        public string AnfrageDurch { get => _anfrageDurch; set => _anfrageDurch = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_A_ANFRAGENR")]
        public string Anfragennummer { get => _anfragennummer; set => _anfragennummer = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_A_ANREDE")]
        public string Anrede
        {
            get => _anrede;
            set => _anrede = value ?? "Sehr geehrte Damen und Herren";
        }
        public string? BELE_A_BAUVORHABEN { get; set; }
        [CompareField("BELE_A_BESTAET_DURCH")]
        public string BestelltDurch { get => _bestelltDurch; set => _bestelltDurch = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_A_BESTAET_NR")]
        public string Bestellnummer { get => _bestellnummer; set => _bestellnummer = (value ?? String.Empty).Trim(); }
        public string? BELE_A_DRUCKART { get; set; }
        public string? BELE_A_ENDTEXT { get; set; }
        public string? BELE_A_ERLOESKONTO { get; set; }
        public string? BELE_A_FAXNUMMER { get; set; }
        public string? BELE_A_FORMULAR { get; set; }
        public string? BELE_A_GELIEFERT_DURCH { get; set; }
        public string? BELE_A_GRUPPE { get; set; }
        public string? BELE_A_KOPFTEXT { get; set; }
        [CompareField("BELE_A_KUNDENNR")]
        public string Kundennummer { get; set; } = String.Empty;
        [CompareField("BELE_A_LAND")]
        public string Land { get => _land; set => _land = (value ?? "DE").Trim(); }
        [CompareField("BELE_A_LIEFERZEIT")]
        public string Lieferzeit { get => _lieferzeit; set => _lieferzeit = (value ?? String.Empty).Trim(); }
        public string? BELE_A_LIEF_ADR_NAME { get; set; }
        public string? BELE_A_LIEF_KUNDNR { get; set; }
        [CompareField("BELE_A_NAME1")]
        public string Name1 { get => _name1; set => _name1 = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_A_NAME2")]
        public string Name2 { get => _name2; set => _name2 = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_A_NAME3")]
        public string Name3 { get => _name3; set => _name3 = (value ?? String.Empty).Trim(); }
        public string? BELE_A_NR_ZUSATZ { get; set; }
        [CompareField("BELE_A_ORT")]
        public string Ort { get => _ort; set => _ort = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_A_PLZ")]
        public string Postleitzahl { get => _postleitzahl; set => _postleitzahl = (value ?? String.Empty).Trim(); }
        public string? BELE_A_STATUSFELD { get; set; }
        [CompareField("BELE_A_STRASSE")]
        public string Strasse { get => _strasse; set => _strasse = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_A_TYP")]
        public string Belegtyp { get; set; } = String.Empty;
        public string? BELE_A_WAEHRUNG { get; set; }
        public string? BELE_A_ZEICHEN { get; set; }
        [CompareField("BELE_A_ZUHAENDEN")]
        public string Ansprechpartner { get => _ansprechpartner; set => _ansprechpartner = (value ?? String.Empty).Trim(); }


        public string? WK5_BELE_A_GRUND_BERECHNUNG { get; set; }
        public string? WK5_BELE_A_GRUND_STORNO { get; set; }
        #endregion
        #region Blob
        [CompareField("BELE_B_FUSSTEXT")]
        public string Fußtext { get => _fußtext; set => _fußtext = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_B_KOPFTEXT")]
        public string Kopftext { get => _kopftext; set => _kopftext = (value ?? String.Empty).Trim(); }
        [CompareField("BELE_B_NOTIZ")]
        public string Notiz { get => _notiz; set => _notiz = (value ?? String.Empty).Trim(); }
        #endregion
        #region Datum
        [CompareField("BELE_D_ANFRAGEVOM")]
        public DateTime AnfrageVom { get; set; } = DateTime.Now;
        public DateTime BELE_D_ANLAGEDATUM { get; set; }
        [CompareField("BELE_D_BESTAET_DAT")]
        public DateTime BestelltAm { get; set; } = DateTime.Now;
        public DateTime BELE_D_DATE { get; set; } = DateTime.Now;
        public DateTime BELE_D_DRUCKDATUM { get; set; }
        public DateTime BELE_D_FIBUUEBERGDATE { get; set; }
        public DateTime BELE_D_GELIEFERT { get; set; }

        public DateTime BELE_D_LETZTERABRUF { get; set; } = DateTime.Now.AddYears(1);

        [CompareField("BELE_D_GUELTIGBIS")]
        public DateTime GültigBis { get; set; } = DateTime.Now.AddDays(14);

        public DateTime BELE_D_KURSDATE { get; set; }
        [CompareField("BELE_D_LIEFERDATE")]
        public DateTime Liefertermin { get; set; }
        [CompareField("BELE_D_NACHFRAGDAT")]
        public DateTime NachzufragenAm { get; set; } = DateTime.Now.AddDays(17);
        public DateTime BELE_D_VALUTA { get; set; }
        public DateTime BELE_D_VERRECHNET { get; set; }
        public DateTime BELE_TIMESTAMP { get; set; }
        public DateTime? WK5_BELE_D_BEZAHLT_DATUM { get; set; } = null;
        #endregion
        #region Logische Werte
        public bool BELE_L_ABGELEHNT { get; set; }
        public bool BELE_L_ARCHIVIERT_KARLEY { get; set; }
        public bool BELE_L_AU_FIXIERT { get; set; }
        public bool BELE_L_BERECHNET { get; set; }
        public bool BELE_L_BEZAHLT { get; set; }
        public bool BELE_L_EDI { get; set; }
        public bool BELE_L_ERLEDIGT { get; set; }
        public bool BELE_L_FIBUUEBERGABE { get; set; }
        public bool BELE_L_FIBUUEBERGDATE { get; set; }
        public bool BELE_L_FIXTERMIN { get; set; }
        public bool BELE_L_GARANTIE { get; set; }
        public bool BELE_L_GERUESTET { get; set; }
        public bool BELE_L_GESPERRT { get; set; }
        public bool BELE_L_KASSENBELEG { get; set; }
        public bool BELE_L_LSOHNEBERECH { get; set; }
        [CompareField("BELE_L_MWST")]
        public bool MwstBerechnen { get; set; }
        [CompareField("BELE_L_NEUTRAL")]
        public bool NeutralerVersender { get; set; }
        public bool BELE_L_REPARATUR { get; set; }
        public bool BELE_L_RUESTFREI { get; set; }
        public bool BELE_L_STORNO { get; set; }
        public bool BELE_L_UBERNAHME { get; set; }
        public bool BELE_L_VERRECHNET { get; set; }
        public bool BELE_L_VERSENDET { get; set; }
        public bool BELE_L_VORKASSE { get; set; }
        public bool BELE_L_WIEDERKEHREND { get; set; }
        #endregion
        #region Numerische Werte
        public int BELE_N_ANLAGEUSER { get; set; }
        public decimal BELE_N_BRUTTO { get; set; }
        public int BELE_N_EDI_NR { get; set; }
        public int BELE_N_FILIALE { get; set; }
        [CompareField("BELE_N_FRACHTEK")]
        public decimal FrachtkostenEk { get; set; }
        [CompareField("BELE_N_FRACHTVK")]
        public decimal Frachtkosten { get; set; }
        public decimal BELE_N_GEWICHT { get; set; }
        public int BELE_N_GUGRUND { get; set; }
        public int BELE_N_KOSTENST { get; set; }
        public decimal BELE_N_KURS { get; set; }
        public int BELE_N_LASTUSER { get; set; }
        [CompareField("BELE_N_LIEFADRESSE")]
        public int LieferanschriftId { get; set; }
        [CompareField("BELE_N_LIEFERUNG")]
        public int LieferbedingungId { get; set; }
        public decimal BELE_N_MWST { get; set; }
        public decimal BELE_N_MWST_SATZ1 { get; set; }
        public decimal BELE_N_MWST_SATZ1_AUF { get; set; }
        public decimal BELE_N_MWST_SATZ1_BETRAG { get; set; }
        public decimal BELE_N_MWST_SATZ2 { get; set; }
        public decimal BELE_N_MWST_SATZ2_AUF { get; set; }
        public decimal BELE_N_MWST_SATZ2_BETRAG { get; set; }
        public decimal BELE_N_NETTO { get; set; }
        [CompareField("BELE_N_NR")]
        public int Belegnummer { get; set; }
        [CompareField("BELE_N_NR_AN")]
        public int AngebotsnummerVerknüpfung { get; set; }
        [CompareField("BELE_N_NR_AU")]
        public int AuftragsnummerVerknüpfung { get; set; }
        [CompareField("BELE_N_NR_GU")]
        public int GutschriftnummerVerknüpfung { get; set; }
        [CompareField("BELE_N_NR_LS")]
        public int LieferscheinnummerVerknüpfung { get; set; }
        [CompareField("BELE_N_NR_RE")]
        public int RechnungsnummerVerknüpfung { get; set; }
        [CompareField("BELE_N_NR_RMA")]
        public int RmaNummerVerknüpfung { get; set; }
        [CompareField("BELE_N_NR_WARTUNG")]
        public int WartungsnummerVerknüpfung { get; set; }
        public int BELE_N_PACKSTUECK { get; set; }
        public int BELE_N_PROJEKTNR { get; set; }
        public decimal BELE_N_PROV_VERT { get; set; }
        public decimal BELE_N_RABATT { get; set; }
        public decimal BELE_N_RABATTPOS { get; set; }
        [CompareField("BELE_N_RECHADRESSE")]
        public int RechnungsanschriftId { get; set; }
        public int BELE_N_RE_TYP { get; set; }
        public decimal BELE_N_ROHERTRAG { get; set; }
        public decimal BELE_N_SONDERWERT { get; set; }
        public int BELE_N_SPEDADRESSE { get; set; }
        public int BELE_N_SPERRUSERID { get; set; }
        public int BELE_N_VALUTATAGE { get; set; }
        [CompareField("BELE_N_VERPACKUNG")]
        public decimal Verpackungskosten { get; set; }
        [CompareField("BELE_N_VERSICHER")]
        public decimal Versicherungskosten { get; set; }
        [CompareField("BELE_N_VERTRETER")]
        public int VertreterId { get; set; }
        [CompareField("BELE_N_ZAHLUNG")]
        public int ZahlungsbedingungId { get; set; }
        public decimal WK5_BELE_N_BEZAHLT_WERT { get; set; }
        [CompareField("BELE_L_ABHOLUNG_INFORMIERT")]
        public bool ÜberAbholungInformiert { get; set; }

        public bool BELE_L_ABRUF { get; set; }
        #endregion

        #region WK5_Zusätze
        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob der Versand doppelt berehcnet werden muss, oder nicht.
        /// </summary>
        [CompareField("WK5_BELE_L_DOPPELT_VERSAND")]
        public bool VersandDoppeltBerechnen { get; set; }
        /// <summary>
        /// Ruft einen Wert ab, der angibt, wie oft die Versandkosten bereits berechnet wurden.
        /// </summary>
        public int WK5_BELE_N_VERSAND_BERECHNET { get; set; }
        public bool WK5_BELE_L_SAMMELRECHNUNG { get; set; }
        public bool WK5_BELE_L_PAUSIERT { get; set; }
        /// <summary>
        /// Gibt an, ob für den beleg eine Wiedervorlage erstellt werden soll oder nicht.
        /// <para>
        /// Dieser Wert ist derzeit nur für Angebote relevant.
        /// </para>
        /// </summary>
        public bool WK5_BELE_L_WIEDERVORLAGE { get; set; }
        /// <summary>
        /// Gibt an, dass ein Auftrag nicht von uns verschickt wird, sondern wir die Artikel direkt über den Hersteller an den Kunden versenden lassen.
        /// </summary>
        public bool WK5_BELE_L_DIREKTLIEFERUNG { get; set; }
        public bool WK5_BELE_L_SHOPIMPORT { get; set; }
        public int WK5_BELE_N_NACHZUFRAGEN_BEI { get; set; }
        public string? WK5_BELE_A_TRANSAKTIONS_ID { get; set; }
        /// <summary>
        /// Ruft ein Flag ab, dass angibt, ob der Beleg bereits an Starmoney exportiert worden ist.
        /// <para>
        /// Dieses Flag ist nur für Rechnungen die als Zahlungskondition Lastschrift haben relevant.
        /// </para>
        /// </summary>
        public bool WK5_BELE_L_STARMONEY_EXPORTIERT { get; set; }

        [CompareField("WK5_BELE_L_OHNE_BERECHNUNG")]
        public bool OhneBerechnung { get; set; }

        public bool WK5_BELE_L_LIMITPRUEFUNG { get; set; }
        [CompareField("BELE_L_TECHNIKBELEG")]
        public bool IstTechnikbeleg { get; set; }
        [CompareField("BELE_L_EIGENVERSCHULDEN")]
        public bool Eigenverschulden { get; set; }
        [CompareField("BELE_A_OHNE_BERECHNUNG_GRUND")]
        public string OhneBerechnungGrund { get => _ohneBegründungGrund; set => _ohneBegründungGrund = value ?? string.Empty; }
        [CompareField("BELE_B_NEUTRALER_LS")]
        public byte[] NeutralerLieferschein { get => _neutralerLieferschein; set => _neutralerLieferschein = value ?? Array.Empty<byte>(); }

        [CompareField("BELE_N_ABLEHNUNG")]
        public int Ablehnungsgrund { get; set; }
        [CompareField("BELE_A_SPEDITION_AU_NR")]
        public string SpeditionsAuftragsnummer { get => _speditionsAuftragsnummer; set => _speditionsAuftragsnummer = value ?? string.Empty; }
        #endregion
        #endregion


        public List<Belegposition> Positionen { get; set; } = new List<Belegposition>();

        public bool BelegExistiert { get; private set; }
        public string Kundenname
        {
            get
            {
                StringBuilder namensBuilder = new StringBuilder();
                if (Name1 != null)
                {
                    namensBuilder.Append(Name1);
                }

                if (Name2 != null)
                {
                    namensBuilder.Append(" ");
                    namensBuilder.Append(Name2);
                }
                if (Name3 != null)
                {
                    namensBuilder.Append(" ");
                    namensBuilder.Append(Name3);
                }

                return namensBuilder.ToString();
            }
        }

        public bool ZahlungErhalten(ZahlungsbedingungCollection zahlungsbedingungen)
        {

            bool zahlungErhalten = true;
            bool zahlartIstRechnung = zahlungsbedingungen.IstRechnung(ZahlungsbedingungId);
            bool zahlartIstLastschrift = zahlungsbedingungen.IstLastschrift(ZahlungsbedingungId);

            if (zahlungsbedingungen.IstVorkasse(ZahlungsbedingungId) && !BELE_L_BEZAHLT)
            {
                zahlungErhalten = false;
            }

            return zahlungErhalten || zahlartIstRechnung || zahlartIstLastschrift;

        }

        public Zahlungsbedingung GetZahlungsbedingung(ZahlungsbedingungCollection zahlungsbedingungen) => zahlungsbedingungen[ZahlungsbedingungId] ?? new Zahlungsbedingung() { ZABD_N_NR = -1, ZABD_A_TEXT1 = "Unbekannt" };

        #endregion

        #region Weiteres



        public bool IstAbholung => LieferbedingungId == 10;
        public bool IstVersandBrief => LieferbedingungId == 12;
        public bool IstVersandExpressVersand => LieferbedingungId == 6;
        public bool IstVersandDownload => LieferbedingungId == 7;
        public bool IstVersandPalette => LieferbedingungId == 11;

        public decimal Zusatzkosten => Frachtkosten + Verpackungskosten + Versicherungskosten;
        #endregion
        internal Beleg()
        {

        }
        protected Beleg(string belegTyp, int belegnummer)
        {
            Belegtyp = belegTyp;
            Belegnummer = belegnummer;
        }

        #region Beleg laden
        protected virtual async Task<Beleg?> LadeBelegAsync(FbController2 fbController)
        {
            fbController.AddParameter("@BELE_N_NR", Belegnummer);
            fbController.AddParameter("@BELE_A_TYP", Belegtyp);
            var belegRow = await fbController.SelectRowAsync("SELECT * FROM BELEGE WHERE BELE_A_TYP = @BELE_A_TYP AND BELE_N_NR = @BELE_N_NR");

            return belegRow is null ? null : ObjectErweiterung.DataRowZuObjekt(this, belegRow);
        }
        internal virtual async Task LadePositionenAsync(FbController2 fbController)
        {
            fbController.AddParameter("@BPOS_N_NR", Belegnummer);
            fbController.AddParameter("@BPOS_A_TYP", Belegtyp);
            var posData = await fbController.SelectDataAsync($"{Belegposition.POSITIONEN_SELECT_SQL} BPOS_N_NR = @BPOS_N_NR AND BPOS_A_TYP = @BPOS_A_TYP ORDER BY BPOS_N_POS");

            List<Belegposition> castedPositionen = new List<Belegposition>();
            foreach (DataRow row in posData.Rows)
            {
                Belegposition position = ObjectErweiterung.DataRowZuObjekt(new Belegposition(), row);

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

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

                castedPositionen.Add(position);
            }

            int currentBundleId = 0;
            foreach (var pos in castedPositionen)
            {
                if (pos.BundleId != 0)
                {
                    if (pos.BundleId != currentBundleId)
                    {
                        currentBundleId = pos.BundleId;

                        foreach (var bundlePos in castedPositionen.Where(x => x.BundleId == currentBundleId && pos != x))
                        {
                            bundlePos.BundleParent = pos;
                            pos.BundleArtikel.Add(bundlePos);
                        }
                    }
                }

                Positionen.Add(pos);
            }
        }
        #endregion
        #region Positionen abrufen
        /// <summary>
        /// Liefert alle <see cref="PackBelegposition"/>en des Beleges, die gepackt werden können.
        /// </summary>
        /// <returns></returns>
        public virtual IEnumerable<Belegposition> GetEndPositionen()
        {
            if (Positionen != null)
            {
                foreach (var artikel in Positionen.OrderBy(x => x.PosNr))
                {
                    if (artikel.PositionIstStückliste)
                    {
                        foreach (var subArtikel in GetStücklistenPositionen(artikel))
                        {
                            yield return subArtikel;
                        }
                    }
                    else
                    {
                        yield return artikel;
                    }
                }
            }
        }

        /// <summary>
        /// Liefert alle Artikel 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;
                }
            }
        }
        public virtual Belegposition? GetPosition(int BPOS_N_POSID)
        {
            if (Positionen is null)
            {
                return null;
            }

            var suchePosition =
                from Belegposition pos in Positionen
                where pos.PosId == BPOS_N_POSID
                select pos;

            return suchePosition.FirstOrDefault();
        }
        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 Preisberechnungen



        [Obsolete("Bitte auf die Procedure WK5_ERLOESKALKULATOR umstellen")]
        /// <summary>
        /// Berechnet den wahren Verkaufspreis eines Belegs anhand der Chargen + Rabatt
        /// </summary>
        /// <returns>Gibt einen <see cref="decimal"/> zurück der den echten Verkaufspreis enthält</returns>
        public decimal GetTrueVk()
        {
            decimal total = 0.0m;
            //Hier können wir nicht die Endpositionen verwenden, weil in einer Stückliste jeder Subartikel den Preis der Belegposition hat.
            //Bsp.: PP100INKS hat PJIC-1, PJIC-2 als Subartikel. PP100INKS kostet 192 Euro. Bei der Berechnung würden 384 Euro rauskommen weil bei PJIC-1 und 2 auch jeweils 192 Euro eingetragen ist
            foreach (Belegposition belegposition in Positionen)//this.GetEndPositionen())
            {
                total += belegposition.GetTrueVk();
            }
            total += Frachtkosten;
            return total;
        }



        #endregion

        /// <summary>
        /// Ruft die nächste freie Belegnummer für den entsprechenden Belegtypen ab.
        /// </summary>
        /// <param name="fbController"></param>
        /// <param name="belegTyp"></param>
        /// <param name="vorgängerBelegnummer">Wenn es keinen Vorgängerbeleg gibt, dann ist dieser Wert 0.</param>
        /// <returns></returns>
        public static async Task<int> GetNeueBelegnummerAsync(FbController2 fbController, BelegTyp belegTyp, int vorgängerBelegnummer = 0)
        {
            fbController.AddParameter("@KREIS_ART", 1);
            fbController.AddParameter("@BELE_TYP", EnumHelper.GetBelegTypString(belegTyp));
            fbController.AddParameter("@INP_LSNR", vorgängerBelegnummer);
            var belegnummerRow = await fbController.SelectRowAsync("SELECT * FROM BELEG_NEW (@KREIS_ART, @BELE_TYP, @INP_LSNR)");
            int belegnummer = 0;

            if (belegnummerRow is not null)
            {
                belegnummer = belegnummerRow["NEW_BELENR"] is int NEW_BELENR ? NEW_BELENR : 0;
            }

            return belegnummer;
        }

        /// <summary>
        /// Gibt das Gesamtgewicht eines Beleg zurück.
        /// </summary>
        /// <returns>Gibt einen Double mit dem Gewicht zurück</returns>
        public virtual decimal GetBelegGewicht(bool alternativUndEventualAbziehen = false)
        {
            decimal gewicht = 0;
            foreach (var item in GetEndPositionen())
            {
                if ((item.Option.Equals("Alternativposition") || item.Option.Equals("Eventualposition")) && alternativUndEventualAbziehen)
                    continue;
                gewicht += item.Gewicht * item.Menge;
            }
            return gewicht;
        }

        public static async Task<Beleg?> GetBelegAsync(BelegTyp belegtyp, int belegnummer, FbController2 fbController)
        {
            return belegtyp switch
            {
                BelegTyp.Angebot => await Angebot.GetAngebotAsync(belegnummer, fbController),
                BelegTyp.Auftrag => await Auftrag.GetAuftragAsync(belegnummer, fbController),
                BelegTyp.Gutschrift => await Gutschrift.GetGutschriftAsync(belegnummer, fbController),
                BelegTyp.Lieferschein => await Lieferschein.GetLieferscheinAsync(belegnummer, fbController),
                BelegTyp.Rechnung => await Rechnung.GetRechnungAsync(belegnummer, fbController),
                _ => null
            };
        }

        public decimal GetNetto(OptionCollection optionen)
        {
            var tmp = Positionen.Where(x => String.IsNullOrWhiteSpace(x.Option) || optionen.WirdBerechnet(x.Option));

            return tmp.Sum(x => x.Menge * x.Preis);


        }
        public decimal GetRabatt(OptionCollection optionen) => GetNetto(optionen) - GetNettoBetrag(optionen) + Zusatzkosten;
        public decimal GetNettoBetrag(OptionCollection optionen)
        {
            decimal netto = Zusatzkosten;

            foreach (var pos in Positionen)
            {
                if (String.IsNullOrWhiteSpace(pos.Option) || optionen.WirdBerechnet(pos.Option))
                {
                    netto += pos.Menge * pos.PreisMitRabatt;
                }
            }
            return netto;
        }
        public decimal GetMwst(OptionCollection optionen, Mehrwertsteuer? versandMehrwertsteuer)
        {
            if (!MwstBerechnen)
            {
                return 0;
            }
            decimal mwst = 0;
            foreach (var pos in Positionen)
            {
                if (String.IsNullOrWhiteSpace(pos.Option) || optionen.WirdBerechnet(pos.Option))
                {
                    mwst += pos.Menge * pos.PreisMitRabatt * pos.MwstSatz / 100;
                }
            }

            if (versandMehrwertsteuer is not null)
            {
                mwst += Zusatzkosten * versandMehrwertsteuer.MWST_N_PROZENT / 100;
            }
            //decimal mwst = versandMehrwertsteuer is null
            //? Positionen.Where(x => String.IsNullOrWhiteSpace(x.Option) || optionen.WirdBerechnet(x.Option)).Sum(x => x.Menge * x.PreisMitRabatt * x.MwstSatz / 100)
            //: Positionen.Where(x => String.IsNullOrWhiteSpace(x.Option) || optionen.WirdBerechnet(x.Option)).Sum(x => x.Menge * x.PreisMitRabatt * x.MwstSatz / 100) + (Zusatzkosten * versandMehrwertsteuer.MWST_N_PROZENT / 100);

            return mwst;
        }
        public decimal GetBruttoBetrag(OptionCollection optionen, Mehrwertsteuer? versandMehrwertsteuer) => GetNettoBetrag(optionen) + GetMwst(optionen, versandMehrwertsteuer);

        public decimal GetEinkaufspreisBetrag(OptionCollection optionen)
        {
            var tmp = Positionen.Where(x => String.IsNullOrWhiteSpace(x.Option) || optionen.WirdBerechnet(x.Option));

            if(Belegtyp is "RE" or "LS" or "GU")
            {
                return tmp.Where(x => !x.IstBundle).Sum(x => x.Menge * x.PosEinkaufspreis);
            }

            return tmp.Sum(x => x.Menge * x.PosEinkaufspreis);
        }
        public decimal GetRoherlös(OptionCollection optionen) => GetNettoBetrag(optionen) - GetEinkaufspreisBetrag(optionen);

        public decimal GetRoherlösProzent(OptionCollection optionen)
        {
            decimal netto = GetNettoBetrag(optionen);
            if (netto is 0)
            {
                netto = 1;
            }

            return GetRoherlös(optionen) / netto * 100;
        }
    }
    public class Auftrag : Beleg
    {

        public async Task<bool> WirdKomplettDurchBestellungGeliefert()
        {
            using FbController2 fbController = new FbController2();
            List<Bestellung> bestellungen = await GetBestellungenAsync(fbController).ToListAsync();
            if (bestellungen.Count <= 0)
            {
                return false;
            }



            foreach (Belegposition pos in GetEndPositionen())
            {
                if (!pos.ARTI_L_LAGERFUEHR)
                {
                    continue;
                }

                bool isCovered = false;
                foreach (Bestellung bestellung in bestellungen)
                {
                    if (!bestellung.Direktlieferung)
                    {
                        continue;
                    }

                    foreach (Bestellposition bestellposition in bestellung.Positionen)
                    {
                        if (bestellposition.BEPO_A_ARTIKELNR.Equals(pos.Artikelnummer, StringComparison.OrdinalIgnoreCase))
                        {
                            if (bestellposition.BEPO_N_MENGE >= pos.Menge)
                            {
                                isCovered = true;
                            }
                        }
                    }
                }

                if (!isCovered)
                {
                    return false;
                }
            }

            return true;
        }

        public async Task<bool> WirdKomplettDurchDirektlieferungGeliefert()
        {
            List<Bestellung> bestellungen = await GetKundenBestellungenAsync().ToListAsync();
            if (bestellungen.Count <= 0)
            {
                return false;
            }

            foreach (Belegposition pos in GetEndPositionen())
            {
                if (!pos.ARTI_L_LAGERFUEHR)
                {
                    continue;
                }

                bool isCovered = false;
                foreach (Bestellung bestellung in bestellungen)
                {
                    foreach (Bestellposition bestellposition in bestellung.Positionen)
                    {
                        if (bestellposition.BEPO_A_ARTIKELNR.Equals(pos.Artikelnummer, StringComparison.OrdinalIgnoreCase))
                        {
                            if (bestellposition.BEPO_N_MENGE >= pos.Menge)
                            {
                                isCovered = true;
                            }
                        }
                    }
                }

                if (!isCovered)
                {
                    return false;
                }
            }

            return true;
        }


        /// <summary> 
        /// Prüft ob für alle Artikel eines Auftrags die Mengen Lieferbar sind 
        /// </summary> 
        public bool Lieferbar
        {
            get
            {
                bool _lieferbar = true;
                foreach (var pos in Positionen.GroupBy(p => new { p.Artikelnummer, p.Bestand, p.ARTI_L_LAGERFUEHR, p.PositionIstStückliste }).Select(p => new { Artikelnummer = p.Key.Artikelnummer, Menge = p.Sum(s => s.Menge - s.BPOS_N_MENGEGELIEF), LagerIntern = p.Key.Bestand, ARTI_L_LAGERFUEHR = p.Key.ARTI_L_LAGERFUEHR, PositionIstStückliste = p.Key.PositionIstStückliste }))
                {

                    if (pos.ARTI_L_LAGERFUEHR || (!pos.ARTI_L_LAGERFUEHR && pos.LagerIntern > 0) || pos.PositionIstStückliste)
                    {

                        if (pos.Menge > pos.LagerIntern)
                        {
                            _lieferbar = false;
                            break;
                        }
                    }
                }

                return _lieferbar;
            }
        }
        /// <summary> 
        /// Prüft ob bereits Teilmengen eines Auftrag geliefert wurden 
        /// </summary> 
        public bool TeilmengenBereitsGeliefert
        {
            get
            {
                foreach (var pos in Positionen)
                {
                    if (pos.BPOS_N_MENGEGELIEF > 0)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        /// <summary> 
        /// Holt sich alle Bestellungen dieses Beleges die zu dem Kunden geliefert werden 
        /// </summary> 
        /// <returns>Gibt ein <see cref="IEnumerable{Bestellung}"/> zurück  das alle Bestellungen dieses Beleg enthält die zu dem Kunden geliefert werden</returns> 
        public async IAsyncEnumerable<Bestellung> GetKundenBestellungenAsync()
        {
            string query = "SELECT BEST_N_NR FROM BESTELLUNGEN WHERE BEST_N_AB_NR = @BEST_N_AB_NR AND BEST_A_LIEFERKUNDE = @BEST_A_LIEFERKUNDE";
            using FbController2 fbController = new FbController2();
            if (AuftragsnummerVerknüpfung > 0)
            {
                fbController.AddParameter("@BEST_N_AB_NR", AuftragsnummerVerknüpfung);
                fbController.AddParameter("@BEST_A_LIEFERKUNDE", Kundennummer);
                DataTable data = await fbController.SelectDataAsync(query);
                foreach (DataRow row in data.Rows)
                {
                    if (Int32.TryParse(row["BEST_N_NR"].ToString(), out int BEST_N_NR))
                    {
                        var bestellung = await Bestellung.GetBestellungAsync(BEST_N_NR, fbController);
                        if (bestellung is not null)
                        {
                            yield return bestellung;
                        }
                    }
                }
            }
        }

        /// <summary> 
        /// Holt sich alle Bestellungen die in dem Auftrag hinterlegt sind 
        /// </summary> 
        /// <returns>Gibt ein <see cref="IEnumerable{Bestellung}"/> zurück das alle Bestellungen des Auftrag enhtält</returns> 
        public async IAsyncEnumerable<Bestellung> GetBestellungenAsync(FbController2 fbController)
        {
            string query = "SELECT BEST_N_NR FROM BESTELLUNGEN WHERE BEST_N_AB_NR = @BEST_N_AB_NR";

            if (this.AuftragsnummerVerknüpfung > 0)
            {
                fbController.AddParameter("@BEST_N_AB_NR", this.AuftragsnummerVerknüpfung);
                DataTable data = await fbController.SelectDataAsync(query);
                foreach (DataRow row in data.Rows)
                {
                    if (Int32.TryParse(row["BEST_N_NR"].ToString(), out int BEST_N_NR))
                    {
                        var bestellung = await Bestellung.GetBestellungAsync(BEST_N_NR, fbController);
                        if (bestellung is not null)
                        {
                            yield return bestellung;
                        }
                    }
                }
            }
        }
        public Auftrag()
        {
            Belegtyp = "AU";
        }

        protected Auftrag(int belegnummer) : base("AU", belegnummer)
        {

        }
        public static async Task<Auftrag?> GetAuftragAsync(int auftragsnummer, FbController2 fbController)
        {
            Auftrag auftrag = new Auftrag(auftragsnummer);
            var beleg2 = await auftrag.LadeBelegAsync(fbController);
            if (beleg2 is null)
            {
                return null;
            }
            await auftrag.LadePositionenAsync(fbController);
            return auftrag;
        }

        public async Task<bool> SkontoZielErreicht(DateTime zuDatum)
        {
            return (await GetSkontoProzente(zuDatum).ToListAsync()).Count > 0;
        }

        public async IAsyncEnumerable<int> GetSkontoProzente(DateTime zuDatum)
        {
            Zahlungsbedingung? zabd = await Zahlungsbedingung.GetZahlungsbedingungAsync(ZahlungsbedingungId);
            if (zabd is not null)
            {
                TimeSpan diff = zuDatum - BELE_D_DATE;
                bool skonto1erreicht = (zabd.ZABD_N_SKONTO1TAGE > 0 && diff.TotalDays <= zabd.ZABD_N_SKONTO1TAGE) || (zabd.ZABD_N_SKONTO1TAGE <= 0 && zabd.ZABD_N_SKONTO1PROZ > 0);
                if (skonto1erreicht)
                {
                    yield return zabd.ZABD_N_SKONTO1PROZ;
                }

                bool skonto2erreicht = (zabd.ZABD_N_SKONTO2TAGE > 0 && diff.TotalDays <= zabd.ZABD_N_SKONTO2TAGE) || (zabd.ZABD_N_SKONTO2TAGE <= 0 && zabd.ZABD_N_SKONTO2PROZ > 0);
                if (skonto2erreicht)
                {
                    yield return zabd.ZABD_N_SKONTO2PROZ;
                }
            }
        }


    }

    public class Lieferschein : Beleg
    {
        public Lieferschein()
        {
            Belegtyp = "LS";
        }
        protected Lieferschein(int belegnummer) : base("LS", belegnummer)
        {

        }
        public static async Task<Lieferschein?> GetLieferscheinAsync(int auftragsnummer, FbController2 fbController)
        {
            Lieferschein lieferschein = new Lieferschein(auftragsnummer);
            var beleg2 = await lieferschein.LadeBelegAsync(fbController);
            if (beleg2 is null)
            {
                return null;
            }
            await lieferschein.LadePositionenAsync(fbController);
            return lieferschein;
        }
        [Obsolete("Bitte verwenden Sie stattdessen den ChargenService")]
        public async IAsyncEnumerable<Belegcharge> GetChargenAsync(FbController2 fbController)
        {
            fbController.AddParameter("@BPOS_A_TYP", "LS");
            fbController.AddParameter("@BPOS_N_NR", Belegnummer);
            DataTable data = await fbController.SelectDataAsync(@"SELECT BC.* FROM BELEGPOS BP
LEFT JOIN BELEGCHARGEN BC ON BC.BCHA_N_POSID = BP.BPOS_N_POSID
WHERE BPOS_A_TYP = @BPOS_A_TYP AND BPOS_N_NR = @BPOS_N_NR");

            foreach (DataRow row in data.Rows)
            {
                yield return ObjectErweiterung.DataRowZuObjekt(new Belegcharge(), row);
            }
        }

        public bool BenötigtChargen
        {
            get
            {
                foreach (var pos in Positionen)
                {
                    if (pos.BenötigtWeitereChargen || pos.BenötigtWeitereSeriennummern)
                    {
                        return true;
                    }
                }

                return false;
            }
        }
    }

    public class Rechnung : Beleg
    {

        public Rechnung()
        {
            Belegtyp = "RE";
        }
        protected Rechnung(int belegnummer) : base("RE", belegnummer)
        {

        }
        public static async Task<Rechnung?> GetRechnungAsync(int auftragsnummer, FbController2 fbController)
        {
            Rechnung rechnung = new Rechnung(auftragsnummer);
            var beleg2 = await rechnung.LadeBelegAsync(fbController);
            if (beleg2 is null)
            {
                return null;
            }
            await rechnung.LadePositionenAsync(fbController);
            return rechnung;
        }
        public async Task<bool> SkontoZielErreicht(DateTime zuDatum)
        {
            return (await GetSkontoProzente(zuDatum).ToListAsync()).Count > 0;
        }

        public async IAsyncEnumerable<int> GetSkontoProzente(DateTime zuDatum)
        {
            Zahlungsbedingung? zabd = await Zahlungsbedingung.GetZahlungsbedingungAsync(ZahlungsbedingungId);
            if (zabd is not null)
            {
                TimeSpan diff = zuDatum - BELE_D_DATE;
                bool skonto1erreicht = zabd.ZABD_N_SKONTO1TAGE > 0 && diff.TotalDays <= zabd.ZABD_N_SKONTO1TAGE;
                if (skonto1erreicht)
                {
                    yield return zabd.ZABD_N_SKONTO1PROZ;
                }

                bool skonto2erreicht = zabd.ZABD_N_SKONTO2TAGE > 0 && diff.TotalDays <= zabd.ZABD_N_SKONTO2TAGE;
                if (skonto2erreicht)
                {
                    yield return zabd.ZABD_N_SKONTO2PROZ;
                }
            }
        }
    }

    public class Angebot : Beleg
    {

        public Angebot()
        {
            Belegtyp = "AN";
        }
        protected Angebot(int belegnummer) : base("AN", belegnummer)
        {

        }
        public static async Task<Angebot?> GetAngebotAsync(int auftragsnummer, FbController2 fbController)
        {
            Angebot angebot = new Angebot(auftragsnummer);
            var beleg2 = await angebot.LadeBelegAsync(fbController);
            if (beleg2 is null)
            {
                return null;
            }
            await angebot.LadePositionenAsync(fbController);
            return angebot;
        }

        public IEnumerable<Belegposition> GetNichtZuBerechnendePositionen(OptionCollection optionen)
        {
            foreach (var pos in Positionen.Where(x => !string.IsNullOrWhiteSpace(x.Option) && x.PosId > 0))
            {
                Option? option = optionen[pos.Option];
                if (option is not null && !option.OPTI_L_BERECHNEN)
                {
                    yield return pos;
                }
            }
        }
    }

    public class Gutschrift : Beleg
    {

        public Gutschrift()
        {
            Belegtyp = "GU";
        }
        protected Gutschrift(int belegnummer) : base("GU", belegnummer)
        {

        }
        public static async Task<Gutschrift?> GetGutschriftAsync(int auftragsnummer, FbController2 fbController)
        {
            Gutschrift gutschrift = new Gutschrift(auftragsnummer);
            var beleg2 = await gutschrift.LadeBelegAsync(fbController);
            if (beleg2 is null)
            {
                return null;
            }
            await gutschrift.LadePositionenAsync(fbController);
            return gutschrift;
        }
    }

    public class AuftragFreigegeben : Auftrag
    {
        /// <summary>
        /// Ruft das Datum für den Auftrag ab, wann dieser zum Packen freigeben ist.
        /// </summary>
        public DateTime FREI_D_PACKENAB { get; set; }

        [CompareField("KUND_L_SPERRE")]
        public bool KundeGesperrt { get; set; }
        [CompareField("KUND_L_SPERRE_OP")]
        public bool KundeGesperrtBeiOffenenPosten { get; set; }
        public decimal OffenePosten { get; set; }

        public bool HatServiceleistung
        {
            get
            {
                // Vorortservice immer ja
                if (LieferbedingungId == 9)
                {
                    return true;
                }

                foreach (var pos in this.GetEndPositionen().Where(x => x.BPOS_N_MENGEGELIEF < x.Menge))
                {
                    if (pos.Artikelnummer == "VERSAND")
                    {
                        continue;
                    }

                    if (pos.ARTI_A_WARENGRUPPE == "SERVICE" || pos.ARTI_A_WARENGRUPPE == "SERV-SPEZ" || pos.ARTI_L_DIENSTLEIST || pos.BPOS_L_FERTIGUNG)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        internal AuftragFreigegeben()
        {
            Belegtyp = "AU";
        }
        public AuftragFreigegeben(int auftragsnummer) : base(auftragsnummer)
        {

        }

        public static async Task<AuftragFreigegeben?> GetAuftragFreigegebenAsync(int auftragsnummer, FbController2 fbController)
        {
            AuftragFreigegeben auftrag = new AuftragFreigegeben(auftragsnummer);
            var beleg2 = await auftrag.LadeBelegAsync(fbController);
            if (beleg2 is null)
            {
                return null;
            }
            await auftrag.LadePositionenAsync(fbController);
            return auftrag;
        }

        protected override async Task<Beleg?> LadeBelegAsync(FbController2 fbController)
        {

            fbController.AddParameter("@BELE_N_NR", Belegnummer);
            fbController.AddParameter("@BELE_A_TYP", Belegtyp);
            var belegRow = await fbController.SelectRowAsync(@"SELECT B.*, FREI_D_PACKENAB 
FROM BELEGE B 
LEFT JOIN WK5_AUFTRAGSFREIGABE WAU ON WAU.FREI_N_BELENR = B.BELE_N_NR
WHERE BELE_A_TYP = @BELE_A_TYP AND BELE_N_NR = @BELE_N_NR");

            return belegRow is null ? null : ObjectErweiterung.DataRowZuObjekt(this, belegRow);
        }

        public static async Task<AuftragFreigegeben?> GetRechnungAsync(int auftragsnummer, FbController2 fbController)
        {
            AuftragFreigegeben auftrag = new AuftragFreigegeben(auftragsnummer);
            var beleg2 = await auftrag.LadeBelegAsync(fbController);
            if (beleg2 is null)
            {
                return null;
            }
            await auftrag.LadePositionenAsync(fbController);
            return auftrag;
        }
    }

    public class SubBeleg
    {
        [CompareField("BELE_A_TYP")]
        public string Belegtyp { get; set; } = String.Empty;
        [CompareField("BELE_N_NR")]
        public int Belegnummer { get; set; }

        public override string ToString() => $"{Belegtyp}-{Belegnummer}";
        public string GetUrl()
        {
            if (Belegtyp == "RM")
            {
                return $"/RMAS/{Belegnummer}";
            }

            return EnumHelper.GetBelegTyp(Belegtyp) switch
            {
                BelegTyp.Rechnung => $"/Rechnungen/{Belegnummer}",
                BelegTyp.Auftrag => $"/Auftraege/{Belegnummer}",
                BelegTyp.Lieferschein => $"/Lieferscheine/{Belegnummer}",
                BelegTyp.Gutschrift => $"/Gutschriften/{Belegnummer}",
                BelegTyp.Angebot => $"/Angebote/{Belegnummer}",
                _ => String.Empty,
            };
        }
    }

    public class OffeneRechnung
    {
        [CompareField("BELE_N_NR")]
        public int Rechnungsnummer { get; set; }
        [CompareField("BELE_A_KUNDENNR")]
        public string Kundennummer { get; set; } = string.Empty;
        [CompareField("BELE_A_NAME1")]
        public string Kundenname { get; set; } = string.Empty;
    }
}
