<?php namespace QUI\ERP\Coupons; use QUI; use QUI\Permissions\Permission; use QUI\ERP\Discount\Handler as DiscountHandler; /** * Class CouponCode */ class CouponCode { /** * CouponCode ID * * @var int */ protected $id; /** * Actual code * * @var string */ protected $code; /** * IDs of users that this CouponCode is restricted to * * @var int[] */ protected $userIds = []; /** * IDs of groups that this CouponCode is restricted to * * @var int[] */ protected $groupIds = []; /** * IDs of all linked discounts * * @var int[] */ protected $discountIds = []; /** * List of usages of this CouponCode * * @var array */ protected $usages = []; /** * Creation Date * * @var \DateTime */ protected $CreateDate; /** * Date until the CouponCode is valid * * @var \DateTime */ protected $ValidUntilDate = null; /** * CouponCode title * * @var string */ protected $title = null; /** * Flag - Is the CouponCode valid? * * @var bool */ protected $valid = true; /** * Max usages * * @var string */ protected $maxUsages = Handler::MAX_USAGE_ONCE_PER_USER; /** * CouponCode constructor. * * @param int $id - Invite Code ID * @throws \QUI\ERP\Coupons\CouponCodeException */ public function __construct($id) { $result = QUI::getDataBase()->fetch([ 'from' => Handler::getTable(), 'where' => [ 'id' => $id ] ]); if (empty($result)) { throw new CouponCodeException([ 'quiqqer/coupons', 'exception.CouponCode.not_found', [ 'id' => $id ] ], 404); } $data = current($result); $this->id = (int)$data['id']; $this->code = $data['code']; $this->title = $data['title']; if (!empty($data['usages'])) { $this->usages = json_decode($data['usages'], true); } if (!empty($data['userIds'])) { $this->userIds = json_decode($data['userIds'], true); } if (!empty($data['groupIds'])) { $this->groupIds = json_decode($data['groupIds'], true); } if (!empty($data['maxUsages'])) { $this->maxUsages = $data['maxUsages']; } if (!empty($data['discountIds'])) { $this->discountIds = json_decode($data['discountIds'], true); } $this->CreateDate = new \DateTime($data['createDate']); if (!empty($data['validUntilDate'])) { $this->ValidUntilDate = new \DateTime($data['validUntilDate']); } $this->checkValidity(); } /** * @return int */ public function getId() { return $this->id; } /** * @return string */ public function getCode() { return $this->code; } /** * @return \DateTime */ public function getCreateDate() { return $this->CreateDate; } /** * Get usage data * * @return array */ public function getUsages() { return $this->usages; } /** * @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; } /** * 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 * @return void * @throws CouponCodeException * @throws QUI\Exception */ public function redeem($User = null, $Order = null) { 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 ]; if (!is_null($Order) && $Order instanceof QUI\ERP\Order\Order) { $usage['orderPrefixedId'] = $Order->getPrefixedId(); } $this->usages[] = $usage; QUI::getDataBase()->update( Handler::getTable(), [ 'usages' => json_encode($this->usages) ], [ 'id' => $this->id ] ); $this->checkValidity(); QUI::getEvents()->fireEvent( 'quiqqerCouponsRedeem', [ 'User' => $User, 'CouponCode' => $this ] ); } /** * Check if the given User can redeem this CouponCode * * @param QUI\Interfaces\Users\User $User - If omitted, use session user * @return void * @throws CouponCodeException - Thrown if not redeemable by the given User */ public function checkRedemption($User) { 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) if (!empty($this->userIds)) { if (in_array($User->getId(), $this->userIds)) { if ($this->maxUsages !== Handler::MAX_USAGE_UNLIMITED && $this->hasUserRedeemed($User)) { throw new CouponCodeException([ 'quiqqer/coupons', 'exception.CouponCode.already_used' ]); } } else { throw new CouponCodeException([ 'quiqqer/coupons', 'exception.CouponCode.user_not_allowed' ]); } } // Restriction to QUIQQER group(s) if (!empty($this->groupIds)) { $userInGroup = false; foreach ($this->groupIds as $groupId) { if ($User->isInGroup($groupId)) { $userInGroup = true; break; } } if ($userInGroup) { if ($this->maxUsages !== Handler::MAX_USAGE_UNLIMITED && $this->hasUserRedeemed($User)) { throw new CouponCodeException([ 'quiqqer/coupons', 'exception.CouponCode.already_used' ]); } } else { throw new CouponCodeException([ 'quiqqer/coupons', 'exception.CouponCode.user_not_allowed_group' ]); } } } /** * Check if the given User can redeem this CouponCode * * @param QUI\Users\User $User - If omitted, use session user * @return bool */ public function isRedeemable($User = null) { try { $this->checkRedemption($User); } catch (CouponCodeException $Exception) { return false; } return true; } /** * Check if this CouponCode is still valid * * @return bool */ public function isValid() { return $this->valid; } /** * Checks if an CouponCode has been redeemed by a user * * @param QUI\Interfaces\Users\User $User * @return bool */ public function hasUserRedeemed($User) { $userId = $User->getId(); foreach ($this->usages as $usage) { if ($usage['userId'] === $userId) { return true; } } return false; } /** * Permanently delete this CouponCode * * @return void * @throws \QUI\Permissions\Exception */ public function delete() { Permission::checkPermission(Handler::PERMISSION_DELETE); QUI::getDataBase()->delete( Handler::getTable(), [ 'id' => $this->id ] ); } /** * Get CouponCode attributes as array * * @return array */ public function toArray() { $data = [ 'id' => $this->getId(), 'code' => $this->getCode(), 'userIds' => $this->userIds, 'groupIds' => $this->groupIds, 'createDate' => $this->getCreateDate()->format('Y-m-d H:i:s'), 'usages' => $this->usages, 'validUntilDate' => false, 'title' => $this->getTitle() ?: false, 'isValid' => $this->isValid(), 'maxUsages' => $this->maxUsages, 'discountIds' => $this->discountIds ]; $ValidUntilDate = $this->getValidUntilDate(); if ($ValidUntilDate) { $data['validUntilDate'] = $ValidUntilDate->format('Y-m-d'); } return $data; } /** * 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(); if ($Now > $this->ValidUntilDate) { $this->valid = false; return; } } if ($this->maxUsages === Handler::MAX_USAGE_UNLIMITED) { return; } if ($this->maxUsages === Handler::MAX_USAGE_ONCE && !empty($this->usages)) { $this->valid = false; return; } // 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; return; } } elseif (!empty($this->usages)) { $this->valid = false; 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; } $priceFactors = []; $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; $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() ] ]); } } if (empty($priceFactors)) { return; } /** * @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); } }