core/lib/Thelia/Action/Sale.php line 209
- <?php
- /*
- * This file is part of the Thelia package.
- * http://www.thelia.net
- *
- * (c) OpenStudio <info@thelia.net>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Thelia\Action;
- use Propel\Runtime\ActiveQuery\Criteria;
- use Propel\Runtime\Connection\ConnectionInterface;
- use Propel\Runtime\Exception\PropelException;
- use Propel\Runtime\Propel;
- use Symfony\Component\EventDispatcher\EventDispatcherInterface;
- use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Thelia\Core\Event\Sale\ProductSaleStatusUpdateEvent;
- use Thelia\Core\Event\Sale\SaleActiveStatusCheckEvent;
- use Thelia\Core\Event\Sale\SaleClearStatusEvent;
- use Thelia\Core\Event\Sale\SaleCreateEvent;
- use Thelia\Core\Event\Sale\SaleDeleteEvent;
- use Thelia\Core\Event\Sale\SaleToggleActivityEvent;
- use Thelia\Core\Event\Sale\SaleUpdateEvent;
- use Thelia\Core\Event\TheliaEvents;
- use Thelia\Model\Country as CountryModel;
- use Thelia\Model\Map\SaleTableMap;
- use Thelia\Model\ProductPriceQuery;
- use Thelia\Model\ProductSaleElements;
- use Thelia\Model\ProductSaleElementsQuery;
- use Thelia\Model\Sale as SaleModel;
- use Thelia\Model\SaleOffsetCurrency;
- use Thelia\Model\SaleOffsetCurrencyQuery;
- use Thelia\Model\SaleProduct;
- use Thelia\Model\SaleProductQuery;
- use Thelia\Model\SaleQuery;
- use Thelia\TaxEngine\Calculator;
- /**
- * Class Sale.
- *
- * @author Franck Allimant <franck@cqfdev.fr>
- */
- class Sale extends BaseAction implements EventSubscriberInterface
- {
- /**
- * Update PSE for a given product.
- *
- * @param array $pseList an array of priduct sale elements
- * @param bool $promoStatus true if the PSEs are on sale, false otherwise
- * @param int $offsetType the offset type, see SaleModel::OFFSET_* constants
- * @param Calculator $taxCalculator the tax calculator
- * @param array $saleOffsetByCurrency an array of price offset for each currency (currency ID => offset_amount)
- *
- * @throws PropelException
- */
- protected function updateProductSaleElementsPrices($pseList, $promoStatus, $offsetType, Calculator $taxCalculator, $saleOffsetByCurrency, ConnectionInterface $con): void
- {
- /** @var ProductSaleElements $pse */
- foreach ($pseList as $pse) {
- if ($pse->getPromo() != $promoStatus) {
- $pse
- ->setPromo($promoStatus)
- ->save($con)
- ;
- }
- /** @var SaleOffsetCurrency $offsetByCurrency */
- foreach ($saleOffsetByCurrency as $currencyId => $offset) {
- $productPrice = ProductPriceQuery::create()
- ->filterByProductSaleElementsId($pse->getId())
- ->filterByCurrencyId($currencyId)
- ->findOne($con);
- if (null !== $productPrice) {
- // Get the taxed price
- $priceWithTax = $taxCalculator->getTaxedPrice($productPrice->getPrice());
- // Remove the price offset to get the taxed promo price
- switch ($offsetType) {
- case SaleModel::OFFSET_TYPE_AMOUNT:
- $promoPrice = max(0, $priceWithTax - $offset);
- break;
- case SaleModel::OFFSET_TYPE_PERCENTAGE:
- $promoPrice = $priceWithTax * (1 - $offset / 100);
- break;
- default:
- $promoPrice = $priceWithTax;
- }
- // and then get the untaxed promo price.
- $promoPrice = $taxCalculator->getUntaxedPrice($promoPrice);
- $productPrice
- ->setPromoPrice($promoPrice)
- ->save($con)
- ;
- }
- }
- }
- }
- /**
- * Update the promo status of the sale's selected products and combinations.
- *
- * @throws \RuntimeException
- * @throws \Exception
- * @throws \Propel\Runtime\Exception\PropelException
- */
- public function updateProductsSaleStatus(ProductSaleStatusUpdateEvent $event): void
- {
- $taxCalculator = new Calculator();
- $sale = $event->getSale();
- // Get all selected product sale elements for this sale
- if (null !== $saleProducts = SaleProductQuery::create()->filterBySale($sale)->orderByProductId()) {
- $saleOffsetByCurrency = $sale->getPriceOffsets();
- $offsetType = $sale->getPriceOffsetType();
- $con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
- $con->beginTransaction();
- try {
- /** @var SaleProduct $saleProduct */
- foreach ($saleProducts as $saleProduct) {
- // Reset all sale status on product's PSE
- ProductSaleElementsQuery::create()
- ->filterByProductId($saleProduct->getProductId())
- ->update(['Promo' => false], $con)
- ;
- $taxCalculator->load(
- $saleProduct->getProduct($con),
- CountryModel::getShopLocation()
- );
- $attributeAvId = $saleProduct->getAttributeAvId();
- $pseRequest = ProductSaleElementsQuery::create()
- ->filterByProductId($saleProduct->getProductId())
- ;
- // If no attribute AV id is defined, consider ALL product combinations
- if (null !== $attributeAvId) {
- // Find PSE attached to combination containing this attribute av :
- // SELECT * from product_sale_elements pse
- // left join attribute_combination ac on ac.product_sale_elements_id = pse.id
- // where pse.product_id=363
- // and ac.attribute_av_id = 7
- // group by pse.id
- $pseRequest
- ->useAttributeCombinationQuery(null, Criteria::LEFT_JOIN)
- ->filterByAttributeAvId($attributeAvId)
- ->endUse()
- ;
- }
- $pseList = $pseRequest->find();
- if (null !== $pseList) {
- $this->updateProductSaleElementsPrices(
- $pseList,
- $sale->getActive(),
- $offsetType,
- $taxCalculator,
- $saleOffsetByCurrency,
- $con
- );
- }
- }
- $con->commit();
- } catch (PropelException $e) {
- $con->rollback();
- throw $e;
- }
- }
- }
- /**
- * Create a new Sale.
- */
- public function create(SaleCreateEvent $event): void
- {
- $sale = new SaleModel();
- $sale
- ->setLocale($event->getLocale())
- ->setTitle($event->getTitle())
- ->setSaleLabel($event->getSaleLabel())
- ->save()
- ;
- $event->setSale($sale);
- }
- /**
- * Process update sale.
- *
- * @throws PropelException
- */
- public function update(SaleUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
- {
- if (null !== $sale = SaleQuery::create()->findPk($event->getSaleId())) {
- $con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
- $con->beginTransaction();
- try {
- // Disable all promo flag on sale's currently selected products,
- // to reset promo status of the products that may have been removed from the selection.
- $sale->setActive(false);
- $dispatcher->dispatch(
- new ProductSaleStatusUpdateEvent($sale),
- TheliaEvents::UPDATE_PRODUCT_SALE_STATUS
- );
- $sale
- ->setActive($event->getActive())
- ->setStartDate($event->getStartDate())
- ->setEndDate($event->getEndDate())
- ->setPriceOffsetType($event->getPriceOffsetType())
- ->setDisplayInitialPrice($event->getDisplayInitialPrice())
- ->setLocale($event->getLocale())
- ->setSaleLabel($event->getSaleLabel())
- ->setTitle($event->getTitle())
- ->setDescription($event->getDescription())
- ->setChapo($event->getChapo())
- ->setPostscriptum($event->getPostscriptum())
- ->save($con)
- ;
- $event->setSale($sale);
- // Update price offsets
- SaleOffsetCurrencyQuery::create()->filterBySaleId($sale->getId())->delete($con);
- foreach ($event->getPriceOffsets() as $currencyId => $priceOffset) {
- $saleOffset = new SaleOffsetCurrency();
- $saleOffset
- ->setCurrencyId($currencyId)
- ->setSaleId($sale->getId())
- ->setPriceOffsetValue($priceOffset)
- ->save($con)
- ;
- }
- // Update products
- SaleProductQuery::create()->filterBySaleId($sale->getId())->delete($con);
- $productAttributesArray = $event->getProductAttributes();
- foreach ($event->getProducts() as $productId) {
- if (isset($productAttributesArray[$productId])) {
- foreach ($productAttributesArray[$productId] as $attributeId) {
- $saleProduct = new SaleProduct();
- $saleProduct
- ->setSaleId($sale->getId())
- ->setProductId($productId)
- ->setAttributeAvId($attributeId)
- ->save($con)
- ;
- }
- } else {
- $saleProduct = new SaleProduct();
- $saleProduct
- ->setSaleId($sale->getId())
- ->setProductId($productId)
- ->setAttributeAvId(null)
- ->save($con)
- ;
- }
- }
- // Update related products sale status if the Sale is active. This is not required if the sale is
- // not active, as we de-activated promotion for this sale at the beginning ofd this method
- if ($sale->getActive()) {
- $dispatcher->dispatch(
- new ProductSaleStatusUpdateEvent($sale),
- TheliaEvents::UPDATE_PRODUCT_SALE_STATUS
- );
- }
- $con->commit();
- } catch (PropelException $e) {
- $con->rollback();
- throw $e;
- }
- }
- }
- /**
- * Toggle Sale activity.
- *
- * @throws \Propel\Runtime\Exception\PropelException
- */
- public function toggleActivity(SaleToggleActivityEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
- {
- $sale = $event->getSale();
- $con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
- $con->beginTransaction();
- try {
- $sale
- ->setActive(!$sale->getActive())
- ->save($con);
- // Update related products sale status
- $dispatcher->dispatch(
- new ProductSaleStatusUpdateEvent($sale),
- TheliaEvents::UPDATE_PRODUCT_SALE_STATUS
- );
- $event->setSale($sale);
- $con->commit();
- } catch (PropelException $e) {
- $con->rollback();
- throw $e;
- }
- }
- /**
- * Delete a sale.
- *
- * @throws \Propel\Runtime\Exception\PropelException
- */
- public function delete(SaleDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
- {
- if (null !== $sale = SaleQuery::create()->findPk($event->getSaleId())) {
- $con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
- $con->beginTransaction();
- try {
- // Update related products sale status, if required
- if ($sale->getActive()) {
- $sale->setActive(false);
- // Update related products sale status
- $dispatcher->dispatch(
- new ProductSaleStatusUpdateEvent($sale),
- TheliaEvents::UPDATE_PRODUCT_SALE_STATUS
- );
- }
- $sale->delete($con);
- $event->setSale($sale);
- $con->commit();
- } catch (PropelException $e) {
- $con->rollback();
- throw $e;
- }
- }
- }
- /**
- * Clear all sales.
- *
- * @throws \Exception
- */
- public function clearStatus(/* @noinspection PhpUnusedParameterInspection */ SaleClearStatusEvent $event): void
- {
- $con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
- $con->beginTransaction();
- try {
- // Set the active status of all Sales to false
- SaleQuery::create()
- ->filterByActive(true)
- ->update(['Active' => false], $con)
- ;
- // Reset all sale status on PSE
- ProductSaleElementsQuery::create()
- ->filterByPromo(true)
- ->update(['Promo' => false], $con)
- ;
- $con->commit();
- } catch (PropelException $e) {
- $con->rollback();
- throw $e;
- }
- }
- /**
- * This method check the activation and deactivation dates of sales, and perform
- * the required action depending on the current date.
- *
- * @throws \Propel\Runtime\Exception\PropelException
- */
- public function checkSaleActivation(SaleActiveStatusCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
- {
- $con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
- $con->beginTransaction();
- try {
- $now = time();
- // Disable expired sales
- if (null !== $salesToDisable = SaleQuery::create()
- ->filterByActive(true)
- ->filterByEndDate($now, Criteria::LESS_THAN)
- ->find()) {
- /** @var SaleModel $sale */
- foreach ($salesToDisable as $sale) {
- $sale->setActive(false)->save();
- // Update related products sale status
- $dispatcher->dispatch(
- new ProductSaleStatusUpdateEvent($sale),
- TheliaEvents::UPDATE_PRODUCT_SALE_STATUS
- );
- }
- }
- // Enable sales that should be enabled.
- if (null !== $salesToEnable = SaleQuery::create()
- ->filterByActive(false)
- ->filterByStartDate($now, Criteria::LESS_EQUAL)
- ->filterByEndDate($now, Criteria::GREATER_EQUAL)
- ->find()) {
- /** @var SaleModel $sale */
- foreach ($salesToEnable as $sale) {
- $sale->setActive(true)->save();
- // Update related products sale status
- $dispatcher->dispatch(
- new ProductSaleStatusUpdateEvent($sale),
- TheliaEvents::UPDATE_PRODUCT_SALE_STATUS
- );
- }
- }
- $con->commit();
- } catch (PropelException $e) {
- $con->rollback();
- throw $e;
- }
- }
- /**
- * {@inheritdoc}
- */
- public static function getSubscribedEvents()
- {
- return [
- TheliaEvents::SALE_CREATE => ['create', 128],
- TheliaEvents::SALE_UPDATE => ['update', 128],
- TheliaEvents::SALE_DELETE => ['delete', 128],
- TheliaEvents::SALE_TOGGLE_ACTIVITY => ['toggleActivity', 128],
- TheliaEvents::SALE_CLEAR_SALE_STATUS => ['clearStatus', 128],
- TheliaEvents::UPDATE_PRODUCT_SALE_STATUS => ['updateProductsSaleStatus', 128],
- TheliaEvents::CHECK_SALE_ACTIVATION_EVENT => ['checkSaleActivation', 128],
- ];
- }
- }