custom/plugins/PickwareErpStarter/src/Stock/ProductAvailableStockUpdater.php line 71

Open in your IDE?
  1. <?php
  2. /*
  3.  * Copyright (c) Pickware GmbH. All rights reserved.
  4.  * This file is part of software that is released under a proprietary license.
  5.  * You must not copy, modify, distribute, make publicly available, or execute
  6.  * its contents or parts thereof without express permission by the copyright
  7.  * holder, unless otherwise permitted by law.
  8.  */
  9. declare(strict_types=1);
  10. namespace Pickware\PickwareErpStarter\Stock;
  11. use Doctrine\DBAL\Connection;
  12. use Pickware\DalBundle\RetryableTransaction;
  13. use Pickware\PickwareErpStarter\Product\PickwareProductInitializer;
  14. use Shopware\Core\Defaults;
  15. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  16. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  17. class ProductAvailableStockUpdater implements EventSubscriberInterface
  18. {
  19.     private Connection $db;
  20.     private PickwareProductInitializer $pickwareProductInitializer;
  21.     private EventDispatcherInterface $eventDispatcher;
  22.     public function __construct(
  23.         Connection $db,
  24.         PickwareProductInitializer $pickwareProductInitializer,
  25.         EventDispatcherInterface $eventDispatcher
  26.     ) {
  27.         $this->db $db;
  28.         $this->pickwareProductInitializer $pickwareProductInitializer;
  29.         $this->eventDispatcher $eventDispatcher;
  30.     }
  31.     /**
  32.      * available stock = product stock - reserved stock - stock not available for sale
  33.      *
  34.      * If any of the 3 stock values on the right changes, we need to recalculate the available stock.
  35.      */
  36.     public static function getSubscribedEvents(): array
  37.     {
  38.         return [
  39.             StockUpdatedForStockMovementsEvent::class => 'productStockUpdated',
  40.             ProductReservedStockUpdatedEvent::class => 'productReservedStockUpdated',
  41.             StockNotAvailableForSaleUpdatedEvent::class => 'stockNotAvailableForSaleUpdated',
  42.             StockNotAvailableForSaleUpdatedForAllProductsInWarehousesEvent::class => 'stockNotAvailableForSaleUpdatedForAllProductsInWarehouses',
  43.         ];
  44.     }
  45.     public function productStockUpdated(StockUpdatedForStockMovementsEvent $event): void
  46.     {
  47.         $productIds array_values(array_map(
  48.             fn (array $stockMovement) => $stockMovement['productId'],
  49.             $event->getStockMovements(),
  50.         ));
  51.         $this->recalculateProductAvailableStock($productIds);
  52.     }
  53.     public function productReservedStockUpdated(ProductReservedStockUpdatedEvent $event): void
  54.     {
  55.         $this->recalculateProductAvailableStock($event->getProductIds());
  56.     }
  57.     public function stockNotAvailableForSaleUpdated(StockNotAvailableForSaleUpdatedEvent $event): void
  58.     {
  59.         $this->recalculateProductAvailableStock($event->getProductIds());
  60.     }
  61.     public function stockNotAvailableForSaleUpdatedForAllProductsInWarehouses(StockNotAvailableForSaleUpdatedForAllProductsInWarehousesEvent $event): void
  62.     {
  63.         if (count($event->getWarehouseIds()) === 0) {
  64.             return;
  65.         }
  66.         $this->db->executeStatement(
  67.             'UPDATE `pickware_erp_warehouse_stock` warehouseStock
  68.             INNER JOIN `product`
  69.             ON `product`.`id` = warehouseStock.`product_id`
  70.             AND `product`.`version_id` = warehouseStock.`product_version_id`
  71.             SET product.`available_stock` = product.`available_stock` + (' . ($event->isStockNotAvailableForSaleIncrease() ? -1) . ' * warehouseStock.`quantity`)
  72.             WHERE warehouseStock.`warehouse_id` IN (:warehouseIds)
  73.             AND warehouseStock.`quantity` > 0
  74.             AND `product`.`version_id` = :liveVersionId;',
  75.             [
  76.                 'warehouseIds' => array_map('hex2bin'$event->getWarehouseIds()),
  77.                 'liveVersionId' => hex2bin(Defaults::LIVE_VERSION),
  78.             ],
  79.             [
  80.                 'warehouseIds' => Connection::PARAM_STR_ARRAY,
  81.             ],
  82.         );
  83.     }
  84.     public function recalculateProductAvailableStock(array $productIds): void
  85.     {
  86.         if (count($productIds) === 0) {
  87.             return;
  88.         }
  89.         $this->pickwareProductInitializer->ensurePickwareProductsExist($productIds);
  90.         RetryableTransaction::retryable($this->db, function () use ($productIds): void {
  91.             $this->db->executeStatement(
  92.                 'UPDATE `product`
  93.                 LEFT JOIN `pickware_erp_pickware_product` pickwareProduct
  94.                     ON pickwareProduct.`product_id` = `product`.`id`
  95.                     AND pickwareProduct.`product_version_id` = `product`.`version_id`
  96.                 # The available stock can be negative
  97.                 SET `product`.`available_stock` = `product`.`stock` - pickwareProduct.`stock_not_available_for_sale` - pickwareProduct.`reserved_stock`
  98.                 WHERE `product`.`version_id` = :liveVersionId
  99.                   AND `product`.`id` IN (:productIds)',
  100.                 [
  101.                     'liveVersionId' => hex2bin(Defaults::LIVE_VERSION),
  102.                     'productIds' => array_map('hex2bin'$productIds),
  103.                 ],
  104.                 [
  105.                     'productIds' => Connection::PARAM_STR_ARRAY,
  106.                 ],
  107.             );
  108.         });
  109.         $this->eventDispatcher->dispatch(new ProductAvailableStockUpdatedEvent($productIds));
  110.     }
  111. }