core/lib/Thelia/Action/Coupon.php line 181

  1. <?php
  2. /*
  3.  * This file is part of the Thelia package.
  4.  * http://www.thelia.net
  5.  *
  6.  * (c) OpenStudio <info@thelia.net>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Thelia\Action;
  12. use Propel\Runtime\Propel;
  13. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  14. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  15. use Symfony\Component\HttpFoundation\RequestStack;
  16. use Symfony\Contracts\EventDispatcher\Event;
  17. use Thelia\Condition\ConditionCollection;
  18. use Thelia\Condition\ConditionFactory;
  19. use Thelia\Condition\Implementation\ConditionInterface;
  20. use Thelia\Condition\Implementation\MatchForEveryone;
  21. use Thelia\Core\Event\Coupon\CouponConsumeEvent;
  22. use Thelia\Core\Event\Coupon\CouponCreateOrUpdateEvent;
  23. use Thelia\Core\Event\Coupon\CouponDeleteEvent;
  24. use Thelia\Core\Event\Order\OrderEvent;
  25. use Thelia\Core\Event\TheliaEvents;
  26. use Thelia\Coupon\CouponFactory;
  27. use Thelia\Coupon\CouponManager;
  28. use Thelia\Coupon\Type\CouponInterface;
  29. use Thelia\Model\Coupon as CouponModel;
  30. use Thelia\Model\CouponCountry;
  31. use Thelia\Model\CouponCountryQuery;
  32. use Thelia\Model\CouponModule;
  33. use Thelia\Model\CouponModuleQuery;
  34. use Thelia\Model\CouponQuery;
  35. use Thelia\Model\Event\AddressEvent;
  36. use Thelia\Model\Map\OrderCouponTableMap;
  37. use Thelia\Model\OrderCoupon;
  38. use Thelia\Model\OrderCouponCountry;
  39. use Thelia\Model\OrderCouponModule;
  40. use Thelia\Model\OrderCouponQuery;
  41. /**
  42.  * Process Coupon Events.
  43.  *
  44.  * @author  Guillaume MOREL <gmorel@openstudio.fr>, Franck Allimant <franck@cqfdev.fr>
  45.  */
  46. class Coupon extends BaseAction implements EventSubscriberInterface
  47. {
  48.     /** @var RequestStack */
  49.     protected $requestStack;
  50.     /** @var CouponFactory */
  51.     protected $couponFactory;
  52.     /** @var CouponManager */
  53.     protected $couponManager;
  54.     /** @var ConditionInterface */
  55.     protected $noConditionRule;
  56.     /** @var ConditionFactory */
  57.     protected $conditionFactory;
  58.     public function __construct(
  59.         RequestStack $requestStack,
  60.         CouponFactory $couponFactory,
  61.         CouponManager $couponManager,
  62.         MatchForEveryone $noConditionRule,
  63.         ConditionFactory $conditionFactory
  64.     ) {
  65.         $this->requestStack $requestStack;
  66.         $this->couponFactory $couponFactory;
  67.         $this->couponManager $couponManager;
  68.         $this->noConditionRule $noConditionRule;
  69.         $this->conditionFactory $conditionFactory;
  70.     }
  71.     /**
  72.      * Occurring when a Coupon is about to be created.
  73.      *
  74.      * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
  75.      */
  76.     public function create(CouponCreateOrUpdateEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  77.     {
  78.         $coupon = new CouponModel();
  79.         $this->createOrUpdate($coupon$event$dispatcher);
  80.     }
  81.     /**
  82.      * Occurring when a Coupon is about to be updated.
  83.      *
  84.      * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
  85.      */
  86.     public function update(CouponCreateOrUpdateEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  87.     {
  88.         $coupon $event->getCouponModel();
  89.         $this->createOrUpdate($coupon$event$dispatcher);
  90.     }
  91.     public function delete(CouponDeleteEvent $event): void
  92.     {
  93.         $coupon $event->getCoupon();
  94.         if (null === $coupon) {
  95.             throw new \InvalidArgumentException(
  96.                 'The coupon should not be null'
  97.             );
  98.         }
  99.         $coupon->delete();
  100.         $event->setCoupon(null);
  101.     }
  102.     /**
  103.      * Occurring when a Coupon condition is about to be updated.
  104.      *
  105.      * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon condition
  106.      */
  107.     public function updateCondition(CouponCreateOrUpdateEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  108.     {
  109.         $modelCoupon $event->getCouponModel();
  110.         $this->createOrUpdateCondition($modelCoupon$event$dispatcher);
  111.     }
  112.     /**
  113.      * Clear all coupons in session.
  114.      */
  115.     public function clearAllCoupons(Event $event$eventNameEventDispatcherInterface $dispatcher): void
  116.     {
  117.         // Tell coupons to clear any data they may have stored
  118.         $this->couponManager->clear();
  119.         $this->getSession()->setConsumedCoupons([]);
  120.         $this->updateOrderDiscount($event$eventName$dispatcher);
  121.     }
  122.     /**
  123.      * Occurring when a Coupon condition is about to be consumed.
  124.      *
  125.      * @param CouponConsumeEvent $event Event consuming Coupon
  126.      */
  127.     public function consume(CouponConsumeEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  128.     {
  129.         $totalDiscount 0;
  130.         $isValid false;
  131.         /** @var CouponInterface $coupon */
  132.         $coupon $this->couponFactory->buildCouponFromCode($event->getCode());
  133.         if ($coupon) {
  134.             $isValid $coupon->isMatching();
  135.             if ($isValid) {
  136.                 $this->couponManager->pushCouponInSession($event->getCode());
  137.                 $totalDiscount $this->couponManager->getDiscount();
  138.                 $this->getSession()
  139.                     ->getSessionCart($dispatcher)
  140.                     ->setDiscount($totalDiscount)
  141.                     ->save();
  142.                 $this->getSession()
  143.                     ->getOrder()
  144.                     ->setDiscount($totalDiscount)
  145.                 ;
  146.             }
  147.         }
  148.         $event->setIsValid($isValid);
  149.         $event->setDiscount($totalDiscount);
  150.     }
  151.     public function updateOrderDiscount(Event $event$eventNameEventDispatcherInterface $dispatcher): void
  152.     {
  153.         $discount $this->couponManager->getDiscount();
  154.         $this->getSession()
  155.             ->getSessionCart($dispatcher)
  156.             ->setDiscount($discount)
  157.             ->save();
  158.         $this->getSession()
  159.             ->getOrder()
  160.             ->setDiscount($discount);
  161.     }
  162.     /**
  163.      * Call the Model and delegate the create or delete action
  164.      * Feed the Event with the updated model.
  165.      *
  166.      * @param CouponModel               $coupon Model to save
  167.      * @param CouponCreateOrUpdateEvent $event  Event containing data
  168.      */
  169.     protected function createOrUpdate(CouponModel $couponCouponCreateOrUpdateEvent $eventEventDispatcherInterface $dispatcher): void
  170.     {
  171.         // Set default condition if none found
  172.         /** @var ConditionInterface $noConditionRule */
  173.         $noConditionRule $this->noConditionRule;
  174.         /** @var ConditionFactory $conditionFactory */
  175.         $conditionFactory $this->conditionFactory;
  176.         $couponRuleCollection = new ConditionCollection();
  177.         $couponRuleCollection[] = $noConditionRule;
  178.         $defaultSerializedRule $conditionFactory->serializeConditionCollection(
  179.             $couponRuleCollection
  180.         );
  181.         $coupon->createOrUpdate(
  182.             $event->getCode(),
  183.             $event->getTitle(),
  184.             $event->getEffects(),
  185.             $event->getServiceId(),
  186.             $event->isRemovingPostage(),
  187.             $event->getShortDescription(),
  188.             $event->getDescription(),
  189.             $event->isEnabled(),
  190.             $event->getExpirationDate(),
  191.             $event->isAvailableOnSpecialOffers(),
  192.             $event->isCumulative(),
  193.             $event->getMaxUsage(),
  194.             $defaultSerializedRule,
  195.             $event->getLocale(),
  196.             $event->getFreeShippingForCountries(),
  197.             $event->getFreeShippingForMethods(),
  198.             $event->getPerCustomerUsageCount(),
  199.             $event->getStartDate()
  200.         );
  201.         $event->setCouponModel($coupon);
  202.     }
  203.     /**
  204.      * Call the Model and delegate the create or delete action
  205.      * Feed the Event with the updated model.
  206.      *
  207.      * @param CouponModel               $coupon Model to save
  208.      * @param CouponCreateOrUpdateEvent $event  Event containing data
  209.      */
  210.     protected function createOrUpdateCondition(CouponModel $couponCouponCreateOrUpdateEvent $eventEventDispatcherInterface $dispatcher): void
  211.     {
  212.         /** @var ConditionFactory $conditionFactory */
  213.         $conditionFactory $this->conditionFactory;
  214.         $coupon->createOrUpdateConditions(
  215.             $conditionFactory->serializeConditionCollection($event->getConditions()),
  216.             $event->getLocale()
  217.         );
  218.         $event->setCouponModel($coupon);
  219.     }
  220.     public function testFreePostage(OrderEvent $event): void
  221.     {
  222.         $order $event->getOrder();
  223.         if ($this->couponManager->isCouponRemovingPostage($order)) {
  224.             $order->setPostage(0);
  225.             $event->setOrder($order);
  226.             $event->stopPropagation();
  227.         }
  228.     }
  229.     /**
  230.      * @throws \Exception if something goes wrong
  231.      */
  232.     public function afterOrder(OrderEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  233.     {
  234.         /** @var CouponInterface[] $consumedCoupons */
  235.         $consumedCoupons $this->couponManager->getCouponsKept();
  236.         if (\is_array($consumedCoupons) && \count($consumedCoupons) > 0) {
  237.             $con Propel::getWriteConnection(OrderCouponTableMap::DATABASE_NAME);
  238.             $con->beginTransaction();
  239.             try {
  240.                 foreach ($consumedCoupons as $couponCode) {
  241.                     $couponQuery CouponQuery::create();
  242.                     $couponModel $couponQuery->findOneByCode($couponCode->getCode());
  243.                     $couponModel->setLocale($this->getSession()->getLang()->getLocale());
  244.                     /* decrease coupon quantity */
  245.                     $this->couponManager->decrementQuantity($couponModel$event->getOrder()->getCustomerId());
  246.                     /* memorize coupon */
  247.                     $orderCoupon = new OrderCoupon();
  248.                     $orderCoupon->setOrder($event->getOrder())
  249.                         ->setCode($couponModel->getCode())
  250.                         ->setType($couponModel->getType())
  251.                         ->setAmount($couponCode->exec())
  252.                         ->setTitle($couponModel->getTitle())
  253.                         ->setShortDescription($couponModel->getShortDescription())
  254.                         ->setDescription($couponModel->getDescription())
  255.                         ->setStartDate($couponModel->getStartDate())
  256.                         ->setExpirationDate($couponModel->getExpirationDate())
  257.                         ->setIsCumulative($couponModel->getIsCumulative())
  258.                         ->setIsRemovingPostage($couponModel->getIsRemovingPostage())
  259.                         ->setIsAvailableOnSpecialOffers($couponModel->getIsAvailableOnSpecialOffers())
  260.                         ->setSerializedConditions($couponModel->getSerializedConditions())
  261.                         ->setPerCustomerUsageCount($couponModel->getPerCustomerUsageCount())
  262.                     ;
  263.                     $orderCoupon->save();
  264.                     // Copy order coupon free shipping data for countries and modules
  265.                     $couponCountries CouponCountryQuery::create()->filterByCouponId($couponModel->getId())->find();
  266.                     /** @var CouponCountry $couponCountry */
  267.                     foreach ($couponCountries as $couponCountry) {
  268.                         $occ = new OrderCouponCountry();
  269.                         $occ
  270.                             ->setCouponId($orderCoupon->getId())
  271.                             ->setCountryId($couponCountry->getCountryId())
  272.                             ->save();
  273.                     }
  274.                     $couponModules CouponModuleQuery::create()->filterByCouponId($couponModel->getId())->find();
  275.                     /** @var CouponModule $couponModule */
  276.                     foreach ($couponModules as $couponModule) {
  277.                         $ocm = new OrderCouponModule();
  278.                         $ocm
  279.                             ->setCouponId($orderCoupon->getId())
  280.                             ->setModuleId($couponModule->getModuleId())
  281.                             ->save();
  282.                     }
  283.                 }
  284.                 $con->commit();
  285.             } catch (\Exception  $ex) {
  286.                 $con->rollBack();
  287.                 throw $ex;
  288.             }
  289.         }
  290.         // Clear all coupons.
  291.         $dispatcher->dispatch(new Event(), TheliaEvents::COUPON_CLEAR_ALL);
  292.     }
  293.     /**
  294.      * Cancels order coupons usage when order is canceled or refunded,
  295.      * or use canceled coupons again if the order is no longer canceled or refunded.
  296.      *
  297.      * @param string $eventName
  298.      *
  299.      * @throws \Exception
  300.      * @throws \Propel\Runtime\Exception\PropelException
  301.      */
  302.     public function orderStatusChange(OrderEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  303.     {
  304.         // The order has been canceled or refunded ?
  305.         if ($event->getOrder()->isCancelled() || $event->getOrder()->isRefunded()) {
  306.             // Cancel usage of all coupons for this order
  307.             $usedCoupons OrderCouponQuery::create()
  308.                 ->filterByUsageCanceled(false)
  309.                 ->findByOrderId($event->getOrder()->getId());
  310.             $customerId $event->getOrder()->getCustomerId();
  311.             /** @var OrderCoupon $usedCoupon */
  312.             foreach ($usedCoupons as $usedCoupon) {
  313.                 if (null !== $couponModel CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
  314.                     // If the coupon still exists, restore one usage to the usage count.
  315.                     $this->couponManager->incrementQuantity($couponModel$customerId);
  316.                 }
  317.                 // Mark coupon usage as canceled in the OrderCoupon table
  318.                 $usedCoupon->setUsageCanceled(true)->save();
  319.             }
  320.         } else {
  321.             // Mark canceled coupons for this order as used again
  322.             $usedCoupons OrderCouponQuery::create()
  323.                 ->filterByUsageCanceled(true)
  324.                 ->findByOrderId($event->getOrder()->getId());
  325.             $customerId $event->getOrder()->getCustomerId();
  326.             /** @var OrderCoupon $usedCoupon */
  327.             foreach ($usedCoupons as $usedCoupon) {
  328.                 if (null !== $couponModel CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
  329.                     // If the coupon still exists, mark the coupon as used
  330.                     $this->couponManager->decrementQuantity($couponModel$customerId);
  331.                 }
  332.                 // The coupon is no longer canceled
  333.                 $usedCoupon->setUsageCanceled(false)->save();
  334.             }
  335.         }
  336.     }
  337.     /**
  338.      * {@inheritdoc}
  339.      */
  340.     public static function getSubscribedEvents()
  341.     {
  342.         return [
  343.             TheliaEvents::COUPON_CREATE => ['create'128],
  344.             TheliaEvents::COUPON_UPDATE => ['update'128],
  345.             TheliaEvents::COUPON_DELETE => ['delete'128],
  346.             TheliaEvents::COUPON_CONSUME => ['consume'128],
  347.             TheliaEvents::COUPON_CLEAR_ALL => ['clearAllCoupons'128],
  348.             TheliaEvents::COUPON_CONDITION_UPDATE => ['updateCondition'128],
  349.             TheliaEvents::ORDER_SET_POSTAGE => ['testFreePostage'132],
  350.             TheliaEvents::ORDER_BEFORE_PAYMENT => ['afterOrder'128],
  351.             TheliaEvents::ORDER_UPDATE_STATUS => ['orderStatusChange'10],
  352.             TheliaEvents::CART_ADDITEM => ['updateOrderDiscount'10],
  353.             TheliaEvents::CART_UPDATEITEM => ['updateOrderDiscount'10],
  354.             TheliaEvents::CART_DELETEITEM => ['updateOrderDiscount'10],
  355.             TheliaEvents::CUSTOMER_LOGIN => ['updateOrderDiscount'10],
  356.             AddressEvent::POST_UPDATE => ['updateOrderDiscount'10],
  357.         ];
  358.     }
  359.     /**
  360.      * Returns the session from the current request.
  361.      *
  362.      * @return \Thelia\Core\HttpFoundation\Session\Session
  363.      */
  364.     protected function getSession()
  365.     {
  366.         return $this->requestStack->getCurrentRequest()->getSession();
  367.     }
  368. }