diff --git a/InventoryBundleProduct/Model/IsProductSalableCondition/IsBundleSaleableCondition.php b/InventoryBundleProduct/Model/IsProductSalableCondition/IsBundleSaleableCondition.php deleted file mode 100644 index 5430921c4b8f..000000000000 --- a/InventoryBundleProduct/Model/IsProductSalableCondition/IsBundleSaleableCondition.php +++ /dev/null @@ -1,77 +0,0 @@ -bundleProductType = $type; - $this->repository = $repository; - $this->getBundleProductStockStatus = $getBundleProductStockStatus; - } - - /** - * Is product salable for bundle product. - * - * @param string $sku - * @param int $stockId - * - * @return bool - */ - public function execute(string $sku, int $stockId): bool - { - $status = false; - try { - $product = $this->repository->get($sku); - if ($product->getTypeId() === Type::TYPE_CODE) { - /** @noinspection PhpParamsInspection */ - $options = $this->bundleProductType->getOptionsCollection($product); - $status = (int)$this->getBundleProductStockStatus->execute( - $product, - $options->getItems(), - $stockId - ); - } - } catch (LocalizedException $e) { - $status = false; - } - - return $status; - } -} diff --git a/InventoryBundleProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php b/InventoryBundleProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php deleted file mode 100644 index 5e95a438df8a..000000000000 --- a/InventoryBundleProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php +++ /dev/null @@ -1,96 +0,0 @@ -bundleProductType = $bundleProductType; - $this->getBundleProductStockStatus = $getBundleProductStockStatus; - $this->storeManager = $storeManager; - $this->stockResolver = $stockResolver; - } - - /** - * Process bundle product stock status, considering bundle selections. - * - * @param Stock $subject - * @param Product $product - * @param int|null $status - * @return array - * @throws LocalizedException - * @throws NoSuchEntityException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeAssignStatusToProduct( - Stock $subject, - Product $product, - $status = null - ): array { - if ($product->getTypeId() === Type::TYPE_CODE) { - $website = $this->storeManager->getWebsite(); - $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()); - $options = $this->bundleProductType->getOptionsCollection($product); - try { - $status = (int)$this->getBundleProductStockStatus->execute( - $product, - $options->getItems(), - $stock->getStockId() - ); - } catch (LocalizedException $e) { - $status = 0; - } - } - - return [$product, $status]; - } -} diff --git a/InventoryBundleProduct/Plugin/InventorySales/IsBundleProductSalable.php b/InventoryBundleProduct/Plugin/InventorySales/IsBundleProductSalable.php index a519a4456a76..6cb952962f14 100644 --- a/InventoryBundleProduct/Plugin/InventorySales/IsBundleProductSalable.php +++ b/InventoryBundleProduct/Plugin/InventorySales/IsBundleProductSalable.php @@ -78,26 +78,17 @@ public function aroundExecute( int $stockId ): bool { try { - $types = $this->getProductTypesBySkus->execute([$sku]); - $isProductSalable = $proceed($sku, $stockId); - if (!isset($types[$sku]) || $types[$sku] !== Type::TYPE_CODE || !$isProductSalable) { + if (!$isProductSalable) { return $isProductSalable; } - // TODO: remove in https://github.com/magento/inventory/issues/3201 - // Product salability MUST NOT BE CALLED during product load. - // Tests stabilization. - /** @var \Magento\Framework\Registry $registry */ - $registry = ObjectManager::getInstance()->get(\Magento\Framework\Registry::class); - $key = 'inventory_check_product' . $sku; - - if ($registry->registry($key)) { - $product = $registry->registry($key); - } else { - $product = $this->productRepository->get($sku); + $types = $this->getProductTypesBySkus->execute([$sku]); + if (!isset($types[$sku]) || $types[$sku] !== Type::TYPE_CODE) { + return $isProductSalable; } + $product = $this->productRepository->get($sku); /** @noinspection PhpParamsInspection */ $options = $this->bundleProductType->getOptionsCollection($product); $status = $this->getBundleProductStockStatus->execute( diff --git a/InventoryBundleProduct/etc/di.xml b/InventoryBundleProduct/etc/di.xml index 665ab2b50cd6..e9d615337e5a 100644 --- a/InventoryBundleProduct/etc/di.xml +++ b/InventoryBundleProduct/etc/di.xml @@ -26,9 +26,6 @@ - - - diff --git a/InventoryCatalog/Model/IsProductSalable.php b/InventoryCatalog/Model/IsProductSalable.php new file mode 100644 index 000000000000..148006a0ef96 --- /dev/null +++ b/InventoryCatalog/Model/IsProductSalable.php @@ -0,0 +1,73 @@ +getStockIdForCurrentWebsite = $getStockIdForCurrentWebsite; + $this->areProductsSalable = $areProductsSalable; + } + + /** + * Verify product salable status. + * + * @param Product $product + * @return bool + */ + public function execute(Product $product): bool + { + if (null === $product->getSku() || (int)$product->getStatus() === Status::STATUS_DISABLED) { + return false; + } + if ($product->getData('is_salable') !== null) { + return (bool)$product->getData('is_salable'); + } + $stockId = $this->getStockIdForCurrentWebsite->execute(); + if (isset($this->productStatusCache[$stockId][$product->getSku()])) { + return $this->productStatusCache[$stockId][$product->getSku()]; + } + + $stockId = $this->getStockIdForCurrentWebsite->execute(); + $result = current($this->areProductsSalable->execute([$product->getSku()], $stockId)); + $salabilityStatus = $result->isSalable(); + $this->productStatusCache[$stockId][$product->getSku()] = $salabilityStatus; + + return $salabilityStatus; + } +} diff --git a/InventoryCatalog/Plugin/Catalog/Model/Type/Simple/IsSalablePlugin.php b/InventoryCatalog/Plugin/Catalog/Model/Type/Simple/IsSalablePlugin.php new file mode 100644 index 000000000000..3a6ea88803ef --- /dev/null +++ b/InventoryCatalog/Plugin/Catalog/Model/Type/Simple/IsSalablePlugin.php @@ -0,0 +1,46 @@ +isProductSalable = $isProductSalable; + } + + /** + * Fetches is salable status from multi-stock. + * + * @param Simple $subject + * @param \Closure $proceed + * @param Product $product + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundIsSalable(Simple $subject, \Closure $proceed, Product $product): bool + { + return $this->isProductSalable->execute($product); + } +} diff --git a/InventoryCatalog/Plugin/Catalog/Model/Type/Virtual/IsSalablePlugin.php b/InventoryCatalog/Plugin/Catalog/Model/Type/Virtual/IsSalablePlugin.php new file mode 100644 index 000000000000..2554d070a554 --- /dev/null +++ b/InventoryCatalog/Plugin/Catalog/Model/Type/Virtual/IsSalablePlugin.php @@ -0,0 +1,45 @@ +isProductSalable = $isProductSalable; + } + + /** + * Fetches is salable status from multi-stock. + * + * @param Product\Type\Virtual $subject + * @param \Closure $proceed + * @param Product $product + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundIsSalable(Product\Type\Virtual $subject, \Closure $proceed, Product $product): bool + { + return $this->isProductSalable->execute($product); + } +} diff --git a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php index 6593389973a0..721fc72de5e1 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php @@ -12,6 +12,7 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; use Magento\InventorySalesApi\Api\AreProductsSalableInterface; use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; @@ -49,25 +50,33 @@ class AdaptGetStockStatusPlugin */ private $stockResolver; + /** + * @var DefaultStockProviderInterface + */ + private $defaultStockProvider; + /** * @param AreProductsSalableInterface $areProductsSalable * @param GetProductSalableQtyInterface $getProductSalableQty * @param GetSkusByProductIdsInterface $getSkusByProductIds * @param StoreManagerInterface $storeManager * @param StockResolverInterface $stockResolver + * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( AreProductsSalableInterface $areProductsSalable, GetProductSalableQtyInterface $getProductSalableQty, GetSkusByProductIdsInterface $getSkusByProductIds, StoreManagerInterface $storeManager, - StockResolverInterface $stockResolver + StockResolverInterface $stockResolver, + DefaultStockProviderInterface $defaultStockProvider ) { $this->areProductsSalable = $areProductsSalable; $this->getProductSalableQty = $getProductSalableQty; $this->getSkusByProductIds = $getSkusByProductIds; $this->storeManager = $storeManager; $this->stockResolver = $stockResolver; + $this->defaultStockProvider = $defaultStockProvider; } /** @@ -92,6 +101,9 @@ public function afterGetStockStatus( ? $this->storeManager->getWebsite()->getCode() : $this->storeManager->getWebsite($scopeId)->getCode(); $stockId = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode)->getStockId(); + if ($this->defaultStockProvider->getId() === $stockId) { + return $stockStatus; + } $sku = $this->getSkusByProductIds->execute([$productId])[$productId]; $result = $this->areProductsSalable->execute([$sku], $stockId); diff --git a/InventoryCatalog/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php deleted file mode 100644 index ba54a8a82e64..000000000000 --- a/InventoryCatalog/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php +++ /dev/null @@ -1,108 +0,0 @@ -getStockIdForCurrentWebsite = $getStockIdForCurrentWebsite; - $this->areProductsSalable = $areProductsSalable; - $this->defaultStockProvider = $defaultStockProvider; - $this->getProductIdsBySkus = $getProductIdsBySkus; - } - - /** - * Assign stock status to product considering multi stock environment. - * - * @param Stock $subject - * @param Product $product - * @param int|null $status - * @return array - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeAssignStatusToProduct( - Stock $subject, - Product $product, - ?int $status = null - ): array { - if (null === $product->getSku()) { - return [$product, $status]; - } - - // TODO: remove in https://github.com/magento/inventory/issues/3201 - // Product salability MUST NOT BE CALLED during product load. - // Tests stabilization. - /** @var \Magento\Framework\Registry $registry */ - $registry = ObjectManager::getInstance()->get(\Magento\Framework\Registry::class); - $key = 'inventory_check_product' . $product->getSku(); - try { - if ($registry->registry($key)) { - $registry->unregister($key); - } - $registry->register($key, $product); - - $this->getProductIdsBySkus->execute([$product->getSku()]); - if (null === $status) { - $stockId = $this->getStockIdForCurrentWebsite->execute(); - $result = $this->areProductsSalable->execute([$product->getSku()], $stockId); - $result = current($result); - $registry->unregister($key); - return [$product, (int)$result->isSalable()]; - } - $registry->unregister($key); - } catch (NoSuchEntityException $e) { - $registry->unregister($key); - return [$product, $status]; - } - return [$product, $status]; - } -} diff --git a/InventoryCatalog/Plugin/Downloadable/Model/Product/Type/IsSalablePlugin.php b/InventoryCatalog/Plugin/Downloadable/Model/Product/Type/IsSalablePlugin.php new file mode 100644 index 000000000000..b4f41a6f021b --- /dev/null +++ b/InventoryCatalog/Plugin/Downloadable/Model/Product/Type/IsSalablePlugin.php @@ -0,0 +1,46 @@ +isProductSalable = $isProductSalable; + } + + /** + * Fetches is salable status from multi-stock. + * + * @param Type $subject + * @param \Closure $proceed + * @param Product $product + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundIsSalable(Type $subject, \Closure $proceed, Product $product): bool + { + return $subject->hasLinks($product) && $this->isProductSalable->execute($product); + } +} diff --git a/InventoryCatalog/composer.json b/InventoryCatalog/composer.json index c3651026905c..939c8bc967db 100644 --- a/InventoryCatalog/composer.json +++ b/InventoryCatalog/composer.json @@ -5,6 +5,7 @@ "php": "~7.3.0||~7.4.0", "magento/framework": "*", "magento/module-catalog": "*", + "magento/module-downloadable": "*", "magento/module-catalog-inventory": "*", "magento/module-inventory-api": "*", "magento/module-inventory-catalog-api": "*", diff --git a/InventoryCatalog/etc/di.xml b/InventoryCatalog/etc/di.xml index 5c29afadc6e6..a67b3c398975 100644 --- a/InventoryCatalog/etc/di.xml +++ b/InventoryCatalog/etc/di.xml @@ -52,7 +52,6 @@ - @@ -164,4 +163,13 @@ + + + + + + + + + diff --git a/InventoryCatalog/etc/events.xml b/InventoryCatalog/etc/events.xml new file mode 100644 index 000000000000..3ff4e7df341a --- /dev/null +++ b/InventoryCatalog/etc/events.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/InventoryConfigurableProduct/Model/IsProductSalableCondition/IsConfigurableProductSalable.php b/InventoryConfigurableProduct/Model/IsProductSalableCondition/IsConfigurableProductSalable.php deleted file mode 100644 index 05d5084f8088..000000000000 --- a/InventoryConfigurableProduct/Model/IsProductSalableCondition/IsConfigurableProductSalable.php +++ /dev/null @@ -1,84 +0,0 @@ -repository = $repository; - $this->areProductsSalable = $areProductsSalable; - $this->configurable = $configurable; - } - - /** - * Is configurable product salable. - * - * @param string $sku - * @param int $stockId - * - * @return bool - */ - public function execute(string $sku, int $stockId): bool - { - try { - $status = false; - $product = $this->repository->get($sku); - if ($product->getTypeId() === Configurable::TYPE_CODE) { - /** @noinspection PhpParamsInspection */ - $options = $this->configurable->getConfigurableOptions($product); - $skus = [[]]; - foreach ($options as $attribute) { - $skus[] = array_column($attribute, 'sku'); - } - $skus = array_merge(...$skus); - $results = $this->areProductsSalable->execute($skus, $stockId); - foreach ($results as $result) { - if ($result->isSalable()) { - $status = true; - break; - } - } - } - } catch (NoSuchEntityException $e) { - $status = false; - } - - return $status; - } -} diff --git a/InventoryConfigurableProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php b/InventoryConfigurableProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php deleted file mode 100644 index 68449ceca0ef..000000000000 --- a/InventoryConfigurableProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php +++ /dev/null @@ -1,97 +0,0 @@ -configurable = $configurable; - $this->areProductsSalable = $areProductsSalable; - $this->storeManager = $storeManager; - $this->stockResolver = $stockResolver; - } - - /** - * Process configurable product stock status, considering configurable options. - * - * @param Stock $subject - * @param Product $product - * @param int|null $status - * @return array - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeAssignStatusToProduct( - Stock $subject, - Product $product, - $status = null - ): array { - if ($product->getTypeId() === Configurable::TYPE_CODE) { - $website = $this->storeManager->getWebsite(); - $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()); - $options = $this->configurable->getConfigurableOptions($product); - $status = 0; - $skus = [[]]; - foreach ($options as $attribute) { - $skus[] = array_column($attribute, 'sku'); - } - $skus = array_merge(...$skus); - $results = $this->areProductsSalable->execute($skus, $stock->getStockId()); - foreach ($results as $result) { - if ($result->isSalable()) { - $status = 1; - break; - } - } - } - - return [$product, $status]; - } -} diff --git a/InventoryConfigurableProduct/Plugin/InventorySales/IsConfigurableProductSalable.php b/InventoryConfigurableProduct/Plugin/InventorySales/IsConfigurableProductSalable.php index 49922c0971b6..54b77e3a7f54 100644 --- a/InventoryConfigurableProduct/Plugin/InventorySales/IsConfigurableProductSalable.php +++ b/InventoryConfigurableProduct/Plugin/InventorySales/IsConfigurableProductSalable.php @@ -80,30 +80,18 @@ public function aroundExecute( int $stockId ): bool { try { - $types = $this->getProductTypesBySkus->execute([$sku]); - $isProductSalable = $proceed($sku, $stockId); - if (!isset($types[$sku]) || $types[$sku] !== Configurable::TYPE_CODE || !$isProductSalable) { + if (!$isProductSalable) { return $isProductSalable; } - // TODO: remove in https://github.com/magento/inventory/issues/3201 - // Product salability MUST NOT BE CALLED during product load. - // Tests stabilization. - /** @var \Magento\Framework\Registry $registry */ - $registry = ObjectManager::getInstance()->get(\Magento\Framework\Registry::class); - $key = 'inventory_check_product' . $sku; - - if ($registry->registry($key)) { - $product = $registry->registry($key); - } else { - $product = $this->productRepository->get($sku); + $types = $this->getProductTypesBySkus->execute([$sku]); + if (!isset($types[$sku]) || $types[$sku] !== Configurable::TYPE_CODE) { + return $isProductSalable; } + $product = $this->productRepository->get($sku); $resultStatus = false; - // TODO: remove in https://github.com/magento/inventory/issues/3201 - $product->unsetData('_cache_instance_used_product_attributes'); - $product->unsetData('_cache_instance_configurable_attributes'); $options = $this->configurableProductType->getConfigurableOptions($product); $skus = [[]]; foreach ($options as $attribute) { diff --git a/InventoryConfigurableProduct/composer.json b/InventoryConfigurableProduct/composer.json index bee13a97a54d..a04c0f281477 100644 --- a/InventoryConfigurableProduct/composer.json +++ b/InventoryConfigurableProduct/composer.json @@ -14,6 +14,9 @@ "magento/module-configurable-product": "*", "magento/module-inventory-sales": "*" }, + "suggest": { + "magento/module-inventory-sales": "*" + }, "type": "magento2-module", "license": [ "OSL-3.0", diff --git a/InventoryConfigurableProduct/etc/frontend/di.xml b/InventoryConfigurableProduct/etc/frontend/di.xml index e2e71d3e3cdf..68515ce01275 100644 --- a/InventoryConfigurableProduct/etc/frontend/di.xml +++ b/InventoryConfigurableProduct/etc/frontend/di.xml @@ -12,7 +12,4 @@ - - -