diff --git a/InventoryAdminUi/Test/Mftf/Test/StorefrontPriceOfConfigurableProductAssignedToTestStockAndTestWebsiteWhenOptionOfDisplayingOutOfStockProductsEnabledTest.xml b/InventoryAdminUi/Test/Mftf/Test/StorefrontPriceOfConfigurableProductAssignedToTestStockAndTestWebsiteWhenOptionOfDisplayingOutOfStockProductsEnabledTest.xml index c9dd470ef25a..df768c1dd2ac 100644 --- a/InventoryAdminUi/Test/Mftf/Test/StorefrontPriceOfConfigurableProductAssignedToTestStockAndTestWebsiteWhenOptionOfDisplayingOutOfStockProductsEnabledTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/StorefrontPriceOfConfigurableProductAssignedToTestStockAndTestWebsiteWhenOptionOfDisplayingOutOfStockProductsEnabledTest.xml @@ -114,6 +114,8 @@ + + diff --git a/InventoryConfigurableProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php b/InventoryConfigurableProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php index bf7d279336db..e9e341ba30d4 100644 --- a/InventoryConfigurableProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php +++ b/InventoryConfigurableProduct/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php @@ -8,6 +8,7 @@ namespace Magento\InventoryConfigurableProduct\Plugin\CatalogInventory\Helper\Stock; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\CatalogInventory\Helper\Stock; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; @@ -74,6 +75,12 @@ public function beforeAssignStatusToProduct( $status = null ): array { if ($product->getTypeId() === Configurable::TYPE_CODE) { + $salable = (int)$product->getStatus() === Status::STATUS_ENABLED + && $product->getQuantityAndStockStatus()['is_in_stock']; + if (!$salable) { + return [$product, (int)$salable]; + } + $website = $this->storeManager->getWebsite(); $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()); $options = $this->configurable->getConfigurableOptions($product); diff --git a/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php b/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php index 481056b5eb18..c9fdcc77da0d 100644 --- a/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php +++ b/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php @@ -8,6 +8,8 @@ namespace Magento\InventoryConfigurableProductIndexer\Indexer; use Exception; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; @@ -40,22 +42,30 @@ class SelectBuilder */ private $metadataPool; + /** + * @var ScopeConfigInterface|null + */ + private $config; + /** * @param ResourceConnection $resourceConnection * @param IndexNameBuilder $indexNameBuilder * @param IndexNameResolverInterface $indexNameResolver * @param MetadataPool $metadataPool + * @param ScopeConfigInterface|null $config */ public function __construct( ResourceConnection $resourceConnection, IndexNameBuilder $indexNameBuilder, IndexNameResolverInterface $indexNameResolver, - MetadataPool $metadataPool + MetadataPool $metadataPool, + ScopeConfigInterface $config = null ) { $this->resourceConnection = $resourceConnection; $this->indexNameBuilder = $indexNameBuilder; $this->indexNameResolver = $indexNameResolver; $this->metadataPool = $metadataPool; + $this->config = $config ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -79,14 +89,19 @@ public function execute(int $stockId): Select $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $linkField = $metadata->getLinkField(); - + $manageStock = (int)$this->config->getValue('cataloginventory/item_options/manage_stock'); + $isSalableExpr = $manageStock + ? 'IF(MAX(parent_stock.manage_stock = 0), MAX(stock.is_salable), IF(MAX(parent_stock.is_in_stock) = 1, ' + . 'MAX(stock.is_salable), 0))' + : 'IF(MAX(parent_stock.use_config_manage_stock) = 0, IF(MAX(parent_stock.manage_stock) = 1 AND ' + . 'MAX(parent_stock.is_in_stock = 0), 0, MAX(stock.is_salable)), MAX(stock.is_salable))'; $select = $connection->select() ->from( ['stock' => $indexTableName], [ IndexStructure::SKU => 'parent_product_entity.sku', IndexStructure::QUANTITY => 'SUM(stock.quantity)', - IndexStructure::IS_SALABLE => 'MAX(stock.is_salable)', + IndexStructure::IS_SALABLE => $isSalableExpr, ] )->joinInner( ['product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')], @@ -100,6 +115,10 @@ public function execute(int $stockId): Select ['parent_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')], 'parent_product_entity.' . $linkField . ' = parent_link.parent_id', [] + )->joinInner( + ['parent_stock' => $this->resourceConnection->getTableName('cataloginventory_stock_item')], + 'parent_product_entity.entity_id = parent_stock.product_id', + [] ) ->group(['parent_product_entity.sku']); diff --git a/InventoryGroupedProductAdminUi/Plugin/Catalog/Model/Product/Link/ProcessSourceItemsAfterSaveAssociatedLinks.php b/InventoryGroupedProductAdminUi/Plugin/Catalog/Model/Product/ProcessSourceItemsPlugin.php similarity index 76% rename from InventoryGroupedProductAdminUi/Plugin/Catalog/Model/Product/Link/ProcessSourceItemsAfterSaveAssociatedLinks.php rename to InventoryGroupedProductAdminUi/Plugin/Catalog/Model/Product/ProcessSourceItemsPlugin.php index 24ac54936804..de903734816c 100644 --- a/InventoryGroupedProductAdminUi/Plugin/Catalog/Model/Product/Link/ProcessSourceItemsAfterSaveAssociatedLinks.php +++ b/InventoryGroupedProductAdminUi/Plugin/Catalog/Model/Product/ProcessSourceItemsPlugin.php @@ -5,19 +5,18 @@ */ declare(strict_types=1); -namespace Magento\InventoryGroupedProductAdminUi\Plugin\Catalog\Model\Product\Link; +namespace Magento\InventoryGroupedProductAdminUi\Plugin\Catalog\Model\Product; -use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Model\Product\Link; +use Magento\Catalog\Model\Product; use Magento\GroupedProduct\Model\Product\Type\Grouped as GroupedProductType; use Magento\InventoryApi\Api\Data\SourceItemInterface; use Magento\InventoryApi\Api\GetSourceItemsBySkuInterface; use Magento\InventoryCatalogAdminUi\Observer\SourceItemsProcessor; /** - * After save source links process child source items for reindex grouped product inventory. + * After save grouped product process child source items for reindex grouped product inventory. */ -class ProcessSourceItemsAfterSaveAssociatedLinks +class ProcessSourceItemsPlugin { /** * @var GetSourceItemsBySkuInterface @@ -42,22 +41,22 @@ public function __construct( } /** - * @param Link $subject - * @param Link $result - * @param ProductInterface $product - * @return Link + * Process source items after grouped product has been saved. + * + * @param Product $subject + * @param Product $result + * @return Product * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterSaveProductRelations( - Link $subject, - Link $result, - ProductInterface $product - ): Link { - if ($product->getTypeId() !== GroupedProductType::TYPE_CODE) { + public function afterAfterSave( + Product $subject, + Product $result + ): Product { + if ($result->getTypeId() !== GroupedProductType::TYPE_CODE) { return $result; } - foreach ($product->getProductLinks() as $productLink) { + foreach ($result->getProductLinks() as $productLink) { if ($productLink->getLinkType() === 'associated') { $this->processSourceItemsForSku($productLink->getLinkedProductSku()); } @@ -80,7 +79,7 @@ private function processSourceItemsForSku(string $sku): void $processData[] = [ SourceItemInterface::SOURCE_CODE => $sourceItem->getSourceCode(), SourceItemInterface::QUANTITY => $sourceItem->getQuantity(), - SourceItemInterface::STATUS => $sourceItem->getStatus() + SourceItemInterface::STATUS => $sourceItem->getStatus(), ]; } diff --git a/InventoryGroupedProductAdminUi/etc/di.xml b/InventoryGroupedProductAdminUi/etc/di.xml index 47d6c6e308f2..8c79c708d678 100644 --- a/InventoryGroupedProductAdminUi/etc/di.xml +++ b/InventoryGroupedProductAdminUi/etc/di.xml @@ -6,7 +6,7 @@ */ --> - - + + diff --git a/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php b/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php index 262e4951b661..412654c93f1e 100644 --- a/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php +++ b/InventoryGroupedProductIndexer/Indexer/SelectBuilder.php @@ -9,6 +9,8 @@ use Exception; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; @@ -41,22 +43,30 @@ class SelectBuilder */ private $metadataPool; + /** + * @var ScopeConfigInterface|null + */ + private $config; + /** * @param ResourceConnection $resourceConnection * @param IndexNameBuilder $indexNameBuilder * @param IndexNameResolverInterface $indexNameResolver * @param MetadataPool $metadataPool + * @param ScopeConfigInterface|null $config */ public function __construct( ResourceConnection $resourceConnection, IndexNameBuilder $indexNameBuilder, IndexNameResolverInterface $indexNameResolver, - MetadataPool $metadataPool + MetadataPool $metadataPool, + ScopeConfigInterface $config = null ) { $this->resourceConnection = $resourceConnection; $this->indexNameBuilder = $indexNameBuilder; $this->indexNameResolver = $indexNameResolver; $this->metadataPool = $metadataPool; + $this->config = $config ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -80,14 +90,19 @@ public function execute(int $stockId): Select $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $linkField = $metadata->getLinkField(); - + $manageStock = (int)$this->config->getValue('cataloginventory/item_options/manage_stock'); + $isSalableExpr = $manageStock + ? 'IF(MAX(parent_stock.manage_stock = 0), MAX(stock.is_salable), IF(MAX(parent_stock.is_in_stock) = 1, ' + . 'MAX(stock.is_salable), 0))' + : 'IF(MAX(parent_stock.use_config_manage_stock) = 0, IF(MAX(parent_stock.manage_stock) = 1 AND ' + . 'MAX(parent_stock.is_in_stock = 0), 0, MAX(stock.is_salable)), MAX(stock.is_salable))'; $select = $connection->select() ->from( ['stock' => $indexTableName], [ IndexStructure::SKU => 'parent_product_entity.sku', IndexStructure::QUANTITY => 'SUM(stock.quantity)', - IndexStructure::IS_SALABLE => 'MAX(stock.is_salable)', + IndexStructure::IS_SALABLE => $isSalableExpr, ] )->joinInner( ['product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')], @@ -95,13 +110,17 @@ public function execute(int $stockId): Select [] )->joinInner( ['parent_link' => $this->resourceConnection->getTableName('catalog_product_link')], - 'parent_link.linked_product_id = product_entity.entity_id + 'parent_link.linked_product_id = product_entity.entity_id AND parent_link.link_type_id = ' . Link::LINK_TYPE_GROUPED, [] )->joinInner( ['parent_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')], 'parent_product_entity.' . $linkField . ' = parent_link.product_id', [] + )->joinInner( + ['parent_stock' => $this->resourceConnection->getTableName('cataloginventory_stock_item')], + 'parent_product_entity.entity_id = parent_stock.product_id', + [] ) ->group(['parent_product_entity.sku']);