Skip to content
Code-Schnipsel Gruppen Projekte
Calc.php 33,9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Henning Leutz's avatar
    Henning Leutz committed
    <?php
    
    /**
     * This file contains QUI\ERP\Accounting\Calc
     */
    
    Henning Leutz's avatar
    Henning Leutz committed
    namespace QUI\ERP\Accounting;
    
    
    use QUI;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use QUI\ERP\Accounting\Calc as ErpCalc;
    
    use QUI\ERP\Money\Price;
    
    use QUI\Interfaces\Users\User as UserInterface;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use QUI\ERP\Accounting\Invoice\Invoice;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use QUI\ERP\Accounting\Invoice\InvoiceTemporary;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use QUI\ERP\Accounting\Invoice\Handler;
    
    Henning Leutz's avatar
    Henning Leutz committed
    /**
     * Class Calc
    
     * Calculations for Accounting
    
    Henning Leutz's avatar
    Henning Leutz committed
     *
    
     * @info Produkt Berechnungen sind zu finden unter: QUI\ERP\Products\Utils\Calc
     *
    
    Henning Leutz's avatar
    Henning Leutz committed
     * @package QUI\ERP\Accounting
     */
    class Calc
    {
    
        const CALCULATION_PERCENTAGE = 1;
    
        const CALCULATION_COMPLEMENT = 2;
    
        const CALCULATION_COMPLETE = 3;
    
        const CALCULATION_BASIS_NETTO = 1;
    
    
        /**
         * Basis calculation -> from current price
         */
    
        const CALCULATION_BASIS_CURRENTPRICE = 2;
    
    
        /**
         * Basis brutto
         * include all price factors (from netto calculated price)
         * warning: its not brutto VAT
         *
         * geht vnn der netto basis aus, welche alle price faktoren schon beinhaltet
         * alle felder sind in diesem price schon enthalten
         */
    
        const CALCULATION_BASIS_BRUTTO = 3;
    
        /**
         * Berechnet auf Basis des Preises inklusive Steuern
         * Zum Beispiel MwSt
         */
        const CALCULATION_BASIS_VAT_BRUTTO = 4;
    
        /**
         * Berechnet von Gesamtpreis
         */
        const CALCULATION_GRAND_TOTAL = 5;
    
    
        /**
         * @var UserInterface
         */
        protected $User = null;
    
        /**
         * @var null|QUI\ERP\Currency\Currency
         */
        protected $Currency = null;
    
        /**
         * Calc constructor.
         *
         * @param UserInterface|bool $User - calculation user
         */
        public function __construct($User = false)
        {
            if (!QUI::getUsers()->isUser($User)) {
                $User = QUI::getUserBySession();
            }
    
            $this->User = $User;
        }
    
        /**
         * Static instance create
         *
    
         * @param UserInterface|null $User - optional
    
         * @return Calc
         */
    
        public static function getInstance($User = null): Calc
    
        {
            if (!$User && QUI::isBackend()) {
                $User = QUI::getUsers()->getSystemUser();
            }
    
    
            if (!QUI::getUsers()->isUser($User) && !QUI::getUsers()->isSystemUser($User)) {
    
                $User = QUI::getUserBySession();
            }
    
    
            return new self($User);
    
        }
    
        /**
         * Set the calculation user
         * All calculations are made in dependence from this user
         *
         * @param UserInterface $User
         */
        public function setUser(UserInterface $User)
        {
            $this->User = $User;
        }
    
        /**
         * Return the calc user
         *
         * @return UserInterface
         */
        public function getUser()
        {
            return $this->User;
        }
    
        /**
         * Return the currency
         *
         * @return QUI\ERP\Currency\Currency
         */
    
        public function getCurrency(): ?QUI\ERP\Currency\Currency
    
            if (\is_null($this->Currency)) {
    
                $this->Currency = QUI\ERP\Currency\Handler::getDefaultCurrency();
            }
    
            return $this->Currency;
        }
    
    
        /**
         * Calculate a complete article list
         *
         * @param ArticleList $List
         * @param callable|boolean $callback - optional, callback function for the data array
         * @return ArticleList
         */
    
        public function calcArticleList(ArticleList $List, $callback = false): ArticleList
    
            if (!\is_callable($callback)) {
    
                return $List->calc();
            }
    
    
            // user order address
            $Order = $List->getOrder();
    
            if ($Order) {
                $this->getUser()->setAttribute('CurrentAddress', $Order->getDeliveryAddress());
            }
    
    
    
            $articles    = $List->getArticles();
            $isNetto     = QUI\ERP\Utils\User::isNettoUser($this->getUser());
            $isEuVatUser = QUI\ERP\Tax\Utils::isUserEuVatUser($this->getUser());
    
    
            $Currency  = $this->getCurrency();
    
            $precision = $Currency->getPrecision();
    
            $subSum   = 0;
            $nettoSum = 0;
    
            $vatArray = [];
    
    
            foreach ($articles as $Article) {
                // add netto price
                try {
                    QUI::getEvents()->fireEvent(
                        'onQuiqqerErpCalcArticleListArticle',
    
                        [$this, $Article]
    
                    );
                } catch (QUI\Exception $Exception) {
                    QUI\System\Log::write($Exception->getMessage(), QUI\System\Log::LEVEL_ERROR);
                }
    
                $this->calcArticlePrice($Article);
    
                $articleAttributes = $Article->toArray();
    
                $calculated        = $articleAttributes['calculated'];
    
                $subSum   = $subSum + $calculated['sum'];
                $nettoSum = $nettoSum + $calculated['nettoSum'];
    
                $articleVatArray = $calculated['vatArray'];
    
                $vat             = $articleAttributes['vat'];
    
    
                if ($articleVatArray['text'] === '') {
                    continue;
                }
    
    
                if (!isset($vatArray[$vat])) {
                    $vatArray[$vat]        = $articleVatArray;
                    $vatArray[$vat]['sum'] = 0;
                }
    
                $vatArray[$vat]['sum'] = $vatArray[$vat]['sum'] + $articleVatArray['sum'];
            }
    
    
            QUI\ERP\Debug::getInstance()->log('Berechnete Artikelliste MwSt', 'quiqqer/erp');
    
            QUI\ERP\Debug::getInstance()->log($vatArray, 'quiqqer/erp');
    
            try {
                QUI::getEvents()->fireEvent(
                    'onQuiqqerErpCalcArticleList',
    
                    [$this, $List, $nettoSum]
    
                );
            } catch (QUI\Exception $Exception) {
                QUI\System\Log::write($Exception->getMessage(), QUI\System\Log::LEVEL_ERROR);
            }
    
    
            /**
             * Calc price factors
             */
    
            $priceFactors   = $List->getPriceFactors();
    
            $priceFactorSum = 0;
    
    
    Henning Leutz's avatar
    Henning Leutz committed
            // nur wenn wir welche benötigen, für ERP Artikel ist dies im Moment nicht wirklich nötig
    
            $nettoSubSum = $nettoSum;
    
    
            /* @var $PriceFactor QUI\ERP\Accounting\PriceFactors\Factor */
    
            foreach ($priceFactors as $PriceFactor) {
    
                if ($PriceFactor->getCalculationBasis() === self::CALCULATION_GRAND_TOTAL) {
                    $PriceFactor->setNettoSum($PriceFactor->getValue());
                    $PriceFactor->setValueText('');
                    continue;
                }
    
    
                // percent - Prozent Angabe
                if ($PriceFactor->getCalculation() === self::CALCULATION_PERCENTAGE) {
                    $calcBasis        = $PriceFactor->getCalculationBasis();
                    $priceFactorValue = $PriceFactor->getValue();
                    $vatValue         = $PriceFactor->getVat();
    
    
                    if ($vatValue === null) {
    
                        $vatValue = QUI\ERP\Tax\Utils::getTaxByUser($this->getUser())->getValue();
                    }
    
                    switch ($calcBasis) {
                        default:
                        case self::CALCULATION_BASIS_NETTO:
                            $percentage = $priceFactorValue / 100 * $nettoSubSum;
                            break;
    
                        case self::CALCULATION_BASIS_BRUTTO:
                        case self::CALCULATION_BASIS_CURRENTPRICE:
                            $percentage = $priceFactorValue / 100 * $nettoSum;
                            break;
    
                        case self::CALCULATION_BASIS_VAT_BRUTTO:
                            if ($isNetto) {
                                $bruttoSubSum = $subSum * ($vatValue / 100 + 1);
                                $percentage   = $priceFactorValue / 100 * $bruttoSubSum;
                            } else {
                                $percentage = $priceFactorValue / 100 * $subSum;
                            }
                            break;
    
    
                        case self::CALCULATION_GRAND_TOTAL:
                            // starts later
                            continue 2;
    
                    }
    
                    $percentage = \round($percentage, $precision);
                    $vatSum     = \round($PriceFactor->getVatSum(), $precision);
    
                    // set netto sum
                    $PriceFactor->setNettoSum($percentage);
    
                    if ($isNetto) {
                        $PriceFactor->setSum($PriceFactor->getNettoSum());
    
                    } elseif ($PriceFactor->getCalculationBasis() === self::CALCULATION_BASIS_VAT_BRUTTO) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                        $PriceFactor->setNettoSum($PriceFactor->getNettoSum() - $vatSum);
                        $PriceFactor->setSum($vatSum + $PriceFactor->getNettoSum());
    
                    } else {
                        $PriceFactor->setSum($vatSum + $PriceFactor->getNettoSum());
                    }
    
                    // formatted
                    $PriceFactor->setNettoSumFormatted($Currency->format($PriceFactor->getNettoSum()));
                    $PriceFactor->setSumFormatted($Currency->format($PriceFactor->getSum()));
                }
    
    
                $nettoSum       = $nettoSum + $PriceFactor->getNettoSum();
                $priceFactorSum = $priceFactorSum + $PriceFactor->getNettoSum();
    
    
                if ($isEuVatUser) {
                    $PriceFactor->setEuVatStatus(true);
                }
    
    
                $vat    = $PriceFactor->getVat();
    
                $vatSum = \round($PriceFactor->getVatSum(), $precision);
    
    
                if (!isset($vatArray[$vat])) {
                    $vatArray[$vat] = [
                        'vat'  => $vat,
    
    Henning Leutz's avatar
    Henning Leutz committed
                        'text' => self::getVatText($vat, $this->getUser())
    
                    ];
    
                    $vatArray[$vat]['sum'] = 0;
                }
    
                $vatArray[$vat]['sum'] = $vatArray[$vat]['sum'] + $vatSum;
            }
    
    
            if ($isEuVatUser) {
                $vatArray = [];
            }
    
            $vatLists = [];
            $vatText  = [];
    
            $nettoSum    = \round($nettoSum, $precision);
            $nettoSubSum = \round($nettoSubSum, $precision);
            $subSum      = \round($subSum, $precision);
            $bruttoSum   = $nettoSum;
    
    
            foreach ($vatArray as $vatEntry) {
    
                $vat = $vatEntry['vat'];
    
                $vatLists[$vat]        = true; // liste für MWST texte
                $vatArray[$vat]['sum'] = \round($vatEntry['sum'], $precision);
    
                $bruttoSum = $bruttoSum + $vatArray[$vat]['sum'];
    
            $bruttoSum = \round($bruttoSum, $precision);
    
    
            foreach ($vatLists as $vat => $bool) {
                $vatText[$vat] = self::getVatText($vat, $this->getUser());
            }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
            // delete 0 % vat, 0% vat is allowed to calculate more easily
            if (isset($vatText[0])) {
                unset($vatText[0]);
            }
    
            if (isset($vatArray[0])) {
                unset($vatArray[0]);
            }
    
    
    
            // gegenrechnung, wegen rundungsfehler
            if ($isNetto === false) {
                $priceFactorBruttoSums = 0;
    
                foreach ($priceFactors as $Factor) {
    
                    if ($Factor->getCalculationBasis() !== self::CALCULATION_GRAND_TOTAL) {
                        /* @var $Factor QUI\ERP\Products\Utils\PriceFactor */
                        $priceFactorBruttoSums = $priceFactorBruttoSums + \round($Factor->getSum(), $precision);
                    }
    
                }
    
                $priceFactorBruttoSum = $subSum + $priceFactorBruttoSums;
    
                $priceFactorBruttoSum = \round($priceFactorBruttoSum, $precision);
    
                if ($priceFactorBruttoSum !== \round($bruttoSum, $precision)) {
                    $diff = $priceFactorBruttoSum - \round($bruttoSum, $precision);
    
    
                    // if we have a diff, we change the first vat price factor
    
                    foreach ($priceFactors as $Factor) {
    
                        if ($Factor->getCalculationBasis() === self::CALCULATION_GRAND_TOTAL) {
                            continue;
                        }
    
    
                        if ($Factor instanceof QUI\ERP\Products\Interfaces\PriceFactorWithVatInterface) {
    
                            $Factor->setSum(\round($Factor->getSum() - $diff, $precision));
                            $bruttoSum = \round($bruttoSum, $precision);
    
    
                    if ($added === false) {
                        $bruttoSum = $bruttoSum + $diff;
                    }
    
    Henning Leutz's avatar
    Henning Leutz committed
    
    
                // counterbalance - gegenrechnung
                // works only for one vat entry
                if (\count($vatArray) === 1) {
                    $vat   = \key($vatArray);
                    $netto = $bruttoSum / ($vat / 100 + 1);
    
                    $vatSum = $bruttoSum - $netto;
                    $vatSum = \round($vatSum, $Currency->getPrecision());
                    $diff   = abs($vatArray[$vat]['sum'] - $vatSum);
    
                    if ($diff <= 0.019) {
                        $vatArray[$vat]['sum'] = $vatSum;
                    }
                }
    
            if ($bruttoSum === 0 || $nettoSum === 0) {
    
                $bruttoSum = 0;
                $nettoSum  = 0;
    
    
                foreach ($vatArray as $vat => $entry) {
                    $vatArray[$vat]['sum'] = 0;
                }
    
            // look if CALCULATION_GRAND_TOTAL
            $grandSubSum = $bruttoSum;
    
            foreach ($priceFactors as $Factor) {
                if ($Factor->getCalculationBasis() === self::CALCULATION_GRAND_TOTAL) {
                    $value     = $Factor->getValue();
                    $bruttoSum = $bruttoSum + $value;
    
                    if ($bruttoSum < 0) {
                        $bruttoSum = 0;
                    }
                }
            }
    
    
                'sum'          => $bruttoSum,
    
                'subSum'       => $subSum,
    
                'grandSubSum'  => $grandSubSum,
    
                'nettoSum'     => $nettoSum,
                'nettoSubSum'  => $nettoSubSum,
                'vatArray'     => $vatArray,
                'vatText'      => $vatText,
                'isEuVat'      => $isEuVatUser,
                'isNetto'      => $isNetto,
                'currencyData' => $this->getCurrency()->toArray()
    
         * Calculate the price of an article
         *
         * @param Article $Article
         * @param bool|callable $callback
         * @return mixed
    
        public function calcArticlePrice(Article $Article, $callback = false)
    
            if (!\is_callable($callback)) {
    
            $isNetto     = QUI\ERP\Utils\User::isNettoUser($this->getUser());
            $isEuVatUser = QUI\ERP\Tax\Utils::isUserEuVatUser($this->getUser());
    
            $Currency    = $Article->getCurrency();
    
            if (!$Currency) {
                $Currency = $this->getCurrency();
            }
    
            $nettoPrice      = $Article->getUnitPrice()->value();
    
            $vat             = $Article->getVat();
            $basisNettoPrice = $nettoPrice;
    
            $nettoSubSum     = $this->round($nettoPrice * $Article->getQuantity());
    
    
            if ($isEuVatUser) {
                $vat = 0;
            }
    
    
            // discounts
    
            $Discount             = $Article->getDiscount();
            $nettoPriceNotRounded = $Article->getUnitPriceUnRounded()->getValue();
    
    
            if ($Discount) {
                switch ($Discount->getCalculation()) {
                    // einfache Zahl, Währung --- kein Prozent
                    case Calc::CALCULATION_COMPLEMENT:
    
                        $nettoPrice           = $nettoPrice - ($Discount->getValue() / $Article->getQuantity());
                        $nettoPriceNotRounded = $nettoPriceNotRounded - ($Discount->getValue() / $Article->getQuantity());
    
                        break;
    
                    // Prozent Angabe
                    case Calc::CALCULATION_PERCENTAGE:
    
                        $percentage           = $Discount->getValue() / 100 * $nettoPrice;
                        $nettoPrice           = $nettoPrice - $percentage;
                        $nettoPriceNotRounded = $nettoPriceNotRounded - $percentage;
    
    
            $vatSum      = $nettoPrice * ($vat / 100);
    
            $bruttoPrice = \round($nettoPrice + $vatSum, $Currency->getPrecision());
    
    
            if (!$isNetto) {
                // korrektur rechnung / 1 cent problem
    
                $checkBrutto = $nettoPriceNotRounded * ($vat / 100 + 1);
                $checkBrutto = \round($checkBrutto, $Currency->getPrecision());
    
    
                $checkVat = $checkBrutto - $nettoPriceNotRounded;
                $checkVat = \round($checkVat * $Article->getQuantity(), $Currency->getPrecision());
    
                // sum
                $nettoSum = $this->round($nettoPrice * $Article->getQuantity());
                $vatSum   = $nettoSum * ($vat / 100);
    
                // korrektur rechnung / 1 cent problem
                if ($checkBrutto !== $bruttoPrice) {
                    $bruttoPrice = $checkBrutto;
                    $vatSum      = $checkVat;
                }
    
                // if the user is brutto
                // and we have a quantity
                // we need to calc first the brutto product price of one product
                // -> because of 1 cent rounding error
                $bruttoSum = $bruttoPrice * $Article->getQuantity();
            } else {
    
                // sum
                $nettoSum = $this->round($nettoPrice * $Article->getQuantity());
                $vatSum   = $nettoSum * ($vat / 100);
    
    
                $bruttoSum = $this->round($nettoSum + $vatSum);
            }
    
    
            $price      = $isNetto ? $nettoPrice : $bruttoPrice;
            $sum        = $isNetto ? $nettoSum : $bruttoSum;
            $basisPrice = $isNetto ? $basisNettoPrice : $basisNettoPrice + ($basisNettoPrice * $vat / 100);
    
    
            $vatArray = [
    
                'sum'  => $vatSum,
    
                'text' => $this->getVatText($vat, $this->getUser())
    
                'Kalkulierter Artikel Preis '.$Article->getId(),
    
                'basisPrice' => $basisPrice,
                'price'      => $price,
                'sum'        => $sum,
    
                'nettoBasisPrice' => $basisNettoPrice,
                'nettoPrice'      => $nettoPrice,
                'nettoSubSum'     => $nettoSubSum,
                'nettoSum'        => $nettoSum,
    
                'currencyData' => $this->getCurrency()->toArray(),
    
                'vatArray'     => $vatArray,
                'vatText'      => $vatArray['text'],
                'isEuVat'      => $isEuVatUser,
    
                'isNetto'      => $isNetto
    
    
            QUI\ERP\Debug::getInstance()->log($data, 'quiqqer/erp');
    
            $callback($data);
    
    
            return $Article->getPrice();
        }
    
        /**
         * Rounds the value via shop config
         *
    
         * @param string|int|float $value
    
        public function round($value): float
    
        {
            $decimalSeparator  = $this->getUser()->getLocale()->getDecimalSeparator();
            $groupingSeparator = $this->getUser()->getLocale()->getGroupingSeparator();
    
            $precision         = QUI\ERP\Defaults::getPrecision();
    
            if (\strpos($value, $decimalSeparator) && $decimalSeparator != '.') {
                $value = \str_replace($groupingSeparator, '', $value);
    
            $value = \str_replace(',', '.', $value);
    
            $value = \floatval($value);
    
            $value = \round($value, $precision);
    
    
            return $value;
        }
    
        /**
         * Return the tax message for an user
         *
         * @return string
         */
        public function getVatTextByUser()
        {
    
            try {
                $Tax = QUI\ERP\Tax\Utils::getTaxByUser($this->getUser());
            } catch (QUI\Exception $Exception) {
                return '';
            }
    
    
            return $this->getVatText($Tax->getValue(), $this->getUser());
        }
    
        /**
         * Return tax text
         * eq: incl or zzgl
         *
         * @param integer $vat
         * @param UserInterface $User
    
    Henning Leutz's avatar
    Henning Leutz committed
         * @param null|QUI\Locale $Locale - optional
         *
    
    Henning Leutz's avatar
    Henning Leutz committed
        public static function getVatText($vat, UserInterface $User, $Locale = null)
    
    Henning Leutz's avatar
    Henning Leutz committed
            if ($Locale === null) {
                $Locale = $User->getLocale();
            }
    
    
            if (QUI\ERP\Utils\User::isNettoUser($User)) {
                if (QUI\ERP\Tax\Utils::isUserEuVatUser($User)) {
                    return $Locale->get(
                        'quiqqer/tax',
                        'message.vat.text.netto.EUVAT',
    
                        ['vat' => $vat]
    
                    );
                }
    
                // vat ist leer und kein EU vat user
                if (!$vat) {
                    return '';
                }
    
                return $Locale->get(
                    'quiqqer/tax',
                    'message.vat.text.netto',
    
                    ['vat' => $vat]
    
                );
            }
    
            if (QUI\ERP\Tax\Utils::isUserEuVatUser($User)) {
                return $Locale->get(
                    'quiqqer/tax',
                    'message.vat.text.brutto.EUVAT',
    
                    ['vat' => $vat]
    
            // vat ist leer und kein EU vat user
            if (!$vat) {
                return '';
    
    Henning Leutz's avatar
    Henning Leutz committed
    
    
            return $Locale->get(
                'quiqqer/tax',
                'message.vat.text.brutto',
    
                ['vat' => $vat]
    
    
        /**
         * Calculates the individual amounts paid of an invoice
         *
    
    Henning Leutz's avatar
    Henning Leutz committed
         * @param Invoice $Invoice
    
         * @return array
    
    Henning Leutz's avatar
    Henning Leutz committed
         * @throws QUI\ERP\Exception
    
         * @deprecated use calculatePayments
    
    Henning Leutz's avatar
    Henning Leutz committed
        public static function calculateInvoicePayments(Invoice $Invoice)
    
            return self::calculatePayments($Invoice);
        }
    
        /**
         * Calculates the individual amounts paid of an invoice / order
         *
    
         * @param InvoiceTemporary|Invoice|QUI\ERP\Order\AbstractOrder|QUI\ERP\Purchasing\Processes\PurchasingProcess $ToCalculate
    
         * @return array
         *
         * @throws QUI\ERP\Exception
         */
    
        public static function calculatePayments($ToCalculate): array
    
    Henning Leutz's avatar
    Henning Leutz committed
            if (self::isAllowedForCalculation($ToCalculate) === false) {
                QUI\ERP\Debug::getInstance()->log(
    
                    'Calc->calculatePayments(); Object is not allowed to calculate '.\get_class($ToCalculate)
    
    Henning Leutz's avatar
    Henning Leutz committed
                );
    
    
                throw new QUI\ERP\Exception('Object is not allowed to calculate');
            }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
            QUI\ERP\Debug::getInstance()->log(
                'Calc->calculatePayments(); Transaction'
            );
    
    Henning Leutz's avatar
    Henning Leutz committed
            // if payment status is paid, take it immediately and do not query any transactions
    
            if ($ToCalculate->getAttribute('paid_status') === QUI\ERP\Constants::PAYMENT_STATUS_PAID) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                $paidData = $ToCalculate->getAttribute('paid_data');
                $paid     = 0;
    
    
                if (!\is_array($paidData)) {
                    $paidData = \json_decode($paidData, true);
    
    Henning Leutz's avatar
    Henning Leutz committed
                }
    
    
                if (!\is_array($paidData)) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $paidData = [];
                }
    
                foreach ($paidData as $entry) {
                    if (isset($entry['amount'])) {
    
                        $paid = $paid + \floatval($entry['amount']);
    
    Henning Leutz's avatar
    Henning Leutz committed
                    }
                }
    
                $ToCalculate->setAttribute('paid', $paid);
                $ToCalculate->setAttribute('toPay', 0);
    
                QUI\ERP\Debug::getInstance()->log([
                    'paidData'   => $ToCalculate->getAttribute('paid_data'),
                    'paidDate'   => $ToCalculate->getAttribute('paid_date'),
                    'paidStatus' => $ToCalculate->getAttribute('paid_status'),
                    'paid'       => $ToCalculate->getAttribute('paid'),
                    'toPay'      => $ToCalculate->getAttribute('toPay')
                ]);
    
                return [
                    'paidData'   => $ToCalculate->getAttribute('paid_data'),
                    'paidDate'   => $ToCalculate->getAttribute('paid_date'),
                    'paidStatus' => $ToCalculate->getAttribute('paid_status'),
                    'paid'       => $ToCalculate->getAttribute('paid'),
                    'toPay'      => $ToCalculate->getAttribute('toPay')
                ];
            }
    
    
            // calc with transactions
    
    Henning Leutz's avatar
    Henning Leutz committed
            $Transactions = QUI\ERP\Accounting\Payments\Transactions\Handler::getInstance();
            $transactions = $Transactions->getTransactionsByHash($ToCalculate->getHash());
    
            $calculations = $ToCalculate->getArticles()->getCalculations();
    
    Henning Leutz's avatar
    Henning Leutz committed
            if (!isset($calculations['sum'])) {
                $calculations['sum'] = 0;
            }
    
    
            $paidData = [];
    
            $paidDate = 0;
            $sum      = 0;
    
    Henning Leutz's avatar
    Henning Leutz committed
            QUI\ERP\Debug::getInstance()->log(
                'Calc->calculatePayments(); total: '.$total
            );
    
    
            $isValidTimeStamp = function ($timestamp) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                try {
                    new \DateTime('@'.$timestamp);
                } catch (\Exception $e) {
                    return false;
                }
    
                return true;
    
    Henning Leutz's avatar
    Henning Leutz committed
            foreach ($transactions as $Transaction) {
                /* @var $Transaction QUI\ERP\Accounting\Payments\Transactions\Transaction */
    
                if (!$Transaction->isComplete()) {
                    // don't add incomplete transactions
    
    Henning Leutz's avatar
    Henning Leutz committed
                    continue;
                }
    
    
                // calculate the paid amount
    
    Henning Leutz's avatar
    Henning Leutz committed
                $amount = Price::validatePrice($Transaction->getAmount());
    
    
                // set the newest date
    
    Henning Leutz's avatar
    Henning Leutz committed
                $date = $Transaction->getDate();
    
    
                if ($isValidTimeStamp($date) === false) {
    
    
                    if ($isValidTimeStamp($date) === false) {
    
                    }
                } else {
                    $date = (int)$date;
                }
    
                if ($date > $paidDate) {
                    $paidDate = $date;
                }
    
    
                // Falls das gezahlte mehr ist
                if ($total < ($sum + $amount)) {
                    $amount = $total - $sum;
    
                    // @todo Information in Rechnung hinterlegen
                    // @todo Automatische Gutschrift erstellen
                }
    
                $sum = $sum + $amount;
    
    
                $paidData[] = [
    
    Henning Leutz's avatar
    Henning Leutz committed
                    'amount' => $amount,
                    'date'   => $date,
                    'txid'   => $Transaction->getTxId()
    
    Henning Leutz's avatar
    Henning Leutz committed
            $paid  = Price::validatePrice($sum);
    
            $toPay = Price::validatePrice($calculations['sum']);
    
    Henning Leutz's avatar
    Henning Leutz committed
            // workaround fix
            if ($ToCalculate->getAttribute('paid_date') != $paidDate) {
    
                try {
                    QUI::getDataBase()->update(
                        Handler::getInstance()->invoiceTable(),
                        ['paid_date' => $paidDate],
                        ['id' => $ToCalculate->getCleanId()]
                    );
                } catch (QUI\Database\Exception $Exception) {
                    QUI\System\Log::writeException($Exception);
    
                    throw new QUI\ERP\Exception(
                        ['quiqqer/erp', 'exception.something.went.wrong'],
                        $Exception->getCode()
                    );
                }
    
            $ToCalculate->setAttribute('paid_data', \json_encode($paidData));
    
            $ToCalculate->setAttribute('paid_date', $paidDate);
            $ToCalculate->setAttribute('paid', $sum);
    
    Henning Leutz's avatar
    Henning Leutz committed
            $ToCalculate->setAttribute('toPay', $toPay - $paid);
    
            if ($ToCalculate instanceof QUI\ERP\Order\AbstractOrder
    
                && $ToCalculate->getAttribute('paid_status') === QUI\ERP\Constants::PAYMENT_STATUS_PLAN) {
    
                // Leave everything as it is because a subscription plan order can never be set to "paid"
    
            } elseif ($ToCalculate->getAttribute('paid_status') === QUI\ERP\Constants::TYPE_INVOICE_REVERSAL
                      || $ToCalculate->getAttribute('paid_status') === QUI\ERP\Constants::TYPE_INVOICE_CANCEL
    
                      || $ToCalculate->getAttribute('paid_status') === QUI\ERP\Constants::PAYMENT_STATUS_DEBIT
    
    Henning Leutz's avatar
    Henning Leutz committed
            ) {
                // Leave everything as it is
    
            } elseif ((float)$ToCalculate->getAttribute('toPay') == 0) {
    
                $ToCalculate->setAttribute('paid_status', QUI\ERP\Constants::PAYMENT_STATUS_PAID);
    
            } elseif ($ToCalculate->getAttribute('paid') == 0) {
    
                $ToCalculate->setAttribute('paid_status', QUI\ERP\Constants::PAYMENT_STATUS_OPEN);
    
            } elseif ($ToCalculate->getAttribute('toPay')
    
                      && $calculations['sum'] != $ToCalculate->getAttribute('paid')
    
                $ToCalculate->setAttribute('paid_status', QUI\ERP\Constants::PAYMENT_STATUS_PART);
    
            QUI\ERP\Debug::getInstance()->log([
    
    Henning Leutz's avatar
    Henning Leutz committed
                'paidData'   => $paidData,
                'paidDate'   => $ToCalculate->getAttribute('paid_date'),
                'paid'       => $ToCalculate->getAttribute('paid'),
                'toPay'      => $ToCalculate->getAttribute('toPay'),
                'paidStatus' => $ToCalculate->getAttribute('paid_status'),
                'sum'        => $sum
    
    Henning Leutz's avatar
    Henning Leutz committed
                'paidData'   => $paidData,
                'paidDate'   => $ToCalculate->getAttribute('paid_date'),
                'paidStatus' => $ToCalculate->getAttribute('paid_status'),
                'paid'       => $ToCalculate->getAttribute('paid'),
                'toPay'      => $ToCalculate->getAttribute('toPay')
    
    Henning Leutz's avatar
    Henning Leutz committed
        /**
         * Is the object allowed for calculation
         *
    
    Henning Leutz's avatar
    Henning Leutz committed
         * @param InvoiceTemporary|Invoice|QUI\ERP\Order\AbstractOrder $ToCalculate
    
    Henning Leutz's avatar
    Henning Leutz committed
         * @return bool
         */
    
    Henning Leutz's avatar
    Henning Leutz committed
        public static function isAllowedForCalculation($ToCalculate)
        {
            if ($ToCalculate instanceof Invoice) {
                return true;
            }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
            if ($ToCalculate instanceof InvoiceTemporary) {
                return true;
            }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
            if ($ToCalculate instanceof QUI\ERP\Order\AbstractOrder) {
                return true;
    
            }
    
            if ($ToCalculate instanceof QUI\ERP\Purchasing\Processes\PurchasingProcess) {
                return true;
    
        /**
         * Calculate the total of the invoice list
         *
         * @param array $invoiceList - list of invoice array
    
         * @param QUI\ERP\Currency\Currency|null $Currency
    
         * @return array
         */
    
        public static function calculateTotal(array $invoiceList, $Currency = null): array
    
            if ($Currency === null) {
                try {
                    $currency = \json_decode($invoiceList[0]['currency_data'], true);
                    $Currency = QUI\ERP\Currency\Handler::getCurrency($currency['code']);
                } catch (QUI\Exception $Exception) {
                    $Currency = QUI\ERP\Defaults::getCurrency();
                }
            }
    
    
            if (!\count($invoiceList)) {
    
                $display = $Currency->format(0);
    
                    'netto_toPay'         => 0,
                    'netto_paid'          => 0,
                    'netto_total'         => 0,
                    'display_netto_toPay' => $display,
                    'display_netto_paid'  => $display,
                    'display_netto_total' => $display,
    
                    'vat_toPay'         => 0,
                    'vat_paid'          => 0,
                    'vat_total'         => 0,
                    'display_vat_toPay' => $display,
                    'display_vat_paid'  => $display,
                    'display_vat_total' => $display,
    
                    'brutto_toPay'         => 0,
                    'brutto_paid'          => 0,
                    'brutto_total'         => 0,
                    'display_brutto_toPay' => $display,
                    'display_brutto_paid'  => $display,
                    'display_brutto_total' => $display
    
            }
    
            $nettoTotal = 0;
            $vatTotal   = 0;
    
            $bruttoToPay = 0;
            $bruttoPaid  = 0;
            $bruttoTotal = 0;
    
            $vatPaid     = 0;
            $nettoToPay  = 0;
    
    
            foreach ($invoiceList as $invoice) {
    
    //            if (isset($invoice['type']) && (int)$invoice['type'] === Handler::TYPE_INVOICE_CANCEL ||
    //                isset($invoice['type']) && (int)$invoice['type'] === Handler::TYPE_INVOICE_STORNO
    //            ) {
    //                continue;
    //            }
    //          soll doch mit berechnet werden
    
                $invBruttoSum  = floatval($invoice['calculated_sum']);
                $invVatSum     = floatval($invoice['calculated_vatsum']);
                $invPaid       = floatval($invoice['calculated_paid']);
                $invToPay      = floatval($invoice['calculated_toPay']);
                $invNettoTotal = floatval($invoice['calculated_nettosum']);
                $invVatSumPC   = QUI\Utils\Math::percent($invVatSum, $invBruttoSum);
    
                if ($invVatSumPC) {
                    $invVatPaid = $invPaid * $invVatSumPC / 100;
                } else {
                    $invVatPaid = 0;
                }
    
    
                $invNettoPaid  = $invPaid - $invVatPaid;
    
                $invNettoToPay = $invNettoTotal - $invNettoPaid;
    
    
                // complete + addition
                $vatPaid     = $vatPaid + $invVatPaid;
                $bruttoTotal = $bruttoTotal + $invBruttoSum;
                $bruttoPaid  = $bruttoPaid + $invPaid;