custom/plugins/CioPodProducts/src/Subscriber/BusinessEventCollectorSubscriber.php line 62

Open in your IDE?
  1. <?php
  2. namespace CioPodProducts\Subscriber;
  3. use CioPodProducts\CioPodProducts;
  4. use CioPodProducts\Error\PodMixedCartError;
  5. use CioPodProducts\Event\PodCustomerApprovalEvent;
  6. use CioPodProducts\Event\PodFinalApprovalEvent;
  7. use CioPodProducts\Event\PodOrderCreatedEvent;
  8. use CioPodProducts\Event\PodPhotographerGalleryUploadedEvent;
  9. use CioPodProducts\Event\PodSupplierPreviewUploadedEvent;
  10. use CioPodProducts\Helper\PodOrdersAssociationsHelper;
  11. use CioPodProducts\Service\PodTypeConfigService;
  12. use Shopware\Core\Checkout\Cart\Cart;
  13. use Shopware\Core\Checkout\Cart\Error\Error;
  14. use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
  15. use Shopware\Core\Checkout\Order\OrderEntity;
  16. use Shopware\Core\Content\Product\ProductEntity;
  17. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  20. use Shopware\Core\Framework\Event\BusinessEventCollectorEvent;
  21. use Shopware\Core\Framework\Uuid\Uuid;
  22. use Shopware\Storefront\Event\StorefrontRenderEvent;
  23. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPage;
  24. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  25. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  26. use Shopware\Core\Framework\Event\BusinessEventCollector;
  27. class BusinessEventCollectorSubscriber implements EventSubscriberInterface
  28. {
  29.     protected BusinessEventCollector $businessEventCollector;
  30.     protected EventDispatcherInterface $eventDispatcher;
  31.     protected EntityRepository $entityRepository;
  32.     protected EntityRepository $orderRepository;
  33.     protected PodTypeConfigService $podTypeConfigService;
  34.     public function __construct(BusinessEventCollector $businessEventCollectorEventDispatcherInterface $eventDispatcherEntityRepository $productRepositoryEntityRepository $orderRepositoryPodTypeConfigService $podTypeConfigService)
  35.     {
  36.         $this->businessEventCollector $businessEventCollector;
  37.         $this->eventDispatcher $eventDispatcher;
  38.         $this->entityRepository $productRepository;
  39.         $this->orderRepository $orderRepository;
  40.         $this->podTypeConfigService $podTypeConfigService;
  41.     }
  42.     public static function getSubscribedEvents()
  43.     {
  44.         return [
  45.             BusinessEventCollectorEvent::NAME => 'onBusinessEventCollectorEvent',
  46.             // Run after PodCheckoutFormSubscriber on the same event, so order customFields are enriched first.
  47.             CheckoutOrderPlacedEvent::class => ['onCheckoutOrderPlacedEvent', -1000],
  48.             StorefrontRenderEvent::class => 'onStorefrontRenderEvent',
  49.         ];
  50.     }
  51.     public function onBusinessEventCollectorEvent(BusinessEventCollectorEvent $event)
  52.     {
  53.         $collection $event->getCollection();
  54.         // add event PodOrderCreatedEvent to the collection
  55.         $definition $this->businessEventCollector->define(PodOrderCreatedEvent::class);
  56.         if (!$definition) {
  57.             return;
  58.         }
  59.         $collection->set($definition->getName(), $definition);
  60.         // add event PodPhotographerGalleryUploadedEvent to the collection
  61.         $definition $this->businessEventCollector->define(PodPhotographerGalleryUploadedEvent::class);
  62.         if (!$definition) {
  63.             return;
  64.         }
  65.         $collection->set($definition->getName(), $definition);
  66.         // add event PodSupplierPreviewUploadedEvent to the collection
  67.         $definition $this->businessEventCollector->define(PodSupplierPreviewUploadedEvent::class);
  68.         if (!$definition) {
  69.             return;
  70.         }
  71.         $collection->set($definition->getName(), $definition);
  72.         // add event PodCustomerApprovalEvent to the collection
  73.         $definition $this->businessEventCollector->define(PodCustomerApprovalEvent::class);
  74.         if (!$definition) {
  75.             return;
  76.         }
  77.         $collection->set($definition->getName(), $definition);
  78.         // add event PodFinalApprovalEvent to the collection
  79.         $definition $this->businessEventCollector->define(PodFinalApprovalEvent::class);
  80.         if (!$definition) {
  81.             return;
  82.         }
  83.         $collection->set($definition->getName(), $definition);
  84.     }
  85.     public function onCheckoutOrderPlacedEvent(CheckoutOrderPlacedEvent $event): void
  86.     {
  87.         $lineItems $event->getOrder()->getLineItems();
  88.         if ($lineItems === null || $lineItems->count() === 0) {
  89.             return;
  90.         }
  91.         // Sammle POD-LineItems und deren Typen
  92.         $podLineItems = [];
  93.         $podTypes = [];
  94.         foreach ($lineItems as $lineItem) {
  95.             $payload $lineItem->getPayload() ?? [];
  96.             $cf $payload['customFields'] ?? [];
  97.             $isPod = ($cf[CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD] ?? false) === true;
  98.             $podTypeKey $cf['custom_pod_products_podProductType'] ?? null;
  99.             if ($isPod && $podTypeKey) {
  100.                 $podLineItems[] = $lineItem;
  101.                 $podTypes[] = $podTypeKey;
  102.             }
  103.         }
  104.         // Keine POD-Positionen -> nichts tun
  105.         if (count($podLineItems) === 0) {
  106.             return;
  107.         }
  108.         $podTypes array_unique($podTypes);
  109.         // Mehrere Typen gleichzeitig => derzeit nicht zulässig, abbrechen
  110.         if (count($podTypes) > 1) {
  111.             return;
  112.         }
  113.         $podTypeKey $podTypes[0];
  114.         // Typ-Konfiguration laden
  115.         $typeConfig $this->podTypeConfigService->getTypeConfig($podTypeKey$event->getSalesChannelId());
  116.         $podUniqueItem $typeConfig['podUniqueItem'] ?? false;
  117.         // Wenn unique: exakt eine Position erlaubt
  118.         if ($podUniqueItem && count($podLineItems) !== 1) {
  119.             return;
  120.         }
  121.         // Wenn nicht unique: alle POD-LineItems müssen denselben Typ haben (oben geprüft) → erlaubt auch mehrere
  122.         // Für jede POD-Position: Payload-CustomFields sicherstellen
  123.         $updates = [];
  124.         foreach ($podLineItems as $li) {
  125.             $payload $li->getPayload() ?? [];
  126.             if (!isset($payload['customFields']) || !is_array($payload['customFields'])) {
  127.                 $payload['customFields'] = [];
  128.             }
  129.             $updates[] = [
  130.                 'id' => $li->getId(),
  131.                 'payload' => $payload,
  132.                 'productId' => $li->getProductId(),
  133.                 'referencedId' => $li->getReferencedId()
  134.             ];
  135.         }
  136.         if (!empty($updates)) {
  137.             $this->orderRepository->update([
  138.                 [
  139.                     'id' => $event->getOrder()->getId(),
  140.                     'lineItems' => $updates,
  141.                 ]
  142.             ], $event->getContext());
  143.         }
  144.         // Order mit Assoziationen nachladen und Event dispatchen
  145.         $criteria = new Criteria();
  146.         $criteria->addFilter(new EqualsFilter('id'$event->getOrder()->getId()));
  147.         PodOrdersAssociationsHelper::addAssociationsToCriteria($criteria);
  148.         $order $this->orderRepository->search($criteria$event->getContext())->first();
  149.         if (!$order instanceof OrderEntity) {
  150.             return;
  151.         }
  152.         $podOrderCreatedEvent = new PodOrderCreatedEvent($event->getContext(), $order$event->getSalesChannelId());
  153.         $this->eventDispatcher->dispatch($podOrderCreatedEvent);
  154.     }
  155.     public function onStorefrontRenderEvent(StorefrontRenderEvent $event)
  156.     {
  157.         $parameters $event->getParameters();
  158.         if (!\is_array($parameters) || !array_key_exists('page'$parameters)) {
  159.             return;
  160.         }
  161.         $page $parameters['page'];
  162.         if (!$page instanceof CheckoutConfirmPage) {
  163.             return;
  164.         }
  165.         $lineItems $page->getCart()->getLineItems();
  166.         $hasPodProduct false;
  167.         $hasNonPodProduct false;
  168.         $hasInvalidPodProduct false;
  169.         $hasPodProductTypes = [];
  170.         $errors = [];
  171.         foreach ($lineItems as $lineItem) {
  172.             $payload $lineItem->getPayload();
  173.             $customFields = (is_array($payload) && array_key_exists('customFields'$payload) && is_array($payload['customFields']))
  174.                 ? $payload['customFields']
  175.                 : [];
  176.             $isPod = (array_key_exists(CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD$customFields)
  177.                 && $customFields[CioPodProducts::POD_PRODUCTS_CUSTOM_FIELD_IS_POD] === true);
  178.             if ($isPod) {
  179.                 $hasPodProduct true;
  180.                 // CIO-AI-Driven: POD without type is considered invalid and must block (per requirement).
  181.                 if (!array_key_exists('custom_pod_products_podProductType'$customFields) || empty($customFields['custom_pod_products_podProductType'])) {
  182.                     $hasInvalidPodProduct true;
  183.                 }
  184.             } else {
  185.                 // CIO-AI-Driven: Anything that is not POD must not be mixed with POD (incl. promotion/custom line items).
  186.                 $hasNonPodProduct true;
  187.             }
  188.             if (array_key_exists('custom_pod_products_podProductType'$customFields) &&
  189.                 !empty($customFields['custom_pod_products_podProductType'])) {
  190.                 $hasPodProductTypes[] = $customFields['custom_pod_products_podProductType'];
  191.             }
  192.         }
  193.         $podProductTypes array_unique($hasPodProductTypes);
  194.         // POD darf niemals mit Nicht-POD gemischt werden (inkl. Promotion/Gutschein/Custom-LineItems).
  195.         // Zusätzlich: POD ohne Typ ist ungültig und blockiert.
  196.         if ($hasPodProduct && ($hasNonPodProduct || $hasInvalidPodProduct)) {
  197.             $this->addCartError($page->getCart(), new PodMixedCartError());
  198.             $this->addFlashErrors($event, [['type' => 'norm''message' => 'pod.checkout.noMixedBasketsError']]);
  199.             return;
  200.         }
  201.         // dd($podProductTypes, $this->podTypeConfigService->getTypeConfig($podProductTypes[0], $event->getSalesChannelContext()->getSalesChannelId()));
  202.         // mehrere pod product typen dürfen niemals gemischt bestellt werden
  203.         if ($hasPodProduct && count($podProductTypes) > 1) {
  204.             $this->addCartError($page->getCart(), new PodMixedCartError());
  205.             $this->addFlashErrors($event, [['type' => 'norm''message' => 'pod.checkout.noMixedBasketsError']]);
  206.             return;
  207.         }
  208.         if ($hasPodProduct && count($podProductTypes) === 1) {
  209.             $config $this->podTypeConfigService->getTypeConfig($podProductTypes[array_key_first($podProductTypes)], $event->getSalesChannelContext()->getSalesChannelId());
  210.             $podUniqueItem $config['podUniqueItem'];
  211.             // produkte mit einem pod produkt typen erlauben per default nur ein produkt im warenkorb
  212.             if ($podUniqueItem && $lineItems->count() > 1) {
  213.                 $this->addCartError($page->getCart(), new PodMixedCartError());
  214.                 $this->addFlashErrors($event, [['type' => 'norm''message' => 'pod.checkout.noMixedBasketsError']]);
  215.             }
  216.         }
  217.     }
  218.     protected function addFlashErrors(StorefrontRenderEvent $event, array $errors)
  219.     {
  220.         if (array_key_exists('errors'$event->getParameters())) {
  221.             $event->setParameter('errors'array_merge($event->getParameters()['errors'], $errors));
  222.         } else {
  223.             $event->setParameter('errors'$errors);
  224.         }
  225.     }
  226.     protected function addCartError(Cart $cartError $error)
  227.     {
  228.         $cart->addErrors(new PodMixedCartError());
  229.     }
  230. }