From ab4a8841e8d60d090208415e1c760b6aa2676646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Odwa=C5=BCny?= Date: Mon, 7 Oct 2024 01:48:32 +0200 Subject: [PATCH] upgrade doctrine ord traits and event listeners --- composer.json | 3 +- src/Command/ProcessPromotionsCommand.php | 162 ++++++------- .../ORM/ChannelPricingRepositoryTrait.php | 224 +++++++++++++----- .../ORM/HasAnyBeenUpdatedSinceTrait.php | 18 +- src/Doctrine/ORM/PromotionRepository.php | 11 +- .../AddChannelPricingIndicesSubscriber.php | 3 +- .../AddTimestampableIndicesSubscriber.php | 14 +- .../ChannelPricingRepositoryInterface.php | 9 +- src/Resources/config/services/command.xml | 9 +- 9 files changed, 287 insertions(+), 166 deletions(-) diff --git a/composer.json b/composer.json index ba4fe40..b9b5c32 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "sylius/resource-bundle": "^1.6", "symfony/config": "^5.4 || ^6.0", "symfony/lock": "^5.4 || ^6.0", - "thecodingmachine/safe": "^1.1" + "thecodingmachine/safe": "^1.1", + "setono/job-status-bundle": "^0.2.3" }, "require-dev": { "roave/security-advisories": "dev-master", diff --git a/src/Command/ProcessPromotionsCommand.php b/src/Command/ProcessPromotionsCommand.php index 7e69ce9..de30abf 100644 --- a/src/Command/ProcessPromotionsCommand.php +++ b/src/Command/ProcessPromotionsCommand.php @@ -6,9 +6,11 @@ use DateTimeInterface; use Doctrine\ORM\EntityRepository; -use function Safe\file_get_contents; -use function Safe\file_put_contents; -use function Safe\sprintf; +use Setono\JobStatusBundle\Entity\JobInterface; +use Setono\JobStatusBundle\Entity\Spec\LastJobWithType; +use Setono\JobStatusBundle\Factory\JobFactoryInterface; +use Setono\JobStatusBundle\Manager\JobManagerInterface; +use Setono\JobStatusBundle\Repository\JobRepositoryInterface; use Setono\SyliusCatalogPromotionPlugin\Model\PromotionInterface; use Setono\SyliusCatalogPromotionPlugin\Repository\ChannelPricingRepositoryInterface; use Setono\SyliusCatalogPromotionPlugin\Repository\ProductRepositoryInterface; @@ -22,49 +24,59 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\DependencyInjection\Container; use Webmozart\Assert\Assert; final class ProcessPromotionsCommand extends Command { use LockableTrait; + private const JOB_TYPE = 'sscp_process_promotions'; + protected static $defaultName = 'setono:sylius-catalog-promotion:process'; - /** @var ChannelPricingRepositoryInterface */ - private $channelPricingRepository; + private JobRepositoryInterface $jobRepository; + + private JobFactoryInterface $jobFactory; + + private JobManagerInterface $jobManager; - /** @var ProductRepositoryInterface */ - private $productRepository; + private ChannelPricingRepositoryInterface $channelPricingRepository; - /** @var ProductVariantRepositoryInterface */ - private $productVariantRepository; + private ProductRepositoryInterface $productRepository; - /** @var PromotionRepositoryInterface */ - private $promotionRepository; + private ProductVariantRepositoryInterface $productVariantRepository; - /** @var ServiceRegistryInterface */ - private $ruleRegistry; + private PromotionRepositoryInterface $promotionRepository; - /** @var string */ - private $logsDir; + private ServiceRegistryInterface $ruleRegistry; + private int $jobTtl; + + /** + * @param int $jobTtl the ttl we set for the job (in seconds) + */ public function __construct( + JobRepositoryInterface $jobRepository, + JobFactoryInterface $jobFactory, + JobManagerInterface $jobManager, ChannelPricingRepositoryInterface $channelPricingRepository, ProductRepositoryInterface $productRepository, ProductVariantRepositoryInterface $productVariantRepository, PromotionRepositoryInterface $promotionRepository, ServiceRegistryInterface $ruleRegistry, - string $logsDir + int $jobTtl ) { parent::__construct(); + $this->jobRepository = $jobRepository; + $this->jobFactory = $jobFactory; + $this->jobManager = $jobManager; $this->channelPricingRepository = $channelPricingRepository; $this->productRepository = $productRepository; $this->productVariantRepository = $productVariantRepository; $this->promotionRepository = $promotionRepository; $this->ruleRegistry = $ruleRegistry; - $this->logsDir = $logsDir; + $this->jobTtl = $jobTtl; } protected function configure(): void @@ -79,26 +91,48 @@ protected function execute(InputInterface $input, OutputInterface $output): int { Assert::isInstanceOf($this->productVariantRepository, EntityRepository::class); - if (!$this->lock()) { - $output->writeln('The command is already running in another process.'); + /** @var JobInterface|mixed|null $lastJob */ + $lastJob = $this->jobRepository->matchOneOrNullResult(new LastJobWithType(self::JOB_TYPE)); + Assert::nullOrIsInstanceOf($lastJob, JobInterface::class); + + if (null !== $lastJob && $lastJob->isRunning()) { + $output->writeln('The job is already running'); return 0; } - /** @var bool $force */ - $force = $input->getOption('force'); - $startTime = new \DateTime(); + $force = true === $input->getOption('force'); - /** @var PromotionInterface[] $promotions */ $promotions = $this->promotionRepository->findForProcessing(); - $promotionIds = array_map(static function (PromotionInterface $promotion): int { return (int) $promotion->getId(); }, $promotions); + if (!$force && !$this->isProcessingAllowed($promotionIds, $lastJob)) { + $output->writeln( + 'Nothing to process at the moment. Run command with --force option to force process', + OutputInterface::VERBOSITY_VERBOSE + ); + + return 0; + } + + $job = $this->jobFactory->createNew(); + $job->setExclusive(true); + $job->setType(self::JOB_TYPE); + $job->setName('Sylius Catalog Promotion plugin: Process promotions'); + $job->setTtl($this->jobTtl); + $job->setMetadataEntry('promotions', $promotionIds); + + $this->jobManager->start($job, 3); + + $startTime = $job->getStartedAt(); + Assert::notNull($startTime); + $bulkIdentifier = uniqid('bulk-', true); - $this->channelPricingRepository->resetMultiplier($startTime); + $this->channelPricingRepository->resetMultiplier($startTime, $bulkIdentifier); + $this->jobManager->advance($job); foreach ($promotions as $promotion) { $qb = $this->productVariantRepository->createQueryBuilder('o'); @@ -114,13 +148,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } - if (!$this->ruleRegistry->has($rule->getType())) { + if (!$this->ruleRegistry->has((string) $rule->getType())) { // todo should this throw an exception or give an error somewhere? continue; } /** @var RuleInterface $ruleQueryBuilder */ - $ruleQueryBuilder = $this->ruleRegistry->get($rule->getType()); + $ruleQueryBuilder = $this->ruleRegistry->get((string) $rule->getType()); $ruleQueryBuilder->filter($qb, $rule->getConfiguration()); } @@ -131,9 +165,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int do { $qb->setFirstResult($i * $bulkSize); + + /** @var array $productVariantIds */ $productVariantIds = $qb->getQuery()->getResult(); $this->channelPricingRepository->updateMultiplier( + (string) $promotion->getCode(), $promotion->getMultiplier(), $productVariantIds, $promotion->getChannelCodes(), @@ -147,33 +184,37 @@ protected function execute(InputInterface $input, OutputInterface $output): int } while (count($productVariantIds) !== 0); } - $this->channelPricingRepository->updatePrices($startTime, $bulkIdentifier); + $this->jobManager->advance($job); - $this->setExecution([ - 'start' => $startTime, - 'end' => new \DateTime(), - 'promotions' => $promotionIds, - ]); + $this->channelPricingRepository->updatePrices($bulkIdentifier); + + $this->jobManager->advance($job); + $this->jobManager->finish($job); return 0; } - private function isProcessingAllowed(array $promotionIds): bool + private function isProcessingAllowed(array $promotionIds, ?JobInterface $lastJob): bool { - // If there was no executions - we can process - $lastExecution = $this->getLastExecution(); - if (null === $lastExecution) { + // if there isn't no last job we can just process + if (null === $lastJob) { return true; } + $lastPromotionIds = $lastJob->getMetadataEntry('promotions'); + Assert::isArray($lastPromotionIds); + // If last execution promotions not exact same as found for processing // or not at the same order - we can process - if ($lastExecution['promotions'] !== $promotionIds) { + if ($lastPromotionIds !== $promotionIds) { return true; } + $lastJobStartedAt = $lastJob->getStartedAt(); + Assert::notNull($lastJobStartedAt); + // If any relevant entities were updated - we can process - if ($this->hasAnyBeenUpdatedSince($lastExecution['start'])) { + if ($this->hasAnyBeenUpdatedSince($lastJobStartedAt)) { return true; } @@ -188,45 +229,6 @@ private function hasAnyBeenUpdatedSince(DateTimeInterface $dateTime): bool $this->productVariantRepository->hasAnyBeenUpdatedSince($dateTime) || $this->channelPricingRepository->hasAnyBeenUpdatedSince($dateTime) || $this->promotionRepository->hasAnyBeenUpdatedSince($dateTime) - ; - } - - // @todo Move storing last execution data to some memory storage / cache? - private function getLastExecution(): ?array - { - $filename = $this->getExecutionLogFilename(); - - if (!file_exists($filename)) { - return null; - } - - $execution = unserialize(file_get_contents($filename), [ - 'allowed_classes' => true, - ]); - - if (false === $execution) { - return null; - } - - // validate contents - if (!isset($execution['start'], $execution['end'], $execution['promotions'])) { - return null; - } - - return $execution; - } - - private function setExecution(array $execution): void - { - $filename = $this->getExecutionLogFilename(); - - file_put_contents($filename, serialize($execution)); - } - - private function getExecutionLogFilename(): string - { - return sprintf('%s/%s.log', - $this->logsDir, Container::underscore(str_replace('\\', '', get_class($this))) - ); + ; } } diff --git a/src/Doctrine/ORM/ChannelPricingRepositoryTrait.php b/src/Doctrine/ORM/ChannelPricingRepositoryTrait.php index 360a7e3..f4148f4 100644 --- a/src/Doctrine/ORM/ChannelPricingRepositoryTrait.php +++ b/src/Doctrine/ORM/ChannelPricingRepositoryTrait.php @@ -5,6 +5,7 @@ namespace Setono\SyliusCatalogPromotionPlugin\Doctrine\ORM; use DateTimeInterface; +use Doctrine\DBAL\TransactionIsolationLevel; use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository; /** @@ -14,25 +15,76 @@ trait ChannelPricingRepositoryTrait { use HasAnyBeenUpdatedSinceTrait; - public function resetMultiplier(DateTimeInterface $dateTime): void + public function resetMultiplier(DateTimeInterface $dateTime, string $bulkIdentifier): void { \assert($this instanceof EntityRepository); - $this - ->createQueryBuilder('o') - ->update() - ->set('o.multiplier', 1) - ->set('o.bulkIdentifier', ':null') - ->set('o.updatedAt', ':updatedAt') - ->andWhere('o.multiplier != 1') - ->setParameter('null', null) - ->setParameter('updatedAt', $dateTime) - ->getQuery() - ->execute() - ; + $connection = $this->_em->getConnection(); + $oldTransactionIsolation = (int) $connection->getTransactionIsolation(); + $connection->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED); + + do { + $connection->beginTransaction(); + + try { + $qb = $this->createQueryBuilder('o'); + $ids = $qb + ->select('o.id') + // this ensures that the loop we are in doesn't turn into an infinite loop + ->andWhere( + $qb->expr()->orX( + 'o.bulkIdentifier != :bulkIdentifier', + 'o.bulkIdentifier is null' + ) + ) + ->andWhere( + $qb->expr()->orX( + // if the multiplier is different from 1 we know that it was discounted before, and we reset it + 'o.multiplier != 1', + + // if the previous job timed out, the bulk identifier will be different from the + // bulk identifier for this run. This will ensure that they will also be handled in this run + 'o.bulkIdentifier is not null', + + // if the applied promotions is not null we know that it was discounted before, and we reset it + 'o.appliedPromotions is not null' + ) + ) + ->setParameter('bulkIdentifier', $bulkIdentifier) + ->setMaxResults(100) + ->getQuery() + ->getResult() + ; + + $res = (int) $this + ->createQueryBuilder('o') + ->update() + ->set('o.multiplier', 1) + ->set('o.updatedAt', ':updatedAt') + ->set('o.bulkIdentifier', ':bulkIdentifier') + ->set('o.appliedPromotions', ':null') + ->andWhere('o.id IN (:ids)') + ->setParameter('updatedAt', $dateTime) + ->setParameter('bulkIdentifier', $bulkIdentifier) + ->setParameter('null', null) + ->setParameter('ids', $ids) + ->getQuery() + ->execute() + ; + + $connection->commit(); + } catch (\Throwable $e) { + $connection->rollBack(); + + throw $e; + } + } while ($res > 0); + + $connection->setTransactionIsolation($oldTransactionIsolation); } public function updateMultiplier( + string $promotionCode, float $multiplier, array $productVariantIds, array $channelCodes, @@ -52,12 +104,36 @@ public function updateMultiplier( $qb->update() ->andWhere('channelPricing.productVariant IN (:productVariantIds)') ->andWhere('channelPricing.channelCode IN (:channelCodes)') + // this 'or' is a safety check. If the previous run timed out, but managed to update some multipliers + // this will end up in discounts being compounded. With this check we ensure we only operate on pricings + // from this run or pricings that haven't been touched + ->andWhere($qb->expr()->orX( + 'channelPricing.bulkIdentifier is null', + 'channelPricing.bulkIdentifier = :bulkIdentifier', + )) + // here is another safety check. If the promotion code is already applied, + // do not select this pricing for a discount + ->andWhere($qb->expr()->orX( + 'channelPricing.appliedPromotions IS NULL', + $qb->expr()->andX( + 'channelPricing.appliedPromotions NOT LIKE :promotionEnding', + 'channelPricing.appliedPromotions NOT LIKE :promotionMiddle', + ) + )) ->set('channelPricing.updatedAt', ':date') ->set('channelPricing.bulkIdentifier', ':bulkIdentifier') + ->set('channelPricing.appliedPromotions', "CONCAT(COALESCE(channelPricing.appliedPromotions, ''), CONCAT(',', :promotion))") ->setParameter('productVariantIds', $productVariantIds) ->setParameter('channelCodes', $channelCodes) ->setParameter('date', $dateTime) ->setParameter('bulkIdentifier', $bulkIdentifier) + ->setParameter('promotion', $promotionCode) + // if you are checking for the promo code 'all_10_percent' there are two options for the applied_promotions column: + // 1. ,single_tshirt,all_10_percent + // 2. ,all_10_percent,single_tshirt + // and these two wildcards selections will handle those two options + ->setParameter('promotionEnding', '%,' . $promotionCode) + ->setParameter('promotionMiddle', '%,' . $promotionCode . ',%') ; if ($manuallyDiscountedProductsExcluded) { @@ -75,58 +151,80 @@ public function updateMultiplier( $qb->getQuery()->execute(); } - public function updatePrices(DateTimeInterface $dateTime, string $bulkIdentifier): void + public function updatePrices(string $bulkIdentifier): void { \assert($this instanceof EntityRepository); - $this->createQueryBuilder('o') - ->update() - ->set('o.price', 'ROUND(o.originalPrice * o.multiplier)') - ->andWhere('o.originalPrice is not null') - ->andWhere('o.updatedAt >= :date') - ->andWhere('o.bulkIdentifier = :bulkIdentifier') - ->setParameter('date', $dateTime) - ->setParameter('bulkIdentifier', $bulkIdentifier) - ->getQuery() - ->execute() - ; - - $this->createQueryBuilder('o') - ->update() - ->set('o.originalPrice', 'o.price') - ->set('o.price', 'ROUND(o.price * o.multiplier)') - ->andWhere('o.originalPrice is null') - ->andWhere('o.multiplier != 1') - ->andWhere('o.updatedAt >= :date') - ->andWhere('o.bulkIdentifier = :bulkIdentifier') - ->setParameter('date', $dateTime) - ->setParameter('bulkIdentifier', $bulkIdentifier) - ->getQuery() - ->execute() - ; - - $this->createQueryBuilder('o') - ->update() - ->set('o.originalPrice', ':originalPrice') - ->andWhere('o.price = o.originalPrice') - ->andWhere('o.updatedAt >= :date') - ->andWhere('o.bulkIdentifier = :bulkIdentifier') - ->setParameter('originalPrice', null) - ->setParameter('date', $dateTime) - ->setParameter('bulkIdentifier', $bulkIdentifier) - ->getQuery() - ->execute() - ; - - $this->createQueryBuilder('o') - ->update() - ->set('o.price', 'o.originalPrice') - ->andWhere('o.updatedAt <= :date') - ->andWhere('o.bulkIdentifier is null') - ->andWhere('o.originalPrice is not null') - ->setParameter('date', $dateTime) - ->getQuery() - ->execute() - ; + $connection = $this->_em->getConnection(); + $oldTransactionIsolation = (int) $connection->getTransactionIsolation(); + $connection->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED); + + do { + $res = 0; + $connection->beginTransaction(); + + try { + // get an array of ids to work on + $ids = $this->createQueryBuilder('o') + ->select('o.id') + ->andWhere('o.bulkIdentifier = :bulkIdentifier') + ->setParameter('bulkIdentifier', $bulkIdentifier) + ->setMaxResults(100) + ->getQuery() + ->getResult(); + + // this query handles the case where an original price is set + // i.e. we have made discounts on this product before + $this->createQueryBuilder('o') + ->update() + ->set('o.price', 'ROUND(o.originalPrice * o.multiplier)') + ->andWhere('o.originalPrice is not null') + ->andWhere('o.id in (:ids)') + ->setParameter('ids', $ids) + ->getQuery() + ->execute(); + + // this query handles the case where a discount hasn't been applied before + // so we want to move the current price to the original price before changing the price + $this->createQueryBuilder('o') + ->update() + ->set('o.originalPrice', 'o.price') + ->set('o.price', 'ROUND(o.price * o.multiplier)') + ->andWhere('o.originalPrice is null') + ->andWhere('o.multiplier != 1') + ->andWhere('o.id in (:ids)') + ->setParameter('ids', $ids) + ->getQuery() + ->execute(); + + // this query sets the original price to null where the original price equals the price + $this->createQueryBuilder('o') + ->update() + ->set('o.originalPrice', ':originalPrice') + ->andWhere('o.price = o.originalPrice') + ->andWhere('o.id in (:ids)') + ->setParameter('ids', $ids) + ->setParameter('originalPrice', null) + ->getQuery() + ->execute(); + + // set the bulk identifier to null to ensure the loop will come to an end ;) + $res = (int) $this + ->createQueryBuilder('o') + ->update() + ->set('o.bulkIdentifier', ':null') + ->andWhere('o.id IN (:ids)') + ->setParameter('null', null) + ->setParameter('ids', $ids) + ->getQuery() + ->execute(); + + $connection->commit(); + } catch (\Throwable $e) { + $connection->rollBack(); + } + } while ($res > 0); + + $connection->setTransactionIsolation($oldTransactionIsolation); } } diff --git a/src/Doctrine/ORM/HasAnyBeenUpdatedSinceTrait.php b/src/Doctrine/ORM/HasAnyBeenUpdatedSinceTrait.php index 91af244..9ed4e4e 100644 --- a/src/Doctrine/ORM/HasAnyBeenUpdatedSinceTrait.php +++ b/src/Doctrine/ORM/HasAnyBeenUpdatedSinceTrait.php @@ -5,15 +5,13 @@ namespace Setono\SyliusCatalogPromotionPlugin\Doctrine\ORM; use DateTimeInterface; -use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\EntityRepository; +/** + * @mixin EntityRepository + */ trait HasAnyBeenUpdatedSinceTrait { - /** - * @return QueryBuilder - */ - abstract public function createQueryBuilder($alias, $indexBy = null); - public function hasAnyBeenUpdatedSince(DateTimeInterface $dateTime): bool { $qb = $this->createQueryBuilder('o') @@ -29,9 +27,9 @@ public function hasAnyBeenUpdatedSince(DateTimeInterface $dateTime): bool */ $updated = (int) $qb - ->andWhere('o.updatedAt is not null', 'o.updatedAt > :date') - ->getQuery() - ->getSingleScalarResult() > 0 + ->andWhere('o.updatedAt is not null', 'o.updatedAt > :date') + ->getQuery() + ->getSingleScalarResult() > 0 ; if ($updated) { @@ -44,6 +42,6 @@ public function hasAnyBeenUpdatedSince(DateTimeInterface $dateTime): bool ->andWhere('o.createdAt is not null', 'o.createdAt > :date') ->getQuery() ->getSingleScalarResult() > 0 - ; + ; } } diff --git a/src/Doctrine/ORM/PromotionRepository.php b/src/Doctrine/ORM/PromotionRepository.php index 1fb162f..344a468 100644 --- a/src/Doctrine/ORM/PromotionRepository.php +++ b/src/Doctrine/ORM/PromotionRepository.php @@ -4,9 +4,11 @@ namespace Setono\SyliusCatalogPromotionPlugin\Doctrine\ORM; -use Safe\DateTime; +use DateTime; +use Setono\SyliusCatalogPromotionPlugin\Model\PromotionInterface; use Setono\SyliusCatalogPromotionPlugin\Repository\PromotionRepositoryInterface; use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository; +use Webmozart\Assert\Assert; class PromotionRepository extends EntityRepository implements PromotionRepositoryInterface { @@ -16,7 +18,7 @@ public function findForProcessing(): array { $dt = new DateTime(); - return $this->createQueryBuilder('o') + $res = $this->createQueryBuilder('o') ->andWhere('o.enabled = true') ->andWhere('SIZE(o.channels) > 0') ->andWhere('o.startsAt is null OR o.startsAt <= :date') @@ -27,5 +29,10 @@ public function findForProcessing(): array ->getQuery() ->getResult() ; + + Assert::isArray($res); + Assert::allIsInstanceOf($res, PromotionInterface::class); + + return $res; } } diff --git a/src/EventListener/AddChannelPricingIndicesSubscriber.php b/src/EventListener/AddChannelPricingIndicesSubscriber.php index 775039c..388c874 100644 --- a/src/EventListener/AddChannelPricingIndicesSubscriber.php +++ b/src/EventListener/AddChannelPricingIndicesSubscriber.php @@ -8,8 +8,8 @@ use Doctrine\ORM\Event\LoadClassMetadataEventArgs; use Doctrine\ORM\Events; use RuntimeException; -use function Safe\sprintf; use Setono\SyliusCatalogPromotionPlugin\Model\ChannelPricingInterface; +use function sprintf; final class AddChannelPricingIndicesSubscriber implements EventSubscriber { @@ -34,6 +34,7 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $event): void $columnName = $metadata->getColumnName('multiplier'); + /** @psalm-suppress PropertyTypeCoercion */ $metadata->table = array_merge_recursive([ 'indexes' => [ [ diff --git a/src/EventListener/AddTimestampableIndicesSubscriber.php b/src/EventListener/AddTimestampableIndicesSubscriber.php index 2165de5..9f0bf25 100644 --- a/src/EventListener/AddTimestampableIndicesSubscriber.php +++ b/src/EventListener/AddTimestampableIndicesSubscriber.php @@ -9,14 +9,17 @@ use Doctrine\ORM\Events; use Doctrine\ORM\Mapping\ClassMetadata; use RuntimeException; -use function Safe\sprintf; +use function sprintf; use Sylius\Component\Resource\Model\TimestampableInterface; final class AddTimestampableIndicesSubscriber implements EventSubscriber { - /** @var array */ - private $classes; + /** @var array */ + private array $classes; + /** + * @param array $classes + */ public function __construct(array $classes) { $this->classes = $classes; @@ -48,7 +51,9 @@ private static function addIndices(ClassMetadata $metadata): void if (!is_subclass_of($metadata->name, TimestampableInterface::class, true)) { throw new RuntimeException(sprintf( - 'The class %s must implement the interface, %s', $metadata->name, TimestampableInterface::class + 'The class %s must implement the interface, %s', + $metadata->name, + TimestampableInterface::class )); } @@ -65,6 +70,7 @@ private static function addIndices(ClassMetadata $metadata): void ]; } + /** @psalm-suppress PropertyTypeCoercion */ $metadata->table = array_merge_recursive([ 'indexes' => $indices, ], $metadata->table); diff --git a/src/Repository/ChannelPricingRepositoryInterface.php b/src/Repository/ChannelPricingRepositoryInterface.php index ecebf11..6b30881 100644 --- a/src/Repository/ChannelPricingRepositoryInterface.php +++ b/src/Repository/ChannelPricingRepositoryInterface.php @@ -9,14 +9,15 @@ interface ChannelPricingRepositoryInterface extends HasAnyBeenUpdatedSinceRepositoryInterface { /** - * This could reset the multiplier on ALL channel pricing + * This resets the multiplier on all channel pricings */ - public function resetMultiplier(DateTimeInterface $dateTime): void; + public function resetMultiplier(DateTimeInterface $dateTime, string $bulkIdentifier): void; /** * @param bool $exclusive If true this method will overwrite the multiplier instead of multiplying it */ public function updateMultiplier( + string $promotionCode, float $multiplier, array $productVariantIds, array $channelCodes, @@ -27,7 +28,7 @@ public function updateMultiplier( ): void; /** - * This method will update ALL channel prices based on the multiplier and updated after $dateTime + * This method will update ALL channel prices in the given bulk */ - public function updatePrices(DateTimeInterface $dateTime, string $bulkIdentifier): void; + public function updatePrices(string $bulkIdentifier): void; } diff --git a/src/Resources/config/services/command.xml b/src/Resources/config/services/command.xml index 2598d4d..863ead0 100644 --- a/src/Resources/config/services/command.xml +++ b/src/Resources/config/services/command.xml @@ -2,15 +2,22 @@ + + + 1800 + + + + - %kernel.logs_dir% + %setono_sylius_catalog_promotion.command.process_promotions.job.ttl%