modules/CDev/Coupons/src/Model/Coupon.php line 55

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2011-present Qualiteam software Ltd. All rights reserved.
  4.  * See https://www.x-cart.com/license-agreement.html for license details.
  5.  */
  6. namespace CDev\Coupons\Model;
  7. use ApiPlatform\Core\Annotation as ApiPlatform;
  8. use CDev\Coupons\API\Endpoint\Coupon\DTO\CouponInput;
  9. use CDev\Coupons\API\Endpoint\Coupon\DTO\CouponOutput;
  10. use Doctrine\ORM\Mapping as ORM;
  11. /**
  12.  * Coupon
  13.  *
  14.  * @ORM\Entity
  15.  * @ORM\Table  (name="coupons",
  16.  *      indexes={
  17.  *          @ORM\Index (name="ce", columns={"code", "enabled"})
  18.  *      }
  19.  * )
  20.  *
  21.  * @ApiPlatform\ApiResource(
  22.  *     input=CouponInput::class,
  23.  *     output=CouponOutput::class,
  24.  *     itemOperations={
  25.  *          "get"={
  26.  *              "method"="GET",
  27.  *              "path"="/coupons/{id}.{_format}",
  28.  *              "identifiers"={"id"},
  29.  *              "requirements"={"id"="\d+"}
  30.  *          },
  31.  *          "put"={
  32.  *              "method"="PUT",
  33.  *              "path"="/coupons/{id}.{_format}",
  34.  *              "identifiers"={"id"},
  35.  *              "requirements"={"id"="\d+"}
  36.  *          },
  37.  *          "delete"={
  38.  *              "method"="DELETE",
  39.  *              "path"="/coupons/{id}.{_format}",
  40.  *              "identifiers"={"id"},
  41.  *              "requirements"={"id"="\d+"}
  42.  *          }
  43.  *     },
  44.  *     collectionOperations={
  45.  *          "get"={
  46.  *              "method"="GET",
  47.  *              "identifiers"={},
  48.  *              "path"="/coupons.{_format}"
  49.  *          },
  50.  *          "post"={
  51.  *              "method"="POST",
  52.  *              "identifiers"={},
  53.  *              "path"="/coupons.{_format}"
  54.  *          }
  55.  *     }
  56.  * )
  57.  */
  58. class Coupon extends \XLite\Model\AEntity
  59. {
  60.     /**
  61.      * Coupon types
  62.      */
  63.     public const TYPE_PERCENT  '%';
  64.     public const TYPE_ABSOLUTE '$';
  65.     /**
  66.      * Coupon validation error codes
  67.      */
  68.     public const ERROR_DISABLED      'disabled';
  69.     public const ERROR_EXPIRED       'expired';
  70.     public const ERROR_USES          'uses';
  71.     public const ERROR_TOTAL         'total';
  72.     public const ERROR_PRODUCT_CLASS 'product_class';
  73.     public const ERROR_MEMBERSHIP    'membership';
  74.     public const ERROR_SINGLE_USE    'singleUse';
  75.     public const ERROR_SINGLE_USE2   'singleUse2';
  76.     public const ERROR_CATEGORY      'category';
  77.     /**
  78.      * Product unique ID
  79.      *
  80.      * @var   integer
  81.      *
  82.      * @ORM\Id
  83.      * @ORM\GeneratedValue (strategy="AUTO")
  84.      * @ORM\Column         (type="integer", options={ "unsigned": true })
  85.      */
  86.     protected $id;
  87.     /**
  88.      * Code
  89.      *
  90.      * @var   string
  91.      *
  92.      * @ORM\Column (type="string", options={ "fixed": true }, length=16)
  93.      */
  94.     protected $code;
  95.     /**
  96.      * Enabled status
  97.      *
  98.      * @var   boolean
  99.      *
  100.      * @ORM\Column (type="boolean")
  101.      */
  102.     protected $enabled true;
  103.     /**
  104.      * Value
  105.      *
  106.      * @var   float
  107.      *
  108.      * @ORM\Column (type="decimal", precision=14, scale=4)
  109.      */
  110.     protected $value 0.0000;
  111.     /**
  112.      * Type
  113.      *
  114.      * @var   string
  115.      *
  116.      * @ORM\Column (type="string", options={ "fixed": true }, length=1)
  117.      */
  118.     protected $type self::TYPE_PERCENT;
  119.     /**
  120.      * Comment
  121.      *
  122.      * @var   string
  123.      *
  124.      * @ORM\Column (type="string", length=64)
  125.      */
  126.     protected $comment '';
  127.     /**
  128.      * Uses count
  129.      *
  130.      * @var   integer
  131.      *
  132.      * @ORM\Column (type="integer", options={ "unsigned": true })
  133.      */
  134.     protected $uses 0;
  135.     /**
  136.      * Date range (begin)
  137.      *
  138.      * @var   integer
  139.      *
  140.      * @ORM\Column (type="integer", options={ "unsigned": true })
  141.      */
  142.     protected $dateRangeBegin 0;
  143.     /**
  144.      * Date range (end)
  145.      *
  146.      * @var   integer
  147.      *
  148.      * @ORM\Column (type="integer", options={ "unsigned": true })
  149.      */
  150.     protected $dateRangeEnd 0;
  151.     /**
  152.      * Total range (begin)
  153.      *
  154.      * @var   float
  155.      *
  156.      * @ORM\Column (type="decimal", precision=14, scale=4)
  157.      */
  158.     protected $totalRangeBegin 0;
  159.     /**
  160.      * Total range (end)
  161.      *
  162.      * @var   float
  163.      *
  164.      * @ORM\Column (type="decimal", precision=14, scale=4)
  165.      */
  166.     protected $totalRangeEnd 0;
  167.     /**
  168.      * Uses limit
  169.      *
  170.      * @var   integer
  171.      *
  172.      * @ORM\Column (type="integer", options={ "unsigned": true })
  173.      */
  174.     protected $usesLimit 0;
  175.     /**
  176.      * Uses limit per user
  177.      *
  178.      * @var   integer
  179.      *
  180.      * @ORM\Column (type="integer", options={ "unsigned": true })
  181.      */
  182.     protected $usesLimitPerUser 0;
  183.     /**
  184.      * Flag: Can a coupon be used together with other coupons (false) or no (true)
  185.      *
  186.      * @var boolean
  187.      *
  188.      * @ORM\Column (type="boolean")
  189.      */
  190.     protected $singleUse false;
  191.     /**
  192.      * Flag: Coupon is used for specific products or not
  193.      *
  194.      * @var boolean
  195.      *
  196.      * @ORM\Column (type="boolean")
  197.      */
  198.     protected $specificProducts false;
  199.     /**
  200.      * Product classes
  201.      *
  202.      * @var   \Doctrine\Common\Collections\ArrayCollection
  203.      *
  204.      * @ORM\ManyToMany (targetEntity="XLite\Model\ProductClass", inversedBy="coupons")
  205.      * @ORM\JoinTable (name="product_class_coupons",
  206.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  207.      *      inverseJoinColumns={@ORM\JoinColumn (name="class_id", referencedColumnName="id", onDelete="CASCADE")}
  208.      * )
  209.      */
  210.     protected $productClasses;
  211.     /**
  212.      * Memberships
  213.      *
  214.      * @var   \Doctrine\Common\Collections\ArrayCollection
  215.      *
  216.      * @ORM\ManyToMany (targetEntity="XLite\Model\Membership", inversedBy="coupons")
  217.      * @ORM\JoinTable (name="membership_coupons",
  218.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  219.      *      inverseJoinColumns={@ORM\JoinColumn (name="membership_id", referencedColumnName="membership_id", onDelete="CASCADE")}
  220.      * )
  221.      */
  222.     protected $memberships;
  223.     /**
  224.      * Zones
  225.      *
  226.      * @var   \Doctrine\Common\Collections\ArrayCollection
  227.      *
  228.      * @ORM\ManyToMany (targetEntity="XLite\Model\Zone", inversedBy="coupons")
  229.      * @ORM\JoinTable (name="zone_coupons",
  230.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  231.      *      inverseJoinColumns={@ORM\JoinColumn (name="zone_id", referencedColumnName="zone_id", onDelete="CASCADE")}
  232.      * )
  233.      */
  234.     protected $zones;
  235.     /**
  236.      * Coupon products
  237.      *
  238.      * @var   \Doctrine\Common\Collections\ArrayCollection
  239.      *
  240.      * @ORM\OneToMany (targetEntity="CDev\Coupons\Model\CouponProduct", mappedBy="coupon", cascade={"persist"})
  241.      */
  242.     protected $couponProducts;
  243.     /**
  244.      * Used coupons
  245.      *
  246.      * @var   \Doctrine\Common\Collections\Collection
  247.      *
  248.      * @ORM\OneToMany (targetEntity="CDev\Coupons\Model\UsedCoupon", mappedBy="coupon")
  249.      */
  250.     protected $usedCoupons;
  251.     /**
  252.      * Categories
  253.      *
  254.      * @var   \Doctrine\Common\Collections\ArrayCollection
  255.      *
  256.      * @ORM\ManyToMany (targetEntity="XLite\Model\Category", inversedBy="coupons")
  257.      * @ORM\JoinTable (name="coupon_categories",
  258.      *      joinColumns={@ORM\JoinColumn (name="coupon_id", referencedColumnName="id", onDelete="CASCADE")},
  259.      *      inverseJoinColumns={@ORM\JoinColumn (name="category_id", referencedColumnName="category_id", onDelete="CASCADE")}
  260.      * )
  261.      */
  262.     protected $categories;
  263.     protected static $runtimeCacheForUsedCouponsCount = [];
  264.     /**
  265.      * Constructor
  266.      *
  267.      * @param array $data Entity properties OPTIONAL
  268.      */
  269.     public function __construct(array $data = [])
  270.     {
  271.         $this->productClasses = new \Doctrine\Common\Collections\ArrayCollection();
  272.         $this->memberships    = new \Doctrine\Common\Collections\ArrayCollection();
  273.         $this->zones    = new \Doctrine\Common\Collections\ArrayCollection();
  274.         $this->couponProducts    = new \Doctrine\Common\Collections\ArrayCollection();
  275.         $this->usedCoupons    = new \Doctrine\Common\Collections\ArrayCollection();
  276.         $this->categories     = new \Doctrine\Common\Collections\ArrayCollection();
  277.         parent::__construct($data);
  278.     }
  279.     /**
  280.      * Check - discount is absolute or not
  281.      *
  282.      * @return boolean
  283.      */
  284.     public function isAbsolute()
  285.     {
  286.         return $this->getType() === static::TYPE_ABSOLUTE;
  287.     }
  288.     /**
  289.      * Check - coupon is started
  290.      *
  291.      * @return boolean
  292.      */
  293.     public function isStarted()
  294.     {
  295.         return $this->getDateRangeBegin() === || $this->getDateRangeBegin() < \XLite\Core\Converter::time();
  296.     }
  297.     /**
  298.      * Check - coupon is expired or not
  299.      *
  300.      * @return boolean
  301.      */
  302.     public function isExpired()
  303.     {
  304.         return $this->getDateRangeEnd() && $this->getDateRangeEnd() < \XLite\Core\Converter::time();
  305.     }
  306.     /**
  307.      * Check coupon activity
  308.      *
  309.      * @param \XLite\Model\Order $order Order OPTIONAL
  310.      *
  311.      * @return boolean
  312.      */
  313.     public function isActive(\XLite\Model\Order $order null)
  314.     {
  315.         try {
  316.             $result $this->checkCompatibility($order);
  317.         } catch (\CDev\Coupons\Core\CompatibilityException $exception) {
  318.             $result false;
  319.         }
  320.         return $result;
  321.     }
  322.     /**
  323.      * Get public code
  324.      *
  325.      * @return string
  326.      */
  327.     public function getPublicCode()
  328.     {
  329.         return $this->getCode();
  330.     }
  331.     /**
  332.      * Get coupon public name
  333.      *
  334.      * @return string
  335.      */
  336.     public function getPublicName()
  337.     {
  338.         $suffix '';
  339.         if ($this->getType() === \CDev\Coupons\Model\Coupon::TYPE_PERCENT) {
  340.             $suffix sprintf('(%s%%)'$this->getValue());
  341.         }
  342.         return $this->getPublicCode() . ' ' $suffix;
  343.     }
  344.     // {{{ Amount
  345.     /**
  346.      * Get amount
  347.      *
  348.      * @param \XLite\Model\Order $order Order
  349.      *
  350.      * @return float
  351.      */
  352.     public function getAmount(\XLite\Model\Order $order)
  353.     {
  354.         $total $this->getOrderTotal($order);
  355.         return $this->isAbsolute()
  356.             ? min($total$this->getValue())
  357.             : ($total $this->getValue() / 100);
  358.     }
  359.     /**
  360.      * Get order total
  361.      *
  362.      * @param \XLite\Model\Order $order Order
  363.      *
  364.      * @return float
  365.      */
  366.     protected function getOrderTotal(\XLite\Model\Order $order)
  367.     {
  368.         return array_reduce($this->getValidOrderItems($order), static function ($carry$item) {
  369.             return $carry $item->getSubtotal();
  370.         }, 0);
  371.     }
  372.     /**
  373.      * Get order items which are valid for the coupon
  374.      *
  375.      * @param \XLite\Model\Order $order Order
  376.      *
  377.      * @return array
  378.      */
  379.     protected function getValidOrderItems($order)
  380.     {
  381.         return $order->getValidItemsByCoupon($this);
  382.     }
  383.     /**
  384.      * Is coupon valid for product
  385.      *
  386.      * @param \XLite\Model\Product $product Product
  387.      *
  388.      * @return boolean
  389.      */
  390.     public function isValidForProduct(\XLite\Model\Product $product)
  391.     {
  392.         $result true;
  393.         if (count($this->getProductClasses())) {
  394.             // Check product class
  395.             $result $product->getProductClass()
  396.                 && $this->getProductClasses()->contains($product->getProductClass());
  397.         }
  398.         if ($result && count($this->getCategories())) {
  399.             // Check categories
  400.             $result false;
  401.             foreach ($product->getCategories() as $category) {
  402.                 if ($this->getCategories()->contains($category)) {
  403.                     $result true;
  404.                     break;
  405.                 }
  406.             }
  407.         }
  408.         if ($result && $this->getSpecificProducts()) {
  409.             // Check product
  410.             $result in_array($product->getProductId(), $this->getApplicableProductIds());
  411.         }
  412.         return $result;
  413.     }
  414.     // }}}
  415.     /**
  416.      * Check coupon compatibility
  417.      *
  418.      * @param \XLite\Model\Order $order Order
  419.      *
  420.      * @throws \CDev\Coupons\Core\CompatibilityException
  421.      *
  422.      * @return boolean
  423.      */
  424.     public function checkCompatibility(\XLite\Model\Order $order null)
  425.     {
  426.         if (!$this->getEnabled()) {
  427.             $this->throwCompatibilityException(
  428.                 '',
  429.                 'Sorry, the coupon you entered is invalid. Make sure the coupon code is spelled correctly'
  430.             );
  431.         }
  432.         $this->checkDate();
  433.         $this->checkUsage();
  434.         if ($order) {
  435.             if ($order->getProfile()) {
  436.                 $this->checkPerUserUsage($order->getProfile(), $order->containsCoupon($this));
  437.             }
  438.             $this->checkConflictsWithCoupons($order);
  439.             $this->checkMembership($order);
  440.             $this->checkCategory($order);
  441.             $this->checkProductClass($order);
  442.             $this->checkProducts($order);
  443.             $this->checkOrderTotal($order);
  444.             $this->checkZone($order);
  445.         }
  446.         return true;
  447.     }
  448.     // {{{ Date
  449.     /**
  450.      * Check coupon dates
  451.      *
  452.      * @throws \CDev\Coupons\Core\CompatibilityException
  453.      *
  454.      * @return void
  455.      */
  456.     protected function checkDate()
  457.     {
  458.         if (!$this->isStarted()) {
  459.             $this->throwCompatibilityException(
  460.                 '',
  461.                 'Sorry, the coupon you entered is invalid. Make sure the coupon code is spelled correctly'
  462.             );
  463.         }
  464.         if ($this->isExpired()) {
  465.             $this->throwCompatibilityException(
  466.                 '',
  467.                 'Sorry, the coupon has expired'
  468.             );
  469.         }
  470.     }
  471.     // }}}
  472.     // {{{ Usage
  473.     /**
  474.      * Check coupon usages
  475.      *
  476.      * @throws \CDev\Coupons\Core\CompatibilityException
  477.      *
  478.      * @return void
  479.      */
  480.     protected function checkUsage()
  481.     {
  482.         if ($this->getUsesLimit() && $this->getUsesLimit() <= $this->getUses()) {
  483.             $this->throwCompatibilityException(
  484.                 '',
  485.                 'Sorry, the coupon use limit has been reached'
  486.             );
  487.         }
  488.     }
  489.     /**
  490.      * Check coupon usages per user
  491.      *
  492.      * @throws \CDev\Coupons\Core\CompatibilityException
  493.      *
  494.      * @return void
  495.      */
  496.     protected function checkPerUserUsage(\XLite\Model\Profile $profile$inOrder)
  497.     {
  498.         if (>= $this->getUsesLimitPerUser()) {
  499.             return;
  500.         }
  501.         $profileUsesCount null;
  502.         if (array_key_exists($profile->getLogin(), static::$runtimeCacheForUsedCouponsCount)) {
  503.             $profileUsesCount = static::$runtimeCacheForUsedCouponsCount[$profile->getLogin()];
  504.         } else {
  505.             $profileUsesCount $this->calculatePerUserUsage($profile);
  506.             static::$runtimeCacheForUsedCouponsCount[$profile->getLogin()] = $profileUsesCount;
  507.         }
  508.         if ($inOrder) {
  509.             $profileUsesCount -= 1;
  510.         }
  511.         if ($this->getUsesLimitPerUser() <= $profileUsesCount) {
  512.             $this->throwCompatibilityException(
  513.                 '',
  514.                 'Sorry, the coupon use limit has been reached'
  515.             );
  516.         }
  517.     }
  518.     /**
  519.      * @param \XLite\Model\Profile $profile
  520.      *
  521.      * @return int
  522.      */
  523.     protected function calculatePerUserUsage(\XLite\Model\Profile $profile)
  524.     {
  525.         return $this->getUsedCoupons()->filter(
  526.             static function ($usedCoupon) use ($profile) {
  527.                 /** @var \CDev\Coupons\Model\UsedCoupon $usedCoupon */
  528.                 $orderProfileIdentificator $usedCoupon->getOrder()->getProfile()
  529.                     ? $usedCoupon->getOrder()->getProfile()->getLogin()
  530.                     : null;
  531.                 $currentProfileIdentificator $profile->getLogin();
  532.                 return $orderProfileIdentificator
  533.                     && $currentProfileIdentificator
  534.                     && $orderProfileIdentificator === $currentProfileIdentificator;
  535.             }
  536.         )->count();
  537.     }
  538.     // }}}
  539.     // {{{ Coupons conflicts
  540.     /**
  541.      * Check if coupon is unique within an order
  542.      *
  543.      * @param \XLite\Model\Order $order Order
  544.      *
  545.      * @throws \CDev\Coupons\Core\CompatibilityException
  546.      *
  547.      * @return boolean
  548.      */
  549.     public function checkUnique(\XLite\Model\Order $order)
  550.     {
  551.         if ($order->containsCoupon($this)) {
  552.             $this->throwCompatibilityException(
  553.                 '',
  554.                 'You have already used the coupon'
  555.             );
  556.         }
  557.         return true;
  558.     }
  559.     /**
  560.      * Check coupon usages
  561.      *
  562.      * @param \XLite\Model\Order $order Order
  563.      *
  564.      * @throws \CDev\Coupons\Core\CompatibilityException
  565.      *
  566.      * @return void
  567.      */
  568.     protected function checkConflictsWithCoupons(\XLite\Model\Order $order)
  569.     {
  570.         if (!$order->containsCoupon($this)) {
  571.             if ($this->getSingleUse() && count($this->getOrderUsedCoupons($order))) {
  572.                 $this->throwCompatibilityException(
  573.                     static::ERROR_SINGLE_USE,
  574.                     'This coupon cannot be combined with other coupons'
  575.                 );
  576.             }
  577.             if (!$this->getSingleUse() && $this->hasOrderSingleCoupon($order)) {
  578.                 $this->throwCompatibilityException(
  579.                     static::ERROR_SINGLE_USE2,
  580.                     'Sorry, this coupon cannot be combined with the coupon already applied. Revome the previously applied coupon and try again.'
  581.                 );
  582.             }
  583.         }
  584.     }
  585.     /**
  586.      * @param \XLite\Model\Order $order
  587.      *
  588.      * @return array
  589.      */
  590.     protected function getOrderUsedCoupons($order)
  591.     {
  592.         return $order->getUsedCoupons();
  593.     }
  594.     /**
  595.      * @param \XLite\Model\Order $order
  596.      *
  597.      * @return bool
  598.      */
  599.     protected function hasOrderSingleCoupon($order)
  600.     {
  601.         return $order->hasSingleUseCoupon();
  602.     }
  603.     // }}}
  604.     // {{{ Total
  605.     /**
  606.      * Check order total
  607.      *
  608.      * @param \XLite\Model\Order $order Order
  609.      *
  610.      * @throws \CDev\Coupons\Core\CompatibilityException
  611.      *
  612.      * @return void
  613.      */
  614.     protected function checkOrderTotal(\XLite\Model\Order $order)
  615.     {
  616.         $total $this->getOrderTotal($order);
  617.         $currency $order->getCurrency();
  618.         $rangeBegin $this->getTotalRangeBegin();
  619.         $rangeEnd $this->getTotalRangeEnd();
  620.         $betweenTotalCoupon $rangeBegin && $rangeEnd 0;
  621.         $rangeBeginValid $rangeBegin === 0.0 || $rangeBegin <= $total;
  622.         $rangeEndValid $rangeEnd === 0.0 || $rangeEnd >= $total;
  623.         $hasProductsConditions $this->getSpecificProducts()
  624.             || count($this->getCategories()) > 0
  625.             || count($this->getProductClasses()) > 0;
  626.         if ($betweenTotalCoupon && (!$rangeBeginValid || !$rangeEndValid)) {
  627.             $text $hasProductsConditions
  628.                 $this->getBetweenSubtotalConditionalExceptionText()
  629.                 : $this->getBetweenSubtotalExceptionText();
  630.             $this->throwCompatibilityException(
  631.                 static::ERROR_TOTAL,
  632.                 $text,
  633.                 [
  634.                     'min' => implode(''$currency->formatParts($rangeBegin)),
  635.                     'max' => implode(''$currency->formatParts($rangeEnd)),
  636.                 ]
  637.             );
  638.         } elseif (!$rangeBeginValid) {
  639.             $text $hasProductsConditions
  640.                 $this->getLeastSubtotalConditionalExceptionText()
  641.                 : $this->getLeastSubtotalExceptionText();
  642.             $this->throwCompatibilityException(
  643.                 static::ERROR_TOTAL,
  644.                 $text,
  645.                 [
  646.                     'min' => implode(''$currency->formatParts($rangeBegin))
  647.                 ]
  648.             );
  649.         } elseif (!$rangeEndValid) {
  650.             $text $hasProductsConditions
  651.                 $this->getExceedSubtotalConditionalExceptionText()
  652.                 : $this->getExceedSubtotalExceptionText();
  653.             $this->throwCompatibilityException(
  654.                 static::ERROR_TOTAL,
  655.                 $text,
  656.                 [
  657.                     'max' => implode(''$currency->formatParts($rangeEnd))
  658.                 ]
  659.             );
  660.         }
  661.     }
  662.     /**
  663.      * Return text of exception
  664.      *
  665.      * @return void
  666.      */
  667.     protected function getBetweenSubtotalExceptionText()
  668.     {
  669.         return 'To use the coupon, your order subtotal must be between X and Y';
  670.     }
  671.     /**
  672.      * Return text of exception
  673.      *
  674.      * @return void
  675.      */
  676.     protected function getLeastSubtotalExceptionText()
  677.     {
  678.         return 'To use the coupon, your order subtotal must be at least X';
  679.     }
  680.     /**
  681.      * Return text of exception
  682.      *
  683.      * @return void
  684.      */
  685.     protected function getExceedSubtotalExceptionText()
  686.     {
  687.         return 'To use the coupon, your order subtotal must not exceed Y';
  688.     }
  689.     /**
  690.      * Return text of exception
  691.      *
  692.      * @return void
  693.      */
  694.     protected function getBetweenSubtotalConditionalExceptionText()
  695.     {
  696.         return 'To use the coupon, your order subtotal must be between X and Y for specific products';
  697.     }
  698.     /**
  699.      * Return text of exception
  700.      *
  701.      * @return void
  702.      */
  703.     protected function getLeastSubtotalConditionalExceptionText()
  704.     {
  705.         return 'To use the coupon, your order subtotal must be at least X for specific products';
  706.     }
  707.     /**
  708.      * Return text of exception
  709.      *
  710.      * @return void
  711.      */
  712.     protected function getExceedSubtotalConditionalExceptionText()
  713.     {
  714.         return 'To use the coupon, your order subtotal must not exceed Y for specific products';
  715.     }
  716.     // }}}
  717.     // {{{ Category
  718.     /**
  719.      * Check coupon category
  720.      *
  721.      * @param \XLite\Model\Order $order Order
  722.      *
  723.      * @throws \CDev\Coupons\Core\CompatibilityException
  724.      *
  725.      * @return void
  726.      */
  727.     protected function checkCategory(\XLite\Model\Order $order)
  728.     {
  729.         if ($this->getCategories()->count()) {
  730.             $found false;
  731.             foreach ($order->getItems() as $item) {
  732.                 foreach ($item->getProduct()->getCategories() as $category) {
  733.                     if ($this->getCategories()->contains($category)) {
  734.                         $found true;
  735.                         break;
  736.                     }
  737.                 }
  738.                 if ($found) {
  739.                     break;
  740.                 }
  741.             }
  742.             if (!$found) {
  743.                 $this->throwCompatibilityException(
  744.                     '',
  745.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  746.                 );
  747.             }
  748.         }
  749.     }
  750.     // }}}
  751.     // {{{ Products
  752.     /**
  753.      * Check coupon products
  754.      *
  755.      * @param \XLite\Model\Order $order Order
  756.      *
  757.      * @throws \CDev\Coupons\Core\CompatibilityException
  758.      *
  759.      * @return void
  760.      */
  761.     protected function checkProducts(\XLite\Model\Order $order)
  762.     {
  763.         if ($this->getSpecificProducts()) {
  764.             $applicableProductIds $this->getApplicableProductIds();
  765.             $found false;
  766.             foreach ($order->getItems() as $item) {
  767.                 if (in_array($item->getProduct()->getProductId(), $applicableProductIds)) {
  768.                     $found true;
  769.                     break;
  770.                 }
  771.             }
  772.             if (!$found) {
  773.                 $this->throwCompatibilityException(
  774.                     '',
  775.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  776.                 );
  777.             }
  778.         }
  779.     }
  780.     // }}}
  781.     // {{{ Membership
  782.     /**
  783.      * Check coupon membership
  784.      *
  785.      * @param \XLite\Model\Order $order Order
  786.      *
  787.      * @throws \CDev\Coupons\Core\CompatibilityException
  788.      *
  789.      * @return void
  790.      */
  791.     protected function checkMembership(\XLite\Model\Order $order)
  792.     {
  793.         if (
  794.             $this->getMemberships()->count()
  795.             && (!$order->getProfile()
  796.                 || !$this->getMemberships()->contains($order->getProfile()->getMembership())
  797.             )
  798.         ) {
  799.             $this->throwCompatibilityException(
  800.                 '',
  801.                 'Sorry, the coupon you entered is not valid for your membership level. Contact the administrator'
  802.             );
  803.         }
  804.     }
  805.     // }}}
  806.     // {{{ Zone
  807.     /**
  808.      * Check coupon zone
  809.      *
  810.      * @param \XLite\Model\Order $order Order
  811.      *
  812.      * @throws \CDev\Coupons\Core\CompatibilityException
  813.      *
  814.      * @return void
  815.      */
  816.     protected function checkZone(\XLite\Model\Order $order)
  817.     {
  818.         $profile $order->getProfile();
  819.         $shippingAddress $profile $profile->getShippingAddress() : null;
  820.         if (!$shippingAddress) {
  821.             $shippingAddress \XLite\Model\Address::createDefaultShippingAddress();
  822.         }
  823.         if ($shippingAddress && !$this->getZones()->isEmpty()) {
  824.             $applicableZones \XLite\Core\Database::getRepo('XLite\Model\Zone')->findApplicableZones($shippingAddress->toArray());
  825.             $couponZoneIds array_map(static function ($zone) {
  826.                 return $zone->getZoneId();
  827.             }, $this->getZones()->toArray());
  828.             $isApplicable false;
  829.             foreach ($applicableZones as $zone) {
  830.                 if (in_array($zone->getZoneId(), $couponZoneIds)) {
  831.                     $isApplicable true;
  832.                     break;
  833.                 }
  834.             }
  835.             if (!$isApplicable) {
  836.                 $this->throwCompatibilityException(
  837.                     '',
  838.                     'Sorry, the coupon you entered cannot be applied to this delivery address'
  839.                 );
  840.             }
  841.         }
  842.     }
  843.     // }}}
  844.     // {{{ Product class
  845.     /**
  846.      * Check coupon product class
  847.      *
  848.      * @param \XLite\Model\Order $order Order
  849.      *
  850.      * @throws \CDev\Coupons\Core\CompatibilityException
  851.      *
  852.      * @return void
  853.      */
  854.     protected function checkProductClass(\XLite\Model\Order $order)
  855.     {
  856.         if ($this->getProductClasses()->count()) {
  857.             $found false;
  858.             foreach ($order->getItems() as $item) {
  859.                 if (
  860.                     $item->getProduct()->getProductClass()
  861.                     && $this->getProductClasses()->contains($item->getProduct()->getProductClass())
  862.                 ) {
  863.                     $found true;
  864.                     break;
  865.                 }
  866.             }
  867.             if (!$found) {
  868.                 $this->throwCompatibilityException(
  869.                     '',
  870.                     'Sorry, the coupon you entered cannot be applied to the items in your cart'
  871.                 );
  872.             }
  873.         }
  874.     }
  875.     // }}}
  876.     /**
  877.      * Throws exception
  878.      *
  879.      * @param string $code    Message params
  880.      * @param string $message Message text
  881.      * @param array  $params  Message params
  882.      *
  883.      * @throws \CDev\Coupons\Core\CompatibilityException
  884.      *
  885.      * @return void
  886.      */
  887.     protected function throwCompatibilityException($code ''$message null, array $params = [])
  888.     {
  889.         throw new \CDev\Coupons\Core\CompatibilityException($message$params$this$code);
  890.     }
  891.     /**
  892.      * Get id
  893.      *
  894.      * @return integer
  895.      */
  896.     public function getId()
  897.     {
  898.         return $this->id;
  899.     }
  900.     /**
  901.      * Set code
  902.      *
  903.      * @param string $code
  904.      * @return Coupon
  905.      */
  906.     public function setCode($code)
  907.     {
  908.         $this->code $code;
  909.         return $this;
  910.     }
  911.     /**
  912.      * Get code
  913.      *
  914.      * @return string
  915.      */
  916.     public function getCode()
  917.     {
  918.         return $this->code;
  919.     }
  920.     /**
  921.      * Set enabled
  922.      *
  923.      * @param boolean $enabled
  924.      * @return Coupon
  925.      */
  926.     public function setEnabled($enabled)
  927.     {
  928.         $this->enabled = (bool)$enabled;
  929.         return $this;
  930.     }
  931.     /**
  932.      * Get enabled
  933.      *
  934.      * @return boolean
  935.      */
  936.     public function getEnabled()
  937.     {
  938.         return $this->enabled;
  939.     }
  940.     /**
  941.      * Set value
  942.      *
  943.      * @param float $value
  944.      * @return Coupon
  945.      */
  946.     public function setValue($value)
  947.     {
  948.         $this->value $value;
  949.         return $this;
  950.     }
  951.     /**
  952.      * Get value
  953.      *
  954.      * @return float
  955.      */
  956.     public function getValue()
  957.     {
  958.         return $this->value;
  959.     }
  960.     /**
  961.      * Set type
  962.      *
  963.      * @param string $type
  964.      * @return Coupon
  965.      */
  966.     public function setType($type)
  967.     {
  968.         $this->type $type;
  969.         return $this;
  970.     }
  971.     /**
  972.      * Get type
  973.      *
  974.      * @return string
  975.      */
  976.     public function getType()
  977.     {
  978.         return $this->type;
  979.     }
  980.     /**
  981.      * Set comment
  982.      *
  983.      * @param string $comment
  984.      * @return Coupon
  985.      */
  986.     public function setComment($comment)
  987.     {
  988.         $this->comment $comment;
  989.         return $this;
  990.     }
  991.     /**
  992.      * Get comment
  993.      *
  994.      * @return string
  995.      */
  996.     public function getComment()
  997.     {
  998.         return $this->comment;
  999.     }
  1000.     /**
  1001.      * Set uses
  1002.      *
  1003.      * @param integer $uses
  1004.      * @return Coupon
  1005.      */
  1006.     public function setUses($uses)
  1007.     {
  1008.         $this->uses $uses;
  1009.         return $this;
  1010.     }
  1011.     /**
  1012.      * Get uses
  1013.      *
  1014.      * @return integer
  1015.      */
  1016.     public function getUses()
  1017.     {
  1018.         return $this->uses;
  1019.     }
  1020.     /**
  1021.      * Set dateRangeBegin
  1022.      *
  1023.      * @param integer $dateRangeBegin
  1024.      * @return Coupon
  1025.      */
  1026.     public function setDateRangeBegin($dateRangeBegin)
  1027.     {
  1028.         $this->dateRangeBegin $dateRangeBegin;
  1029.         return $this;
  1030.     }
  1031.     /**
  1032.      * Get dateRangeBegin
  1033.      *
  1034.      * @return integer
  1035.      */
  1036.     public function getDateRangeBegin()
  1037.     {
  1038.         return $this->dateRangeBegin;
  1039.     }
  1040.     /**
  1041.      * Set dateRangeEnd
  1042.      *
  1043.      * @param integer $dateRangeEnd
  1044.      * @return Coupon
  1045.      */
  1046.     public function setDateRangeEnd($dateRangeEnd)
  1047.     {
  1048.         $this->dateRangeEnd $dateRangeEnd;
  1049.         return $this;
  1050.     }
  1051.     /**
  1052.      * Get dateRangeEnd
  1053.      *
  1054.      * @return integer
  1055.      */
  1056.     public function getDateRangeEnd()
  1057.     {
  1058.         return $this->dateRangeEnd;
  1059.     }
  1060.     /**
  1061.      * Set totalRangeBegin
  1062.      *
  1063.      * @param float $totalRangeBegin
  1064.      * @return Coupon
  1065.      */
  1066.     public function setTotalRangeBegin($totalRangeBegin)
  1067.     {
  1068.         $this->totalRangeBegin $totalRangeBegin;
  1069.         return $this;
  1070.     }
  1071.     /**
  1072.      * Get totalRangeBegin
  1073.      *
  1074.      * @return float
  1075.      */
  1076.     public function getTotalRangeBegin()
  1077.     {
  1078.         return $this->totalRangeBegin;
  1079.     }
  1080.     /**
  1081.      * Set totalRangeEnd
  1082.      *
  1083.      * @param float $totalRangeEnd
  1084.      * @return Coupon
  1085.      */
  1086.     public function setTotalRangeEnd($totalRangeEnd)
  1087.     {
  1088.         $this->totalRangeEnd $totalRangeEnd;
  1089.         return $this;
  1090.     }
  1091.     /**
  1092.      * Get totalRangeEnd
  1093.      *
  1094.      * @return float
  1095.      */
  1096.     public function getTotalRangeEnd()
  1097.     {
  1098.         return $this->totalRangeEnd;
  1099.     }
  1100.     /**
  1101.      * Set usesLimit
  1102.      *
  1103.      * @param integer $usesLimit
  1104.      * @return Coupon
  1105.      */
  1106.     public function setUsesLimit($usesLimit)
  1107.     {
  1108.         $this->usesLimit $usesLimit;
  1109.         return $this;
  1110.     }
  1111.     /**
  1112.      * Get usesLimit
  1113.      *
  1114.      * @return integer
  1115.      */
  1116.     public function getUsesLimit()
  1117.     {
  1118.         return $this->usesLimit;
  1119.     }
  1120.     /**
  1121.      * Set usesLimitPerUser
  1122.      *
  1123.      * @param integer $usesLimitPerUser
  1124.      * @return Coupon
  1125.      */
  1126.     public function setUsesLimitPerUser($usesLimitPerUser)
  1127.     {
  1128.         $this->usesLimitPerUser $usesLimitPerUser;
  1129.         return $this;
  1130.     }
  1131.     /**
  1132.      * Get usesLimitPerUser
  1133.      *
  1134.      * @return integer
  1135.      */
  1136.     public function getUsesLimitPerUser()
  1137.     {
  1138.         return $this->usesLimitPerUser;
  1139.     }
  1140.     /**
  1141.      * Set singleUse
  1142.      *
  1143.      * @param boolean $singleUse
  1144.      * @return Coupon
  1145.      */
  1146.     public function setSingleUse($singleUse)
  1147.     {
  1148.         $this->singleUse $singleUse;
  1149.         return $this;
  1150.     }
  1151.     /**
  1152.      * Get singleUse
  1153.      *
  1154.      * @return boolean
  1155.      */
  1156.     public function getSingleUse()
  1157.     {
  1158.         return $this->singleUse;
  1159.     }
  1160.     /**
  1161.      * Set specificProducts
  1162.      *
  1163.      * @param boolean $specificProducts
  1164.      * @return Coupon
  1165.      */
  1166.     public function setSpecificProducts($specificProducts)
  1167.     {
  1168.         $this->specificProducts $specificProducts;
  1169.         return $this;
  1170.     }
  1171.     /**
  1172.      * Get specificProducts
  1173.      *
  1174.      * @return boolean
  1175.      */
  1176.     public function getSpecificProducts()
  1177.     {
  1178.         return $this->specificProducts;
  1179.     }
  1180.     /**
  1181.      * Add productClasses
  1182.      *
  1183.      * @param \XLite\Model\ProductClass $productClasses
  1184.      * @return Coupon
  1185.      */
  1186.     public function addProductClasses(\XLite\Model\ProductClass $productClasses)
  1187.     {
  1188.         $this->productClasses[] = $productClasses;
  1189.         return $this;
  1190.     }
  1191.     /**
  1192.      * Get productClasses
  1193.      *
  1194.      * @return \Doctrine\Common\Collections\Collection
  1195.      */
  1196.     public function getProductClasses()
  1197.     {
  1198.         return $this->productClasses;
  1199.     }
  1200.     /**
  1201.      * Clear product classes
  1202.      */
  1203.     public function clearProductClasses()
  1204.     {
  1205.         foreach ($this->getProductClasses()->getKeys() as $key) {
  1206.             $this->getProductClasses()->remove($key);
  1207.         }
  1208.     }
  1209.     /**
  1210.      * Add memberships
  1211.      *
  1212.      * @param \XLite\Model\Membership $memberships
  1213.      * @return Coupon
  1214.      */
  1215.     public function addMemberships(\XLite\Model\Membership $memberships)
  1216.     {
  1217.         $this->memberships[] = $memberships;
  1218.         return $this;
  1219.     }
  1220.     /**
  1221.      * Get memberships
  1222.      *
  1223.      * @return \Doctrine\Common\Collections\Collection
  1224.      */
  1225.     public function getMemberships()
  1226.     {
  1227.         return $this->memberships;
  1228.     }
  1229.     /**
  1230.      * Add coupon products
  1231.      *
  1232.      * @param \CDev\Coupons\Model\CouponProduct $couponProduct
  1233.      * @return Coupon
  1234.      */
  1235.     public function addCouponProducts(\CDev\Coupons\Model\CouponProduct $couponProduct)
  1236.     {
  1237.         $this->couponProducts[] = $couponProduct;
  1238.         return $this;
  1239.     }
  1240.     /**
  1241.      * Get product ids if coupon is specificProducts
  1242.      *
  1243.      * @return array
  1244.      */
  1245.     public function getApplicableProductIds()
  1246.     {
  1247.         $ids = [];
  1248.         if ($this->isPersistent() && $this->getSpecificProducts()) {
  1249.             $ids \XLite\Core\Database::getRepo('CDev\Coupons\Model\CouponProduct')
  1250.                 ->getCouponProductIds($this->getId());
  1251.         }
  1252.         return $ids;
  1253.     }
  1254.     /**
  1255.      * Get coupon products
  1256.      *
  1257.      * @return \Doctrine\Common\Collections\Collection
  1258.      */
  1259.     public function getCouponProducts()
  1260.     {
  1261.         return $this->couponProducts;
  1262.     }
  1263.     /**
  1264.      * Clear memberships
  1265.      */
  1266.     public function clearMemberships()
  1267.     {
  1268.         foreach ($this->getMemberships()->getKeys() as $key) {
  1269.             $this->getMemberships()->remove($key);
  1270.         }
  1271.     }
  1272.     /**
  1273.      * Add zones
  1274.      *
  1275.      * @param \XLite\Model\Zone $zone
  1276.      * @return Coupon
  1277.      */
  1278.     public function addZones(\XLite\Model\Zone $zone)
  1279.     {
  1280.         $this->zones[] = $zone;
  1281.         return $this;
  1282.     }
  1283.     /**
  1284.      * Get zones
  1285.      *
  1286.      * @return \Doctrine\Common\Collections\ArrayCollection
  1287.      */
  1288.     public function getZones()
  1289.     {
  1290.         return $this->zones;
  1291.     }
  1292.     /**
  1293.      * Clear zones
  1294.      */
  1295.     public function clearZones()
  1296.     {
  1297.         foreach ($this->getZones()->getKeys() as $key) {
  1298.             $this->getZones()->remove($key);
  1299.         }
  1300.     }
  1301.     /**
  1302.      * Add usedCoupons
  1303.      *
  1304.      * @param \CDev\Coupons\Model\UsedCoupon $usedCoupons
  1305.      * @return Coupon
  1306.      */
  1307.     public function addUsedCoupons(\CDev\Coupons\Model\UsedCoupon $usedCoupons)
  1308.     {
  1309.         $this->usedCoupons[] = $usedCoupons;
  1310.         return $this;
  1311.     }
  1312.     /**
  1313.      * Get usedCoupons
  1314.      *
  1315.      * @return \Doctrine\Common\Collections\Collection|\CDev\Coupons\Model\UsedCoupon[]
  1316.      */
  1317.     public function getUsedCoupons()
  1318.     {
  1319.         return $this->usedCoupons;
  1320.     }
  1321.     /**
  1322.      * Add categories
  1323.      *
  1324.      * @param \XLite\Model\Category $categories
  1325.      * @return Coupon
  1326.      */
  1327.     public function addCategories(\XLite\Model\Category $categories)
  1328.     {
  1329.         $this->getCategories()->add($categories);
  1330.         return $this;
  1331.     }
  1332.     /**
  1333.      * Get categories
  1334.      *
  1335.      * @return \Doctrine\Common\Collections\Collection
  1336.      */
  1337.     public function getCategories()
  1338.     {
  1339.         return $this->categories;
  1340.     }
  1341.     /**
  1342.      * Clear categories
  1343.      */
  1344.     public function clearCategories()
  1345.     {
  1346.         foreach ($this->getCategories()->getKeys() as $key) {
  1347.             $this->getCategories()->remove($key);
  1348.         }
  1349.     }
  1350. }