<?php declare(strict_types=1);
namespace Shopware\Core\Content\Flow\Dispatching\Action;
use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
use Shopware\Core\Checkout\Order\SalesChannel\OrderService;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper;
use Shopware\Core\Framework\Event\DelayAware;
use Shopware\Core\Framework\Event\FlowEvent;
use Shopware\Core\Framework\Event\OrderAware;
use Shopware\Core\Framework\ShopwareHttpException;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\StateMachine\Exception\IllegalTransitionException;
use Shopware\Core\System\StateMachine\Exception\StateMachineNotFoundException;
use Symfony\Component\HttpFoundation\ParameterBag;
class SetOrderStateAction extends FlowAction
{
public const FORCE_TRANSITION = 'force_transition';
private const ORDER = 'order';
private const ORDER_DELIVERY = 'order_delivery';
private const ORDER_TRANSACTION = 'order_transaction';
private Connection $connection;
private LoggerInterface $logger;
private OrderService $orderService;
/**
* @internal
*/
public function __construct(
Connection $connection,
LoggerInterface $logger,
OrderService $orderService
) {
$this->connection = $connection;
$this->logger = $logger;
$this->orderService = $orderService;
}
public static function getName(): string
{
return 'action.set.order.state';
}
/**
* @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
*/
public static function getSubscribedEvents()
{
return [
self::getName() => 'handle',
];
}
public function requirements(): array
{
return [OrderAware::class, DelayAware::class];
}
public function handle(FlowEvent $event): void
{
$config = $event->getConfig();
if (empty($config)) {
return;
}
$baseEvent = $event->getEvent();
if (!$baseEvent instanceof OrderAware) {
return;
}
$context = $baseEvent->getContext();
if ($config[self::FORCE_TRANSITION] ?? false) {
$context->addState(self::FORCE_TRANSITION);
}
$this->connection->beginTransaction();
try {
$orderId = $baseEvent->getOrderId();
$transitions = array_filter([
self::ORDER => $config[self::ORDER] ?? null,
self::ORDER_DELIVERY => $config[self::ORDER_DELIVERY] ?? null,
self::ORDER_TRANSACTION => $config[self::ORDER_TRANSACTION] ?? null,
]);
foreach ($transitions as $machine => $toPlace) {
$this->transitState((string) $machine, $orderId, (string) $toPlace, $context);
}
$this->connection->commit();
} catch (ShopwareHttpException $e) {
$this->connection->rollBack();
$this->logger->error($e->getMessage());
} finally {
$context->removeState(self::FORCE_TRANSITION);
}
}
/**
* @throws IllegalTransitionException
* @throws StateMachineNotFoundException
*/
private function transitState(string $machine, string $orderId, string $toPlace, Context $context): void
{
if (!$toPlace) {
return;
}
$data = new ParameterBag();
$machineId = $machine === self::ORDER ? $orderId : $this->getMachineId($machine, $orderId);
if (!$machineId) {
throw new StateMachineNotFoundException($machine);
}
$actionName = $this->getAvailableActionName($machine, $machineId, $toPlace);
if (!$actionName) {
$actionName = $toPlace;
}
switch ($machine) {
case self::ORDER:
$this->orderService->orderStateTransition($orderId, $actionName, $data, $context);
return;
case self::ORDER_DELIVERY:
$this->orderService->orderDeliveryStateTransition($machineId, $actionName, $data, $context);
return;
case self::ORDER_TRANSACTION:
$this->orderService->orderTransactionStateTransition($machineId, $actionName, $data, $context);
return;
default:
throw new StateMachineNotFoundException($machine);
}
}
private function getMachineId(string $machine, string $orderId): ?string
{
$query = $this->connection->createQueryBuilder();
$query->select('LOWER(HEX(id))');
$query->from($machine);
$query->where('`order_id` = :id');
$query->setParameter('id', Uuid::fromHexToBytes($orderId));
return $query->execute()->fetchOne() ?: null;
}
private function getAvailableActionName(string $machine, string $machineId, string $toPlace): ?string
{
$query = $this->connection->createQueryBuilder();
$query->select('action_name');
$query->from('state_machine_transition');
$query->where('from_state_id = :fromStateId');
$query->andWhere('to_state_id = :toPlaceId');
$query->setParameters([
'fromStateId' => $this->getFromPlaceId($machine, $machineId),
'toPlaceId' => $this->getToPlaceId($toPlace, $machine),
]);
return $query->execute()->fetchOne() ?: null;
}
private function getToPlaceId(string $toPlace, string $machine): ?string
{
$query = $this->connection->createQueryBuilder();
$query->select('id');
$query->from('state_machine_state');
$query->where('technical_name = :toPlace');
$query->andWhere('state_machine_id = :stateMachineId');
$query->setParameters([
'toPlace' => $toPlace,
'stateMachineId' => $this->getStateMachineId($machine),
]);
return $query->execute()->fetchOne() ?: null;
}
private function getFromPlaceId(string $machine, string $machineId): ?string
{
$escaped = EntityDefinitionQueryHelper::escape($machine);
$query = $this->connection->createQueryBuilder();
$query->select('state_id');
$query->from($escaped);
$query->where('id = :id');
$query->setParameter('id', Uuid::fromHexToBytes($machineId));
return $query->execute()->fetchOne() ?: null;
}
private function getStateMachineId(string $machine): ?string
{
$query = $this->connection->createQueryBuilder();
$query->select('id');
$query->from('state_machine');
$query->where('technical_name = :technicalName');
$query->setParameter('technicalName', $machine . '.state');
return $query->execute()->fetchOne() ?: null;
}
}