<?php declare(strict_types=1);
/*
* (c) shopware AG <info@shopware.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Swag\PayPal\Pos\Sync\Inventory;
use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
use Shopware\Core\Checkout\Order\OrderDefinition;
use Shopware\Core\Checkout\Order\OrderEvents;
use Shopware\Core\Checkout\Order\OrderStates;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\StateMachine\Event\StateMachineTransitionEvent;
use Swag\PayPal\Pos\MessageQueue\Message\InventoryUpdateMessage;
use Swag\PayPal\SwagPayPal;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DelayStamp;
/**
* @internal
*/
class StockSubscriber implements EventSubscriberInterface
{
/**
* Needed for letting the StockUpdater first recalculate availableStock
*/
private const DELAY = 10000;
private EntityRepository $orderLineItemRepository;
private MessageBusInterface $messageBus;
private EntityRepository $salesChannelRepository;
public function __construct(
EntityRepository $orderLineItemRepository,
MessageBusInterface $messageBus,
EntityRepository $salesChannelRepository
) {
$this->orderLineItemRepository = $orderLineItemRepository;
$this->messageBus = $messageBus;
$this->salesChannelRepository = $salesChannelRepository;
}
/**
* Returns a list of custom business events to listen where the product maybe changed
*/
public static function getSubscribedEvents(): array
{
return [
CheckoutOrderPlacedEvent::class => 'orderPlaced',
StateMachineTransitionEvent::class => 'stateChanged',
OrderEvents::ORDER_LINE_ITEM_WRITTEN_EVENT => 'lineItemWritten',
OrderEvents::ORDER_LINE_ITEM_DELETED_EVENT => 'lineItemWritten',
];
}
/**
* If the product of an order item changed, the stocks of the old product and the new product must be updated.
*/
public function lineItemWritten(EntityWrittenEvent $event): void
{
$ids = [];
foreach ($event->getWriteResults() as $result) {
if ($result->hasPayload('referencedId') && $result->getProperty('type') === LineItem::PRODUCT_LINE_ITEM_TYPE) {
$ids[] = $result->getProperty('referencedId');
}
if ($result->getOperation() === EntityWriteResult::OPERATION_INSERT) {
continue;
}
$changeSet = $result->getChangeSet();
if (!$changeSet) {
continue;
}
$type = $changeSet->getBefore('type');
if ($type !== LineItem::PRODUCT_LINE_ITEM_TYPE) {
continue;
}
if (!$changeSet->hasChanged('referenced_id') && !$changeSet->hasChanged('quantity')) {
continue;
}
$ids[] = $changeSet->getBefore('referenced_id');
$ids[] = $changeSet->getAfter('referenced_id');
}
$this->startSync(\array_unique(\array_filter($ids)), $event->getContext());
}
public function stateChanged(StateMachineTransitionEvent $event): void
{
$context = $event->getContext();
if ($context->getVersionId() !== Defaults::LIVE_VERSION) {
return;
}
if ($event->getEntityName() !== OrderDefinition::ENTITY_NAME) {
return;
}
$to = $event->getToPlace()->getTechnicalName();
$from = $event->getFromPlace()->getTechnicalName();
if ($to !== OrderStates::STATE_COMPLETED && $from !== OrderStates::STATE_COMPLETED
&& $to !== OrderStates::STATE_CANCELLED && $from !== OrderStates::STATE_CANCELLED) {
return;
}
if ($this->posSalesChannelDoesNotExist($context)) {
return;
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('orderId', $event->getEntityId()));
$criteria->addFilter(new EqualsFilter('type', LineItem::PRODUCT_LINE_ITEM_TYPE));
/** @var OrderLineItemCollection $lineItems */
$lineItems = $this->orderLineItemRepository->search($criteria, $context)->getEntities();
$ids = [];
foreach ($lineItems as $lineItem) {
/* @var OrderLineItemEntity $lineItem */
$ids[] = $lineItem->getReferencedId();
}
$this->startSync($ids, $context);
}
public function orderPlaced(CheckoutOrderPlacedEvent $event): void
{
$ids = [];
$lineItems = $event->getOrder()->getLineItems();
if ($lineItems === null) {
return;
}
foreach ($lineItems as $lineItem) {
if ($lineItem->getType() !== LineItem::PRODUCT_LINE_ITEM_TYPE) {
continue;
}
$ids[] = $lineItem->getReferencedId();
}
$this->startSync($ids, $event->getContext());
}
private function posSalesChannelDoesNotExist(Context $context): bool
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('typeId', SwagPayPal::SALES_CHANNEL_TYPE_POS));
$criteria->addFilter(new EqualsFilter('active', true));
return $this->salesChannelRepository->searchIds($criteria, $context)->getTotal() === 0;
}
private function startSync(array $productIds, Context $context): void
{
if (empty($productIds)) {
return;
}
if ($context->getVersionId() !== Defaults::LIVE_VERSION) {
return;
}
if ($this->posSalesChannelDoesNotExist($context)) {
return;
}
$message = new InventoryUpdateMessage();
$message->setIds($productIds);
$envelope = new Envelope($message, [
new DelayStamp(self::DELAY),
]);
$this->messageBus->dispatch($envelope);
}
}