<?php declare(strict_types=1);
namespace Shopware\Core\Checkout\Customer\Subscriber;
use Doctrine\DBAL\Connection;
use Shopware\Core\Checkout\Order\OrderDefinition;
use Shopware\Core\Checkout\Order\OrderStates;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\RetryableQuery;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\DeleteCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\StateMachine\Event\StateMachineTransitionEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CustomerMetaFieldSubscriber implements EventSubscriberInterface
{
private Connection $connection;
/**
* @internal
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public static function getSubscribedEvents(): array
{
return [
StateMachineTransitionEvent::class => 'fillCustomerMetaDataFields',
PreWriteValidationEvent::class => 'deleteOrder',
];
}
public function fillCustomerMetaDataFields(StateMachineTransitionEvent $event): void
{
if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
return;
}
if ($event->getEntityName() !== 'order') {
return;
}
if ($event->getToPlace()->getTechnicalName() !== OrderStates::STATE_COMPLETED && $event->getFromPlace()->getTechnicalName() !== OrderStates::STATE_COMPLETED) {
return;
}
$this->updateCustomer([$event->getEntityId()]);
}
public function deleteOrder(PreWriteValidationEvent $event): void
{
if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
return;
}
$orderIds = [];
foreach ($event->getCommands() as $command) {
if ($command->getDefinition()->getClass() === OrderDefinition::class
&& $command instanceof DeleteCommand
) {
$orderIds[] = Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
}
}
$this->updateCustomer($orderIds, true);
}
/**
* @param array<string> $orderIds
*/
private function updateCustomer(array $orderIds, bool $isDelete = false): void
{
if (empty($orderIds)) {
return;
}
$customerIds = $this->connection->fetchFirstColumn(
'SELECT DISTINCT LOWER(HEX(customer_id)) FROM `order_customer` WHERE order_id IN (:ids) AND order_version_id = :version AND customer_id IS NOT NULL',
['ids' => Uuid::fromHexToBytesList($orderIds), 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)],
['ids' => Connection::PARAM_STR_ARRAY]
);
if (empty($customerIds)) {
return;
}
$parameters = [
'customerIds' => Uuid::fromHexToBytesList($customerIds),
'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION),
'state' => OrderStates::STATE_COMPLETED,
];
$types = [
'customerIds' => Connection::PARAM_STR_ARRAY,
];
$whereOrder = '';
if ($isDelete) {
$whereOrder = 'AND `order`.id NOT IN (:exceptOrderIds)';
$parameters['exceptOrderIds'] = Uuid::fromHexToBytesList($orderIds);
$types['exceptOrderIds'] = Connection::PARAM_STR_ARRAY;
}
$select = '
SELECT `order_customer`.customer_id as id,
COUNT(`order`.id) as order_count,
SUM(`order`.amount_total) as order_total_amount,
MAX(`order`.order_date_time) as last_order_date
FROM `order_customer`
INNER JOIN `order`
ON `order`.id = `order_customer`.order_id
AND `order`.version_id = `order_customer`.order_version_id
AND `order`.version_id = :version
' . $whereOrder . '
INNER JOIN `state_machine_state`
ON `state_machine_state`.id = `order`.state_id
AND `state_machine_state`.technical_name = :state
WHERE `order_customer`.customer_id IN (:customerIds)
GROUP BY `order_customer`.customer_id
';
$data = $this->connection->fetchAllAssociative($select, $parameters, $types);
if (empty($data)) {
foreach ($customerIds as $customerId) {
$data[] = [
'id' => Uuid::fromHexToBytes($customerId),
'order_count' => 0,
'order_total_amount' => 0,
'last_order_date' => null,
];
}
}
$update = new RetryableQuery(
$this->connection,
$this->connection->prepare('UPDATE `customer` SET order_count = :order_count, order_total_amount = :order_total_amount, last_order_date = :last_order_date WHERE id = :id')
);
foreach ($data as $record) {
$update->execute($record);
}
}
}