Skip to content
Code-Schnipsel Gruppen Projekte
CouponCode.php 16,2 KiB
Newer Older
Patrick Müller's avatar
Patrick Müller committed
<?php

namespace QUI\ERP\Coupons;

use function GuzzleHttp\Promise\queue;
Patrick Müller's avatar
Patrick Müller committed
use QUI;
use QUI\Permissions\Permission;
use QUI\ERP\Discount\Handler as DiscountHandler;
Patrick Müller's avatar
Patrick Müller committed

/**
Patrick Müller's avatar
Patrick Müller committed
 * Class CouponCode
Patrick Müller's avatar
Patrick Müller committed
 */
class CouponCode
{
    /**
Patrick Müller's avatar
Patrick Müller committed
     * CouponCode ID
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var int
     */
    protected $id;

    /**
     * Actual code
     *
     * @var string
     */
    protected $code;

    /**
Patrick Müller's avatar
Patrick Müller committed
     * IDs of users that this CouponCode is restricted to
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var int[]
Patrick Müller's avatar
Patrick Müller committed
     */
Patrick Müller's avatar
Patrick Müller committed
    protected $userIds = [];
Patrick Müller's avatar
Patrick Müller committed

    /**
Patrick Müller's avatar
Patrick Müller committed
     * IDs of groups that this CouponCode is restricted to
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var int[]
Patrick Müller's avatar
Patrick Müller committed
     */
Patrick Müller's avatar
Patrick Müller committed
    protected $groupIds = [];
Patrick Müller's avatar
Patrick Müller committed

    /**
     * IDs of all linked discounts
     *
     * @var int[]
     */
    protected $discountIds = [];

Patrick Müller's avatar
Patrick Müller committed
    /**
Patrick Müller's avatar
Patrick Müller committed
     * List of usages of this CouponCode
Patrick Müller's avatar
Patrick Müller committed
     *
Patrick Müller's avatar
Patrick Müller committed
     * @var array
Patrick Müller's avatar
Patrick Müller committed
     */
Patrick Müller's avatar
Patrick Müller committed
    protected $usages = [];
Patrick Müller's avatar
Patrick Müller committed

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Creation Date
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var \DateTime
     */
Patrick Müller's avatar
Patrick Müller committed
    protected $CreateDate;
Patrick Müller's avatar
Patrick Müller committed

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Date until the CouponCode is valid
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var \DateTime
     */
    protected $ValidUntilDate = null;

    /**
Patrick Müller's avatar
Patrick Müller committed
     * CouponCode title
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var string
     */
Patrick Müller's avatar
Patrick Müller committed
    protected $title = null;
Patrick Müller's avatar
Patrick Müller committed

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Flag - Is the CouponCode valid?
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var bool
     */
Patrick Müller's avatar
Patrick Müller committed
    protected $valid = true;
Patrick Müller's avatar
Patrick Müller committed

    /**
     * Max usages
Patrick Müller's avatar
Patrick Müller committed
     *
     * @var string
Patrick Müller's avatar
Patrick Müller committed
     */
    protected $maxUsages = Handler::MAX_USAGE_ONCE_PER_USER;
Patrick Müller's avatar
Patrick Müller committed

    /**
Patrick Müller's avatar
Patrick Müller committed
     * CouponCode constructor.
Patrick Müller's avatar
Patrick Müller committed
     *
     * @param int $id - Invite Code ID
Patrick Müller's avatar
Patrick Müller committed
     * @throws \QUI\ERP\Coupons\CouponCodeException
Patrick Müller's avatar
Patrick Müller committed
     */
    public function __construct($id)
    {
Patrick Müller's avatar
Patrick Müller committed
        $result = QUI::getDataBase()->fetch([
Patrick Müller's avatar
Patrick Müller committed
            'from'  => Handler::getTable(),
Patrick Müller's avatar
Patrick Müller committed
            'where' => [
Patrick Müller's avatar
Patrick Müller committed
                'id' => $id
Patrick Müller's avatar
Patrick Müller committed
            ]
        ]);
Patrick Müller's avatar
Patrick Müller committed

        if (empty($result)) {
Patrick Müller's avatar
Patrick Müller committed
            throw new CouponCodeException([
                'quiqqer/coupons',
                'exception.CouponCode.not_found',
                [
Patrick Müller's avatar
Patrick Müller committed
                    'id' => $id
Patrick Müller's avatar
Patrick Müller committed
                ]
            ], 404);
Patrick Müller's avatar
Patrick Müller committed
        }

        $data = current($result);

Patrick Müller's avatar
Patrick Müller committed
        $this->id    = (int)$data['id'];
        $this->code  = $data['code'];
        $this->title = $data['title'];
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
        if (!empty($data['usages'])) {
            $this->usages = json_decode($data['usages'], true);
Patrick Müller's avatar
Patrick Müller committed
        }

Patrick Müller's avatar
Patrick Müller committed
        if (!empty($data['userIds'])) {
            $this->userIds = json_decode($data['userIds'], true);
        }
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
        if (!empty($data['groupIds'])) {
            $this->groupIds = json_decode($data['groupIds'], true);
Patrick Müller's avatar
Patrick Müller committed
        }

        if (!empty($data['maxUsages'])) {
            $this->maxUsages = $data['maxUsages'];
Patrick Müller's avatar
Patrick Müller committed
        }

        if (!empty($data['discountIds'])) {
            $this->discountIds = json_decode($data['discountIds'], true);
        }

Patrick Müller's avatar
Patrick Müller committed
        $this->CreateDate = new \DateTime($data['createDate']);

Patrick Müller's avatar
Patrick Müller committed
        if (!empty($data['validUntilDate'])) {
            $this->ValidUntilDate = new \DateTime($data['validUntilDate']);
        }
Patrick Müller's avatar
Patrick Müller committed

        $this->checkValidity();
Patrick Müller's avatar
Patrick Müller committed
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getCode()
    {
        return $this->code;
    }

    /**
     * @return \DateTime
     */
    public function getCreateDate()
    {
        return $this->CreateDate;
    }

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Get usage data
     *
     * @return array
Patrick Müller's avatar
Patrick Müller committed
     */
Patrick Müller's avatar
Patrick Müller committed
    public function getUsages()
Patrick Müller's avatar
Patrick Müller committed
    {
Patrick Müller's avatar
Patrick Müller committed
        return $this->usages;
Patrick Müller's avatar
Patrick Müller committed
    }

    /**
     * @return \DateTime|null
     */
    public function getValidUntilDate()
    {
        return $this->ValidUntilDate;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @return int[]
     */
    public function getDiscountIds()
    {
        return $this->discountIds;
    }

    /**
     * Get all discounts associated with this CouponCode
     *
     * @return QUI\ERP\Discount\Discount[]
     */
    public function getDiscounts()
    {
        $discounts       = [];
        $DiscountHandler = DiscountHandler::getInstance();

        foreach ($this->discountIds as $discountId) {
            try {
                $discounts[] = $DiscountHandler->getChild($discountId);
            } catch (\Exception $Exception) {
                QUI\System\Log::writeDebugException($Exception);
            }
        }

        return $discounts;
    }

Patrick Müller's avatar
Patrick Müller committed
    /**
Patrick Müller's avatar
Patrick Müller committed
     * Redeems this CouponCode
     *
     * Hint: This may invalidate the code for future use
     *
     * @param QUI\Users\User $User - The user that redeems the CouponCode [if omitted use Session User]
     * @param QUI\ERP\Order\Order $Order (optional) - Link redemption to a specific Order
Patrick Müller's avatar
Patrick Müller committed
     * @return void
     * @throws CouponCodeException
Patrick Müller's avatar
Patrick Müller committed
     * @throws QUI\Exception
Patrick Müller's avatar
Patrick Müller committed
     */
    public function redeem($User = null, $Order = null)
Patrick Müller's avatar
Patrick Müller committed
    {
Patrick Müller's avatar
Patrick Müller committed
        if (is_null($User)) {
            $User = QUI::getUserBySession();
        }

        $this->checkRedemption($User);

        $Now = new \DateTime();

        $usage = [
            'userId'          => $User->getId(),
            'date'            => $Now->format('Y-m-d H:i:s'),
            'orderPrefixedId' => false
Patrick Müller's avatar
Patrick Müller committed
        ];

        if (!is_null($Order) && $Order instanceof QUI\ERP\Order\Order) {
            $usage['orderPrefixedId'] = $Order->getPrefixedId();
        }

        $this->usages[] = $usage;

Patrick Müller's avatar
Patrick Müller committed
        QUI::getDataBase()->update(
            Handler::getTable(),
            [
                'usages' => json_encode($this->usages)
            ],
            [
                'id' => $this->id
            ]
        );

        $this->checkValidity();
Patrick Müller's avatar
Patrick Müller committed

        QUI::getEvents()->fireEvent(
            'quiqqerCouponsRedeem',
            [
                'User'       => $User,
                'CouponCode' => $this
            ]
        );
Patrick Müller's avatar
Patrick Müller committed
    }

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Check if the given User can redeem this CouponCode
Patrick Müller's avatar
Patrick Müller committed
     *
     * @param QUI\Interfaces\Users\User $User - If omitted, use session user
Patrick Müller's avatar
Patrick Müller committed
     * @return void
Patrick Müller's avatar
Patrick Müller committed
     * @throws CouponCodeException - Thrown if not redeemable by the given User
Patrick Müller's avatar
Patrick Müller committed
     */
    public function checkRedemption($User)
Patrick Müller's avatar
Patrick Müller committed
    {
        if (!$this->isValid()) {
            throw new CouponCodeException([
                'quiqqer/coupons',
                'exception.CouponCode.no_longer_valid'
            ]);
        }

        $DiscountHandler = DiscountHandler::getInstance();
        $discountsValid  = false;
        $discountError   = false;

        foreach ($this->discountIds as $discountId) {
            try {
                /** @var QUI\ERP\Discount\Discount $Discount */
                $Discount = $DiscountHandler->getChild($discountId);
            } catch (\Exception $Exception) {
                $discountError = $Exception->getMessage();
                continue;
            }

            if ($Discount->canUsedBy($User)) {
                $discountsValid = true;
                break;
            }
        }

        if (!$discountsValid) {
            if (count($this->discountIds) === 1) {
                throw new CouponCodeException([
                    'quiqqer/coupons',
                    'exception.CouponCode.discount_invalid',
                    [
                        'reason' => $discountError
                    ]
                ]);
            } else {
                throw new CouponCodeException([
                    'quiqqer/coupons',
                    'exception.CouponCode.discounts_invalid'
                ]);
            }
        }

        // Max usage restrictions
        switch ($this->maxUsages) {
            case Handler::MAX_USAGE_ONCE_PER_USER:
                if ($this->hasUserRedeemed($User)) {
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.CouponCode.already_used'
                    ]);
                }
                break;

            case Handler::MAX_USAGE_ONCE:
                if (!empty($this->usages)) {
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.CouponCode.already_used'
                    ]);
                }
                break;
        }

        // Restriction to QUIQQER user(s)
Patrick Müller's avatar
Patrick Müller committed
        if (!empty($this->userIds)) {
            if (in_array($User->getId(), $this->userIds)) {
                if ($this->maxUsages !== Handler::MAX_USAGE_UNLIMITED
                    && $this->hasUserRedeemed($User)) {
Patrick Müller's avatar
Patrick Müller committed
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.CouponCode.already_used'
                    ]);
                }
            } else {
                throw new CouponCodeException([
                    'quiqqer/coupons',
                    'exception.CouponCode.user_not_allowed'
                ]);
            }
Patrick Müller's avatar
Patrick Müller committed
        }

        // Restriction to QUIQQER group(s)
Patrick Müller's avatar
Patrick Müller committed
        if (!empty($this->groupIds)) {
            $userInGroup = false;
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
            foreach ($this->groupIds as $groupId) {
                if ($User->isInGroup($groupId)) {
                    $userInGroup = true;
                    break;
                }
            }
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
            if ($userInGroup) {
                if ($this->maxUsages !== Handler::MAX_USAGE_UNLIMITED
                    && $this->hasUserRedeemed($User)) {
Patrick Müller's avatar
Patrick Müller committed
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.CouponCode.already_used'
                    ]);
                }
            } else {
                throw new CouponCodeException([
                    'quiqqer/coupons',
                    'exception.CouponCode.user_not_allowed_group'
                ]);
            }
        }
Patrick Müller's avatar
Patrick Müller committed
    }

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Check if the given User can redeem this CouponCode
Patrick Müller's avatar
Patrick Müller committed
     *
Patrick Müller's avatar
Patrick Müller committed
     * @param QUI\Users\User $User - If omitted, use session user
Patrick Müller's avatar
Patrick Müller committed
     * @return bool
     */
Patrick Müller's avatar
Patrick Müller committed
    public function isRedeemable($User = null)
Patrick Müller's avatar
Patrick Müller committed
    {
Patrick Müller's avatar
Patrick Müller committed
        try {
            $this->checkRedemption($User);
        } catch (CouponCodeException $Exception) {
            return false;
        }

        return true;
Patrick Müller's avatar
Patrick Müller committed
    }

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Check if this CouponCode is still valid
Patrick Müller's avatar
Patrick Müller committed
     *
     * @return bool
     */
Patrick Müller's avatar
Patrick Müller committed
    public function isValid()
Patrick Müller's avatar
Patrick Müller committed
    {
Patrick Müller's avatar
Patrick Müller committed
        return $this->valid;
Patrick Müller's avatar
Patrick Müller committed
    }

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Checks if an CouponCode has been redeemed by a user
Patrick Müller's avatar
Patrick Müller committed
     *
Henning Leutz's avatar
Henning Leutz committed
     * @param QUI\Interfaces\Users\User $User
Patrick Müller's avatar
Patrick Müller committed
     * @return bool
Patrick Müller's avatar
Patrick Müller committed
     */
Patrick Müller's avatar
Patrick Müller committed
    public function hasUserRedeemed($User)
Patrick Müller's avatar
Patrick Müller committed
    {
Patrick Müller's avatar
Patrick Müller committed
        $userId = $User->getId();
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
        foreach ($this->usages as $usage) {
            if ($usage['userId'] === $userId) {
                return true;
            }
Patrick Müller's avatar
Patrick Müller committed
        }

Patrick Müller's avatar
Patrick Müller committed
        return false;
Patrick Müller's avatar
Patrick Müller committed
    }

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Permanently delete this CouponCode
Patrick Müller's avatar
Patrick Müller committed
     *
     * @return void
     * @throws \QUI\Permissions\Exception
Patrick Müller's avatar
Patrick Müller committed
     */
    public function delete()
    {
        Permission::checkPermission(Handler::PERMISSION_DELETE);

        QUI::getDataBase()->delete(
            Handler::getTable(),
Patrick Müller's avatar
Patrick Müller committed
            [
Patrick Müller's avatar
Patrick Müller committed
                'id' => $this->id
Patrick Müller's avatar
Patrick Müller committed
            ]
Patrick Müller's avatar
Patrick Müller committed
        );
    }

    /**
Patrick Müller's avatar
Patrick Müller committed
     * Get CouponCode attributes as array
Patrick Müller's avatar
Patrick Müller committed
     *
     * @return array
     */
    public function toArray()
    {
Patrick Müller's avatar
Patrick Müller committed
        $data = [
Patrick Müller's avatar
Patrick Müller committed
            'id'             => $this->getId(),
            'code'           => $this->getCode(),
Patrick Müller's avatar
Patrick Müller committed
            'userIds'        => $this->userIds,
            'groupIds'       => $this->groupIds,
Patrick Müller's avatar
Patrick Müller committed
            'createDate'     => $this->getCreateDate()->format('Y-m-d H:i:s'),
Patrick Müller's avatar
Patrick Müller committed
            'usages'         => $this->usages,
Patrick Müller's avatar
Patrick Müller committed
            'validUntilDate' => false,
            'title'          => $this->getTitle() ?: false,
Patrick Müller's avatar
Patrick Müller committed
            'isValid'        => $this->isValid(),
            'maxUsages'      => $this->maxUsages,
            'discountIds'    => $this->discountIds
Patrick Müller's avatar
Patrick Müller committed
        ];
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
        $ValidUntilDate = $this->getValidUntilDate();
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
        if ($ValidUntilDate) {
            $data['validUntilDate'] = $ValidUntilDate->format('Y-m-d');
Patrick Müller's avatar
Patrick Müller committed
        }

Patrick Müller's avatar
Patrick Müller committed
        return $data;
    }
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
    /**
     * Checks if this CouponCode is still valid
     *
     * @return void
     */
    protected function checkValidity()
    {
        // Check if the expiration date has been reached
        if (!empty($this->ValidUntilDate)) {
            $Now = new \DateTime();
Patrick Müller's avatar
Patrick Müller committed

Patrick Müller's avatar
Patrick Müller committed
            if ($Now > $this->ValidUntilDate) {
                $this->valid = false;
                return;
            }
        }
Patrick Müller's avatar
Patrick Müller committed

        if ($this->maxUsages === Handler::MAX_USAGE_UNLIMITED) {
            return;
        }

        if ($this->maxUsages === Handler::MAX_USAGE_ONCE && !empty($this->usages)) {
            $this->valid = false;
Patrick Müller's avatar
Patrick Müller committed
            return;
Patrick Müller's avatar
Patrick Müller committed
        }

Patrick Müller's avatar
Patrick Müller committed
        // If the CouponCode is restricted to certain users -> Check if all those
        // users have already redeemed the code
        if (!empty($this->userIds)) {
            $usedByAllUsers = true;

            foreach ($this->userIds as $userId) {
                foreach ($this->usages as $usage) {
                    if ($userId == $usage['userId']) {
                        continue 2;
                    }
                }

                $usedByAllUsers = false;
                break;
            }

            if ($usedByAllUsers) {
                $this->valid = false;
Patrick Müller's avatar
Patrick Müller committed
                return;
            }
        }
    }

    /**
     * @param QUI\ERP\Order\OrderInProcess $Order
     * @throws QUI\Exception
     */
    public function addToOrder(QUI\ERP\Order\OrderInProcess $Order)
    {
        $coupons = $Order->getDataEntry('quiqqer-coupons');

        if (!$coupons) {
            return;
        }

        if (!is_array($coupons)) {
            return;
Patrick Müller's avatar
Patrick Müller committed
        }

        $priceFactors = [];
Henning Leutz's avatar
Henning Leutz committed
        $articles     = [];

        foreach ($coupons as $coupon) {
            /* @var $Coupon CouponCode */
            try {
                $Coupon = Handler::getCouponCodeByCode($coupon);
            } catch (\Exception $Exception) {
                continue;
            }

            // coupon check
            if (!$Coupon->isRedeemable($Order->getCustomer())) {
                continue;
            }

            /* @var $Discount QUI\ERP\Discount\Discount */
            $discounts = $Coupon->getDiscounts();

            foreach ($discounts as $Discount) {
                $PriceFactor = $Discount->toPriceFactor();
                $PriceFactor->setTitle(
                    QUI::getLocale()->get('quiqqer/coupons', 'coupon.discount.title', [
                        'code' => $Coupon->getCode()
                    ])
                );

                $priceFactors[] = $PriceFactor;
Henning Leutz's avatar
Henning Leutz committed

                $articles[] = new QUI\ERP\Accounting\Invoice\Articles\Text([
                    'id'          => '-',
                    'articleNo'   => $Coupon->getCode(),
                    'title'       => $PriceFactor->getTitle(),
                    'description' => '',
                    'unitPrice'   => 0,
                    'control'     => '',
                    'quantity'    => 1,
                    'customData'  => [
                        'package' => 'quiqqer/coupon',
                        'code'    => $Coupon->getCode()
                    ]
                ]);
Henning Leutz's avatar
Henning Leutz committed
        if (empty($priceFactors)) {
            return;
Henning Leutz's avatar
Henning Leutz committed

        /**
         * @param QUI\ERP\Accounting\Invoice\Articles\Text $Article
         * @return boolean
         */
        $isInArticles = function ($Article) use ($Order) {
            $articles = $Order->getArticles();
            $code     = $Article->getCustomData()['code'];

            foreach ($articles as $Entry) {
                if (!method_exists($Entry, 'getCustomData')) {
                    continue;
                }

                $customData = $Entry->getCustomData();

                if (!$customData || !is_array($customData)) {
                    continue;
                }

                if (!isset($customData['package']) || !isset($customData['code'])) {
                    continue;
                }

                return $customData['package'] === 'quiqqer/coupon'
                       && $customData['code'] === $code;
            }

            return false;
        };

        foreach ($articles as $Article) {
            /* @var $PriceFactor QUI\ERP\Accounting\Invoice\Articles\Text */
            if ($isInArticles($Article) === false) {
                $Order->addArticle($Article);
            }
        }

        $Order->update();
        $Order->addPriceFactors($priceFactors);
Patrick Müller's avatar
Patrick Müller committed
    }
}