Files
2021-01-14 18:04:26 +01:00

499 lines
18 KiB
PHP

<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* 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\ProductPriceQuery;
use Thelia\Model\Country as CountryModel;
use Thelia\Model\Map\SaleTableMap;
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
*
* @package Thelia\Action
* @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)
* @param ConnectionInterface $con
* @throws PropelException
*/
protected function updateProductSaleElementsPrices($pseList, $promoStatus, $offsetType, Calculator $taxCalculator, $saleOffsetByCurrency, ConnectionInterface $con)
{
/** @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
*
* @param ProductSaleStatusUpdateEvent $event
* @throws \RuntimeException
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
public function updateProductsSaleStatus(ProductSaleStatusUpdateEvent $event)
{
$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 (! \is_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
*
* @param SaleCreateEvent $event
*/
public function create(SaleCreateEvent $event)
{
$sale = new SaleModel();
$sale
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setSaleLabel($event->getSaleLabel())
->save()
;
$event->setSale($sale);
}
/**
* Process update sale
*
* @param SaleUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws PropelException
*/
public function update(SaleUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $sale = SaleQuery::create()->findPk($event->getSaleId())) {
$sale->setDispatcher($dispatcher);
$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(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
$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(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
}
/**
* Toggle Sale activity
*
* @param SaleToggleActivityEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Propel\Runtime\Exception\PropelException
*/
public function toggleActivity(SaleToggleActivityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$sale = $event->getSale();
$con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$sale
->setDispatcher($dispatcher)
->setActive(!$sale->getActive())
->save($con);
// Update related products sale status
$dispatcher->dispatch(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
$event->setSale($sale);
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
/**
* Delete a sale
*
* @param SaleDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Propel\Runtime\Exception\PropelException
*/
public function delete(SaleDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
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(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
$sale->setDispatcher($dispatcher)->delete($con);
$event->setSale($sale);
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
}
/**
* Clear all sales
*
* @param SaleClearStatusEvent $event
* @throws \Exception
*/
public function clearStatus(/** @noinspection PhpUnusedParameterInspection */ SaleClearStatusEvent $event)
{
$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.
*
* @param SaleActiveStatusCheckEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Propel\Runtime\Exception\PropelException
*/
public function checkSaleActivation(SaleActiveStatusCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$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(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
}
// 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(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
}
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
/**
* @inheritdoc
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::SALE_CREATE => array('create', 128),
TheliaEvents::SALE_UPDATE => array('update', 128),
TheliaEvents::SALE_DELETE => array('delete', 128),
TheliaEvents::SALE_TOGGLE_ACTIVITY => array('toggleActivity', 128),
TheliaEvents::SALE_CLEAR_SALE_STATUS => array('clearStatus', 128),
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS => array('updateProductsSaleStatus', 128),
TheliaEvents::CHECK_SALE_ACTIVATION_EVENT => array('checkSaleActivation', 128),
);
}
}