vendor/shopware/core/Checkout/Promotion/DataAbstractionLayer/PromotionRedemptionUpdater.php line 93

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Checkout\Promotion\DataAbstractionLayer;
  3. use Doctrine\DBAL\ArrayParameterType;
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
  6. use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
  7. use Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor;
  8. use Shopware\Core\Defaults;
  9. use Shopware\Core\Framework\Context;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\RetryableQuery;
  11. use Shopware\Core\Framework\Log\Package;
  12. use Shopware\Core\Framework\Uuid\Uuid;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. /**
  15.  * @internal
  16.  */
  17. #[Package('checkout')]
  18. class PromotionRedemptionUpdater implements EventSubscriberInterface
  19. {
  20.     /**
  21.      * @internal
  22.      */
  23.     public function __construct(private readonly Connection $connection)
  24.     {
  25.     }
  26.     /**
  27.      * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
  28.      */
  29.     public static function getSubscribedEvents(): array
  30.     {
  31.         return [
  32.             CheckoutOrderPlacedEvent::class => 'orderPlaced',
  33.         ];
  34.     }
  35.     /**
  36.      * @param array<string> $ids
  37.      */
  38.     public function update(array $idsContext $context): void
  39.     {
  40.         $ids array_unique(array_filter($ids));
  41.         if (empty($ids) || $context->getVersionId() !== Defaults::LIVE_VERSION) {
  42.             return;
  43.         }
  44.         $sql = <<<'SQL'
  45.                 SELECT LOWER(HEX(order_line_item.promotion_id)) as promotion_id,
  46.                        COUNT(DISTINCT order_line_item.id) as total,
  47.                        LOWER(HEX(order_customer.customer_id)) as customer_id
  48.                 FROM order_line_item
  49.                 INNER JOIN order_customer
  50.                     ON order_customer.order_id = order_line_item.order_id
  51.                     AND order_customer.version_id = order_line_item.version_id
  52.                 WHERE order_line_item.type = :type
  53.                 AND order_line_item.promotion_id IN (:ids)
  54.                 AND order_line_item.version_id = :versionId
  55.                 GROUP BY order_line_item.promotion_id, order_customer.customer_id
  56. SQL;
  57.         $promotions $this->connection->fetchAllAssociative(
  58.             $sql,
  59.             ['type' => PromotionProcessor::LINE_ITEM_TYPE'ids' => Uuid::fromHexToBytesList($ids), 'versionId' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
  60.             ['ids' => ArrayParameterType::STRING]
  61.         );
  62.         if (empty($promotions)) {
  63.             return;
  64.         }
  65.         $update = new RetryableQuery(
  66.             $this->connection,
  67.             $this->connection->prepare('UPDATE promotion SET order_count = :count, orders_per_customer_count = :customerCount WHERE id = :id')
  68.         );
  69.         // group the promotions to update each promotion with a single update statement
  70.         $promotions $this->groupByPromotion($promotions);
  71.         foreach ($promotions as $id => $totals) {
  72.             $total array_sum($totals);
  73.             $update->execute([
  74.                 'id' => Uuid::fromHexToBytes($id),
  75.                 'count' => (int) $total,
  76.                 'customerCount' => json_encode($totals\JSON_THROW_ON_ERROR),
  77.             ]);
  78.         }
  79.     }
  80.     public function orderPlaced(CheckoutOrderPlacedEvent $event): void
  81.     {
  82.         $lineItems $event->getOrder()->getLineItems();
  83.         $customer $event->getOrder()->getOrderCustomer();
  84.         if (!$lineItems || !$customer) {
  85.             return;
  86.         }
  87.         $promotionIds = [];
  88.         /** @var OrderLineItemEntity $lineItem */
  89.         foreach ($lineItems as $lineItem) {
  90.             if ($lineItem->getType() !== PromotionProcessor::LINE_ITEM_TYPE) {
  91.                 continue;
  92.             }
  93.             $promotionId $lineItem->getPromotionId();
  94.             if ($promotionId === null) {
  95.                 continue;
  96.             }
  97.             $promotionIds[] = $promotionId;
  98.         }
  99.         if (!$promotionIds) {
  100.             return;
  101.         }
  102.         $allCustomerCounts $this->getAllCustomerCounts($promotionIds);
  103.         $update = new RetryableQuery(
  104.             $this->connection,
  105.             $this->connection->prepare('UPDATE promotion SET order_count = order_count + 1, orders_per_customer_count = :customerCount WHERE id = :id')
  106.         );
  107.         foreach ($promotionIds as $promotionId) {
  108.             $customerId $customer->getCustomerId();
  109.             if ($customerId !== null) {
  110.                 $allCustomerCounts[$promotionId][$customerId] = + ($allCustomerCounts[$promotionId][$customerId] ?? 0);
  111.             }
  112.             $update->execute([
  113.                 'id' => Uuid::fromHexToBytes($promotionId),
  114.                 'customerCount' => json_encode($allCustomerCounts[$promotionId], \JSON_THROW_ON_ERROR),
  115.             ]);
  116.         }
  117.     }
  118.     /**
  119.      * @param array<mixed> $promotions
  120.      *
  121.      * @return array<mixed>
  122.      */
  123.     private function groupByPromotion(array $promotions): array
  124.     {
  125.         $grouped = [];
  126.         foreach ($promotions as $promotion) {
  127.             $id $promotion['promotion_id'];
  128.             $customerId $promotion['customer_id'];
  129.             $grouped[$id][$customerId] = (int) $promotion['total'];
  130.         }
  131.         return $grouped;
  132.     }
  133.     /**
  134.      * @param array<string> $promotionIds
  135.      *
  136.      * @return array<string>
  137.      */
  138.     private function getAllCustomerCounts(array $promotionIds): array
  139.     {
  140.         $allCustomerCounts = [];
  141.         $countResult $this->connection->fetchAllAssociative(
  142.             'SELECT `id`, `orders_per_customer_count` FROM `promotion` WHERE `id` IN (:ids)',
  143.             ['ids' => Uuid::fromHexToBytesList($promotionIds)],
  144.             ['ids' => ArrayParameterType::STRING]
  145.         );
  146.         foreach ($countResult as $row) {
  147.             if (!\is_string($row['orders_per_customer_count'])) {
  148.                 $allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = [];
  149.                 continue;
  150.             }
  151.             $customerCount json_decode($row['orders_per_customer_count'], true512\JSON_THROW_ON_ERROR);
  152.             if (!$customerCount) {
  153.                 $allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = [];
  154.                 continue;
  155.             }
  156.             $allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = $customerCount;
  157.         }
  158.         return $allCustomerCounts;
  159.     }
  160. }