custom/plugins/ZeobvGetNotified/src/FlowBuilder/Subscriber/StockChangesSubscriber.php line 119

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Zeobv\GetNotified\FlowBuilder\Subscriber;
  4. use Psr\Log\LoggerInterface;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  7. use Doctrine\DBAL\Connection;
  8. use Doctrine\DBAL\Driver\ResultStatement;
  9. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  10. use Shopware\Core\Checkout\Order\OrderEvents;
  11. use Shopware\Core\Checkout\Order\OrderStates;
  12. use Shopware\Core\Content\Product\Events\ProductIndexerEvent;
  13. use Shopware\Core\Content\Product\ProductEvents;
  14. use Shopware\Core\Defaults;
  15. use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  17. use Shopware\Core\Framework\Uuid\Uuid;
  18. use Shopware\Core\System\StateMachine\Event\StateMachineTransitionEvent;
  19. use Zeobv\GetNotified\FlowBuilder\Event\ProductBackInStockEvent;
  20. use Zeobv\GetNotified\Service\ConfigService;
  21. class StockChangesSubscriber implements EventSubscriberInterface
  22. {
  23.     protected ConfigService $configService;
  24.     protected Connection $connection;
  25.     protected EventDispatcherInterface $dispatcher;
  26.     private LoggerInterface $logger;
  27.     public function __construct(
  28.         ConfigService $configService,
  29.         Connection $connection,
  30.         EventDispatcherInterface $dispatcher,
  31.         LoggerInterface $logger
  32.     ) {
  33.         $this->configService $configService;
  34.         $this->connection $connection;
  35.         $this->dispatcher $dispatcher;
  36.         $this->logger $logger;
  37.     }
  38.     public static function getSubscribedEvents(): array
  39.     {
  40.         return [
  41.             ProductEvents::PRODUCT_INDEXER_EVENT => ['productIndex', -1000],
  42.             StateMachineTransitionEvent::class => ['stateChanged', -1000],
  43.             OrderEvents::ORDER_LINE_ITEM_WRITTEN_EVENT => ['lineItemWritten', -1000],
  44.             OrderEvents::ORDER_LINE_ITEM_DELETED_EVENT => ['lineItemWritten', -1000],
  45.         ];
  46.     }
  47.     public function productIndex(ProductIndexerEvent $event): void
  48.     {
  49.         $this->checkStockAvailability($event->getIds());
  50.     }
  51.     /**
  52.      * If the product of an order item changed, it's possible for a product to come back in stock
  53.      */
  54.     public function lineItemWritten(EntityWrittenEvent $event): void
  55.     {
  56.         $ids = [];
  57.         foreach ($event->getWriteResults() as $result) {
  58.             if ($result->hasPayload('referencedId') && $result->getProperty('type') === LineItem::PRODUCT_LINE_ITEM_TYPE) {
  59.                 $ids[] = $result->getProperty('referencedId');
  60.             }
  61.             if ($result->getOperation() === EntityWriteResult::OPERATION_INSERT) {
  62.                 continue;
  63.             }
  64.             $changeSet $result->getChangeSet();
  65.             if (!$changeSet) {
  66.                 continue;
  67.             }
  68.             if ($result->getOperation() === EntityWriteResult::OPERATION_DELETE) {
  69.                 $ids[] = $changeSet->getBefore('referenced_id');
  70.                 $ids[] = $changeSet->getAfter('referenced_id');
  71.                 continue;
  72.             }
  73.             $type $changeSet->getBefore('type');
  74.             if ($type !== LineItem::PRODUCT_LINE_ITEM_TYPE) {
  75.                 continue;
  76.             }
  77.             if (!$changeSet->hasChanged('referenced_id') && !$changeSet->hasChanged('quantity')) {
  78.                 continue;
  79.             }
  80.             // If the quantity is increased(meaning the stock decreased) we don't need to trigger a back in stock event
  81.             if ($changeSet->getBefore('quantity') ?? <= $changeSet->getAfter('quantity') ?? 0) {
  82.                 continue;
  83.             }
  84.             $ids[] = $changeSet->getAfter('referenced_id');
  85.         }
  86.         $ids array_filter(array_unique($ids));
  87.         if (empty($ids)) {
  88.             return;
  89.         }
  90.         $this->checkStockAvailability($ids);
  91.     }
  92.     /**
  93.      * Triggered on order state changes
  94.      */
  95.     public function stateChanged(StateMachineTransitionEvent $event): void
  96.     {
  97.         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  98.             return;
  99.         }
  100.         if ($event->getEntityName() !== 'order') {
  101.             return;
  102.         }
  103.         # only cancelled can lead to a stock increase
  104.         if ($event->getToPlace()->getTechnicalName() !== OrderStates::STATE_CANCELLED) {
  105.             return;
  106.         }
  107.         $ids $this->getProductIdsOfOrder($event->getEntityId());
  108.         $this->checkStockAvailability($ids);
  109.     }
  110.     private function checkStockAvailability(array $ids): void
  111.     {
  112.         $result $this->getProductStockInformation($ids);
  113.         $useAvailableStock $this->configService->getUseAvailableStock();
  114.         foreach ($result as $productStockData) {
  115.             $stock $useAvailableStock $productStockData['available_stock'] : $productStockData['stock'];
  116.             if ($stock 1) {
  117.                 continue;
  118.             }
  119.             $this->dispatcher->dispatch(new ProductBackInStockEvent(
  120.                 Uuid::fromBytesToHex($productStockData['id'])
  121.             ));
  122.         }
  123.     }
  124.     private function getProductIdsOfOrder(string $orderId): array
  125.     {
  126.         $query $this->connection->createQueryBuilder();
  127.         $query->select(['referenced_id']);
  128.         $query->from('order_line_item');
  129.         $query->andWhere('type = :type');
  130.         $query->andWhere('order_id = :id');
  131.         $query->andWhere('version_id = :version');
  132.         $query->setParameter('id'Uuid::fromHexToBytes($orderId));
  133.         $query->setParameter('version'Uuid::fromHexToBytes(Defaults::LIVE_VERSION));
  134.         $query->setParameter('type'LineItem::PRODUCT_LINE_ITEM_TYPE);
  135.         try {
  136.             /** @var ResultStatement $statement */
  137.             $statement $query->execute();
  138.             $result $statement->fetchAll();
  139.             return array_column($result'referenced_id');
  140.         } catch (\Throwable $e) {
  141.             $this->logger->error($e->getMessage(), ['exception' => $e->getTrace()]);
  142.             return [];
  143.         }
  144.     }
  145.     private function getProductStockInformation(array $productIds): array
  146.     {
  147.         $query $this->connection->createQueryBuilder();
  148.         $query->select(['product.id as id''stock''available_stock']);
  149.         $query->from('product');
  150.         $query->rightJoin('product''zeo_stock_subscriber_product''zsp''zsp.product_id = product.id');
  151.         $query->where('product.id IN (:ids)');
  152.         $query->groupBy('product.id');
  153.         $query->setMaxResults(count($productIds));
  154.         $query->setParameter('ids',  Uuid::fromHexToBytesList($productIds), Connection::PARAM_STR_ARRAY);
  155.         try {
  156.             /** @var ResultStatement $statement */
  157.             $statement $query->execute();
  158.             return $statement->fetchAll();
  159.         } catch (\Throwable $e) {
  160.             $this->logger->error($e->getMessage(), ['exception' => $e->getTrace()]);
  161.             return [];
  162.         }
  163.     }
  164. }