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

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