<?php
/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* http://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Eccube\Service;
use Eccube\Entity\Master\OrderStatus;
use Eccube\Entity\Order;
use Eccube\Repository\Master\OrderStatusRepository;
use Eccube\Service\PurchaseFlow\Processor\PointProcessor;
use Eccube\Service\PurchaseFlow\Processor\StockReduceProcessor;
use Eccube\Service\PurchaseFlow\PurchaseContext;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\Event;
use Symfony\Component\Workflow\StateMachine;
class OrderStateMachine implements EventSubscriberInterface
{
/**
* @var StateMachine
*/
private $machine;
/**
* @var OrderStatusRepository
*/
private $orderStatusRepository;
/**
* @var PointProcessor
*/
private $pointProcessor;
/**
* @var StockReduceProcessor
*/
private $stockReduceProcessor;
public function __construct(StateMachine $_orderStateMachine, OrderStatusRepository $orderStatusRepository, PointProcessor $pointProcessor, StockReduceProcessor $stockReduceProcessor)
{
$this->machine = $_orderStateMachine;
$this->orderStatusRepository = $orderStatusRepository;
$this->pointProcessor = $pointProcessor;
$this->stockReduceProcessor = $stockReduceProcessor;
}
/**
* 指定ステータスに遷移.
*
* @param Order $Order 受注
* @param OrderStatus $OrderStatus 遷移先ステータス
*/
public function apply(Order $Order, OrderStatus $OrderStatus)
{
$context = $this->newContext($Order);
$transition = $this->getTransition($context, $OrderStatus);
if ($transition) {
$this->machine->apply($context, $transition->getName());
} else {
throw new \InvalidArgumentException();
}
}
/**
* 指定ステータスに遷移できるかどうかを判定.
*
* @param Order $Order 受注
* @param OrderStatus $OrderStatus 遷移先ステータス
*
* @return boolean 指定ステータスに遷移できる場合はtrue
*/
public function can(Order $Order, OrderStatus $OrderStatus)
{
return !is_null($this->getTransition($this->newContext($Order), $OrderStatus));
}
private function getTransition(OrderStateMachineContext $context, OrderStatus $OrderStatus)
{
$transitions = $this->machine->getEnabledTransitions($context);
foreach ($transitions as $t) {
if (in_array($OrderStatus->getId(), $t->getTos())) {
return $t;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
'workflow.order.completed' => ['onCompleted'],
'workflow.order.transition.pay' => ['updatePaymentDate'],
'workflow.order.transition.cancel' => [['rollbackStock'], ['rollbackUsePoint']],
'workflow.order.transition.back_to_in_progress' => [['commitStock'], ['commitUsePoint']],
'workflow.order.transition.ship' => [['commitAddPoint']],
'workflow.order.transition.return' => [['rollbackUsePoint'], ['rollbackAddPoint']],
'workflow.order.transition.cancel_return' => [['commitUsePoint'], ['commitAddPoint']],
];
}
/*
* Event handlers.
*/
/**
* 入金日を更新する.
*
* @param Event $event
*/
public function updatePaymentDate(Event $event)
{
/* @var Order $Order */
$Order = $event->getSubject()->getOrder();
$Order->setPaymentDate(new \DateTime());
}
/**
* 会員の保有ポイントを減らす.
*
* @param Event $event
*
* @throws PurchaseFlow\PurchaseException
*/
public function commitUsePoint(Event $event)
{
/* @var Order $Order */
$Order = $event->getSubject()->getOrder();
$this->pointProcessor->prepare($Order, new PurchaseContext());
}
/**
* 利用ポイントを会員に戻す.
*
* @param Event $event
*/
public function rollbackUsePoint(Event $event)
{
/* @var Order $Order */
$Order = $event->getSubject()->getOrder();
$this->pointProcessor->rollback($Order, new PurchaseContext());
}
/**
* 在庫を減らす.
*
* @param Event $event
*
* @throws PurchaseFlow\PurchaseException
*/
public function commitStock(Event $event)
{
/* @var Order $Order */
$Order = $event->getSubject()->getOrder();
$this->stockReduceProcessor->prepare($Order, new PurchaseContext());
}
/**
* 在庫を戻す.
*
* @param Event $event
*/
public function rollbackStock(Event $event)
{
/* @var Order $Order */
$Order = $event->getSubject()->getOrder();
$this->stockReduceProcessor->rollback($Order, new PurchaseContext());
}
/**
* 会員に加算ポイントを付与する.
*
* @param Event $event
*/
public function commitAddPoint(Event $event)
{
/* @var Order $Order */
$Order = $event->getSubject()->getOrder();
$Customer = $Order->getCustomer();
if ($Customer) {
$Customer->setPoint(intval($Customer->getPoint()) + intval($Order->getAddPoint()));
}
}
/**
* 会員に付与した加算ポイントを取り消す.
*
* @param Event $event
*/
public function rollbackAddPoint(Event $event)
{
/* @var Order $Order */
$Order = $event->getSubject()->getOrder();
$Customer = $Order->getCustomer();
if ($Customer) {
$Customer->setPoint(intval($Customer->getPoint()) - intval($Order->getAddPoint()));
}
}
/**
* 受注ステータスを再設定.
* {@link StateMachine}によって遷移が終了したときには{@link Order#OrderStatus}のidが変更されるだけなのでOrderStatusを設定し直す.
*
* @param Event $event
*/
public function onCompleted(Event $event)
{
/** @var $context OrderStateMachineContext */
$context = $event->getSubject();
$Order = $context->getOrder();
$CompletedOrderStatus = $this->orderStatusRepository->find($context->getStatus());
$Order->setOrderStatus($CompletedOrderStatus);
}
private function newContext(Order $Order)
{
return new OrderStateMachineContext((string) $Order->getOrderStatus()->getId(), $Order);
}
}
class OrderStateMachineContext
{
/** @var string */
private $status;
/** @var Order */
private $Order;
/**
* OrderStateMachineContext constructor.
*
* @param string $status
* @param Order $Order
*/
public function __construct($status, Order $Order)
{
$this->status = $status;
$this->Order = $Order;
}
/**
* @return string
*/
public function getStatus()
{
return $this->status;
}
/**
* @param string $status
*/
public function setStatus($status)
{
$this->status = $status;
}
/**
* @return Order
*/
public function getOrder()
{
return $this->Order;
}
// order_state_machine.php の marking_store => property は、デフォルト値である marking を使用するよう強く推奨されている.
// EC-CUBE4.1 までは status を指定していたが、 Symfony5 よりエラーになるためエイリアスを作成して対応する.
/**
* Alias of getStatus()
*/
public function getMarking(): string
{
return $this->getStatus();
}
/**
* Alias of setStatus()
*/
public function setMarking(string $status): void
{
$this->setStatus($status);
}
}