<?php declare(strict_types=1);
namespace Shopware\Core\Checkout\Promotion\DataAbstractionLayer;
use Doctrine\DBAL\Connection;
use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
use Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\RetryableQuery;
use Shopware\Core\Framework\Uuid\Uuid;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PromotionRedemptionUpdater implements EventSubscriberInterface
{
/**
* @var Connection
*/
private $connection;
/**
* @internal
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
*/
public static function getSubscribedEvents()
{
return [
CheckoutOrderPlacedEvent::class => 'orderPlaced',
];
}
/**
* @param array<string> $ids
*/
public function update(array $ids, Context $context): void
{
$ids = array_unique(array_filter($ids));
if (empty($ids) || $context->getVersionId() !== Defaults::LIVE_VERSION) {
return;
}
$sql = <<<'SQL'
SELECT LOWER(HEX(order_line_item.promotion_id)) as promotion_id,
COUNT(DISTINCT order_line_item.id) as total,
LOWER(HEX(order_customer.customer_id)) as customer_id
FROM order_line_item
INNER JOIN order_customer
ON order_customer.order_id = order_line_item.order_id
AND order_customer.version_id = order_line_item.version_id
WHERE order_line_item.type = :type
AND order_line_item.promotion_id IN (:ids)
AND order_line_item.version_id = :versionId
GROUP BY order_line_item.promotion_id, order_customer.customer_id
SQL;
$promotions = $this->connection->fetchAll(
$sql,
['type' => PromotionProcessor::LINE_ITEM_TYPE, 'ids' => Uuid::fromHexToBytesList($ids), 'versionId' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
['ids' => Connection::PARAM_STR_ARRAY]
);
if (empty($promotions)) {
return;
}
$update = new RetryableQuery(
$this->connection,
$this->connection->prepare('UPDATE promotion SET order_count = :count, orders_per_customer_count = :customerCount WHERE id = :id')
);
// group the promotions to update each promotion with a single update statement
$promotions = $this->groupByPromotion($promotions);
foreach ($promotions as $id => $totals) {
$total = array_sum($totals);
$update->execute([
'id' => Uuid::fromHexToBytes($id),
'count' => (int) $total,
'customerCount' => json_encode($totals),
]);
}
}
public function orderPlaced(CheckoutOrderPlacedEvent $event): void
{
$lineItems = $event->getOrder()->getLineItems();
$customer = $event->getOrder()->getOrderCustomer();
if (!$lineItems || !$customer) {
return;
}
$promotionIds = [];
/** @var OrderLineItemEntity $lineItem */
foreach ($lineItems as $lineItem) {
if ($lineItem->getType() !== PromotionProcessor::LINE_ITEM_TYPE) {
continue;
}
$promotionId = $lineItem->getPromotionId();
if ($promotionId === null) {
continue;
}
$promotionIds[] = $promotionId;
}
if (!$promotionIds) {
return;
}
$allCustomerCounts = $this->getAllCustomerCounts($promotionIds);
$update = new RetryableQuery(
$this->connection,
$this->connection->prepare('UPDATE promotion SET order_count = order_count + 1, orders_per_customer_count = :customerCount WHERE id = :id')
);
foreach ($promotionIds as $promotionId) {
$customerId = $customer->getCustomerId();
if ($customerId !== null) {
$allCustomerCounts[$promotionId][$customerId] = 1 + ($allCustomerCounts[$promotionId][$customerId] ?? 0);
}
$update->execute([
'id' => Uuid::fromHexToBytes($promotionId),
'customerCount' => json_encode($allCustomerCounts[$promotionId]),
]);
}
}
/**
* @param array<mixed> $promotions
*
* @return array<mixed>
*/
private function groupByPromotion(array $promotions): array
{
$grouped = [];
foreach ($promotions as $promotion) {
$id = $promotion['promotion_id'];
$customerId = $promotion['customer_id'];
$grouped[$id][$customerId] = (int) $promotion['total'];
}
return $grouped;
}
/**
* @param array<string> $promotionIds
*
* @return array<string>
*/
private function getAllCustomerCounts(array $promotionIds): array
{
$allCustomerCounts = [];
$countResult = $this->connection->fetchAllAssociative(
'SELECT `id`, `orders_per_customer_count` FROM `promotion` WHERE `id` IN (:ids)',
['ids' => Uuid::fromHexToBytesList($promotionIds)],
['ids' => Connection::PARAM_STR_ARRAY]
);
foreach ($countResult as $row) {
if (!\is_string($row['orders_per_customer_count'])) {
$allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = [];
continue;
}
$customerCount = json_decode($row['orders_per_customer_count'], true);
if (!$customerCount) {
$allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = [];
continue;
}
$allCustomerCounts[Uuid::fromBytesToHex($row['id'])] = $customerCount;
}
return $allCustomerCounts;
}
}