Skip to content
Code-Schnipsel Gruppen Projekte
Article.php 19,8 KiB
Newer Older
<?php

/**
 * This file contains QUI\ERP\Accounting\Article
 */

namespace QUI\ERP\Accounting;

use QUI;
use QUI\ERP\Money\Price;
use QUI\ERP\Tax\Utils as TaxUtils;
Henning Leutz's avatar
Henning Leutz committed
use function floatval;
use function get_called_class;
use function method_exists;
Henning Leutz's avatar
Henning Leutz committed

/**
 * Class Article
 *
 * @package QUI\ERP\Accounting
 */
class Article implements ArticleInterface
{
    /**
     * @var array
     */
    protected array $attributes = [
        'id' => '',
Henning Leutz's avatar
Henning Leutz committed
        'control' => '',
    /**
     * Custom fields are data which field out the customer
     * This data is not used for presentation or calculation
     *
     * in a custom field are only allowed string and numeric values
     *
     * @var array
     */
    protected array $customFields = [];
    /**
     * Custom data for plugins and modules
     *
     * @var array
     */
    protected array $customData = [];
    /**
     * Should the price displayed?
     * default = yes
     */
    protected bool $displayPrice = true;
    protected bool $calculated = false;
     * @var float|int
    protected float | int $price;
     * @var float|int
    protected float | int $basisPrice;
    protected float | int | null $nettoPriceNotRounded = null;
     * @var float|int
    protected float | int $sum;
     * The calculated netto sum with quantity and discount
     * @var float|int
    protected float | int $nettoSum = 0;
     * Sum from the article, without discount and with quantity
     *
     * @var float|int
     */
    protected float | int $nettoSubSum = 0;

    /**
     * The article netto price, without discount, without quantity
     * comes from article
     *
     * @var float|int
     */
    protected float | int $nettoPrice = 0;

    /**
     * The article netto price, without discount, without quantity
     * comes from calc
     *
     * @var float|int
     */
    protected float | int $nettoBasisPrice = 0;
    protected mixed $vatArray = [];
     * @var bool
     * @var bool
Henning Leutz's avatar
Henning Leutz committed
    protected float $position = 0;

    /**
     * @var ArticleDiscount|null
     */
    protected ?ArticleDiscount $Discount = null;

    /**
     * @var null|QUI\Interfaces\Users\User
     */
    protected ?QUI\Interfaces\Users\User $User = null;
     * @var null|QUI\ERP\Currency\Currency
    protected ?QUI\ERP\Currency\Currency $Currency = null;
     * @param array $attributes - (id, articleNo, title, description, unitPrice, nettoPriceNotRounded, quantity, discount, customData)
    public function __construct(array $attributes = [])
        $defaults = [
Henning Leutz's avatar
Henning Leutz committed
            'id',
            'uuid',
            'productSetParentUuid',
Henning Leutz's avatar
Henning Leutz committed
            'articleNo',
Henning Leutz's avatar
Henning Leutz committed
            'title',
            'description',
            'unitPrice',
            'nettoPriceNotRounded', // optional
Henning Leutz's avatar
Henning Leutz committed
            'control',
Henning Leutz's avatar
Henning Leutz committed
            'quantity',
            'quantityUnit'
Henning Leutz's avatar
Henning Leutz committed
        foreach ($defaults as $key) {
            if (isset($attributes[$key])) {
                $this->attributes[$key] = $attributes[$key];
            }
Henning Leutz's avatar
Henning Leutz committed
        if (isset($attributes['vat']) && $attributes['vat'] !== '') {
            $this->attributes['vat'] = $attributes['vat'];
Henning Leutz's avatar
Henning Leutz committed
        } else {
            $this->attributes['vat'] = '';
Henning Leutz's avatar
Henning Leutz committed
        if (isset($attributes['position'])) {
            $this->position = (float)$attributes['position'];
        }
        if (isset($attributes['discount'])) {
            $this->Discount = ArticleDiscount::unserialize($attributes['discount']);
            $this->Discount?->setArticle($this);

        if (isset($attributes['calculated'])) {
            $calc = $attributes['calculated'];

            if (isset($calc['nettoPriceNotRounded'])) {
                $this->nettoPriceNotRounded = $calc['nettoPriceNotRounded'];
            $this->price = $calc['price'];
            $this->basisPrice = $calc['basisPrice'];
            $this->sum = $calc['sum'];
            $this->nettoPrice = $calc['nettoPrice'];
            $this->nettoBasisPrice = $calc['nettoBasisPrice'];
            $this->nettoSum = $calc['nettoSum'];

            if (isset($calc['nettoSubSum'])) {
                $this->nettoSubSum = $calc['nettoSubSum'];
            }

            $this->vatArray = $calc['vatArray'];
            $this->isEuVat = $calc['isEuVat'];
            $this->isNetto = $calc['isNetto'];
        if (isset($attributes['customFields']) && is_array($attributes['customFields'])) {
            $this->customFields = $attributes['customFields'];
        }
        if (isset($attributes['customData']) && is_array($attributes['customData'])) {
            $this->customData = $attributes['customData'];
        }

        if (isset($attributes['displayPrice'])) {
            $this->displayPrice = (bool)$attributes['displayPrice'];
        }
Henning Leutz's avatar
Henning Leutz committed
        if (isset($attributes['currency'])) {
            try {
                $this->Currency = QUI\ERP\Currency\Handler::getCurrency($attributes['currency']);
            } catch (QUI\Exception $Exception) {
                QUI\System\Log::writeDebugException($Exception);
            }
        }

        if (!$this->Currency) {
            $this->Currency = QUI\ERP\Currency\Handler::getDefaultCurrency();
        }
Henning Leutz's avatar
Henning Leutz committed
    /**
     * Return the article view
     *
     * @return ArticleView
     */
    public function getView(): ArticleView
Henning Leutz's avatar
Henning Leutz committed
    {
        return new ArticleView($this);
    }

    public function getId(): int
    {
        if (isset($this->attributes['id'])) {
    /**
     * Return the Article UUID
     *
     * @return string|null
     */
    public function getUuid(): ?string
    {
        return !empty($this->attributes['uuid']) ? $this->attributes['uuid'] : null;
    }

    /**
     * Return the UUID of a parent article, if this article is part of a product set.
     *
     * @return string|null
     */
    public function getProductSetParentUuid(): ?string
    {
        return !empty($this->attributes['productSetParentUuid']) ? $this->attributes['productSetParentUuid'] : null;
    }

    public function getArticleNo(): int | string
    {
        if (isset($this->attributes['articleNo'])) {
            return $this->attributes['articleNo'];
        }

        return '';
    }

    /**
     * Return the GTIN Number, if the article has one
     *
     * @return string
     */
    public function getGTIN(): string
    {
        if (isset($this->attributes['gtin'])) {
            return $this->attributes['gtin'];
        }

        return '';
    }

    /**
     * Returns the article title
     *
     * @return string
     */
    public function getTitle(): string
    {
        if (isset($this->attributes['title'])) {
            return $this->attributes['title'];
        }

        return '';
    }

    /**
     * Return the article image
     *
     * @return null|QUI\Projects\Media\Image
     */
    public function getImage(): ?QUI\Projects\Media\Image
    {
        if (isset($this->attributes['image'])) {
            try {
                return QUI\Projects\Media\Utils::getImageByUrl(
                    $this->attributes['image']
                );
            } catch (QUI\Exception $Exception) {
                QUI\System\Log::writeDebugException($Exception);
            }
        }

        $Product = null;

        try {
            $Product = QUI\ERP\Products\Handler\Products::getProductByProductNo(
                $this->getArticleNo()
            );
        } catch (QUI\Exception $Exception) {
            QUI\System\Log::writeDebugException($Exception);
        }

        if (!empty($Product) && method_exists($Product, 'getImage')) {
            $Project = QUI::getRewrite()->getProject();
            $PlaceholderImage = $Project->getMedia()->getPlaceholderImage();
            if ($PlaceholderImage instanceof QUI\Projects\Media\Image) {
                return $PlaceholderImage;
            }
        } catch (QUI\Exception $Exception) {
            QUI\System\Log::writeDebugException($Exception);
        }

        return null;
    }

    /**
     * Returns the article description
     *
     * @return string
     */
    public function getDescription(): string
    {
        if (isset($this->attributes['description'])) {
            return $this->attributes['description'];
        }

        return '';
    }

    /**
     * Returns the article unit price
     *
     * @return QUI\ERP\Money\Price
    public function getUnitPrice(): Price
        $unitPrice = 0;

        if (isset($this->attributes['unitPrice'])) {
            $unitPrice = $this->attributes['unitPrice'];
        return new Price($unitPrice, $this->Currency);
    /**
     * Returns the article unit price
     *
     * @return QUI\ERP\Money\Price
     */
    public function getUnitPriceUnRounded(): Price
        if (isset($this->attributes['nettoPriceNotRounded'])) {
            return new Price($this->attributes['nettoPriceNotRounded'], $this->Currency);
        }

        if ($this->nettoPriceNotRounded !== null) {
            return new Price($this->nettoPriceNotRounded, $this->Currency);
        if (isset($this->attributes['unitPrice'])) {
            $this->nettoPriceNotRounded = $this->attributes['unitPrice'];
        }

Henning Leutz's avatar
Henning Leutz committed
        return new Price($this->nettoPriceNotRounded, $this->Currency);
    }
     * @return QUI\ERP\Money\Price
    public function getSum(): Price
        return new Price($this->sum, $this->Currency);
     * Return the VAT for the article
     *
    public function getVat(): float | int
Henning Leutz's avatar
Henning Leutz committed
        if (isset($this->attributes['vat']) && $this->attributes['vat'] !== '') {
        // check if product exists and has a vat
        if (!empty($this->attributes['id'])) {
            try {
                $Area = null;

                if ($this->getUser()) {
                    $Area = QUI\ERP\Areas\Utils::getAreaByCountry($this->getUser()->getCountry());
                }

                if (!$Area) {
                    $Area = QUI\ERP\Defaults::getArea();
                }

                $Product = QUI\ERP\Products\Handler\Products::getProduct((int)$this->attributes['id']);
                $Vat = $Product->getField(QUI\ERP\Products\Handler\Fields::FIELD_VAT);
                $TaxType = new QUI\ERP\Tax\TaxType($Vat->getValue());
                $TaxEntry = TaxUtils::getTaxEntry($TaxType, $Area);

                return $TaxEntry->getValue();
            } catch (QUI\Exception $Exception) {
                QUI\System\Log::addDebug($Exception->getMessage());
            }
        }

        try {
            if ($this->getUser()) {
                return QUI\ERP\Tax\Utils::getTaxByUser($this->getUser())->getValue();
            }
            // return default vat
            $Area = QUI\ERP\Defaults::getArea();
            $TaxType = QUI\ERP\Tax\Utils::getTaxTypeByArea($Area);
            $TaxEntry = QUI\ERP\Tax\Utils::getTaxEntry($TaxType, $Area);
            return $TaxEntry->getValue();
        } catch (QUI\Exception $Exception) {
            QUI\System\Log::addCritical($Exception->getMessage());
            QUI\System\Log::writeException($Exception);

            return 0;
        }
     * @return null|QUI\Interfaces\Users\User
    public function getUser(): ?QUI\Interfaces\Users\User
        return $this->User;
    }

    /**
     * Set the user to the product, this user will be used for the calculation
     *
     * @param QUI\Interfaces\Users\User $User
     */
    public function setUser(QUI\Interfaces\Users\User $User): void
        $this->User = $User;
    /**
     * Return the currency of the article
     *
     * @return QUI\ERP\Currency\Currency|null
     */
    public function getCurrency(): ?QUI\ERP\Currency\Currency
    {
        return $this->Currency;
    }

    /**
     * Set the currency for the article
     *
     * @param QUI\ERP\Currency\Currency $Currency
     */
    public function setCurrency(QUI\ERP\Currency\Currency $Currency): void
    {
        $this->Currency = $Currency;
    }

    public function getQuantity(): float | int | bool
    {
        if (isset($this->attributes['quantity'])) {
            return $this->attributes['quantity'];
        }

        return 1;
    }

Henning Leutz's avatar
Henning Leutz committed
    /**
     * Returns the article quantity
     *
     * @param null|QUI\Locale $Locale
     * @return string
     */
    public function getQuantityUnit(null | QUI\Locale $Locale = null): string
Henning Leutz's avatar
Henning Leutz committed
    {
        if ($Locale === null) {
            $Locale = QUI::getLocale();
        }

        try {
            if (empty($this->attributes['quantityUnit']['id'])) {
                return '';
            }

Henning Leutz's avatar
Henning Leutz committed
            // @todo cache unit field entries
            $current = $Locale->getCurrent();
            $unitId = $this->attributes['quantityUnit']['id'];
Henning Leutz's avatar
Henning Leutz committed
            $UnitField = QUI\ERP\Products\Handler\Fields::getField(QUI\ERP\Products\Handler\Fields::FIELD_UNIT);
            $options = $UnitField->getOptions();
Henning Leutz's avatar
Henning Leutz committed

            if (isset($options['entries'][$unitId])) {
                $titles = $options['entries'][$unitId]['title'];

                if (!empty($titles[$current])) {
Henning Leutz's avatar
Henning Leutz committed
                    return $titles[$current];
                }
            }
Henning Leutz's avatar
Henning Leutz committed
        }

        return $this->attributes['quantityUnit']['title'];
    }

    /**
     * Return the price from the article
     *
     * @return Price
     */
    public function getPrice(): Price
        return new Price($this->sum, $this->Currency);
    public function displayPrice(): bool
    //region Discounts

     * Set a discount to the article
     *
     * @param float|int $discount
     * @param int $discountType - default = complement
     *
     * @todo überdenken, ganzer artikel ist eigentlich nicht änderbar
     */
    public function setDiscount(float | int $discount, int $discountType = Calc::CALCULATION_COMPLEMENT): void
    {
        switch ($discountType) {
            case Calc::CALCULATION_PERCENTAGE:
            case Calc::CALCULATION_COMPLEMENT:
                break;

            default:
                $discountType = Calc::CALCULATION_COMPLEMENT;
        }

        $this->Discount = new ArticleDiscount($discount, $discountType);
    }

    /**
     * Return the current discount
     *
     * @return ArticleDiscount|null
     */
    public function getDiscount(): ?ArticleDiscount
        $this->Discount?->setArticle($this);
        return $this->Discount;
    }

    /**
     * Has the article a discount
     *
     * @return bool
     */
    public function hasDiscount(): bool
    {
        return !!$this->getDiscount();
    }

    //endregion

    /**
     * @param null|Calc|QUI\ERP\User $Instance
    public function calc(null | Calc | QUI\ERP\User $Instance = null): Article
        if ($Instance instanceof QUI\ERP\User) {
            $Calc = Calc::getInstance($Instance);
        } elseif ($Instance instanceof Calc) {
            $Calc = $Instance;
        } else {
            $Calc = Calc::getInstance();
        if ($this->getUser()) {
            $Calc->setUser($this->getUser());
        }

        $Calc->calcArticlePrice($this, function ($data) {
            $self = $this;
            $self->price = $data['price'];
            $self->sum = $data['sum'];
            if (isset($data['nettoPriceNotRounded'])) {
                $self->nettoPriceNotRounded = $data['nettoPriceNotRounded'];
            $self->basisPrice = $data['basisPrice'];
            $self->nettoPrice = $data['nettoPrice'];
            $self->nettoBasisPrice = $data['nettoBasisPrice'];
            $self->nettoSubSum = $data['nettoSubSum'];
            $self->nettoSum = $data['nettoSum'];

            $self->vatArray = $data['vatArray'];
            $self->isEuVat = $data['isEuVat'];
            $self->isNetto = $data['isNetto'];

            $self->calculated = true;
        });

        return $this;
    }

    /**
     * Return the article as an array
     *
     * @return array
     */
    public function toArray(): array
        $vat = $this->getVat();
        $discount = '';
Henning Leutz's avatar
Henning Leutz committed
        if (isset($this->attributes['vat']) && $this->attributes['vat'] !== '') {
Henning Leutz's avatar
Henning Leutz committed
            $vat = floatval($this->attributes['vat']);
        if ($this->hasDiscount()) {
            $discount = $this->Discount->toJSON();
        }

Henning Leutz's avatar
Henning Leutz committed
        $class = get_called_class();

        if (!empty($this->attributes['control'])) {
            $class = $this->attributes['control'];
        }

Henning Leutz's avatar
Henning Leutz committed

        $quantityUnit = '';

        if (isset($this->attributes['quantityUnit'])) {
            $quantityUnit = $this->attributes['quantityUnit'];
        }


        return [
            // article data
            'id' => $this->getId(),
            'uuid' => $this->getUuid(),
            'productSetParentUuid' => $this->getProductSetParentUuid(),
            'title' => $this->getTitle(),
            'articleNo' => $this->getArticleNo(),
            'gtin' => $this->getGTIN(),
            'description' => $this->getDescription(),
            'unitPrice' => $this->getUnitPrice()->value(),
            'displayPrice' => $this->displayPrice(),
            'quantity' => $this->getQuantity(),
Henning Leutz's avatar
Henning Leutz committed
            'quantityUnit' => $quantityUnit,
            'sum' => $this->getSum()->value(),
            'vat' => $vat,
            'discount' => $discount,
            'control' => $this->attributes['control'],
            'class' => $class,
            'customFields' => $this->customFields,
            'customData' => $this->customData,
Henning Leutz's avatar
Henning Leutz committed
            'position' => $this->position,
            // calculated data
            'calculated' => [
                'price' => $this->price,
                'basisPrice' => $this->basisPrice,
                'nettoPriceNotRounded' => $this->nettoPriceNotRounded,
                'sum' => $this->sum,
                'nettoPrice' => $this->nettoPrice,
                'nettoBasisPrice' => $this->nettoBasisPrice,
                'nettoSubSum' => $this->nettoSubSum,
                'nettoSum' => $this->nettoSum,
                'vatArray' => $this->vatArray,
                'isEuVat' => $this->isEuVat,
                'isNetto' => $this->isNetto
     * Return an article custom field
    public function getCustomField(string $key): mixed
    {
        if (isset($this->customFields[$key])) {
            return $this->customFields[$key];
        }

        return null;
    }

    /**
     * @return array
     */
    public function getCustomFields(): array
    public function getCustomData(): array