custom/plugins/WbfkBundles/src/Subscriber/ProductSubscriber.php line 139

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Wbfk\Bundles\Subscriber;
  4. use Doctrine\DBAL\Connection;
  5. use Doctrine\DBAL\Exception;
  6. use Shopware\Core\Content\Product\ProductEvents;
  7. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  8. use Shopware\Core\Defaults;
  9. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  13. use Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent;
  14. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  15. use Shopware\Core\System\SystemConfig\SystemConfigService;
  16. use Shopware\Storefront\Page\Product\ProductPageCriteriaEvent;
  17. use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
  18. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  19. use Wbfk\Bundles\Core\Content\Product\DataAbstractionLayer\BundleFilter;
  20. use Wbfk\Bundles\Core\Content\Product\DataAbstractionLayer\BundleStockUpdater;
  21. use Wbfk\Bundles\Entity\Bundle\BundleProductCollection;
  22. use Wbfk\Bundles\Service\BundleProductDeliveryAndAvailabilityCalculator;
  23. use WbfkExtensions\Core\Content\WbfkProductExtension\WbfkProductExtensionEntity;
  24. use ApplifactionPriceMachine\Service\ProductPriceCalculationService;
  25. class ProductSubscriber implements EventSubscriberInterface
  26. {
  27.     public function __construct(
  28.         protected readonly BundleProductDeliveryAndAvailabilityCalculator $bundleProductDeliveryAndAvailabilityCalculator,
  29.         protected readonly EntityRepository $bundleProductRepository,
  30.         protected readonly SystemConfigService $systemConfigService,
  31.         protected readonly Connection $connection,
  32.         protected readonly BundleFilter $bundleFilter,
  33.         protected readonly BundleStockUpdater $bundleStockUpdater,
  34.         protected readonly ?ProductPriceCalculationService $productPriceCalculationService
  35.     ) {
  36.     }
  37.     public static function getSubscribedEvents(): array
  38.     {
  39.         return [
  40.             'sales_channel.product.loaded' => 'onProductLoaded',
  41.             ProductPageCriteriaEvent::class => 'onProductCriteriaLoaded',
  42.             ProductPageLoadedEvent::class => 'onProductPageLoaded',
  43.             ProductEvents::PRODUCT_WRITTEN_EVENT => [['queueBundleParentPriceCalculation'], ['updateBundleCloseoutStatus']],
  44.         ];
  45.     }
  46.     public function getBundles(string $productIdSalesChannelContext $scContext): BundleProductCollection
  47.     {
  48.         $context $scContext->getContext();
  49.         $criteria = new Criteria();
  50.         $criteria->setTitle("Getting bundle products child products data");
  51.         $criteria->addAssociation('childProduct');
  52.         $criteria->addFilter(new EqualsFilter('productId'$productId));
  53.         /** @var BundleProductCollection $bundles */
  54.         $bundles $this->bundleProductRepository->search($criteria$context)->getEntities();
  55.         return $bundles;
  56.     }
  57.     public function onProductLoaded(SalesChannelEntityLoadedEvent $event)
  58.     {
  59.         $products $event->getEntities();
  60.         $scContext $event->getSalesChannelContext();
  61.         $sysMaxQuantity $this->systemConfigService->getInt(
  62.             'core.cart.maxQuantity',
  63.             $scContext->getSalesChannel()->getId()
  64.         );
  65.         /** @var SalesChannelProductEntity $scProduct */
  66.         foreach ($products as $scProduct) {
  67.             $bundles $this->getBundles($scProduct->getId(), $scContext);
  68.             if ($bundles->count() === 0) {
  69.                 return null;
  70.             }
  71.             /** @noinspection PhpDeprecationInspection */
  72.             $scProduct->addExtension('wbfk_bundle_product'$bundles);
  73.             // Calculate the max available Quantity.
  74.             $steps $scProduct->get('purchaseSteps') ?? 1;
  75.             $min $scProduct->get('minPurchase') ?? 1;
  76.             $max $scProduct->get('maxPurchase') ?? $sysMaxQuantity;
  77.             // If the max Quantity is set to "0", we ignore it.
  78.             // #1124: ANG12299  kann nicht angenommen werden
  79.             $max $max <= $sysMaxQuantity $max;
  80.             // the amount of times the purchase step is fitting in between min and max added to the minimum
  81.             $max \floor(($max $min) / $steps) * $steps $min;
  82.             $maxPurchase = [$max];
  83.             // Consider Child-Articles
  84.             $parentCloseOut $scProduct->get('isCloseout');
  85.             foreach ($bundles as $bundleProduct) {
  86.                 $childProduct $bundleProduct->getChildProduct();
  87.                 /*
  88.                  * If the bundle or the child is in clearance sale (closeout) we restrict the bundle by the stock of the child article.
  89.                  * But we ignore the configured quantities on the child, since quantities can and should be configured on the bundle product.
  90.                  */
  91.                 if ($childProduct->get('isCloseout') || $parentCloseOut) {
  92.                     $stock = (int)$childProduct->get('availableStock');
  93.                     /** @var WbfkProductExtensionEntity $wpe */
  94.                     if ($wpe $childProduct->getExtension('WbfkProductExtension')) {
  95.                         $stock $wpe->getSuppliersTotalStock();
  96.                     }
  97.                     // Also consider if this child is multiple times in the bundle
  98.                     $maxPurchase[] = floor($stock $bundleProduct->getQuantity());
  99.                 }
  100.             }
  101.             $scProduct->setCalculatedMaxPurchase((int)min($maxPurchase));
  102.         }
  103.     }
  104.     public function onProductCriteriaLoaded(ProductPageCriteriaEvent $event): void
  105.     {
  106.         $event->getCriteria()->addAssociation('wbfk_bundle_product');
  107.     }
  108.     public function onProductPageLoaded(ProductPageLoadedEvent $event): void
  109.     {
  110.         $product $event->getPage()->getProduct();
  111.         if (!$product->getExtension('wbfk_bundle_product')->count()) {
  112.             return;
  113.         }
  114.         $this->bundleProductDeliveryAndAvailabilityCalculator->calculatedDeliveryAndAvailability($product$event->getSalesChannelContext());
  115.     }
  116.     public function queueBundleParentPriceCalculation(EntityWrittenEvent $event): void
  117.     {
  118.         // Only queue the price calculation of bundle parents, if the Price Machine Plugin is installed.
  119.         if (!$this->productPriceCalculationService) {
  120.             return;
  121.         }
  122.         $bundleParentIdSql '
  123.                 SELECT 
  124.                     LOWER(HEX(wbp.product_id)) AS parent_product_id
  125.                 FROM wbfk_bundle_product wbp
  126.                 WHERE LOWER(HEX(wbp.child_product_id)) = :childProductId
  127.                     AND LOWER(HEX(wbp.product_version_id)) = :versionId
  128.                     AND LOWER(HEX(wbp.child_product_version_id)) = :versionId
  129.                 GROUP BY wbp.product_id';
  130.         foreach ($event->getWriteResults() as $entityWriteResult) {
  131.             $bundleChildProductId $entityWriteResult->getPrimaryKey();
  132.             $bundleParentIdResults $this->connection->fetchAllAssociative($bundleParentIdSql, [
  133.                 'childProductId' => $bundleChildProductId,
  134.                 'versionId' => Defaults::LIVE_VERSION,
  135.             ]);
  136.             foreach ($bundleParentIdResults as $bundleParentIdResult) {
  137.                 $this->productPriceCalculationService->queueProductPriceCalculation($bundleParentIdResult['parent_product_id']);
  138.             }
  139.         }
  140.     }
  141.     /**
  142.      * @throws Exception
  143.      */
  144.     public function updateBundleCloseoutStatus(EntityWrittenEvent $event): void
  145.     {
  146.         $closeoutUpdatedProductIds array_reduce($event->getPayloads(), function (array $closeOutUpdatedProductIds, array $payload) {
  147.             if (isset($payload['isCloseout'])) {
  148.                 $closeOutUpdatedProductIds[] = $payload['id'];
  149.             }
  150.             return $closeOutUpdatedProductIds;
  151.         }, []);
  152.         $bundleChildrenIds $this->bundleFilter->filterBundleChildrenIds($closeoutUpdatedProductIds$event->getContext());
  153.         $bundleIds $this->bundleFilter->findParentIds($bundleChildrenIds$event->getContext());
  154.         $this->bundleStockUpdater->updateBundleStockAndAvailability($bundleIds$event->getContext());
  155.     }
  156. }