core/lib/Thelia/Action/Sale.php line 113
<?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 priceswitch ($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 saleif (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 PSEProductSaleElementsQuery::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 combinationsif (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 offsetsSaleOffsetCurrencyQuery::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 productsSaleProductQuery::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 methodif ($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 requiredif ($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 falseSaleQuery::create()->filterByActive(true)->update(['Active' => false], $con);// Reset all sale status on PSEProductSaleElementsQuery::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 salesif (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],];}}