custom/plugins/PickwareErpStarter/src/Stock/ProductStockManagedUpdater.php line 59

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\EntityManager;
  13. use Pickware\PickwareErpStarter\Product\Model\PickwareProductDefinition;
  14. use Pickware\PickwareErpStarter\Product\PickwareProductInsertedEvent;
  15. use Pickware\PickwareErpStarter\Stock\Model\LocationTypeDefinition;
  16. use Pickware\PickwareErpStarter\Stock\Model\StockCollection;
  17. use Pickware\PickwareErpStarter\Stock\Model\StockDefinition;
  18. use Pickware\PickwareErpStarter\StockApi\StockLocationReference;
  19. use Pickware\PickwareErpStarter\StockApi\StockMovement;
  20. use Pickware\PickwareErpStarter\StockApi\StockMovementService;
  21. use Shopware\Core\Content\Product\ProductCollection;
  22. use Shopware\Core\Content\Product\ProductDefinition;
  23. use Shopware\Core\Content\Product\ProductEntity;
  24. use Shopware\Core\Defaults;
  25. use Shopware\Core\Framework\Context;
  26. use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
  27. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  28. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  29. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  30. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  31. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
  32. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
  33. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  34. class ProductStockManagedUpdater implements EventSubscriberInterface
  35. {
  36.     private Connection $db;
  37.     private StockMovementService $stockMovementService;
  38.     private EntityManager $entityManager;
  39.     public function __construct(Connection $dbStockMovementService $stockMovementServiceEntityManager $entityManager)
  40.     {
  41.         $this->db $db;
  42.         $this->stockMovementService $stockMovementService;
  43.         $this->entityManager $entityManager;
  44.     }
  45.     public static function getSubscribedEvents(): array
  46.     {
  47.         return [
  48.             PickwareProductDefinition::ENTITY_WRITTEN_EVENT => 'pickwareProductWritten',
  49.             PickwareProductInsertedEvent::class => 'pickwareProductInserted',
  50.         ];
  51.     }
  52.     public function pickwareProductWritten(EntityWrittenEvent $event) : void
  53.     {
  54.         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  55.             return;
  56.         }
  57.         $pickwareProductStockManageDisabledIds = [];
  58.         $pickwareProductStockManageEnabledIds = [];
  59.         foreach ($event->getWriteResults() as $writeResult) {
  60.             $operation $writeResult->getOperation();
  61.             if ($operation === EntityWriteResult::OPERATION_UPDATE) {
  62.                 if ($writeResult->getChangeSet()
  63.                     && $writeResult->getChangeSet()->hasChanged('is_stock_management_disabled')
  64.                     || isset($writeResult->getPayload()['isStockManagementDisabled'])) {
  65.                     if ($writeResult->getPayload()['isStockManagementDisabled']) {
  66.                         $pickwareProductStockManageDisabledIds[] = $writeResult->getPrimaryKey();
  67.                     } else {
  68.                         $pickwareProductStockManageEnabledIds[] = $writeResult->getPrimaryKey();
  69.                     }
  70.                 }
  71.             }
  72.         }
  73.         $this->moveStockFromBinLocationsToWarehouse($pickwareProductStockManageDisabledIds$event->getContext());
  74.         $this->moveStockFromExternalToWarehouse($pickwareProductStockManageEnabledIds$event->getContext());
  75.     }
  76.     // Check if parent product of new inserted pickware product is not stock managed. If the parent is not stock managed
  77.     // we need to set the new created pickware product to non stock managed
  78.     public function pickwareProductInserted(PickwareProductInsertedEvent $event): void
  79.     {
  80.         $context $event->getContext();
  81.         $productCriteria = new Criteria();
  82.         $productCriteria->addFilter(new EqualsAnyFilter('id'array_unique($event->getProductIds())));
  83.         $productCriteria->addFilter(new NotFilter('OR', [
  84.             new EqualsFilter('parentId'null),
  85.         ]));
  86.         /** @var ProductCollection $variantProducts */
  87.         $variantProducts $this->entityManager->findBy(
  88.             ProductDefinition::class,
  89.             $productCriteria,
  90.             $context,
  91.             [
  92.                 'pickwareErpPickwareProduct',
  93.             ],
  94.         );
  95.         if ($variantProducts->count() === 0) {
  96.             return;
  97.         }
  98.         $variantPickwareProductIdsByParentProductIds = [];
  99.         foreach ($variantProducts as $variantProduct) {
  100.             $variantPickwareProductIdsByParentProductIds[$variantProduct->getParentId()] = $variantProduct
  101.                 ->getExtension('pickwareErpPickwareProduct')
  102.                 ->getId();
  103.         }
  104.         // As we can not fetch the associative parents of the product ids provided, we need to fetch them separately.
  105.         /** @var ProductCollection $variantProducts */
  106.         $parentPickwareProductsByVariants $this->entityManager->findBy(
  107.             ProductDefinition::class,
  108.             [
  109.                 'id' => array_keys($variantPickwareProductIdsByParentProductIds),
  110.                 'pickwareErpPickwareProduct.isStockManagementDisabled' => true,
  111.             ],
  112.             $context,
  113.         );
  114.         $variantPickwareProductsToBeUpdated = [];
  115.         foreach ($parentPickwareProductsByVariants->getElements() as $parentProduct) {
  116.             $variantPickwareProductsToBeUpdated[] = [
  117.                 'id' => $variantPickwareProductIdsByParentProductIds[$parentProduct->getId()],
  118.                 'isStockManagementDisabled' => true,
  119.             ];
  120.         }
  121.         $this->entityManager->update(
  122.             PickwareProductDefinition::class,
  123.             $variantPickwareProductsToBeUpdated,
  124.             $context,
  125.         );
  126.     }
  127.     public function applyStockManagementFromParentsToVariants(array $productIdsbool $isStockManagementDisabledContext $context): void
  128.     {
  129.         $variantPickwareProductsToBeUpdated = [];
  130.         /** @var ProductEntity $product */
  131.         $variantProducts $this->entityManager->findBy(
  132.             ProductDefinition::class,
  133.             [
  134.                 'parentId' => $productIds,
  135.             ],
  136.             $context,
  137.             [
  138.                 'pickwareErpPickwareProduct',
  139.             ],
  140.         );
  141.         foreach ($variantProducts as $variantProduct) {
  142.             $variantPickwareProductsToBeUpdated[] = [
  143.                 'id' => $variantProduct->getExtension('pickwareErpPickwareProduct')->getId(),
  144.                 'isStockManagementDisabled' => $isStockManagementDisabled,
  145.             ];
  146.         }
  147.         $this->entityManager->update(
  148.             PickwareProductDefinition::class,
  149.             $variantPickwareProductsToBeUpdated,
  150.             $context,
  151.         );
  152.     }
  153.     // Move all the stock that is still in bin locations to their respective warehouse as we do not track the stock of
  154.     // the product anymore
  155.     public function moveStockFromBinLocationsToWarehouse(array $pickwareProductIdsContext $context): void
  156.     {
  157.         if (count($pickwareProductIds) === 0) {
  158.             return;
  159.         }
  160.         /** @var ProductEntity $product */
  161.         $productIds $this->entityManager->findIdsBy(
  162.             ProductDefinition::class,
  163.             [
  164.                 'product.pickwareErpPickwareProduct.id' => $pickwareProductIds,
  165.             ],
  166.             $context,
  167.         );
  168.         /** @var StockCollection $stocks */
  169.         $stocks $this->entityManager->findBy(
  170.             StockDefinition::class,
  171.             [
  172.                 'productId' => $productIds,
  173.                 'locationType.technicalName' => LocationTypeDefinition::TECHNICAL_NAME_BIN_LOCATION,
  174.             ],
  175.             $context,
  176.             [
  177.                 'product',
  178.                 'binLocation',
  179.             ],
  180.         );
  181.         // Be sure to set closeout sale to false for these products
  182.         $this->db->executeStatement(
  183.             'UPDATE `product`
  184.             SET `is_closeout` = 0
  185.             WHERE `id` IN (:productId) AND `version_id` = (:liveVersionId)',
  186.             [
  187.                 'productId' => array_map('hex2bin'$productIds),
  188.                 'liveVersionId' => hex2bin(Defaults::LIVE_VERSION),
  189.             ],
  190.             [
  191.                 'productId' => Connection::PARAM_STR_ARRAY,
  192.             ],
  193.         );
  194.         // Delete the default bin location for that product
  195.         $this->db->executeStatement(
  196.             'UPDATE `pickware_erp_product_warehouse_configuration`
  197.             SET `default_bin_location_id` = null
  198.             WHERE `product_id` IN (:productId) AND `product_version_id` = (:liveVersionId)',
  199.             [
  200.                 'productId' => array_map('hex2bin'$productIds),
  201.                 'liveVersionId' => hex2bin(Defaults::LIVE_VERSION),
  202.             ],
  203.             [
  204.                 'productId' => Connection::PARAM_STR_ARRAY,
  205.             ],
  206.         );
  207.         $stockMovements = [];
  208.         foreach ($stocks as $stock) {
  209.             if ($stock->getQuantity() === 0) {
  210.                 // Stock in default bin location may be 0. No stock movement needs to be written.
  211.                 continue;
  212.             }
  213.             $stockMovements[] = StockMovement::create([
  214.                 'productId' => $stock->getProductId(),
  215.                 'source' => StockLocationReference::binLocation($stock->getBinLocationId()),
  216.                 'destination' => StockLocationReference::warehouse($stock->getBinLocation()->getWarehouseId()),
  217.                 'quantity' => $stock->getQuantity(),
  218.             ]);
  219.         }
  220.         $this->stockMovementService->moveStock($stockMovements$context);
  221.         $this->applyStockManagementFromParentsToVariants($productIdstrue$context);
  222.     }
  223.     // After the stock management is turned on again, we need to make sure, that there is no negative stock in the
  224.     // warehouses. Therefore, we move the positive equivalent to the negative quantity from unknown into the warehouses
  225.     public function moveStockFromExternalToWarehouse(array $pickwareProductIdsContext $context): void
  226.     {
  227.         if (count($pickwareProductIds) === 0) {
  228.             return;
  229.         }
  230.         $criteria = new Criteria();
  231.         $criteria->addFilter(new EqualsAnyFilter('product.pickwareErpPickwareProduct.id'$pickwareProductIds));
  232.         $criteria->addFilter(new EqualsAnyFilter('locationType.technicalName', [
  233.             LocationTypeDefinition::TECHNICAL_NAME_WAREHOUSE,
  234.             LocationTypeDefinition::TECHNICAL_NAME_BIN_LOCATION,
  235.         ]));
  236.         $criteria->addFilter(new RangeFilter('quantity', ['lt' => 0]));
  237.         /** @var StockCollection $stocks */
  238.         $stocks $this->entityManager->findBy(
  239.             StockDefinition::class,
  240.             $criteria,
  241.             $context,
  242.             [
  243.                 'locationType',
  244.             ],
  245.         );
  246.         $stockMovements = [];
  247.         foreach ($stocks->getElements() as $stock) {
  248.             // check whether the stock location is a bin location or warehouse and set it as destination
  249.             $destination null;
  250.             if ($stock->getLocationTypeTechnicalName() === LocationTypeDefinition::TECHNICAL_NAME_WAREHOUSE) {
  251.                 $destination StockLocationReference::warehouse($stock->getWarehouseId());
  252.             }
  253.             if ($stock->getLocationTypeTechnicalName() === LocationTypeDefinition::TECHNICAL_NAME_BIN_LOCATION) {
  254.                 $destination StockLocationReference::binLocation($stock->getBinLocationId());
  255.             }
  256.             if (isset($destination)) {
  257.                 $stockMovements[] = StockMovement::create([
  258.                     'productId' => $stock->getProductId(),
  259.                     'source' => StockLocationReference::unknown(),
  260.                     'destination' => $destination,
  261.                     'quantity' => -$stock->getQuantity(),
  262.                 ]);
  263.             }
  264.         }
  265.         if (count($stockMovements) !== 0) {
  266.             $this->stockMovementService->moveStock($stockMovements$context);
  267.         }
  268.         $this->applyStockManagementFromParentsToVariants(
  269.             $this->entityManager->findIdsBy(
  270.                 ProductDefinition::class,
  271.                 [
  272.                     'pickwareErpPickwareProduct.id' => $pickwareProductIds,
  273.                 ],
  274.                 $context,
  275.             ),
  276.             false,
  277.             $context,
  278.         );
  279.     }
  280. }