Skip to content
Code-Schnipsel Gruppen Projekte
Discount.php 18,3 KiB
Newer Older
Henning Leutz's avatar
Henning Leutz committed
<?php

/**
 * This file contains QUI\ERP\Discount\Discount
 */
Henning Leutz's avatar
Henning Leutz committed
namespace QUI\ERP\Discount;

use QUI;
use QUI\Database\Exception;
Henning Leutz's avatar
Henning Leutz committed
use QUI\ERP\Order\OrderInterface;
use QUI\ERP\Products\Interfaces\PriceFactorInterface;
use QUI\ERP\Products\Interfaces\PriceFactorWithVatInterface;
use QUI\ERP\Products\Utils\PriceFactor;
Henning Leutz's avatar
Henning Leutz committed
use QUI\Users\User;
Henning Leutz's avatar
Henning Leutz committed
use QUI\Permissions\Permission;
Henning Leutz's avatar
Henning Leutz committed
use QUI\Utils\Security\Orthos;
Henning Leutz's avatar
Henning Leutz committed
use QUI\ERP\Areas\Utils as AreaUtils;
Henning Leutz's avatar
Henning Leutz committed

use function array_key_exists;
use function explode;
use function implode;
use function is_array;
use function is_numeric;
use function is_string;
use function strtotime;
use function time;

Henning Leutz's avatar
Henning Leutz committed
/**
 * Class Discount
 * @package QUI\ERP\Discount
 */
class Discount extends QUI\CRUD\Child
{
Henning Leutz's avatar
Henning Leutz committed
    /**
     * Discount constructor.
Henning Leutz's avatar
Henning Leutz committed
     * @param int $id
     * @param Handler $Factory
     * @throws Exception
Henning Leutz's avatar
Henning Leutz committed
     */
    public function __construct($id, Handler $Factory)
    {
        parent::__construct($id, $Factory);
Henning Leutz's avatar
Henning Leutz committed
        parent::refresh();
Henning Leutz's avatar
Henning Leutz committed

        // attributes
        switch ($this->getAttribute('discount_type')) {
            case Handler::DISCOUNT_TYPE_CURRENCY:
            case Handler::DISCOUNT_TYPE_PERCENT:
                break;

            default:
                $this->setAttribute('discount_type', Handler::DISCOUNT_TYPE_PERCENT);
                break;
        }

        if ($this->getAttribute('consider_vat') === false) {
            $this->setAttribute('consider_vat', 'auto');
        }

        $scope = (int)$this->getAttribute('scope');

        switch ($scope) {
            case Handler::DISCOUNT_SCOPE_EVERY_PRODUCT:
            case Handler::DISCOUNT_SCOPE_TOTAL:
            case Handler::DISCOUNT_SCOPE_UNIQUE:
                $this->setAttribute('scope', $scope);
                break;

            default:
                $this->setAttribute('scope', Handler::DISCOUNT_SCOPE_TOTAL);
        }


        // cleanup user group save
        $cleanup = QUI\Utils\ArrayHelper::cleanup($this->getAttribute('user_groups'));
        $cleanup = implode(',', $cleanup);

        if (!empty($cleanup)) {
            $cleanup = ',' . $cleanup . ',';
        }

        $this->setAttribute('user_groups', $cleanup);


        // cleanup product(s)
        $cleanup = QUI\Utils\ArrayHelper::cleanup($this->getAttribute('articles'));
        $cleanup = implode(',', $cleanup);

        if (!empty($cleanup)) {
            $cleanup = ',' . $cleanup . ',';
        }

        $this->setAttribute('articles', $cleanup);


        // cleanup user group save
        $cleanup = QUI\Utils\ArrayHelper::cleanup($this->getAttribute('user_groups'));
        $cleanup = implode(',', $cleanup);

        if (!empty($cleanup)) {
            $cleanup = ',' . $cleanup . ',';
        }

        $this->setAttribute('user_groups', $cleanup);


        // cleanup product(s)
        $cleanup = QUI\Utils\ArrayHelper::cleanup($this->getAttribute('articles'));
        $cleanup = implode(',', $cleanup);

        if (!empty($cleanup)) {
            $cleanup = ',' . $cleanup . ',';
        }

        $this->setAttribute('articles', $cleanup);


Henning Leutz's avatar
Henning Leutz committed
        // events
Henning Leutz's avatar
Henning Leutz committed
        $this->Events->addEvent('onDeleteBegin', function () {
            Permission::checkPermission('quiqqer.areas.area.delete');
        });

        $this->Events->addEvent('onDeleteEnd', function () {
            QUI\Translator::delete(
                'quiqqer/discount',
                'discount.' . $this->getId() . '.title'
Henning Leutz's avatar
Henning Leutz committed
            );
        });

        $this->Events->addEvent('onSaveBegin', function () {
            Permission::checkPermission('quiqqer.areas.area.edit');

            if (
                $this->getAttribute('date_from')
Henning Leutz's avatar
Henning Leutz committed
                && !Orthos::checkMySqlDatetimeSyntax($this->getAttribute('date_from'))
            ) {
Henning Leutz's avatar
Henning Leutz committed
                throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                    'quiqqer/discount',
Henning Leutz's avatar
Henning Leutz committed
                    'exception.discount.date_from.wrong'
Henning Leutz's avatar
Henning Leutz committed
                ]);
            if (
                $this->getAttribute('date_until')
Henning Leutz's avatar
Henning Leutz committed
                && !Orthos::checkMySqlDatetimeSyntax($this->getAttribute('date_until'))
            ) {
Henning Leutz's avatar
Henning Leutz committed
                throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                    'quiqqer/discount',
Henning Leutz's avatar
Henning Leutz committed
                    'exception.discount.date_until.wrong'
Henning Leutz's avatar
Henning Leutz committed
                ]);
            $purchaseQuantityFrom = $this->getAttribute('purchase_quantity_from');
Henning Leutz's avatar
Henning Leutz committed
            $purchaseQuantityUntil = $this->getAttribute('purchase_quantity_until');
            $purchaseValueFrom = $this->getAttribute('purchase_value_from');
            $purchaseValueUntil = $this->getAttribute('purchase_value_until');
            if ($purchaseQuantityFrom !== '' && ($purchaseQuantityFrom === false || $purchaseQuantityFrom < 0)) {
Henning Leutz's avatar
Henning Leutz committed
                throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                    'quiqqer/discount',
                    'exception.discount.purchase_quantity_from.wrong'
Henning Leutz's avatar
Henning Leutz committed
                ]);
Henning Leutz's avatar
Henning Leutz committed
            }
            if ($purchaseQuantityUntil !== '' && ($purchaseQuantityUntil === false || $purchaseQuantityUntil < 0)) {
Henning Leutz's avatar
Henning Leutz committed
                throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                    'quiqqer/discount',
                    'exception.discount.purchase_quantity_until.wrong'
Henning Leutz's avatar
Henning Leutz committed
                ]);
            if ($purchaseValueFrom !== '' && ($purchaseValueFrom === false || $purchaseValueFrom < 0)) {
Henning Leutz's avatar
Henning Leutz committed
                throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                    'quiqqer/discount',
                    'exception.discount.purchase_value_from.wrong'
Henning Leutz's avatar
Henning Leutz committed
                ]);
            if ($purchaseValueUntil !== '' && ($purchaseValueUntil === false || $purchaseValueUntil < 0)) {
Henning Leutz's avatar
Henning Leutz committed
                throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                    'quiqqer/discount',
                    'exception.discount.purchase_value_until.wrong'
Henning Leutz's avatar
Henning Leutz committed
                ]);
Henning Leutz's avatar
Henning Leutz committed
            }

            // default nulls
            $attributes = [
                'discount',
                'usage_type',
                'discount_type',
                'date_from',
                'date_until',
                'price_calculation_basis',
                'purchase_quantity_from',
                'purchase_quantity_until',
                'purchase_value_from',
                'purchase_value_until',
                'areas',
                'articles',
                'categories',
                'user_groups',
                'combined',
                'priority',
                'scope',
                'lastSumDiscount',
                'lastProductDiscount',
            ];

            foreach ($attributes as $attribute) {
                if ($this->getAttribute($attribute) === '') {
                    $this->setAttribute($attribute, null);
                }
            }
Henning Leutz's avatar
Henning Leutz committed
    /**
     * @param string $name
     * @param mixed $value
     * @return void
Henning Leutz's avatar
Henning Leutz committed
     */
    public function setAttribute(string $name, mixed $value): void
Henning Leutz's avatar
Henning Leutz committed
    {
        if ($name === 'lastSumDiscount' && empty($value)) {
            $value = null;
        if ($name === 'lastProductDiscount' && empty($value)) {
            $value = null;
        if (
            $name === 'scope' ||
            $name === 'discount_type' ||
            $name === 'usage_type'
            $value = (int)$value;
        parent::setAttribute($name, $value);
Henning Leutz's avatar
Henning Leutz committed
    /**
Henning Leutz's avatar
Henning Leutz committed
     * Return the discount title
     *
     * @param null|QUI\Locale $Locale - optional, locale object
Henning Leutz's avatar
Henning Leutz committed
     * @return string
     */
    public function getTitle(null | QUI\Locale $Locale = null): string
Henning Leutz's avatar
Henning Leutz committed
    {
Henning Leutz's avatar
Henning Leutz committed
        if (!$Locale) {
            $Locale = QUI::getLocale();
        }

        return $Locale->get(
Henning Leutz's avatar
Henning Leutz committed
            'quiqqer/discount',
            'discount.' . $this->getId() . '.title'
Henning Leutz's avatar
Henning Leutz committed
        );
    }

    /**
     * Return the discount status
     *
     * @return boolean
     */
    public function isActive(): bool
Henning Leutz's avatar
Henning Leutz committed
    {
        return (bool)$this->getAttribute('active');
Henning Leutz's avatar
Henning Leutz committed
    }
Henning Leutz's avatar
Henning Leutz committed

Henning Leutz's avatar
Henning Leutz committed
    /**
     * Is the discount combinable with another discount?
     *
     * @param Discount $Discount
     * @return bool
     */
    public function canCombinedWith(Discount $Discount): bool
Henning Leutz's avatar
Henning Leutz committed
    {
        $combine = $this->getAttribute('combine');

        if (empty($combine)) {
            return false;
        }

        if (in_array($Discount->getId(), (array)$combine)) {
            return true;
Henning Leutz's avatar
Henning Leutz committed
        }

        return false;
    }

    /**
     * is the user allowed to use the discount
     *
Henning Leutz's avatar
Henning Leutz committed
     * @param QUI\Interfaces\Users\User $User
Henning Leutz's avatar
Henning Leutz committed
     * @return boolean
     */
    public function canUsedBy(QUI\Interfaces\Users\User $User): bool
Henning Leutz's avatar
Henning Leutz committed
    {
Henning Leutz's avatar
Henning Leutz committed
        if ($this->isActive() === false) {
            return false;
        }

        // usage definitions / limits
        $dateFrom = $this->getAttribute('date_from');
        $dateUntil = $this->getAttribute('date_until');
        $now = time();
        if ($dateFrom && strtotime($dateFrom) > $now) {
        if ($dateUntil && strtotime($dateUntil) < $now) {
Henning Leutz's avatar
Henning Leutz committed
        $userGroupValue = $this->getAttribute('user_groups');
        $areasValue = $this->getAttribute('areas');
        // if groups and areas are empty, everybody is allowed
Henning Leutz's avatar
Henning Leutz committed
        if (empty($userGroupValue) && empty($areasValue)) {
            return true;
        }

        // not in area
        if (!empty($areasValue) && !AreaUtils::isUserInAreas($User, $areasValue)) {
            return false;
        }

Henning Leutz's avatar
Henning Leutz committed
        $userGroups = QUI\Utils\UserGroups::parseUsersGroupsString(
Henning Leutz's avatar
Henning Leutz committed
            $this->getAttribute('user_groups')
        );

        $discountUsers = $userGroups['users'];
Henning Leutz's avatar
Henning Leutz committed
        $discountGroups = $userGroups['groups'];

        // user checking
        foreach ($discountUsers as $uid) {
            if ($User->getId() == $uid) {
                return true;
            }

            if ($User->getUUID() == $uid) {
                return true;
            }
Henning Leutz's avatar
Henning Leutz committed
        }

        // group checking
        $groupsOfUser = $User->getGroups();

        /* @var $Group QUI\Groups\Group */
        foreach ($discountGroups as $gid) {
            foreach ($groupsOfUser as $Group) {
                if ($Group->getId() == $gid) {
Henning Leutz's avatar
Henning Leutz committed
                    return true;
                }

                if ($Group->getUUID() == $gid) {
                    return true;
                }
    /**
     * is the discount usable with this product?
     *
     * @param QUI\ERP\Products\Interfaces\ProductInterface $Product
     * @return boolean
     */
    public function canUsedWith(QUI\ERP\Products\Interfaces\ProductInterface $Product): bool
        if ($Product->getId() === -1) {
        $articles = $this->getAttribute('articles');
        $categories = $this->getAttribute('categories');
        if (is_string($articles)) {
            $articles = explode(',', $articles);
        if (is_string($categories)) {
            $categories = explode(',', $categories);

        // article / product check
        if (empty($articles) && empty($categories)) {
            return true;
        }

        // article / product check
        if (is_array($articles)) {
            foreach ($articles as $articleId) {
                if ($Product->getId() === (int)$articleId) {
        if (empty($categories)) {
        if (!is_array($categories)) {
Henning Leutz's avatar
Henning Leutz committed
            return false;
        }

        foreach ($categories as $category) {
            $productCategories = $Product->getCategories();

            foreach ($productCategories as $Category) {
                /* @var $Category QUI\ERP\Products\Category\Category */
                if ($Category->getId() === (int)$category) {
Henning Leutz's avatar
Henning Leutz committed
    /**
     * @param QUI\ERP\ErpEntityInterface $Order
Henning Leutz's avatar
Henning Leutz committed
     * @return bool
     */
    public function canUsedInOrder(QUI\ERP\ErpEntityInterface $Order): bool
Henning Leutz's avatar
Henning Leutz committed
    {
        if ($this->isActive() === false) {
            return false;
        }

        if (!interface_exists('QUI\ERP\Order\OrderInterface')) {
            return false;
        }

        if (!($Order instanceof QUI\ERP\Order\OrderInterface)) {
            return false;
        }

Henning Leutz's avatar
Henning Leutz committed
        $Articles = $Order->getArticles();

        foreach ($Articles as $Article) {
            /* @var $Article QUI\ERP\Accounting\Article */
            $id = $Article->getId();

            if (!is_numeric($id)) {
Henning Leutz's avatar
Henning Leutz committed
                continue;
            }

            try {
                $Product = QUI\ERP\Products\Handler\Products::getProduct($id);

                if ($this->canUsedWith($Product)) {
                    return true;
                }
            } catch (QUI\Exception) {
Henning Leutz's avatar
Henning Leutz committed
                continue;
            }
        }

        return false;
    }

Henning Leutz's avatar
Henning Leutz committed
    /**
     * Verify the combination between the discounts
     *
     * @param Discount $Discount
     * @throws QUI\ERP\Discount\Exception
Henning Leutz's avatar
Henning Leutz committed
     */
    public function verifyCombinationWith(Discount $Discount): void
Henning Leutz's avatar
Henning Leutz committed
    {
        if ($this->canCombinedWith($Discount) === false) {
Henning Leutz's avatar
Henning Leutz committed
            throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                'quiqqer/discount',
                'exception.discount.not.combinable',
Henning Leutz's avatar
Henning Leutz committed
                [
                    'id' => $this->getId(),
Henning Leutz's avatar
Henning Leutz committed
                    'discountId' => $Discount->getId()
Henning Leutz's avatar
Henning Leutz committed
                ]
            ]);
Henning Leutz's avatar
Henning Leutz committed
        }
    }

    /**
     * Verify the usage of the discount by the user
     *
     * @param User $User
     * @throws QUI\ERP\Discount\Exception
Henning Leutz's avatar
Henning Leutz committed
     */
    public function verifyUser(User $User): void
Henning Leutz's avatar
Henning Leutz committed
    {
        if ($this->canUsedBy($User) === false) {
Henning Leutz's avatar
Henning Leutz committed
            throw new QUI\ERP\Discount\Exception([
Henning Leutz's avatar
Henning Leutz committed
                'quiqqer/discount',
                'exception.discount.user.cant.use.discount',
Henning Leutz's avatar
Henning Leutz committed
                [
                    'id' => $this->getId(),
                    'userId' => $User->getUUID()
Henning Leutz's avatar
Henning Leutz committed
                ]
            ]);

    /**
     * Parse the discount to a price factor
     *
     * @param null $Locale - optional, locale object
     * @param null $Customer - optional,
     * @return PriceFactorInterface|PriceFactorWithVatInterface|PriceFactor
    public function toPriceFactor(
        $Locale = null,
        $Customer = null
    ): QUI\ERP\Products\Interfaces\PriceFactorInterface | QUI\ERP\Products\Interfaces\PriceFactorWithVatInterface | QUI\ERP\Products\Utils\PriceFactor {
        switch ($this->getAttribute('discount_type')) {
            case QUI\ERP\Accounting\Calc::CALCULATION_PERCENTAGE:
                $calculation = QUI\ERP\Accounting\Calc::CALCULATION_PERCENTAGE;
            case QUI\ERP\Accounting\Calc::CALCULATION_COMPLEMENT:
                $calculation = QUI\ERP\Accounting\Calc::CALCULATION_COMPLEMENT;
        $basis = match ($this->getAttribute('price_calculation_basis')) {
            QUI\ERP\Accounting\Calc::CALCULATION_BASIS_NETTO => QUI\ERP\Accounting\Calc::CALCULATION_BASIS_NETTO,
            default => QUI\ERP\Accounting\Calc::CALCULATION_BASIS_CURRENTPRICE,
        };
        // check calculation basis VAT
        $useAuto = $this->getAttribute('consider_vat') === 'auto'
            && $Customer
            && QUI\ERP\Utils\User::isNettoUser($Customer) === false;

        if ($useAuto || $this->getAttribute('consider_vat') === 'brutto') {
            $basis = QUI\ERP\Accounting\Calc::CALCULATION_BASIS_VAT_BRUTTO;
        }

        if ($this->getAttribute('scope') === Handler::DISCOUNT_SCOPE_GRAND_TOTAL) {
            $basis = QUI\ERP\Accounting\Calc::CALCULATION_GRAND_TOTAL;
        }

        try {
            $Plugin = QUI::getPackage('quiqqer/products');
            $Config = $Plugin->getConfig();

            $hideDiscounts = (int)$Config->getValue('products', 'hideDiscounts');
        } catch (QUI\Exception) {
        if ($this->getAttribute('scope') === Handler::DISCOUNT_SCOPE_TOTAL) {
            if ($this->getAttribute('discount_type') === QUI\ERP\Accounting\Calc::CALCULATION_PERCENTAGE) {
                $valueText = false;
            } else {
                $valueText = $this->getTitle($Locale);
            }

Henning Leutz's avatar
Henning Leutz committed
            return new PriceFactor([
                'identifier' => 'discount-' . $this->getId(),
                'title' => $this->getTitle($Locale),
                'valueText' => $valueText,
                'description' => '',
                'priority' => (int)$this->getAttribute('priority'),
                'calculation' => $calculation,
                'basis' => $basis,
                'value' => $this->getAttribute('discount') * -1,
                'visible' => !$hideDiscounts,
                'vat' => $this->getAttribute('vat')
Henning Leutz's avatar
Henning Leutz committed
            ]);
        // to product
Henning Leutz's avatar
Henning Leutz committed
        return new QUI\ERP\Products\Utils\PriceFactor([
            'identifier' => 'discount-' . $this->getId(),
            'title' => $this->getTitle($Locale),
//            'valueText'   => $this->getTitle($Locale),
            'description' => '',
            'priority' => (int)$this->getAttribute('priority'),
            'calculation' => $calculation,
            'basis' => $basis,
            'value' => $this->getAttribute('discount') * -1,
Henning Leutz's avatar
Henning Leutz committed
        ]);

    /**
     * Update the CRUD child
     *
     * @throws QUI\ExceptionStack|QUI\Exception
     */
    public function update(): void
    {
        $this->Events->fireEvent('saveBegin');
        $this->Events->fireEvent('updateBegin');

        $needles = $this->Factory->getChildAttributes();
            if (!array_key_exists($needle, $this->attributes)) {
            if ($needle == 'user_groups') {
                if (!empty($value)) {
                    $value = ',' . $value . ',';
                }
            }

            $savedData[$needle] = $value;
        }

        QUI::getDataBase()->update(
            $this->Factory->getDataBaseTableName(),
            $savedData,
            ['id' => $this->getId()]
        );

        $this->Events->fireEvent('saveEnd');
        $this->Events->fireEvent('updateEnd');
    }
Henning Leutz's avatar
Henning Leutz committed
}