Skip to content
Code-Schnipsel Gruppen Projekte
Calc.php 38,5 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;
    
    
    Henning Leutz's avatar
    Henning Leutz committed
    use DateTime;
    use Exception;
    
    use QUI;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use QUI\ERP\Accounting\Invoice\Handler;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use QUI\ERP\Accounting\Invoice\Invoice;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use QUI\ERP\Money\Price;
    use QUI\Interfaces\Users\User as UserInterface;
    
    use function array_map;
    use function array_sum;
    
    use function class_exists;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use function count;
    use function floatval;
    use function get_class;
    use function is_array;
    use function is_callable;
    use function is_null;
    use function is_string;
    use function json_decode;
    use function json_encode;
    use function key;
    use function round;
    
    Henning Leutz's avatar
    Henning Leutz committed
    use function str_replace;
    use function strpos;
    use function strtotime;
    use function time;
    
    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: it's 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;
    
    
        /**
         * Special transaction attributes for currency exchange
         */
    
        const TRANSACTION_ATTR_TARGET_CURRENCY = 'tx_target_currency';
    
        const TRANSACTION_ATTR_TARGET_CURRENCY_EXCHANGE_RATE = 'tx_target_currency_exchange_rate';
    
        const TRANSACTION_ATTR_SHOP_CURRENCY_EXCHANGE_RATE = 'tx_shop_currency_exchange_rate';
    
        /**
         * @var UserInterface
         */
        protected $User = null;
    
        /**
         * @var null|QUI\ERP\Currency\Currency
         */
    
    Henning Leutz's avatar
    Henning Leutz committed
        protected ?QUI\ERP\Currency\Currency $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
         */
    
    Henning Leutz's avatar
    Henning Leutz committed
        public static function getInstance(UserInterface $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
    
    Henning Leutz's avatar
    Henning Leutz committed
            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
    
    Henning Leutz's avatar
    Henning Leutz committed
            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();
    
            $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;
                }
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                if (!isset($vatArray[(string)$vat])) {
    
                    $vatArray[(string)$vat] = $articleVatArray;
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $vatArray[(string)$vat]['sum'] = 0;
    
    Henning Leutz's avatar
    Henning Leutz committed
                $vatArray[(string)$vat]['sum'] = $vatArray[(string)$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;
                }
    
    
                if ($PriceFactor->getCalculation() === self::CALCULATION_COMPLEMENT) {
    
                    // Standard calculation - Fester Preis
    
                    $vatSum = $PriceFactor->getVatSum();
    
                    if ($isNetto) {
                        $PriceFactor->setSum($PriceFactor->getNettoSum());
                    } elseif ($PriceFactor->getCalculationBasis() === self::CALCULATION_BASIS_VAT_BRUTTO) {
                        $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()));
    
                } elseif ($PriceFactor->getCalculation() === self::CALCULATION_PERCENTAGE) {
                    // percent - Prozent Angabe
    
                    $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;
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $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()));
    
                } else {
                    continue;
    
                $nettoSum = $nettoSum + $PriceFactor->getNettoSum();
    
                $priceFactorSum = $priceFactorSum + $PriceFactor->getNettoSum();
    
    
                if ($isEuVatUser) {
                    $PriceFactor->setEuVatStatus(true);
                }
    
    
                $vat = $PriceFactor->getVat();
    
    Henning Leutz's avatar
    Henning Leutz committed
                $vatSum = round($PriceFactor->getVatSum(), $precision);
    
    Henning Leutz's avatar
    Henning Leutz committed
                if (!isset($vatArray[(string)$vat])) {
                    $vatArray[(string)$vat] = [
    
    Henning Leutz's avatar
    Henning Leutz committed
                        'text' => self::getVatText($vat, $this->getUser())
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $vatArray[(string)$vat]['sum'] = 0;
    
    Henning Leutz's avatar
    Henning Leutz committed
                $vatArray[(string)$vat]['sum'] = $vatArray[(string)$vat]['sum'] + $vatSum;
    
            if ($isEuVatUser) {
                $vatArray = [];
            }
    
            $nettoSum = round($nettoSum, $precision);
    
    Henning Leutz's avatar
    Henning Leutz committed
            $nettoSubSum = round($nettoSubSum, $precision);
    
            $subSum = round($subSum, $precision);
            $bruttoSum = $nettoSum;
    
    
            foreach ($vatArray as $vatEntry) {
    
                $vat = $vatEntry['vat'];
    
    
                $vatLists[(string)$vat] = true; // liste für MWST texte
    
    Henning Leutz's avatar
    Henning Leutz committed
                $vatArray[(string)$vat]['sum'] = round($vatEntry['sum'], $precision);
    
    Henning Leutz's avatar
    Henning Leutz committed
                $bruttoSum = $bruttoSum + $vatArray[(string)$vat]['sum'];
    
    Henning Leutz's avatar
    Henning Leutz committed
            $bruttoSum = round($bruttoSum, $precision);
    
            foreach ($vatLists as $vat => $bool) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                $vatText[(string)$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 */
    
    Henning Leutz's avatar
    Henning Leutz committed
                        $priceFactorBruttoSums = $priceFactorBruttoSums + round($Factor->getSum(), $precision);
    
                }
    
                $priceFactorBruttoSum = $subSum + $priceFactorBruttoSums;
    
    Henning Leutz's avatar
    Henning Leutz committed
                $priceFactorBruttoSum = round($priceFactorBruttoSum, $precision);
    
    Henning Leutz's avatar
    Henning Leutz committed
                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) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                            $Factor->setSum(round($Factor->getSum() - $diff, $precision));
                            $bruttoSum = round($bruttoSum, $precision);
    
    
                    if ($added === false) {
                        $bruttoSum = $bruttoSum + $diff;
    
    Henning Leutz's avatar
    Henning Leutz committed
    
                        // netto check 1cent check
                        $bruttoVatSum = 0;
    
                        foreach ($vatArray as $vat => $data) {
                            $bruttoVatSum = $bruttoVatSum + $data['sum'];
                        }
    
                        if ($bruttoSum - $bruttoVatSum !== $nettoSum) {
                            $nettoSum = $nettoSum + $diff;
                        }
    
    Henning Leutz's avatar
    Henning Leutz committed
    
    
                // counterbalance - gegenrechnung
                // works only for one vat entry
    
    Henning Leutz's avatar
    Henning Leutz committed
                if (count($vatArray) === 1) {
    
                    $vat = key($vatArray);
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $netto = $bruttoSum / ($vat / 100 + 1);
    
                    $vatSum = $bruttoSum - $netto;
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $vatSum = round($vatSum, $Currency->getPrecision());
    
                    $diff = abs($vatArray[(string)$vat]['sum'] - $vatSum);
    
    Henning Leutz's avatar
    Henning Leutz committed
    
                    if ($diff <= 0.019) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                        $vatArray[(string)$vat]['sum'] = $vatSum;
    
            if ($bruttoSum === 0 || $nettoSum === 0) {
    
                $bruttoSum = 0;
    
    
                foreach ($vatArray as $vat => $entry) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $vatArray[(string)$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)
    
    Henning Leutz's avatar
    Henning Leutz committed
            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->getUnitPriceUnRounded()->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);
    
            $precision = $Currency->getPrecision();
            $priceSum = $nettoPrice + $vatSum;
            $bruttoPrice = round($priceSum, $precision);
    
            if (!$isNetto) {
                // korrektur rechnung / 1 cent problem
    
                $checkBrutto = $nettoPriceNotRounded * ($vat / 100 + 1);
    
    Henning Leutz's avatar
    Henning Leutz committed
                $checkBrutto = round($checkBrutto, $Currency->getPrecision());
    
    
                $checkVat = $checkBrutto - $nettoPriceNotRounded;
    
    Henning Leutz's avatar
    Henning Leutz committed
                $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);
    
            $basisPrice = round($basisPrice, QUI\ERP\Defaults::getPrecision());
    
            $vatArray = [
    
                'vat' => $vat,
                '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();
    
    Henning Leutz's avatar
    Henning Leutz committed
            if (strpos($value, $decimalSeparator) && $decimalSeparator != '.') {
                $value = str_replace($groupingSeparator, '', $value);
    
    Henning Leutz's avatar
    Henning Leutz committed
            $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(float $vat, UserInterface $User, QUI\Locale $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): array
    
            return self::calculatePayments($Invoice);
        }
    
        /**
         * Calculates the individual amounts paid of an invoice / order
         *
    
         * @param mixed $ToCalculate
    
         * @return array
         *
    
         * @throws QUI\ERP\Exception|QUI\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 ' . get_class($ToCalculate));
    
    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');
    
    Henning Leutz's avatar
    Henning Leutz committed
    
    
    Henning Leutz's avatar
    Henning Leutz committed
                if (!is_array($paidData)) {
                    $paidData = json_decode($paidData, true);
    
    Henning Leutz's avatar
    Henning Leutz committed
                }
    
    
    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'])) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                        $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'),
    
    Henning Leutz's avatar
    Henning Leutz committed
                    'paidStatus' => $ToCalculate->getAttribute('paid_status'),
    
                    'paid' => $ToCalculate->getAttribute('paid'),
                    'toPay' => $ToCalculate->getAttribute('toPay')
    
    Henning Leutz's avatar
    Henning Leutz committed
                ]);
    
                return [
    
                    'paidData' => $ToCalculate->getAttribute('paid_data'),
                    'paidDate' => $ToCalculate->getAttribute('paid_date'),
    
    Henning Leutz's avatar
    Henning Leutz committed
                    'paidStatus' => $ToCalculate->getAttribute('paid_status'),
    
                    'paid' => $ToCalculate->getAttribute('paid'),
                    'toPay' => $ToCalculate->getAttribute('toPay')
    
    Henning Leutz's avatar
    Henning Leutz committed
                ];
            }
    
    
            // 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;
            $total = $calculations['sum'];
    
    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);
    
    Henning Leutz's avatar
    Henning Leutz committed
                } catch (Exception $e) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                    return false;
                }
    
                return true;
    
            $CalculateCurrency = $ToCalculate->getCurrency();
    
            $ShopCurrency = QUI\ERP\Defaults::getCurrency();
    
    Henning Leutz's avatar
    Henning Leutz committed
            foreach ($transactions as $Transaction) {
    
                if (!$Transaction->isComplete()) {
                    // don't add incomplete transactions
    
    Henning Leutz's avatar
    Henning Leutz committed
                    continue;
                }
    
    
                // calculate the paid amount
    
                $amount = Price::validatePrice($Transaction->getAmount());
    
                $TransactionCurrency = $Transaction->getCurrency();
    
                // If necessary, convert from transaction currency to calculation object currency
                if ($CalculateCurrency->getCode() !== $TransactionCurrency->getCode()) {
                    $targetCurrencyCode = $Transaction->getData(
                        self::TRANSACTION_ATTR_TARGET_CURRENCY
                    );
    
                    $targetCurrencyExchangeRate = $Transaction->getData(
                        self::TRANSACTION_ATTR_TARGET_CURRENCY_EXCHANGE_RATE
                    );
    
                    $shopCurrencyExchangeRate = $Transaction->getData(
                        self::TRANSACTION_ATTR_SHOP_CURRENCY_EXCHANGE_RATE
                    );
    
                    /*
                     * $amount has to DIVIDED by the exchange rate because the exchange rate is always
                     * in relation to the base (shop) currency to the given currency.
                     *
                     * Example: From ETH to EUR -> The exchange rate here is the rate that turn EUR into ETH; so to
                     * get ETH to EUR you have to divide the ETH value by the exchange rate.
                     */
                    if ($targetCurrencyCode === $CalculateCurrency->getCode() && $targetCurrencyExchangeRate) {
                        $amount /= $targetCurrencyExchangeRate;
                    } elseif ($ShopCurrency === $CalculateCurrency->getCode() && $shopCurrencyExchangeRate) {
                        $amount /= $shopCurrencyExchangeRate;
                    } else {
                        $amount = $TransactionCurrency->convert($amount, $CalculateCurrency);
    
                        QUI\System\Log::addWarning(
    
                                'The currency of transaction "%s" for calculation of object %s (%s) is "%s" and differs'
    
                                . ' from the currency of the calculation object ("%s"). But the transaction does not'
                                . ' contain an exchange rate from "%s" to "%s". Thus, the exchange rate that is currently'
                                . ' live in the system is used for converting from "%s" to "%s".',
    
                                $Transaction->getTxId(),
                                $ToCalculate->getId(),
    
                                get_class($ToCalculate),
    
                                $TransactionCurrency->getCode(),
                                $CalculateCurrency->getCode(),
                                $TransactionCurrency->getCode(),
                                $CalculateCurrency->getCode(),
                                $TransactionCurrency->getCode(),
                                $CalculateCurrency->getCode()
                            )
                        );
                    }
    
                    $amount = $CalculateCurrency->amount($amount);
                }
    
    
                // set the newest date
    
    Henning Leutz's avatar
    Henning Leutz committed
                $date = $Transaction->getDate();
    
    
                if ($isValidTimeStamp($date) === false) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                    $date = strtotime($date);
    
    
                    if ($isValidTimeStamp($date) === false) {
    
    Henning Leutz's avatar
    Henning Leutz committed
                        $date = time();
    
                    }
                } 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()
    
            $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()
                    );
                }
    
    Henning Leutz's avatar
    Henning Leutz committed
            $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([
    
                'paidData' => $paidData,
                'paidDate' => $ToCalculate->getAttribute('paid_date'),
                'paid' => $ToCalculate->getAttribute('paid'),
                'toPay' => $ToCalculate->getAttribute('toPay'),
    
    Henning Leutz's avatar
    Henning Leutz committed
                'paidStatus' => $ToCalculate->getAttribute('paid_status'),
    
                'paidData' => $paidData,