diff --git a/Inventory/Test/Integration/GetSourceCodesBySkusTest.php b/Inventory/Test/Integration/GetSourceCodesBySkusTest.php index be649065b1f6..e0971d032fe4 100644 --- a/Inventory/Test/Integration/GetSourceCodesBySkusTest.php +++ b/Inventory/Test/Integration/GetSourceCodesBySkusTest.php @@ -11,6 +11,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetSourceCodesBySkusTest extends TestCase { /** diff --git a/InventoryAdminUi/Test/Mftf/Test/AdminManageStockInConfigurationTurnedOffForDownloadableProductTest.xml b/InventoryAdminUi/Test/Mftf/Test/AdminManageStockInConfigurationTurnedOffForDownloadableProductTest.xml index 42ba567b3c32..ded4c92b1e09 100644 --- a/InventoryAdminUi/Test/Mftf/Test/AdminManageStockInConfigurationTurnedOffForDownloadableProductTest.xml +++ b/InventoryAdminUi/Test/Mftf/Test/AdminManageStockInConfigurationTurnedOffForDownloadableProductTest.xml @@ -23,7 +23,7 @@ - + @@ -76,7 +76,7 @@ - + diff --git a/InventoryAdvancedCheckout/Plugin/Model/AreProductsSalablePlugin.php b/InventoryAdvancedCheckout/Plugin/Model/AreProductsSalablePlugin.php index ed99695aae25..55e5996244b2 100644 --- a/InventoryAdvancedCheckout/Plugin/Model/AreProductsSalablePlugin.php +++ b/InventoryAdvancedCheckout/Plugin/Model/AreProductsSalablePlugin.php @@ -8,14 +8,10 @@ namespace Magento\InventoryAdvancedCheckout\Plugin\Model; use Magento\AdvancedCheckout\Model\AreProductsSalableForRequestedQtyInterface; -use Magento\AdvancedCheckout\Model\Data\IsProductsSalableForRequestedQtyResult; +use Magento\AdvancedCheckout\Model\Data\IsProductsSalableForRequestedQtyResultFactory; +use Magento\AdvancedCheckout\Model\Data\ProductQuantity; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\ObjectManagerInterface; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; -use Magento\InventorySalesApi\Api\AreProductsSalableInterface; -use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; -use Magento\InventorySalesApi\Api\StockResolverInterface; -use Magento\Store\Api\WebsiteRepositoryInterface; /** * Provides multi-sourcing capabilities for Advanced Checkout Order By SKU feature. @@ -23,60 +19,35 @@ class AreProductsSalablePlugin { /** - * @var AreProductsSalableInterface + * @var ProductRepositoryInterface */ - private $areProductsSalable; + private $productRepository; /** - * @var StockResolverInterface + * @var IsProductsSalableForRequestedQtyResultFactory */ - private $stockResolver; + private $isProductsSalableForRequestedQtyResultFactory; /** - * @var WebsiteRepositoryInterface - */ - private $websiteRepository; - - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @param AreProductsSalableInterface $areProductsSalable - * @param StockResolverInterface $stockResolver - * @param WebsiteRepositoryInterface $websiteRepository - * @param DefaultStockProviderInterface $defaultStockProvider - * @param ObjectManagerInterface $objectManager + * @param ProductRepositoryInterface $productRepository + * @param IsProductsSalableForRequestedQtyResultFactory $isProductsSalableForRequestedQtyResultFactory */ public function __construct( - AreProductsSalableInterface $areProductsSalable, - StockResolverInterface $stockResolver, - WebsiteRepositoryInterface $websiteRepository, - DefaultStockProviderInterface $defaultStockProvider, - ObjectManagerInterface $objectManager + ProductRepositoryInterface $productRepository, + IsProductsSalableForRequestedQtyResultFactory $isProductsSalableForRequestedQtyResultFactory ) { - $this->areProductsSalable = $areProductsSalable; - $this->stockResolver = $stockResolver; - $this->websiteRepository = $websiteRepository; - $this->defaultStockProvider = $defaultStockProvider; - $this->objectManager = $objectManager; + $this->productRepository = $productRepository; + $this->isProductsSalableForRequestedQtyResultFactory = $isProductsSalableForRequestedQtyResultFactory; } /** - * Get is product out of stock for given Product id in a given Website id in MSI context. + * Get products salable status for given sku requests. * * @param AreProductsSalableForRequestedQtyInterface $subject * @param callable $proceed - * @param \Magento\AdvancedCheckout\Model\Data\ProductQuantity[] $productQuantities + * @param ProductQuantity[] $productQuantities * @param int $websiteId * @return array - * @throws NoSuchEntityException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundExecute( @@ -85,22 +56,18 @@ public function aroundExecute( array $productQuantities, int $websiteId ): array { - $website = $this->websiteRepository->getById($websiteId); - $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()); - if ($this->defaultStockProvider->getId() === $stock->getStockId()) { - return $proceed($productQuantities, $websiteId); - } - - $skus = []; - foreach ($productQuantities as $productQuantity) { - $skus[] = $productQuantity->getSku(); - } $result = []; - foreach ($this->areProductsSalable->execute($skus, $stock->getStockId()) as $productStock) { - $result[] = $this->objectManager->create( - IsProductsSalableForRequestedQtyResult::class, - ['sku' => $productStock->getSku(), 'isSalable' => $productStock->isSalable()] - ); + foreach ($productQuantities as $productQuantity) { + try { + $product = $this->productRepository->get($productQuantity->getSku()); + $result[] = $this->isProductsSalableForRequestedQtyResultFactory->create( + ['sku' => $product->getSku(), 'isSalable' => $product->isSalable()] + ); + } catch (NoSuchEntityException $e) { + $result[] = $this->isProductsSalableForRequestedQtyResultFactory->create( + ['sku' => $productQuantity->getSku(), 'isSalable' => false] + ); + } } return $result; diff --git a/InventoryAdvancedCheckout/composer.json b/InventoryAdvancedCheckout/composer.json index 0e60183b67fa..42df77216c9a 100644 --- a/InventoryAdvancedCheckout/composer.json +++ b/InventoryAdvancedCheckout/composer.json @@ -4,9 +4,7 @@ "require": { "php": "~7.3.0||~7.4.0", "magento/framework": "*", - "magento/module-store": "*", - "magento/module-inventory-catalog-api": "*", - "magento/module-inventory-sales-api": "*" + "magento/module-catalog": "*" }, "suggest": { "magento/module-advanced-checkout": "*" diff --git a/InventoryBundleProduct/Model/GetBundleProductStockStatus.php b/InventoryBundleProduct/Model/GetBundleProductStockStatus.php index d99449f503e5..f520f502c8e7 100644 --- a/InventoryBundleProduct/Model/GetBundleProductStockStatus.php +++ b/InventoryBundleProduct/Model/GetBundleProductStockStatus.php @@ -24,7 +24,7 @@ class GetBundleProductStockStatus { /** - * @var GetProductSelection + * @var GetProductSelections */ private $getProductSelection; @@ -44,13 +44,13 @@ class GetBundleProductStockStatus private $getStockItemConfiguration; /** - * @param GetProductSelection $getProductSelection + * @param GetProductSelections $getProductSelection * @param AreProductsSalableForRequestedQtyInterface $areProductsSalableForRequestedQty * @param IsProductSalableForRequestedQtyRequestInterfaceFactory $isProductSalableForRequestedQtyRequestFactory * @param GetStockItemConfigurationInterface $getStockItemConfiguration */ public function __construct( - GetProductSelection $getProductSelection, + GetProductSelections $getProductSelection, AreProductsSalableForRequestedQtyInterface $areProductsSalableForRequestedQty, IsProductSalableForRequestedQtyRequestInterfaceFactory $isProductSalableForRequestedQtyRequestFactory, GetStockItemConfigurationInterface $getStockItemConfiguration diff --git a/InventoryBundleProduct/Model/GetProductSelection.php b/InventoryBundleProduct/Model/GetProductSelections.php similarity index 96% rename from InventoryBundleProduct/Model/GetProductSelection.php rename to InventoryBundleProduct/Model/GetProductSelections.php index 04e16baee82f..2afc938c6df8 100644 --- a/InventoryBundleProduct/Model/GetProductSelection.php +++ b/InventoryBundleProduct/Model/GetProductSelections.php @@ -15,9 +15,9 @@ use Magento\Framework\EntityManager\MetadataPool; /** - * Retrieve bundle product selection service. + * Retrieve bundle product selections service. */ -class GetProductSelection +class GetProductSelections { /** * @var CollectionFactory @@ -65,7 +65,6 @@ public function execute(ProductInterface $product, OptionInterface $option): Col $selectionsCollection->setFlag('product_children', true); $selectionsCollection->addFilterByRequiredOptions(); $selectionsCollection->setOptionIdsFilter([$option->getId()]); - $this->selectionCollectionFilterApplier->apply( $selectionsCollection, 'parent_product_id', diff --git a/InventoryBundleProduct/Model/IsBundleProductSalable.php b/InventoryBundleProduct/Model/IsBundleProductSalable.php new file mode 100644 index 000000000000..74bf8e1654e9 --- /dev/null +++ b/InventoryBundleProduct/Model/IsBundleProductSalable.php @@ -0,0 +1,142 @@ +getProductSelection = $getProductSelection; + $this->areProductsSalableForRequestedQty = $areProductsSalableForRequestedQty; + $this->isProductSalableForRequestedQtyRequestFactory = $isProductSalableForRequestedQtyRequestFactory; + $this->getStockItemConfiguration = $getStockItemConfiguration; + } + + /** + * Provides bundle product stock status. + * + * @param ProductInterface $product + * @param OptionInterface[] $bundleOptions + * @param int $stockId + * @return bool + * @throws LocalizedException + * @throws SkuIsNotAssignedToStockException + */ + public function execute(ProductInterface $product, array $bundleOptions, int $stockId): bool + { + $isSalable = false; + foreach ($bundleOptions as $option) { + $hasSalable = false; + $results = $this->getAreSalableSelections($product, $option, $stockId); + foreach ($results as $result) { + if ($result->isSalable()) { + $hasSalable = true; + break; + } + } + if ($hasSalable) { + $isSalable = true; + } + + if (!$hasSalable && $option->getRequired()) { + $isSalable = false; + break; + } + } + + return $isSalable; + } + + /** + * Get are bundle product selections salable. + * + * @param ProductInterface $product + * @param OptionInterface $option + * @param int $stockId + * @return IsProductSalableForRequestedQtyResultInterface[] + * @throws LocalizedException + * @throws SkuIsNotAssignedToStockException + */ + private function getAreSalableSelections(ProductInterface $product, OptionInterface $option, int $stockId): array + { + $bundleSelections = $this->getProductSelection->execute($product, $option); + $skuRequests = []; + foreach ($bundleSelections->getItems() as $selection) { + $skuRequests[] = $this->isProductSalableForRequestedQtyRequestFactory->create( + [ + 'sku' => (string)$selection->getSku(), + 'qty' => $this->getRequestedQty($selection, $stockId), + ] + ); + } + + return $this->areProductsSalableForRequestedQty->execute($skuRequests, $stockId); + } + + /** + * Get bundle product selection qty. + * + * @param Product $product + * @param int $stockId + * @return float + * @throws LocalizedException + * @throws SkuIsNotAssignedToStockException + */ + private function getRequestedQty(Product $product, int $stockId): float + { + if ((int)$product->getSelectionCanChangeQty()) { + $stockItemConfiguration = $this->getStockItemConfiguration->execute((string)$product->getSku(), $stockId); + return $stockItemConfiguration->getMinSaleQty(); + } + + return (float)$product->getSelectionQty(); + } +} diff --git a/InventoryBundleProduct/Model/IsProductSalableCondition/ProductOptionsCondition.php b/InventoryBundleProduct/Model/IsProductSalableCondition/ProductOptionsCondition.php new file mode 100644 index 000000000000..1f6878b8a580 --- /dev/null +++ b/InventoryBundleProduct/Model/IsProductSalableCondition/ProductOptionsCondition.php @@ -0,0 +1,63 @@ +type = $type; + $this->productRepository = $productRepository; + $this->isBundleProductSalable = $isBundleProductSalable; + } + + /** + * @inheritDoc + */ + public function execute(string $sku, int $stockId): bool + { + $product = $this->productRepository->get($sku); + if ($product->getTypeId() !== Type::TYPE_CODE) { + return true; + } + $options = $this->type->getOptionsCollection($product); + + return $this->isBundleProductSalable->execute($product, $options->getItems(), $stockId); + } +} diff --git a/InventoryBundleProduct/Plugin/Bundle/Model/Product/Type/AdaptIsSalablePlugin.php b/InventoryBundleProduct/Plugin/Bundle/Model/Product/Type/AdaptIsSalablePlugin.php deleted file mode 100644 index 481881e3cd66..000000000000 --- a/InventoryBundleProduct/Plugin/Bundle/Model/Product/Type/AdaptIsSalablePlugin.php +++ /dev/null @@ -1,104 +0,0 @@ -isProductSalable = $isProductSalable; - $this->storeManager = $storeManager; - $this->stockByWebsiteIdResolver = $stockByWebsiteIdResolver; - $this->getBundleProductStockStatus = $getBundleProductStockStatus; - $this->defaultStockProvider = $defaultStockProvider; - } - - /** - * Verify, is product salable in multi stock environment. - * - * @param Type $subject - * @param \Closure $proceed - * @param Product $product - * @return bool - */ - public function aroundIsSalable(Type $subject, \Closure $proceed, Product $product): bool - { - $salable = $product->getStatus() == Status::STATUS_ENABLED; - if ($salable && $product->hasData('is_salable')) { - $salable = $product->getData('is_salable'); - } - - if (!(bool)(int)$salable) { - return false; - } - - if ($product->hasData('all_items_salable')) { - return $product->getData('all_items_salable'); - } - - $website = $this->storeManager->getWebsite(); - $stock = $this->stockByWebsiteIdResolver->execute((int)$website->getId()); - if ($this->defaultStockProvider->getId() === $stock->getStockId()) { - return $proceed($product); - } - $options = $subject->getOptionsCollection($product); - $isSalable = $this->getBundleProductStockStatus->execute($product, $options->getItems(), $stock->getStockId()); - $product->setData('all_items_salable', $isSalable); - - return $isSalable; - } -} diff --git a/InventoryBundleProduct/Plugin/Bundle/Model/ResourceModel/Selection/Collection/AdaptAddQuantityFilterPlugin.php b/InventoryBundleProduct/Plugin/Bundle/Model/ResourceModel/Selection/Collection/AdaptAddQuantityFilterPlugin.php index a3192b94b108..bab6006cc510 100644 --- a/InventoryBundleProduct/Plugin/Bundle/Model/ResourceModel/Selection/Collection/AdaptAddQuantityFilterPlugin.php +++ b/InventoryBundleProduct/Plugin/Bundle/Model/ResourceModel/Selection/Collection/AdaptAddQuantityFilterPlugin.php @@ -8,8 +8,9 @@ namespace Magento\InventoryBundleProduct\Plugin\Bundle\Model\ResourceModel\Selection\Collection; use Magento\Bundle\Model\ResourceModel\Selection\Collection; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; -use Magento\InventorySalesApi\Api\AreProductsSalableInterface; +use Magento\CatalogInventory\Model\ResourceModel\Stock\Item; +use Magento\CatalogInventory\Model\Stock; +use Magento\InventoryIndexer\Model\StockIndexTableNameResolverInterface; use Magento\InventorySalesApi\Model\StockByWebsiteIdResolverInterface; use Magento\Store\Model\StoreManagerInterface; @@ -19,9 +20,9 @@ class AdaptAddQuantityFilterPlugin { /** - * @var AreProductsSalableInterface + * @var Item */ - private $areProductsSalable; + private $stockItem; /** * @var StoreManagerInterface @@ -34,26 +35,26 @@ class AdaptAddQuantityFilterPlugin private $stockByWebsiteIdResolver; /** - * @var DefaultStockProviderInterface + * @var StockIndexTableNameResolverInterface */ - private $defaultStockProvider; + private $stockIndexTableNameResolver; /** - * @param AreProductsSalableInterface $areProductsSalable + * @param Item $stockItem * @param StoreManagerInterface $storeManager * @param StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver - * @param DefaultStockProviderInterface $defaultStockProvider + * @param StockIndexTableNameResolverInterface $stockIndexTableNameResolver */ public function __construct( - AreProductsSalableInterface $areProductsSalable, + Item $stockItem, StoreManagerInterface $storeManager, StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver, - DefaultStockProviderInterface $defaultStockProvider + StockIndexTableNameResolverInterface $stockIndexTableNameResolver ) { - $this->areProductsSalable = $areProductsSalable; + $this->stockItem = $stockItem; $this->storeManager = $storeManager; $this->stockByWebsiteIdResolver = $stockByWebsiteIdResolver; - $this->defaultStockProvider = $defaultStockProvider; + $this->stockIndexTableNameResolver = $stockIndexTableNameResolver; } /** @@ -62,31 +63,43 @@ public function __construct( * @param Collection $subject * @param \Closure $proceed * @return Collection + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundAddQuantityFilter( Collection $subject, \Closure $proceed ): Collection { - $website = $this->storeManager->getWebsite(); - $stock = $this->stockByWebsiteIdResolver->execute((int)$website->getId()); - if ($this->defaultStockProvider->getId() === $stock->getStockId()) { - return $proceed(); - } - $skus = []; - $skusToExclude = []; - foreach ($subject->getData() as $item) { - $skus[] = (string)$item['sku']; - } - $results = $this->areProductsSalable->execute($skus, $stock->getStockId()); - foreach ($results as $result) { - if (!$result->isSalable()) { - $skusToExclude[] = $result->getSku(); - } - } - if ($skusToExclude) { - $subject->getSelect()->where('e.sku NOT IN(?)', implode(',', $skusToExclude)); - } - $subject->resetData(); + $store = $this->storeManager->getStore($subject->getStoreId()); + $stock = $this->stockByWebsiteIdResolver->execute((int)$store->getWebsiteId()); + $stockIndexTableName = $this->stockIndexTableNameResolver->execute($stock->getStockId()); + $manageStockExpr = $this->stockItem->getManageStockExpr('stock_item'); + $backordersExpr = $this->stockItem->getBackordersExpr('stock_item'); + $minQtyExpr = $subject->getConnection()->getCheckSql( + 'selection.selection_can_change_qty', + $this->stockItem->getMinSaleQtyExpr('stock_item'), + 'selection.selection_qty' + ); + $where = $manageStockExpr . ' = 0'; + $where .= ' OR (' + . 'inventory_stock.is_salable = ' . Stock::STOCK_IN_STOCK + . ' AND (' + . $backordersExpr . ' != ' . Stock::BACKORDERS_NO + . ' OR ' + . $minQtyExpr . ' <= inventory_stock.quantity' + . ')' + . ')'; + $subject->getSelect() + ->joinInner( + ['inventory_stock' => $stockIndexTableName], + 'e.sku = inventory_stock.sku', + [] + ) + ->joinInner( + ['stock_item' => $this->stockItem->getMainTable()], + 'selection.product_id = stock_item.product_id', + [] + ) + ->where($where); return $subject; } 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/InventoryCatalog/Model/IsProductSalable/IsBundleProductSalablePlugin.php b/InventoryBundleProduct/Plugin/InventoryCatalog/Model/IsProductSalable/IsBundleProductSalablePlugin.php new file mode 100644 index 000000000000..896474d88142 --- /dev/null +++ b/InventoryBundleProduct/Plugin/InventoryCatalog/Model/IsProductSalable/IsBundleProductSalablePlugin.php @@ -0,0 +1,103 @@ +type = $type; + $this->getProductSelections = $getProductSelections; + } + + /** + * Get bundle product status. + * + * @param IsProductSalable $subject + * @param \Closure $proceed + * @param ProductInterface $product + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundExecute(IsProductSalable $subject, \Closure $proceed, ProductInterface $product): bool + { + if ($product->getTypeId() !== Type::TYPE_CODE) { + return $proceed($product); + } + if ($product->hasData('all_items_salable')) { + return $product->getData('all_items_salable'); + } + + $isSalable = $proceed($product); + + if (!$isSalable) { + return false; + } + $bundleOptions = $this->type->getOptionsCollection($product); + $isSalable = $this->isSalable($bundleOptions, $product); + $product->setData('all_items_salable', $isSalable); + + return $isSalable; + } + + /** + * Verify bundle product has salable option. + * + * @param Collection $bundleOptions + * @param ProductInterface $product + * @return bool + * @throws \Exception + */ + private function isSalable(Collection $bundleOptions, ProductInterface $product): bool + { + $isSalable = false; + foreach ($bundleOptions as $option) { + $selections = $this->getProductSelections->execute($product, $option); + $hasSalable = false; + foreach ($selections as $selection) { + if ((int)$selection->getStatus() === Status::STATUS_ENABLED) { + $hasSalable = true; + break; + } + } + if ($hasSalable) { + $isSalable = true; + } + if (!$hasSalable && $option->getRequired()) { + $isSalable = false; + break; + } + } + + return $isSalable; + } +} diff --git a/InventoryBundleProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php b/InventoryBundleProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php index 5f32df6c82d9..5624c2452353 100644 --- a/InventoryBundleProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php +++ b/InventoryBundleProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php @@ -32,6 +32,8 @@ * @magentoDataFixture Magento_InventoryBundleProduct::Test/_files/source_items_for_bundle_options_on_default_source.php * @magentoDataFixture Magento_InventorySalesApi::Test/_files/quote.php * @magentoDataFixture Magento_InventoryIndexer::Test/_files/reindex_inventory.php + * + * @magentoDbIsolation disabled */ class PlaceOrderOnDefaultStockTest extends TestCase { diff --git a/InventoryBundleProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php b/InventoryBundleProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php index 3efe1fa18cc0..f29b9d89d3e9 100644 --- a/InventoryBundleProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php +++ b/InventoryBundleProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php @@ -21,6 +21,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class AddSalesQuoteItemOnDefaultStockTest extends TestCase { /** diff --git a/InventoryBundleProduct/composer.json b/InventoryBundleProduct/composer.json index 3c27572ebecc..f4aff7254a0c 100644 --- a/InventoryBundleProduct/composer.json +++ b/InventoryBundleProduct/composer.json @@ -6,14 +6,18 @@ "magento/framework": "*", "magento/module-bundle": "*", "magento/module-catalog": "*", + "magento/module-catalog-inventory": "*", "magento/module-inventory-api": "*", + "magento/module-inventory-catalog": "*", "magento/module-inventory-catalog-api": "*", "magento/module-inventory-configuration-api": "*", + "magento/module-inventory-indexer": "*", "magento/module-inventory-sales-api": "*", "magento/module-store": "*" }, "suggest": { - "magento/module-catalog-inventory": "*" + "magento/module-catalog-inventory": "*", + "magento/module-inventory-sales": "*" }, "type": "magento2-module", "license": [ diff --git a/InventoryBundleProduct/etc/di.xml b/InventoryBundleProduct/etc/di.xml index 622c345aecdb..7108f0851596 100644 --- a/InventoryBundleProduct/etc/di.xml +++ b/InventoryBundleProduct/etc/di.xml @@ -9,9 +9,6 @@ - - - @@ -26,7 +23,17 @@ - - + + + + + + + + true + Magento\InventoryBundleProduct\Model\IsProductSalableCondition\ProductOptionsCondition + + + diff --git a/InventoryBundleProductIndexer/Indexer/SelectBuilder.php b/InventoryBundleProductIndexer/Indexer/SelectBuilder.php index dc78c4bfdb8b..0a3e660a1725 100644 --- a/InventoryBundleProductIndexer/Indexer/SelectBuilder.php +++ b/InventoryBundleProductIndexer/Indexer/SelectBuilder.php @@ -79,7 +79,6 @@ public function execute(int $stockId): Select ->build(); $indexTableName = $this->indexNameResolver->resolveName($indexName); - $metadata = $this->metadataPool->getMetadata(ProductInterface::class); $linkField = $metadata->getLinkField(); @@ -89,7 +88,6 @@ public function execute(int $stockId): Select [ IndexStructure::SKU => 'parent_product_entity.sku', IndexStructure::QUANTITY => 'SUM(stock.quantity)', - IndexStructure::IS_SALABLE => 'MAX(stock.is_salable)', ] )->joinInner( ['product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')], diff --git a/InventoryBundleProductIndexer/Indexer/SourceItem/IndexDataBySkuListProvider.php b/InventoryBundleProductIndexer/Indexer/SourceItem/IndexDataBySkuListProvider.php index c6b83c5a22bd..4638d0d4b2d2 100644 --- a/InventoryBundleProductIndexer/Indexer/SourceItem/IndexDataBySkuListProvider.php +++ b/InventoryBundleProductIndexer/Indexer/SourceItem/IndexDataBySkuListProvider.php @@ -10,6 +10,7 @@ use Magento\Framework\App\ResourceConnection; use Magento\InventoryBundleProductIndexer\Indexer\SelectBuilder; use Magento\InventoryIndexer\Indexer\IndexStructure; +use Magento\InventorySalesApi\Api\AreProductsSalableInterface; /** * Returns all data for the index by source item list condition. @@ -26,16 +27,24 @@ class IndexDataBySkuListProvider */ private $selectBuilder; + /** + * @var AreProductsSalableInterface + */ + private $areProductsSalable; + /** * @param ResourceConnection $resourceConnection * @param SelectBuilder $selectBuilder + * @param AreProductsSalableInterface $areProductsSalable */ public function __construct( ResourceConnection $resourceConnection, - SelectBuilder $selectBuilder + SelectBuilder $selectBuilder, + AreProductsSalableInterface $areProductsSalable ) { $this->resourceConnection = $resourceConnection; $this->selectBuilder = $selectBuilder; + $this->areProductsSalable = $areProductsSalable; } /** @@ -53,7 +62,17 @@ public function execute(int $stockId, array $skuList): \ArrayIterator $select->where('stock.' . IndexStructure::SKU . ' IN (?)', $skuList); } $connection = $this->resourceConnection->getConnection(); + $results = $connection->fetchAll($select); + $bundleSkus = array_column($results, 'sku'); + $salableResults = $this->areProductsSalable->execute($bundleSkus, $stockId); + foreach ($salableResults as $salableResult) { + foreach ($results as &$result) { + if ($salableResult->getSku() === $result['sku']) { + $result['is_salable'] = (string)(int)$salableResult->isSalable(); + } + } + } - return new \ArrayIterator($connection->fetchAll($select)); + return new \ArrayIterator($results); } } diff --git a/InventoryBundleProductIndexer/Indexer/SourceItem/SourceItemIndexer.php b/InventoryBundleProductIndexer/Indexer/SourceItem/SourceItemIndexer.php index 2c0d805ec8f9..0ba622500574 100644 --- a/InventoryBundleProductIndexer/Indexer/SourceItem/SourceItemIndexer.php +++ b/InventoryBundleProductIndexer/Indexer/SourceItem/SourceItemIndexer.php @@ -9,8 +9,8 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\StateException; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\InventoryIndexer; +use Magento\InventoryIndexer\Indexer\Stock\PrepareIndexDataForClearingIndex; use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexHandlerInterface; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; @@ -52,9 +52,9 @@ class SourceItemIndexer private $siblingSkuListInStockProvider; /** - * @var DefaultStockProviderInterface + * @var PrepareIndexDataForClearingIndex */ - private $defaultStockProvider; + private $prepareIndexDataForClearingIndex; /** * @param ResourceConnection $resourceConnection @@ -63,7 +63,7 @@ class SourceItemIndexer * @param IndexStructureInterface $indexStructure * @param IndexDataBySkuListProvider $indexDataBySkuListProvider * @param SiblingSkuListInStockProvider $siblingSkuListInStockProvider - * @param DefaultStockProviderInterface $defaultStockProvider + * @param PrepareIndexDataForClearingIndex $prepareIndexDataForClearingIndex */ public function __construct( ResourceConnection $resourceConnection, @@ -72,7 +72,7 @@ public function __construct( IndexStructureInterface $indexStructure, IndexDataBySkuListProvider $indexDataBySkuListProvider, SiblingSkuListInStockProvider $siblingSkuListInStockProvider, - DefaultStockProviderInterface $defaultStockProvider + PrepareIndexDataForClearingIndex $prepareIndexDataForClearingIndex ) { $this->resourceConnection = $resourceConnection; $this->indexNameBuilder = $indexNameBuilder; @@ -80,7 +80,7 @@ public function __construct( $this->indexDataBySkuListProvider = $indexDataBySkuListProvider; $this->indexStructure = $indexStructure; $this->siblingSkuListInStockProvider = $siblingSkuListInStockProvider; - $this->defaultStockProvider = $defaultStockProvider; + $this->prepareIndexDataForClearingIndex = $prepareIndexDataForClearingIndex; } /** @@ -97,10 +97,6 @@ public function executeList(array $sourceItemIds): void foreach ($skuListInStockList as $skuListInStock) { $stockId = $skuListInStock->getStockId(); - - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } $skuList = $skuListInStock->getSkuList(); $mainIndexName = $this->indexNameBuilder @@ -117,7 +113,7 @@ public function executeList(array $sourceItemIds): void $this->indexHandler->cleanIndex( $mainIndexName, - $indexData, + $this->prepareIndexDataForClearingIndex->execute($indexData), ResourceConnection::DEFAULT_CONNECTION ); diff --git a/InventoryBundleProductIndexer/Indexer/Stock/IndexDataByStockIdProvider.php b/InventoryBundleProductIndexer/Indexer/Stock/IndexDataByStockIdProvider.php index f2c35d9f2f72..26c25b92b4af 100644 --- a/InventoryBundleProductIndexer/Indexer/Stock/IndexDataByStockIdProvider.php +++ b/InventoryBundleProductIndexer/Indexer/Stock/IndexDataByStockIdProvider.php @@ -9,6 +9,7 @@ use Magento\Framework\App\ResourceConnection; use Magento\InventoryBundleProductIndexer\Indexer\SelectBuilder; +use Magento\InventorySalesApi\Api\AreProductsSalableInterface; /** * Bundle products for given stock provider. @@ -25,21 +26,30 @@ class IndexDataByStockIdProvider */ private $resourceConnection; + /** + * @var AreProductsSalableInterface + */ + private $areProductsSalable; + /** * @param SelectBuilder $selectBuilder * @param ResourceConnection $resourceConnection + * @param AreProductsSalableInterface $areProductsSalable */ - public function __construct(SelectBuilder $selectBuilder, ResourceConnection $resourceConnection) - { + public function __construct( + SelectBuilder $selectBuilder, + ResourceConnection $resourceConnection, + AreProductsSalableInterface $areProductsSalable + ) { $this->selectBuilder = $selectBuilder; $this->resourceConnection = $resourceConnection; + $this->areProductsSalable = $areProductsSalable; } /** * Get bundle products for given stock id. * * @param int $stockId - * * @return \ArrayIterator * @throws \Exception */ @@ -47,7 +57,17 @@ public function execute(int $stockId): \ArrayIterator { $select = $this->selectBuilder->execute($stockId); $connection = $this->resourceConnection->getConnection(); + $results = $connection->fetchAll($select); + $bundleSkus = array_column($results, 'sku'); + $salableResults = $this->areProductsSalable->execute($bundleSkus, $stockId); + foreach ($salableResults as $salableResult) { + foreach ($results as &$result) { + if ($salableResult->getSku() === $result['sku']) { + $result['is_salable'] = (string)(int)$salableResult->isSalable(); + } + } + } - return new \ArrayIterator($connection->fetchAll($select)); + return new \ArrayIterator($results); } } diff --git a/InventoryBundleProductIndexer/Indexer/StockIndexer.php b/InventoryBundleProductIndexer/Indexer/StockIndexer.php index 9ffc7e3145e6..106aa2618c87 100644 --- a/InventoryBundleProductIndexer/Indexer/StockIndexer.php +++ b/InventoryBundleProductIndexer/Indexer/StockIndexer.php @@ -10,7 +10,6 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\StateException; use Magento\InventoryBundleProductIndexer\Indexer\Stock\IndexDataByStockIdProvider; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\Stock\GetAllStockIds; use Magento\InventoryIndexer\Indexer\Stock\PrepareIndexDataForClearingIndex; @@ -55,11 +54,6 @@ class StockIndexer */ private $indexTableSwitcher; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @var PrepareIndexDataForClearingIndex */ @@ -74,7 +68,6 @@ class StockIndexer * @param IndexNameBuilder $indexNameBuilder * @param IndexDataByStockIdProvider $indexDataByStockIdProvider * @param IndexTableSwitcherInterface $indexTableSwitcher - * @param DefaultStockProviderInterface $defaultStockProvider * @param PrepareIndexDataForClearingIndex $prepareIndexDataForClearingIndex */ public function __construct( @@ -84,7 +77,6 @@ public function __construct( IndexNameBuilder $indexNameBuilder, IndexDataByStockIdProvider $indexDataByStockIdProvider, IndexTableSwitcherInterface $indexTableSwitcher, - DefaultStockProviderInterface $defaultStockProvider, PrepareIndexDataForClearingIndex $prepareIndexDataForClearingIndex ) { $this->getAllStockIds = $getAllStockIds; @@ -93,7 +85,6 @@ public function __construct( $this->indexNameBuilder = $indexNameBuilder; $this->indexDataByStockIdProvider = $indexDataByStockIdProvider; $this->indexTableSwitcher = $indexTableSwitcher; - $this->defaultStockProvider = $defaultStockProvider; $this->prepareIndexDataForClearingIndex = $prepareIndexDataForClearingIndex; } @@ -131,10 +122,6 @@ public function executeRow(int $stockId) public function executeList(array $stockIds) { foreach ($stockIds as $stockId) { - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } - $mainIndexName = $this->indexNameBuilder ->setIndexId(InventoryIndexer::INDEXER_ID) ->addDimension('stock_', (string)$stockId) diff --git a/InventoryBundleProductIndexer/Plugin/Catalog/Model/Product/ReindexSourceItemsPlugin.php b/InventoryBundleProductIndexer/Plugin/Catalog/Model/Product/ReindexSourceItemsPlugin.php new file mode 100644 index 000000000000..dd1ca6267b7c --- /dev/null +++ b/InventoryBundleProductIndexer/Plugin/Catalog/Model/Product/ReindexSourceItemsPlugin.php @@ -0,0 +1,134 @@ +getSourceItemsBySku = $getSourceItemsBySku; + $this->getSourceItemIds = $getSourceItemIds; + $this->sourceItemIndexer = $sourceItemIndexer; + $this->getSkusByProductIds = $getSkusByProductIds; + } + + /** + * Reindex bundle source items after product save. + * + * @param Product $subject + * @param Product $result + * @return Product + * @throws LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAfterSave(Product $subject, Product $result): Product + { + if ($result->getTypeId() !== Type::TYPE_CODE) { + return $result; + } + $skus = $this->getSelectionsSku($result); + $sourceItems = [[]]; + foreach ($skus as $sku) { + $sourceItems[] = $this->getSourceItemsBySku->execute($sku); + } + $sourceItems = array_merge(...$sourceItems); + $sourceItemIds = $this->getSourceItemIds->execute($sourceItems); + $this->sourceItemIndexer->executeList($sourceItemIds); + + return $result; + } + + /** + * Retrieve bundle selections skus. + * + * @param Product $result + * @return array + */ + private function getSelectionsSku(Product $result): array + { + $skus = $this->getSkusFromOptions($result); + if ($skus) { + return $skus; + } + $bundleSelectionsData = $result->getBundleSelectionsData() ?: []; + foreach ($bundleSelectionsData as $option) { + $skus[] = array_column($option, 'sku'); + } + $skus = $skus ? array_merge(...$skus) : $skus; + if ($skus) { + return $skus; + } + $ids = []; + foreach ($bundleSelectionsData as $option) { + $ids[] = array_column($option, 'product_id'); + } + $ids = $ids ? array_merge(...$ids) : $ids; + + return $this->getSkusByProductIds->execute($ids); + } + + /** + * Retrieve selection's skus from bundle product options. + * + * @param Product $result + * @return array + */ + private function getSkusFromOptions(Product $result): array + { + $skus = []; + $options = $result->getExtensionAttributes()->getBundleProductOptions() ?: []; + foreach ($options as $option) { + $links = $option->getProductLinks(); + foreach ($links as $link) { + $skus[] = $link->getSku(); + } + } + return $skus; + } +} diff --git a/InventoryBundleProductIndexer/Plugin/InventoryIndexer/Model/Queue/GetDataForUpdate/AddBundleProductDataPlugin.php b/InventoryBundleProductIndexer/Plugin/InventoryIndexer/Model/Queue/GetDataForUpdate/AddBundleProductDataPlugin.php new file mode 100644 index 000000000000..8271e3e786ef --- /dev/null +++ b/InventoryBundleProductIndexer/Plugin/InventoryIndexer/Model/Queue/GetDataForUpdate/AddBundleProductDataPlugin.php @@ -0,0 +1,130 @@ +type = $type; + $this->getProductIdsBySkus = $getProductIdsBySkus; + $this->getSkusByProductIds = $getSkusByProductIds; + $this->getStockItemData = $getStockItemData; + $this->logger = $logger; + $this->areProductsSalable = $areProductsSalable; + } + + /** + * Add bundle product data to index data. + * + * @param GetDataForUpdate $subject + * @param array $result + * @param array $salabilityData + * @param int $stockId + * @return array + * @throws NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute(GetDataForUpdate $subject, array $result, array $salabilityData, int $stockId): array + { + $bundleData = []; + $skus = array_keys($result); + $childrenIds = $this->getProductIdsBySkus->execute($skus); + $bundleProductsIds = []; + foreach ($childrenIds as $childId) { + $bundleProductsIds[] = $this->type->getParentIdsByChild($childId); + } + $bundleProductsIds = array_merge(...$bundleProductsIds); + $bundleSkus = $bundleProductsIds ? $this->getSkusByProductIds->execute($bundleProductsIds) : []; + $areBundleProdcutsSalable = $this->areProductsSalable->execute($bundleSkus, $stockId); + foreach ($areBundleProdcutsSalable as $salableResult) { + if ($salableResult->isSalable() !== $this->getIndexSalabilityStatus($salableResult->getSku(), $stockId)) { + $bundleData[$salableResult->getSku()] = $salableResult->isSalable(); + } + } + + return array_merge($result, $bundleData); + } + + /** + * Get current index is_salable value. + * + * @param string $sku + * @param int $stockId + * @return bool|null + */ + private function getIndexSalabilityStatus(string $sku, int $stockId): ?bool + { + try { + $data = $this->getStockItemData->execute($sku, $stockId); + $isSalable = $data ? (bool)$data[GetStockItemDataInterface::IS_SALABLE] : false; + } catch (LocalizedException $e) { + $this->logger->error($e->getLogMessage()); + return null; + } + + return $isSalable; + } +} diff --git a/InventoryBundleProductIndexer/composer.json b/InventoryBundleProductIndexer/composer.json index 9650c2c35df5..f06ad139d1a6 100644 --- a/InventoryBundleProductIndexer/composer.json +++ b/InventoryBundleProductIndexer/composer.json @@ -9,7 +9,8 @@ "magento/module-inventory-api": "*", "magento/module-inventory-catalog-api": "*", "magento/module-inventory-indexer": "*", - "magento/module-inventory-multi-dimensional-indexer-api": "*" + "magento/module-inventory-multi-dimensional-indexer-api": "*", + "magento/module-inventory-sales-api": "*" }, "suggest": { "magento/module-inventory": "*" diff --git a/InventoryBundleProductIndexer/etc/di.xml b/InventoryBundleProductIndexer/etc/di.xml index 73b7a0bf459e..1232eba41a50 100644 --- a/InventoryBundleProductIndexer/etc/di.xml +++ b/InventoryBundleProductIndexer/etc/di.xml @@ -25,9 +25,10 @@ Magento\InventoryIndexer\Indexer\IndexStructure - - - - + + + + + diff --git a/InventoryBundleProductIndexer/etc/webapi_rest/di.xml b/InventoryBundleProductIndexer/etc/webapi_rest/di.xml new file mode 100644 index 000000000000..d8d2a041908c --- /dev/null +++ b/InventoryBundleProductIndexer/etc/webapi_rest/di.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/InventoryBundleProductIndexer/etc/webapi_soap/di.xml b/InventoryBundleProductIndexer/etc/webapi_soap/di.xml new file mode 100644 index 000000000000..d8d2a041908c --- /dev/null +++ b/InventoryBundleProductIndexer/etc/webapi_soap/di.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/InventoryCache/etc/di.xml b/InventoryCache/etc/di.xml index 1c422230af15..706222f90b8a 100644 --- a/InventoryCache/etc/di.xml +++ b/InventoryCache/etc/di.xml @@ -12,12 +12,12 @@ + + + Magento\Catalog\Model\Product::CACHE_TAG - - - diff --git a/InventoryCatalog/Model/IsProductSalable.php b/InventoryCatalog/Model/IsProductSalable.php new file mode 100644 index 000000000000..7499d66e4cb2 --- /dev/null +++ b/InventoryCatalog/Model/IsProductSalable.php @@ -0,0 +1,80 @@ +stockByWebsiteIdResolver = $stockByWebsiteIdResolver; + $this->areProductsSalable = $areProductsSalable; + $this->isProductSalableDataStorage = $isProductSalableDataStorage; + } + + /** + * Verify product salable status. + * + * @param ProductInterface $product + * @return bool + */ + public function execute(ProductInterface $product): bool + { + if (null === $product->getSku() || + (null !== $product->getStatus() && (int)$product->getStatus() !== Status::STATUS_ENABLED)) { + return false; + } + if ($product->getData('is_salable') !== null) { + return (bool)$product->getData('is_salable'); + } + $websiteId = (int)$product->getStore()->getWebsite()->getId(); + $stockId = $this->stockByWebsiteIdResolver->execute($websiteId)->getStockId(); + //use getData('sku') to get non processed product sku for complex products. + if (null !== $this->isProductSalableDataStorage->getIsSalable($product->getData('sku'), $stockId)) { + return $this->isProductSalableDataStorage->getIsSalable($product->getData('sku'), $stockId); + } + + $result = current($this->areProductsSalable->execute([$product->getData('sku')], $stockId)); + $salabilityStatus = $result->isSalable(); + $this->isProductSalableDataStorage->setIsSalable($product->getData('sku'), $stockId, $salabilityStatus); + + return $salabilityStatus; + } +} diff --git a/InventoryCatalog/Model/IsProductSalable/IsProductSalableDataStorage.php b/InventoryCatalog/Model/IsProductSalable/IsProductSalableDataStorage.php new file mode 100644 index 000000000000..24e68b52045d --- /dev/null +++ b/InventoryCatalog/Model/IsProductSalable/IsProductSalableDataStorage.php @@ -0,0 +1,66 @@ +statuses[$sku][$stockId] = $status; + } + + /** + * Get is salable status for given product and stock. + * + * @param string $sku + * @param int $stockId + * @return bool|null + */ + public function getIsSalable(string $sku, int $stockId): ?bool + { + return $this->statuses[$sku][$stockId] ?? null; + } + + /** + * Clean is salable status for given product and stock. + * + * @param string $sku + * @param int $stockId + * @return void + */ + public function removeIsSalable(string $sku, int $stockId): void + { + unset($this->statuses[$sku][$stockId]); + } + + /** + * Clean is salable statuses for all saved products and stocks. + * + * @return void + */ + public function cleanIsSalable(): void + { + $this->statuses = []; + } +} diff --git a/InventoryCatalog/Model/ResourceModel/AddStockDataToCollection.php b/InventoryCatalog/Model/ResourceModel/AddStockDataToCollection.php index 4edaa61f88a4..38a4dad8b2b5 100644 --- a/InventoryCatalog/Model/ResourceModel/AddStockDataToCollection.php +++ b/InventoryCatalog/Model/ResourceModel/AddStockDataToCollection.php @@ -8,13 +8,11 @@ namespace Magento\InventoryCatalog\Model\ResourceModel; use Magento\Catalog\Model\ResourceModel\Product\Collection; -use Magento\Framework\App\ObjectManager; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\IndexStructure; use Magento\InventoryIndexer\Model\StockIndexTableNameResolverInterface; /** - * Add Stock data to collection + * Add inventory stock data to collection resource. */ class AddStockDataToCollection { @@ -23,25 +21,18 @@ class AddStockDataToCollection */ private $stockIndexTableNameResolver; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @param StockIndexTableNameResolverInterface $stockIndexTableNameResolver - * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( - StockIndexTableNameResolverInterface $stockIndexTableNameResolver, - DefaultStockProviderInterface $defaultStockProvider = null + StockIndexTableNameResolverInterface $stockIndexTableNameResolver ) { $this->stockIndexTableNameResolver = $stockIndexTableNameResolver; - $this->defaultStockProvider = $defaultStockProvider ?: ObjectManager::getInstance() - ->get(DefaultStockProviderInterface::class); } /** + * Add inventory stock data for multi stock environment. + * * @param Collection $collection * @param bool $isFilterInStock * @param int $stockId @@ -49,31 +40,20 @@ public function __construct( */ public function execute(Collection $collection, bool $isFilterInStock, int $stockId) { - if ($stockId === $this->defaultStockProvider->getId()) { - $isSalableColumnName = 'stock_status'; - $resource = $collection->getResource(); - $collection->getSelect() - ->join( - ['stock_status_index' => $resource->getTable('cataloginventory_stock_status')], - sprintf('%s.entity_id = stock_status_index.product_id', Collection::MAIN_TABLE_ALIAS), - [IndexStructure::IS_SALABLE => $isSalableColumnName] - ); - } else { - $stockIndexTableName = $this->stockIndexTableNameResolver->execute($stockId); - $resource = $collection->getResource(); - $collection->getSelect()->join( - ['product' => $resource->getTable('catalog_product_entity')], - sprintf('product.entity_id = %s.entity_id', Collection::MAIN_TABLE_ALIAS), - [] + $stockIndexTableName = $this->stockIndexTableNameResolver->execute($stockId); + $resource = $collection->getResource(); + $collection->getSelect()->join( + ['product' => $resource->getTable('catalog_product_entity')], + sprintf('product.entity_id = %s.entity_id', Collection::MAIN_TABLE_ALIAS), + [] + ); + $isSalableColumnName = IndexStructure::IS_SALABLE; + $collection->getSelect() + ->join( + ['stock_status_index' => $stockIndexTableName], + 'product.sku = stock_status_index.' . IndexStructure::SKU, + [$isSalableColumnName] ); - $isSalableColumnName = IndexStructure::IS_SALABLE; - $collection->getSelect() - ->join( - ['stock_status_index' => $stockIndexTableName], - 'product.sku = stock_status_index.' . IndexStructure::SKU, - [$isSalableColumnName] - ); - } if ($isFilterInStock) { $collection->getSelect() diff --git a/InventoryCatalog/Plugin/Catalog/Model/Product/GetIsSalablePlugin.php b/InventoryCatalog/Plugin/Catalog/Model/Product/GetIsSalablePlugin.php new file mode 100644 index 000000000000..3b8f26cc9a6d --- /dev/null +++ b/InventoryCatalog/Plugin/Catalog/Model/Product/GetIsSalablePlugin.php @@ -0,0 +1,47 @@ +isProductSalable = $isProductSalable; + } + + /** + * Fetches is salable status for multi-stock environment. + * + * @param Product $product + * @param \Closure $proceed + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundGetIsSalable(Product $product, \Closure $proceed): bool + { + if ($product->hasData('is_saleable')) { + return (bool)$product->getData('is_saleable'); + } + return $this->isProductSalable->execute($product); + } +} diff --git a/InventoryCatalog/Plugin/Catalog/Model/Product/IsAvailablePlugin.php b/InventoryCatalog/Plugin/Catalog/Model/Product/IsAvailablePlugin.php new file mode 100644 index 000000000000..92592182305f --- /dev/null +++ b/InventoryCatalog/Plugin/Catalog/Model/Product/IsAvailablePlugin.php @@ -0,0 +1,44 @@ +isProductSalable = $isProductSalable; + } + + /** + * Fetches is salable status from multi-stock. + * + * @param Product $product + * @param \Closure $proceed + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundIsAvailable(Product $product, \Closure $proceed): bool + { + return $this->isProductSalable->execute($product); + } +} diff --git a/InventoryCatalog/Plugin/Catalog/Model/ProductLink/Search/FilterDisabledProductsPlugin.php b/InventoryCatalog/Plugin/Catalog/Model/ProductLink/Search/FilterDisabledProductsPlugin.php new file mode 100644 index 000000000000..6d89a0ec6bdc --- /dev/null +++ b/InventoryCatalog/Plugin/Catalog/Model/ProductLink/Search/FilterDisabledProductsPlugin.php @@ -0,0 +1,34 @@ +addAttributeToFilter(ProductInterface::STATUS, ['eq' => Status::STATUS_ENABLED]); + + return $collection; + } +} diff --git a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusBySkuPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusBySkuPlugin.php index 532db9d8ff58..772862bc7020 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusBySkuPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusBySkuPlugin.php @@ -7,12 +7,10 @@ namespace Magento\InventoryCatalog\Plugin\CatalogInventory\Api\StockRegistry; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\InventorySalesApi\Api\AreProductsSalableInterface; -use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; -use Magento\InventorySalesApi\Api\StockResolverInterface; use Magento\Store\Model\StoreManagerInterface; /** @@ -21,9 +19,9 @@ class AdaptGetProductStockStatusBySkuPlugin { /** - * @var AreProductsSalableInterface + * @var ProductRepositoryInterface */ - private $areProductsSalable; + private $productRepository; /** * @var StoreManagerInterface @@ -31,23 +29,15 @@ class AdaptGetProductStockStatusBySkuPlugin private $storeManager; /** - * @var StockResolverInterface - */ - private $stockResolver; - - /** - * @param AreProductsSalableInterface $areProductsSalable + * @param ProductRepositoryInterface $productRepository * @param StoreManagerInterface $storeManager - * @param StockResolverInterface $stockResolver */ public function __construct( - AreProductsSalableInterface $areProductsSalable, - StoreManagerInterface $storeManager, - StockResolverInterface $stockResolver + ProductRepositoryInterface $productRepository, + StoreManagerInterface $storeManager ) { - $this->areProductsSalable = $areProductsSalable; + $this->productRepository = $productRepository; $this->storeManager = $storeManager; - $this->stockResolver = $stockResolver; } /** @@ -58,8 +48,7 @@ public function __construct( * @param string $productSku * @param int $scopeId * @return int - * @throws LocalizedException - * @throws NoSuchEntityException + * @throws NoSuchEntityException|LocalizedException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundGetProductStockStatusBySku( @@ -68,14 +57,11 @@ public function aroundGetProductStockStatusBySku( $productSku, $scopeId = null ): int { - $websiteCode = null === $scopeId - ? $this->storeManager->getWebsite()->getCode() - : $this->storeManager->getWebsite($scopeId)->getCode(); - $stockId = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode)->getStockId(); - - $result = $this->areProductsSalable->execute([$productSku], $stockId); - $result = current($result); + $store = $this->storeManager->getWebsite($scopeId)->getDefaultStore(); + $product = $store + ? $this->productRepository->get($productSku, false, (int)$store->getId()) + : $this->productRepository->get($productSku); - return (int)$result->isSalable(); + return (int)$product->isAvailable(); } } diff --git a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusPlugin.php index 59c3d9f291df..2cb7b077cf8f 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetProductStockStatusPlugin.php @@ -7,13 +7,10 @@ namespace Magento\InventoryCatalog\Plugin\CatalogInventory\Api\StockRegistry; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; -use Magento\InventorySalesApi\Api\AreProductsSalableInterface; -use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; -use Magento\InventorySalesApi\Api\StockResolverInterface; use Magento\Store\Model\StoreManagerInterface; /** @@ -22,14 +19,9 @@ class AdaptGetProductStockStatusPlugin { /** - * @var AreProductsSalableInterface + * @var ProductRepositoryInterface */ - private $areProductsSalable; - - /** - * @var GetSkusByProductIdsInterface - */ - private $getSkusByProductIds; + private $productRepository; /** * @var StoreManagerInterface @@ -37,26 +29,15 @@ class AdaptGetProductStockStatusPlugin private $storeManager; /** - * @var StockResolverInterface - */ - private $stockResolver; - - /** - * @param AreProductsSalableInterface $areProductsSalable - * @param GetSkusByProductIdsInterface $getSkusByProductIds + * @param ProductRepositoryInterface $productRepository * @param StoreManagerInterface $storeManager - * @param StockResolverInterface $stockResolver */ public function __construct( - AreProductsSalableInterface $areProductsSalable, - GetSkusByProductIdsInterface $getSkusByProductIds, - StoreManagerInterface $storeManager, - StockResolverInterface $stockResolver + ProductRepositoryInterface $productRepository, + StoreManagerInterface $storeManager ) { - $this->areProductsSalable = $areProductsSalable; - $this->getSkusByProductIds = $getSkusByProductIds; + $this->productRepository = $productRepository; $this->storeManager = $storeManager; - $this->stockResolver = $stockResolver; } /** @@ -67,9 +48,8 @@ public function __construct( * @param int $productId * @param int $scopeId * @return int - * @throws LocalizedException - * @throws NoSuchEntityException * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws NoSuchEntityException|LocalizedException */ public function aroundGetProductStockStatus( StockRegistryInterface $subject, @@ -77,15 +57,11 @@ public function aroundGetProductStockStatus( $productId, $scopeId = null ): int { - $websiteCode = null === $scopeId - ? $this->storeManager->getWebsite()->getCode() - : $this->storeManager->getWebsite($scopeId)->getCode(); - $stockId = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode)->getStockId(); - $sku = $this->getSkusByProductIds->execute([$productId])[$productId]; - - $result = $this->areProductsSalable->execute([$sku], $stockId); - $result = current($result); + $store = $this->storeManager->getWebsite($scopeId)->getDefaultStore(); + $product = $store + ? $this->productRepository->getById($productId, false, (int)$store->getId()) + : $this->productRepository->getById($productId); - return (int)$result->isSalable(); + return (int)$product->isAvailable(); } } diff --git a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusBySkuPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusBySkuPlugin.php index 60929a190f36..ee77e8049356 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusBySkuPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusBySkuPlugin.php @@ -7,12 +7,12 @@ namespace Magento\InventoryCatalog\Plugin\CatalogInventory\Api\StockRegistry; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\CatalogInventory\Api\Data\StockStatusInterface; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\InventorySalesApi\Api\AreProductsSalableInterface; use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface; use Magento\InventorySalesApi\Api\StockResolverInterface; @@ -23,11 +23,6 @@ */ class AdaptGetStockStatusBySkuPlugin { - /** - * @var AreProductsSalableInterface - */ - private $areProductsSalable; - /** * @var GetProductSalableQtyInterface */ @@ -44,21 +39,26 @@ class AdaptGetStockStatusBySkuPlugin private $stockResolver; /** - * @param AreProductsSalableInterface $areProductsSalable + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** * @param GetProductSalableQtyInterface $getProductSalableQty * @param StoreManagerInterface $storeManager * @param StockResolverInterface $stockResolver + * @param ProductRepositoryInterface $productRepository */ public function __construct( - AreProductsSalableInterface $areProductsSalable, GetProductSalableQtyInterface $getProductSalableQty, StoreManagerInterface $storeManager, - StockResolverInterface $stockResolver + StockResolverInterface $stockResolver, + ProductRepositoryInterface $productRepository ) { - $this->areProductsSalable = $areProductsSalable; $this->getProductSalableQty = $getProductSalableQty; $this->storeManager = $storeManager; $this->stockResolver = $stockResolver; + $this->productRepository = $productRepository; } /** @@ -79,22 +79,20 @@ public function afterGetStockStatusBySku( $productSku, $scopeId = null ): StockStatusInterface { - $websiteCode = null === $scopeId - ? $this->storeManager->getWebsite()->getCode() - : $this->storeManager->getWebsite($scopeId)->getCode(); - $stockId = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode)->getStockId(); - - $result = $this->areProductsSalable->execute([$productSku], $stockId); - $result = current($result); - + $website = $this->storeManager->getWebsite($scopeId); + $stockId = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()) + ->getStockId(); try { $qty = $this->getProductSalableQty->execute($productSku, $stockId); } catch (InputException $e) { $qty = 0; } - - $stockStatus->setStockStatus((int)$result->isSalable()); + $product = $website->getDefaultStore() + ? $this->productRepository->get($productSku, false, (int)$website->getDefaultStore()->getId()) + : $this->productRepository->get($productSku); + $stockStatus->setStockStatus((int)$product->isAvailable()); $stockStatus->setQty($qty); + return $stockStatus; } } diff --git a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php index 6593389973a0..d01dcfdee51a 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptGetStockStatusPlugin.php @@ -7,13 +7,13 @@ namespace Magento\InventoryCatalog\Plugin\CatalogInventory\Api\StockRegistry; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\CatalogInventory\Api\Data\StockStatusInterface; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; -use Magento\InventorySalesApi\Api\AreProductsSalableInterface; use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface; use Magento\InventorySalesApi\Api\StockResolverInterface; @@ -24,11 +24,6 @@ */ class AdaptGetStockStatusPlugin { - /** - * @var AreProductsSalableInterface - */ - private $areProductsSalable; - /** * @var GetProductSalableQtyInterface */ @@ -50,24 +45,29 @@ class AdaptGetStockStatusPlugin private $stockResolver; /** - * @param AreProductsSalableInterface $areProductsSalable + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** * @param GetProductSalableQtyInterface $getProductSalableQty * @param GetSkusByProductIdsInterface $getSkusByProductIds * @param StoreManagerInterface $storeManager * @param StockResolverInterface $stockResolver + * @param ProductRepositoryInterface $productRepository */ public function __construct( - AreProductsSalableInterface $areProductsSalable, GetProductSalableQtyInterface $getProductSalableQty, GetSkusByProductIdsInterface $getSkusByProductIds, StoreManagerInterface $storeManager, - StockResolverInterface $stockResolver + StockResolverInterface $stockResolver, + ProductRepositoryInterface $productRepository ) { - $this->areProductsSalable = $areProductsSalable; $this->getProductSalableQty = $getProductSalableQty; $this->getSkusByProductIds = $getSkusByProductIds; $this->storeManager = $storeManager; $this->stockResolver = $stockResolver; + $this->productRepository = $productRepository; } /** @@ -88,14 +88,13 @@ public function afterGetStockStatus( $productId, $scopeId = null ): StockStatusInterface { - $websiteCode = null === $scopeId - ? $this->storeManager->getWebsite()->getCode() - : $this->storeManager->getWebsite($scopeId)->getCode(); - $stockId = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode)->getStockId(); + $website = $this->storeManager->getWebsite($scopeId); + $stockId = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode()) + ->getStockId(); $sku = $this->getSkusByProductIds->execute([$productId])[$productId]; - - $result = $this->areProductsSalable->execute([$sku], $stockId); - $result = current($result); + $product = $website->getCurrentStore() === null + ? $this->productRepository->get($sku) + : $this->productRepository->get($sku, false, (int)$website->getDefaultStore()->getId()); try { $qty = $this->getProductSalableQty->execute($sku, $stockId); @@ -103,8 +102,9 @@ public function afterGetStockStatus( $qty = 0; } - $stockStatus->setStockStatus((int)$result->isSalable()); + $stockStatus->setStockStatus((int)$product->isAvailable()); $stockStatus->setQty($qty); + return $stockStatus; } } diff --git a/InventoryCatalog/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php deleted file mode 100644 index fe79eefdd1a3..000000000000 --- a/InventoryCatalog/Plugin/CatalogInventory/Helper/Stock/AdaptAssignStatusToProductPlugin.php +++ /dev/null @@ -1,93 +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 - ): array { - if (null === $product->getSku()) { - return [$product, $status]; - } - - try { - $this->getProductIdsBySkus->execute([$product->getSku()]); - if (null === $status) { - $stockId = $this->getStockIdForCurrentWebsite->execute(); - $result = $this->areProductsSalable->execute([$product->getSku()], $stockId); - $result = current($result); - return [$product, (int)$result->isSalable()]; - } - } catch (NoSuchEntityException $e) { - return [$product, $status]; - } - return [$product, $status]; - } -} diff --git a/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddIsInStockFilterToCollectionPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddIsInStockFilterToCollectionPlugin.php index ae86cb2646fe..397b068ddf36 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddIsInStockFilterToCollectionPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddIsInStockFilterToCollectionPlugin.php @@ -11,7 +11,6 @@ use Magento\CatalogInventory\Model\ResourceModel\Stock\Status; use Magento\InventoryCatalog\Model\GetStockIdForCurrentWebsite; use Magento\InventoryCatalog\Model\ResourceModel\AddIsInStockFilterToCollection; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; /** * Adapt adding is in stock filter to collection for multi stocks. @@ -31,16 +30,13 @@ class AdaptAddIsInStockFilterToCollectionPlugin /** * @param GetStockIdForCurrentWebsite $getStockIdForCurrentWebsite * @param AddIsInStockFilterToCollection $addIsInStockFilterToCollection - * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( GetStockIdForCurrentWebsite $getStockIdForCurrentWebsite, - AddIsInStockFilterToCollection $addIsInStockFilterToCollection, - DefaultStockProviderInterface $defaultStockProvider + AddIsInStockFilterToCollection $addIsInStockFilterToCollection ) { $this->getStockIdForCurrentWebsite = $getStockIdForCurrentWebsite; $this->addIsInStockFilterToCollection = $addIsInStockFilterToCollection; - $this->defaultStockProvider = $defaultStockProvider; } /** @@ -59,11 +55,7 @@ public function aroundAddIsInStockFilterToCollection( $collection ) { $stockId = $this->getStockIdForCurrentWebsite->execute(); - if ($this->defaultStockProvider->getId() === $stockId) { - return $proceed($collection); - } else { - $this->addIsInStockFilterToCollection->execute($collection, $stockId); - } + $this->addIsInStockFilterToCollection->execute($collection, $stockId); return $stockStatus; } diff --git a/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockDataToCollectionPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockDataToCollectionPlugin.php index 342c4adb0d25..0e4fb9d485b0 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockDataToCollectionPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockDataToCollectionPlugin.php @@ -40,6 +40,8 @@ public function __construct( } /** + * Adapt add stock data for collection for multi stock environment. + * * @param Status $stockStatus * @param callable $proceed * @param Collection $collection diff --git a/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockStatusToSelectPlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockStatusToSelectPlugin.php index 2c051c39de77..99bac2255083 100644 --- a/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockStatusToSelectPlugin.php +++ b/InventoryCatalog/Plugin/CatalogInventory/Model/ResourceModel/Stock/Status/AdaptAddStockStatusToSelectPlugin.php @@ -10,8 +10,8 @@ use Magento\CatalogInventory\Model\ResourceModel\Stock\Status; use Magento\Framework\DB\Select; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\InventoryCatalog\Model\ResourceModel\AddStockStatusToSelect; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventorySalesApi\Api\Data\SalesChannelInterface; use Magento\InventorySalesApi\Api\StockResolverInterface; use Magento\Store\Model\Website; @@ -31,24 +31,16 @@ class AdaptAddStockStatusToSelectPlugin */ private $addStockStatusToSelect; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @param StockResolverInterface $stockResolver * @param AddStockStatusToSelect $addStockStatusToSelect - * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( StockResolverInterface $stockResolver, - AddStockStatusToSelect $addStockStatusToSelect, - DefaultStockProviderInterface $defaultStockProvider + AddStockStatusToSelect $addStockStatusToSelect ) { $this->stockResolver = $stockResolver; $this->addStockStatusToSelect = $addStockStatusToSelect; - $this->defaultStockProvider = $defaultStockProvider; } /** @@ -60,6 +52,7 @@ public function __construct( * @param Website $website * @return Status * @throws LocalizedException + * @throws NoSuchEntityException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundAddStockStatusToSelect( @@ -75,11 +68,7 @@ public function aroundAddStockStatusToSelect( $stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $websiteCode); $stockId = (int)$stock->getStockId(); - if ($this->defaultStockProvider->getId() === $stockId) { - return $proceed($select, $website); - } else { - $this->addStockStatusToSelect->execute($select, $stockId); - } + $this->addStockStatusToSelect->execute($select, $stockId); return $stockStatus; } diff --git a/InventoryCatalog/Plugin/CatalogInventory/Model/StockRegistryStorage/CleanIsSalableDataStoragePlugin.php b/InventoryCatalog/Plugin/CatalogInventory/Model/StockRegistryStorage/CleanIsSalableDataStoragePlugin.php new file mode 100644 index 000000000000..0a86d2db21f9 --- /dev/null +++ b/InventoryCatalog/Plugin/CatalogInventory/Model/StockRegistryStorage/CleanIsSalableDataStoragePlugin.php @@ -0,0 +1,42 @@ +isProductSalableDataStorage = $isProductSalableDataStorage; + } + + /** + * Clean is product salable storage after stock registry storage has been cleaned. + * + * @param StockRegistryStorage $subject + * @param void $result + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterClean(StockRegistryStorage $subject, $result): void + { + $this->isProductSalableDataStorage->cleanIsSalable(); + } +} diff --git a/InventoryCatalog/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/PriceIndexUpdatePlugin.php b/InventoryCatalog/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/PriceIndexUpdatePlugin.php index 77e2f3e12a23..62d942bc511a 100644 --- a/InventoryCatalog/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/PriceIndexUpdatePlugin.php +++ b/InventoryCatalog/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/PriceIndexUpdatePlugin.php @@ -47,7 +47,7 @@ public function __construct( * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterExecuteList(Sync $subject, $result, array $stockIds) + public function afterExecuteList(Sync $subject, $result, array $stockIds): void { $productIds = $this->getProductIdsByStockIds->execute($stockIds); if (!empty($productIds)) { diff --git a/InventoryCatalog/Setup/Operation/ReindexDefaultStock.php b/InventoryCatalog/Setup/Operation/ReindexDefaultStock.php index f4309ccce90b..95d2abdb7a4e 100644 --- a/InventoryCatalog/Setup/Operation/ReindexDefaultStock.php +++ b/InventoryCatalog/Setup/Operation/ReindexDefaultStock.php @@ -7,11 +7,17 @@ namespace Magento\InventoryCatalog\Setup\Operation; -use Magento\InventoryIndexer\Indexer\Stock\StockIndexer; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\StateException; use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; +use Magento\InventoryIndexer\Indexer\InventoryIndexer; +use Magento\InventoryIndexer\Indexer\Stock\StockIndexer; +use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; +use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexStructureInterface; /** - * CReindex default stock during installation + * Create default stock index during installation. */ class ReindexDefaultStock { @@ -25,25 +31,52 @@ class ReindexDefaultStock */ private $stockIndexer; + /** + * @var IndexNameBuilder + */ + private $indexNameBuilder; + + /** + * @var IndexStructureInterface + */ + private $indexStructure; + /** * @param DefaultStockProviderInterface $defaultStockProvider * @param StockIndexer $stockIndexer + * @param IndexNameBuilder $indexNameBuilder + * @param IndexStructureInterface $indexStructure */ public function __construct( DefaultStockProviderInterface $defaultStockProvider, - StockIndexer $stockIndexer + StockIndexer $stockIndexer, + IndexNameBuilder $indexNameBuilder, + IndexStructureInterface $indexStructure ) { $this->defaultStockProvider = $defaultStockProvider; $this->stockIndexer = $stockIndexer; + $this->indexNameBuilder = $indexNameBuilder; + $this->indexStructure = $indexStructure; } /** - * Create default stock + * Create default stock. * * @return void + * @throws StateException */ public function execute() { + $stockId = $this->defaultStockProvider->getId(); + $mainIndexName = $this->indexNameBuilder + ->setIndexId(InventoryIndexer::INDEXER_ID) + ->addDimension('stock_', (string)$stockId) + ->setAlias(Alias::ALIAS_MAIN) + ->build(); + + if (!$this->indexStructure->isExist($mainIndexName, ResourceConnection::DEFAULT_CONNECTION)) { + $this->indexStructure->create($mainIndexName, ResourceConnection::DEFAULT_CONNECTION); + } //$this->stockIndexer->executeRow($this->defaultStockProvider->getId()); } } diff --git a/InventoryCatalog/Setup/Patch/Schema/CreateLegacyStockStatusView.php b/InventoryCatalog/Setup/Patch/Schema/CreateLegacyStockStatusView.php deleted file mode 100644 index eec200ce0b00..000000000000 --- a/InventoryCatalog/Setup/Patch/Schema/CreateLegacyStockStatusView.php +++ /dev/null @@ -1,96 +0,0 @@ -schemaSetup = $schemaSetup; - $this->stockIndexTableNameResolver = $stockIndexTableNameResolver; - $this->defaultStockProvider = $defaultStockProvider; - } - - /** - * @inheritdoc - */ - public function getAliases() - { - return []; - } - - /** - * @inheritdoc - */ - public function apply() - { - $this->schemaSetup->startSetup(); - $defaultStockId = $this->defaultStockProvider->getId(); - $viewToLegacyIndex = $this->stockIndexTableNameResolver->execute($defaultStockId); - $legacyStockStatusTable = $this->schemaSetup->getTable('cataloginventory_stock_status'); - $productTable = $this->schemaSetup->getTable('catalog_product_entity'); - $sql = "CREATE - SQL SECURITY INVOKER - VIEW {$viewToLegacyIndex} - AS - SELECT - DISTINCT - legacy_stock_status.product_id, - legacy_stock_status.website_id, - legacy_stock_status.stock_id, - legacy_stock_status.qty quantity, - legacy_stock_status.stock_status is_salable, - product.sku - FROM {$legacyStockStatusTable} legacy_stock_status - INNER JOIN {$productTable} product - ON legacy_stock_status.product_id = product.entity_id;"; - $this->schemaSetup->getConnection()->query($sql); - $this->schemaSetup->endSetup(); - - return $this; - } - - /** - * @inheritdoc - */ - public static function getDependencies() - { - return []; - } -} diff --git a/InventoryCatalog/Test/Integration/Bulk/InventoryTransferTest.php b/InventoryCatalog/Test/Integration/Bulk/InventoryTransferTest.php index 0a7a93a5fdde..f607274c7478 100644 --- a/InventoryCatalog/Test/Integration/Bulk/InventoryTransferTest.php +++ b/InventoryCatalog/Test/Integration/Bulk/InventoryTransferTest.php @@ -15,6 +15,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class InventoryTransferTest extends TestCase { /** @@ -89,7 +92,6 @@ private function getSourceItem(string $sku, string $sourceCode): ?SourceItemInte * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled */ public function testBulkInventoryTransferAndUnassign() { @@ -116,7 +118,6 @@ public function testBulkInventoryTransferAndUnassign() * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled */ public function testBulkInventoryTransferWithOutOfStockOrigin() { @@ -150,7 +151,6 @@ public function testBulkInventoryTransferWithOutOfStockOrigin() * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled */ public function testBulkInventoryTransferToNewSource() { @@ -190,7 +190,6 @@ public function testBulkInventoryTransferToNewSource() * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled */ public function testBulkInventoryTransferFromUnassignedOriginSource() { @@ -224,7 +223,6 @@ public function testBulkInventoryTransferFromUnassignedOriginSource() * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled */ public function testBulkInventoryTransferToAssignedSource() { diff --git a/InventoryCatalog/Test/Integration/Bulk/SourceAssignTest.php b/InventoryCatalog/Test/Integration/Bulk/SourceAssignTest.php index 7104720ffad5..37ddec25953c 100644 --- a/InventoryCatalog/Test/Integration/Bulk/SourceAssignTest.php +++ b/InventoryCatalog/Test/Integration/Bulk/SourceAssignTest.php @@ -62,7 +62,7 @@ private function getSourceItemCodesBySku(string $sku): array * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled */ public function testBulkSourceAssignment() { @@ -121,7 +121,7 @@ public function testBulkSourceAssignment() /** * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/products_all_types.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled */ public function testBulkSourceAssignmentOnMixedProducts() { diff --git a/InventoryCatalog/Test/Integration/Bulk/SourceUnassignTest.php b/InventoryCatalog/Test/Integration/Bulk/SourceUnassignTest.php index 36367e33ee41..a98c9b6d55c0 100644 --- a/InventoryCatalog/Test/Integration/Bulk/SourceUnassignTest.php +++ b/InventoryCatalog/Test/Integration/Bulk/SourceUnassignTest.php @@ -62,7 +62,7 @@ private function getSourceItemCodesBySku(string $sku): array * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled */ public function testBulkSourceUnassignment() { diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusBySkuOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusBySkuOnDefaultStockTest.php index d156512abc94..b813f7e61c1e 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusBySkuOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusBySkuOnDefaultStockTest.php @@ -12,6 +12,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetProductStockStatusBySkuOnDefaultStockTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusOnDefaultStockTest.php index 5d4fc18323d2..3f3ff44d7f8a 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetProductStockStatusOnDefaultStockTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetProductStockStatusOnDefaultStockTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusBySkuOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusBySkuOnDefaultStockTest.php index bbe8aa01d9af..c958de766e88 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusBySkuOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusBySkuOnDefaultStockTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetStockStatusBySkuOnDefaultStockTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusOnDefaultStockTest.php index 6c444988daa2..066c7f080e71 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Api/StockRegistry/GetStockStatusOnDefaultStockTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetStockStatusOnDefaultStockTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddInStockFilterToCollectionOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddInStockFilterToCollectionOnDefaultStockTest.php index 9d63158a9ff9..e027760a68ba 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddInStockFilterToCollectionOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddInStockFilterToCollectionOnDefaultStockTest.php @@ -32,6 +32,7 @@ protected function setUp(): void /** * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php + * @magentoDbIsolation disabled */ public function testAddInStockFilterToCollection() { diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddStockStatusToProductsOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddStockStatusToProductsOnDefaultStockTest.php index 268fac12015f..016d6fd2ffe8 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddStockStatusToProductsOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AddStockStatusToProductsOnDefaultStockTest.php @@ -33,6 +33,7 @@ protected function setUp(): void /** * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php + * @magentoDbIsolation disabled */ public function testAddStockStatusToProducts() { diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AssignStatusToProductOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AssignStatusToProductOnDefaultStockTest.php index 7dd74b9fb3aa..aadcd16b58f0 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AssignStatusToProductOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AssignStatusToProductOnDefaultStockTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class AssignStatusToProductOnDefaultStockTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AssignStatusToProductTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AssignStatusToProductTest.php deleted file mode 100644 index 79b535cb86ce..000000000000 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Helper/Stock/AssignStatusToProductTest.php +++ /dev/null @@ -1,151 +0,0 @@ -stockHelper = Bootstrap::getObjectManager()->get(Stock::class); - $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); - $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); - $this->storeCodeBefore = $this->storeManager->getStore()->getCode(); - } - - /** - * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/stocks.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/stock_source_links.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDataFixture Magento_InventorySalesApi::Test/_files/websites_with_stores.php - * @magentoDataFixture Magento_InventorySalesApi::Test/_files/stock_website_sales_channels.php - * @magentoDataFixture Magento_InventoryIndexer::Test/_files/reindex_inventory.php - * @dataProvider assignStatusToProductDataProvider - * @param string $storeCode - * @param array $productsData - * - * @magentoDbIsolation disabled - */ - public function testAssignStatusToProductIfStatusParameterIsNotPassed(string $storeCode, array $productsData) - { - $this->storeManager->setCurrentStore($storeCode); - - foreach ($productsData as $sku => $expectedStatus) { - $product = $this->productRepository->get($sku); - /** @var Product $product */ - $this->stockHelper->assignStatusToProduct($product); - - self::assertEquals($expectedStatus, $product->isSalable()); - } - } - - /** - * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/stocks.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/stock_source_links.php - * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDataFixture Magento_InventorySalesApi::Test/_files/websites_with_stores.php - * @magentoDataFixture Magento_InventorySalesApi::Test/_files/stock_website_sales_channels.php - * @magentoDataFixture Magento_InventoryIndexer::Test/_files/reindex_inventory.php - * @dataProvider assignStatusToProductDataProvider - * @param string $storeCode - * @param array $productsData - * - * @magentoDbIsolation disabled - */ - public function testAssignStatusToProductIfStatusParameterIsPassed(string $storeCode, array $productsData) - { - $expectedStatus = 1; - $this->storeManager->setCurrentStore($storeCode); - - foreach (array_keys($productsData) as $sku) { - $product = $this->productRepository->get($sku); - /** @var Product $product */ - $this->stockHelper->assignStatusToProduct($product, $expectedStatus); - - self::assertEquals($expectedStatus, $product->isSalable()); - } - } - - /** - * @return array - */ - public function assignStatusToProductDataProvider(): array - { - return [ - 'eu_website' => [ - 'store_for_eu_website', - [ - 'SKU-1' => 1, - 'SKU-2' => 0, - 'SKU-3' => 0, - ], - ], - 'us_website' => [ - 'store_for_us_website', - [ - 'SKU-1' => 0, - 'SKU-2' => 1, - 'SKU-3' => 0, - ], - ], - 'global_website' => [ - 'store_for_global_website', - [ - 'SKU-1' => 1, - 'SKU-2' => 1, - 'SKU-3' => 0, - ], - ], - ]; - } - - /** - * @inheritdoc - */ - protected function tearDown(): void - { - $this->storeManager->setCurrentStore($this->storeCodeBefore); - - parent::tearDown(); - } -} diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddIsInStockFilterToCollectionOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddIsInStockFilterToCollectionOnDefaultStockTest.php index 3deaecd66d62..52aeb76d896a 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddIsInStockFilterToCollectionOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddIsInStockFilterToCollectionOnDefaultStockTest.php @@ -35,6 +35,7 @@ protected function setUp(): void /** * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php + * @magentoDbIsolation disabled */ public function testAddIsInStockFilterToCollection() { diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockDataToCollectionOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockDataToCollectionOnDefaultStockTest.php index 7294cbf05c59..86d22e82bdce 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockDataToCollectionOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockDataToCollectionOnDefaultStockTest.php @@ -32,6 +32,7 @@ protected function setUp(): void /** * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php + * @magentoDbIsolation disabled * * @param int $expectedSize * @param bool $isFilterInStock @@ -54,7 +55,7 @@ public function addStockDataToCollectionDataProvider(): array { return [ [4, true], - [6, false], + [5, false], ]; } } diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectOnDefaultStockTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectOnDefaultStockTest.php index e4a53d61b16d..2051596670bb 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectOnDefaultStockTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectOnDefaultStockTest.php @@ -42,6 +42,7 @@ protected function setUp(): void /** * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php + * @magentoDbIsolation disabled */ public function testAddStockStatusToSelect() { diff --git a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectTest.php b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectTest.php index 786bae1a5ca8..72ae4a590721 100644 --- a/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectTest.php +++ b/InventoryCatalog/Test/Integration/CatalogInventory/Model/ResourceModel/Stock/Status/AddStockStatusToSelectTest.php @@ -70,7 +70,7 @@ public function testAddStockStatusToSelect( $this->stockStatus->addStockStatusToSelect($collection->getSelect(), $this->website); foreach ($collection as $item) { - $item->getIsSalable() == 1 ? $actualIsSalableCount++ : $actualNotSalableCount++; + $item->getData('is_salable') == 1 ? $actualIsSalableCount++ : $actualNotSalableCount++; } self::assertEquals($expectedIsSalableCount, $actualIsSalableCount); diff --git a/InventoryCatalog/Test/Integration/GetDefaultSourceItemBySkuTest.php b/InventoryCatalog/Test/Integration/GetDefaultSourceItemBySkuTest.php index 67c288c0b614..d569a610bb13 100644 --- a/InventoryCatalog/Test/Integration/GetDefaultSourceItemBySkuTest.php +++ b/InventoryCatalog/Test/Integration/GetDefaultSourceItemBySkuTest.php @@ -11,6 +11,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetDefaultSourceItemBySkuTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/GetSourceItemsBySkuAndSourceCodesTest.php b/InventoryCatalog/Test/Integration/GetSourceItemsBySkuAndSourceCodesTest.php index 23d02c517f1c..b90f4b2e976e 100644 --- a/InventoryCatalog/Test/Integration/GetSourceItemsBySkuAndSourceCodesTest.php +++ b/InventoryCatalog/Test/Integration/GetSourceItemsBySkuAndSourceCodesTest.php @@ -11,6 +11,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetSourceItemsBySkuAndSourceCodesTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/SetDataToLegacyStockItemAtSourceItemsSaveTest.php b/InventoryCatalog/Test/Integration/SetDataToLegacyStockItemAtSourceItemsSaveTest.php index a2155262ce02..4da0160215b6 100644 --- a/InventoryCatalog/Test/Integration/SetDataToLegacyStockItemAtSourceItemsSaveTest.php +++ b/InventoryCatalog/Test/Integration/SetDataToLegacyStockItemAtSourceItemsSaveTest.php @@ -19,6 +19,9 @@ use Magento\CatalogInventory\Api\StockItemCriteriaInterface; use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory; +/** + * @magentoDbIsolation disabled + */ class SetDataToLegacyStockItemAtSourceItemsSaveTest extends TestCase { /** diff --git a/InventoryCatalog/Test/Integration/SetDataToLegacyStockStatusAtSourceItemsSaveTest.php b/InventoryCatalog/Test/Integration/SetDataToLegacyStockStatusAtSourceItemsSaveTest.php index 21461ffb5108..9d736b8f19c7 100644 --- a/InventoryCatalog/Test/Integration/SetDataToLegacyStockStatusAtSourceItemsSaveTest.php +++ b/InventoryCatalog/Test/Integration/SetDataToLegacyStockStatusAtSourceItemsSaveTest.php @@ -25,6 +25,8 @@ /** * Tests legacy stock information synchronized with MSI's. * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @magentoDbIsolation disabled */ class SetDataToLegacyStockStatusAtSourceItemsSaveTest extends TestCase { diff --git a/InventoryCatalog/Test/Integration/SetOutOfStockToLegacyStockStatusAtSourceItemsDeleteTest.php b/InventoryCatalog/Test/Integration/SetOutOfStockToLegacyStockStatusAtSourceItemsDeleteTest.php index d4f9a788da30..1aacc09a46a3 100644 --- a/InventoryCatalog/Test/Integration/SetOutOfStockToLegacyStockStatusAtSourceItemsDeleteTest.php +++ b/InventoryCatalog/Test/Integration/SetOutOfStockToLegacyStockStatusAtSourceItemsDeleteTest.php @@ -77,6 +77,7 @@ protected function setUp(): void * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php * @magentoDataFixture Magento_InventoryIndexer::Test/_files/reindex_inventory.php + * @magentoDbIsolation disabled */ public function testSetOutOfStock() { diff --git a/InventoryCatalog/Test/Integration/SetToZeroLegacyStockItemAtSourceItemsDeleteTest.php b/InventoryCatalog/Test/Integration/SetToZeroLegacyStockItemAtSourceItemsDeleteTest.php index 47eef006d6de..645b71255cb6 100644 --- a/InventoryCatalog/Test/Integration/SetToZeroLegacyStockItemAtSourceItemsDeleteTest.php +++ b/InventoryCatalog/Test/Integration/SetToZeroLegacyStockItemAtSourceItemsDeleteTest.php @@ -76,6 +76,7 @@ protected function setUp(): void * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php * @magentoDataFixture Magento_InventoryIndexer::Test/_files/reindex_inventory.php + * @magentoDbIsolation disabled */ public function testSetToZero() { diff --git a/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtLegacyStockItemSaveTest.php b/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtLegacyStockItemSaveTest.php index 111f9ecc8039..9d21fdc8def2 100644 --- a/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtLegacyStockItemSaveTest.php +++ b/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtLegacyStockItemSaveTest.php @@ -37,7 +37,7 @@ protected function setUp(): void * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testSaveLegacyStockItemAssignedToDefaultSource() @@ -60,7 +60,7 @@ public function testSaveLegacyStockItemAssignedToDefaultSource() * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php * @magentoDataFixture Magento_InventoryCatalog::Test/_files/source_items_on_default_source.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testSaveLegacyStockItemNotAssignedToDefaultSource() @@ -94,7 +94,7 @@ public function testSaveLegacyStockItemNotAssignedToDefaultSource() * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testSaveLegacyStockItemWithoutDefaultSourceAssignment() diff --git a/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtProductSaveTest.php b/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtProductSaveTest.php index 5f85b63e1b0a..23ae8432cfcd 100644 --- a/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtProductSaveTest.php +++ b/InventoryCatalog/Test/Integration/UpdateDefaultSourceItemAtProductSaveTest.php @@ -36,7 +36,7 @@ protected function setUp(): void * @magentoDataFixture Magento_InventoryApi::Test/_files/sources.php * @magentoDataFixture Magento_InventoryApi::Test/_files/products.php * @magentoDataFixture Magento_InventoryApi::Test/_files/source_items.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled */ public function testSaveOutOfStockProductNotAssignedToDefaultSource() { diff --git a/InventoryCatalog/composer.json b/InventoryCatalog/composer.json index c3651026905c..1a5c872ba15e 100644 --- a/InventoryCatalog/composer.json +++ b/InventoryCatalog/composer.json @@ -13,7 +13,8 @@ "magento/module-inventory-configuration-api": "*", "magento/module-inventory-indexer": "*", "magento/module-inventory-sales-api": "*", - "magento/module-inventory-configuration": "*" + "magento/module-inventory-configuration": "*", + "magento/module-inventory-multi-dimensional-indexer-api": "*" }, "suggest": { "magento/module-inventory-reservations-api": "*" diff --git a/InventoryCatalog/etc/adminhtml/di.xml b/InventoryCatalog/etc/adminhtml/di.xml new file mode 100644 index 000000000000..07204db2bf2d --- /dev/null +++ b/InventoryCatalog/etc/adminhtml/di.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/InventoryCatalog/etc/di.xml b/InventoryCatalog/etc/di.xml index b0dff41f921b..b9737b18770d 100644 --- a/InventoryCatalog/etc/di.xml +++ b/InventoryCatalog/etc/di.xml @@ -53,7 +53,6 @@ - @@ -165,4 +164,11 @@ + + + + + + + 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/InventoryCatalogAdminUi/Test/Integration/GetSourceItemsDataBySkuTest.php b/InventoryCatalogAdminUi/Test/Integration/GetSourceItemsDataBySkuTest.php index 15efe8dcb808..a87752eb469c 100644 --- a/InventoryCatalogAdminUi/Test/Integration/GetSourceItemsDataBySkuTest.php +++ b/InventoryCatalogAdminUi/Test/Integration/GetSourceItemsDataBySkuTest.php @@ -12,6 +12,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class GetSourceItemsDataBySkuTest extends TestCase { /** diff --git a/InventoryConfigurableProduct/Model/IsConfigurableProductSalable.php b/InventoryConfigurableProduct/Model/IsConfigurableProductSalable.php new file mode 100644 index 000000000000..c6eee7c32868 --- /dev/null +++ b/InventoryConfigurableProduct/Model/IsConfigurableProductSalable.php @@ -0,0 +1,65 @@ +type = $type; + $this->areProductsSalable = $areProductsSalable; + } + + /** + * Verify configurable product salable status considering configurable options. + * + * @param ProductInterface $product + * @param int $stockId + * @return bool + */ + public function execute(ProductInterface $product, int $stockId): bool + { + $salable = false; + $options = $this->type->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()) { + $salable = true; + break; + } + } + + return $salable; + } +} diff --git a/InventoryConfigurableProduct/Model/IsProductSalableCondition/ProductOptionsCondition.php b/InventoryConfigurableProduct/Model/IsProductSalableCondition/ProductOptionsCondition.php new file mode 100644 index 000000000000..e66a42f75162 --- /dev/null +++ b/InventoryConfigurableProduct/Model/IsProductSalableCondition/ProductOptionsCondition.php @@ -0,0 +1,54 @@ +productRepository = $productRepository; + $this->isConfigurableProductSalable = $isConfigurableProductSalable; + } + + /** + * @inheritDoc + */ + public function execute(string $sku, int $stockId): bool + { + $product = $this->productRepository->get($sku); + if ($product->getTypeId() !== Configurable::TYPE_CODE) { + return true; + } + + return $this->isConfigurableProductSalable->execute($product, $stockId); + } +} 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/InventoryCatalog/Model/IsProductSalable/IsConfigurableProductSalablePlugin.php b/InventoryConfigurableProduct/Plugin/InventoryCatalog/Model/IsProductSalable/IsConfigurableProductSalablePlugin.php new file mode 100644 index 000000000000..7b3576480c32 --- /dev/null +++ b/InventoryConfigurableProduct/Plugin/InventoryCatalog/Model/IsProductSalable/IsConfigurableProductSalablePlugin.php @@ -0,0 +1,61 @@ +type = $type; + } + + /** + * Get configurable product status. + * + * @param IsProductSalable $subject + * @param bool $result + * @param ProductInterface $product + * @return bool + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute(IsProductSalable $subject, bool $result, ProductInterface $product): bool + { + if ($product->getTypeId() !== Configurable::TYPE_CODE || !$result) { + return $result; + } + + $collection = $this->type->getUsedProductCollection($product)->addAttributeToFilter( + ProductInterface::STATUS, + Status::STATUS_ENABLED + ); + foreach ($collection->getItems() as $item) { + if ($item->isSalable()) { + return true; + } + } + + return false; + } +} diff --git a/InventoryConfigurableProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php b/InventoryConfigurableProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php index 837d4759a096..54a6dcd9b7b0 100644 --- a/InventoryConfigurableProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php +++ b/InventoryConfigurableProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php @@ -22,6 +22,8 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @magentoDbIsolation disabled */ class AddSalesQuoteItemOnDefaultStockTest extends TestCase { diff --git a/InventoryConfigurableProduct/composer.json b/InventoryConfigurableProduct/composer.json index 5a3e350f7cb3..bcf5b46c7c1a 100644 --- a/InventoryConfigurableProduct/composer.json +++ b/InventoryConfigurableProduct/composer.json @@ -13,6 +13,10 @@ "magento/module-sales": "*", "magento/module-configurable-product": "*" }, + "suggest": { + "magento/module-inventory-catalog": "*", + "magento/module-inventory-sales": "*" + }, "type": "magento2-module", "license": [ "OSL-3.0", diff --git a/InventoryConfigurableProduct/etc/di.xml b/InventoryConfigurableProduct/etc/di.xml index 77358c2d991e..c8c1ff6dfae3 100644 --- a/InventoryConfigurableProduct/etc/di.xml +++ b/InventoryConfigurableProduct/etc/di.xml @@ -26,4 +26,22 @@ + + + Magento\InventorySalesApi\Api\AreProductsSalableInterface\Proxy + + + + + + + + + + true + Magento\InventoryConfigurableProduct\Model\IsProductSalableCondition\ProductOptionsCondition + + + + 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 @@ - - - diff --git a/InventoryConfigurableProductIndexer/Indexer/SourceItem/SourceItemIndexer.php b/InventoryConfigurableProductIndexer/Indexer/SourceItem/SourceItemIndexer.php index c6a798592ada..03e93320c084 100644 --- a/InventoryConfigurableProductIndexer/Indexer/SourceItem/SourceItemIndexer.php +++ b/InventoryConfigurableProductIndexer/Indexer/SourceItem/SourceItemIndexer.php @@ -8,13 +8,15 @@ namespace Magento\InventoryConfigurableProductIndexer\Indexer\SourceItem; use Magento\Framework\App\ResourceConnection; +use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexHandlerInterface; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexStructureInterface; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; -use Magento\InventoryIndexer\Indexer\InventoryIndexer; +/** + * Configurable product source item indexer. + */ class SourceItemIndexer { /** @@ -47,11 +49,6 @@ class SourceItemIndexer */ private $siblingSkuListInStockProvider; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @param ResourceConnection $resourceConnection * @param IndexNameBuilder $indexNameBuilder @@ -59,7 +56,6 @@ class SourceItemIndexer * @param IndexStructureInterface $indexStructure * @param IndexDataBySkuListProvider $indexDataBySkuListProvider * @param SiblingSkuListInStockProvider $siblingSkuListInStockProvider - * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( ResourceConnection $resourceConnection, @@ -67,8 +63,7 @@ public function __construct( IndexHandlerInterface $indexHandler, IndexStructureInterface $indexStructure, IndexDataBySkuListProvider $indexDataBySkuListProvider, - SiblingSkuListInStockProvider $siblingSkuListInStockProvider, - DefaultStockProviderInterface $defaultStockProvider + SiblingSkuListInStockProvider $siblingSkuListInStockProvider ) { $this->resourceConnection = $resourceConnection; $this->indexNameBuilder = $indexNameBuilder; @@ -76,10 +71,11 @@ public function __construct( $this->indexDataBySkuListProvider = $indexDataBySkuListProvider; $this->indexStructure = $indexStructure; $this->siblingSkuListInStockProvider = $siblingSkuListInStockProvider; - $this->defaultStockProvider = $defaultStockProvider; } /** + * Reindex given source items. + * * @param array $sourceItemIds */ public function executeList(array $sourceItemIds) @@ -88,10 +84,6 @@ public function executeList(array $sourceItemIds) foreach ($skuListInStockList as $skuListInStock) { $stockId = $skuListInStock->getStockId(); - - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } $skuList = $skuListInStock->getSkuList(); $mainIndexName = $this->indexNameBuilder diff --git a/InventoryConfigurableProductIndexer/Indexer/Stock/StockIndexer.php b/InventoryConfigurableProductIndexer/Indexer/Stock/StockIndexer.php index 68bba4793de9..9b3064f4222c 100644 --- a/InventoryConfigurableProductIndexer/Indexer/Stock/StockIndexer.php +++ b/InventoryConfigurableProductIndexer/Indexer/Stock/StockIndexer.php @@ -10,7 +10,6 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\StateException; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\Stock\GetAllStockIds; use Magento\InventoryIndexer\Indexer\Stock\PrepareIndexDataForClearingIndex; @@ -57,11 +56,6 @@ class StockIndexer */ private $indexTableSwitcher; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @var PrepareIndexDataForClearingIndex */ @@ -76,7 +70,6 @@ class StockIndexer * @param IndexNameBuilder $indexNameBuilder * @param IndexDataByStockIdProvider $indexDataByStockIdProvider * @param IndexTableSwitcherInterface $indexTableSwitcher - * @param DefaultStockProviderInterface $defaultStockProvider * @param PrepareIndexDataForClearingIndex|null $prepareIndexDataForClearingIndex */ public function __construct( @@ -86,7 +79,6 @@ public function __construct( IndexNameBuilder $indexNameBuilder, IndexDataByStockIdProvider $indexDataByStockIdProvider, IndexTableSwitcherInterface $indexTableSwitcher, - DefaultStockProviderInterface $defaultStockProvider, PrepareIndexDataForClearingIndex $prepareIndexDataForClearingIndex = null ) { $this->getAllStockIds = $getAllStockIds; @@ -95,7 +87,6 @@ public function __construct( $this->indexNameBuilder = $indexNameBuilder; $this->indexDataByStockIdProvider = $indexDataByStockIdProvider; $this->indexTableSwitcher = $indexTableSwitcher; - $this->defaultStockProvider = $defaultStockProvider; $this->prepareIndexDataForClearingIndex = $prepareIndexDataForClearingIndex ?: ObjectManager::getInstance() ->get(PrepareIndexDataForClearingIndex::class); } @@ -134,10 +125,6 @@ public function executeRow(int $stockId): void public function executeList(array $stockIds): void { foreach ($stockIds as $stockId) { - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } - $mainIndexName = $this->indexNameBuilder ->setIndexId(InventoryIndexer::INDEXER_ID) ->addDimension('stock_', (string)$stockId) diff --git a/InventoryConfigurableProductIndexer/Plugin/Catalog/Model/ResourceModel/Product/ReindexSourceItemsPlugin.php b/InventoryConfigurableProductIndexer/Plugin/Catalog/Model/ResourceModel/Product/ReindexSourceItemsPlugin.php new file mode 100644 index 000000000000..56b4d418ca7d --- /dev/null +++ b/InventoryConfigurableProductIndexer/Plugin/Catalog/Model/ResourceModel/Product/ReindexSourceItemsPlugin.php @@ -0,0 +1,89 @@ +getSourceItemsBySku = $getSourceItemsBySku; + $this->getSkusByProductIds = $getSkusByProductIds; + $this->getSourceItemIds = $getSourceItemIds; + $this->sourceItemIndexer = $sourceItemIndexer; + } + + /** + * Reindex configurable source items after product save. + * + * @param Product $subject + * @param Product $result + * @param AbstractModel $product + * @return Product + * @throws NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterSave(Product $subject, Product $result, AbstractModel $product): Product + { + if ($product->getTypeId() !== Configurable::TYPE_CODE) { + return $result; + } + $childrenIds = $product->getExtensionAttributes()->getConfigurableProductLinks() ?: []; + $skus = $this->getSkusByProductIds->execute($childrenIds); + $sourceItems = [[]]; + foreach ($skus as $sku) { + $sourceItems[] = $this->getSourceItemsBySku->execute($sku); + } + $sourceItems = array_merge(...$sourceItems); + $sourceItemIds = $this->getSourceItemIds->execute($sourceItems); + $this->sourceItemIndexer->executeList($sourceItemIds); + + return $result; + } +} diff --git a/InventoryConfigurableProductIndexer/Plugin/InventoryIndexer/Model/Queue/GetDataForUpdate/AddConfigurableProductDataPlugin.php b/InventoryConfigurableProductIndexer/Plugin/InventoryIndexer/Model/Queue/GetDataForUpdate/AddConfigurableProductDataPlugin.php new file mode 100644 index 000000000000..0e62f8fcfb12 --- /dev/null +++ b/InventoryConfigurableProductIndexer/Plugin/InventoryIndexer/Model/Queue/GetDataForUpdate/AddConfigurableProductDataPlugin.php @@ -0,0 +1,130 @@ +type = $type; + $this->getProductIdsBySkus = $getProductIdsBySkus; + $this->getSkusByProductIds = $getSkusByProductIds; + $this->getStockItemData = $getStockItemData; + $this->logger = $logger; + $this->areProductsSalable = $areProductsSalable; + } + + /** + * Add configurable product to index data. + * + * @param GetDataForUpdate $subject + * @param array $result + * @param array $salabilityData + * @param int $stockId + * @return array + * @throws NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute(GetDataForUpdate $subject, array $result, array $salabilityData, int $stockId): array + { + $configurableData = []; + $skus = array_keys($result); + $childrenIds = $this->getProductIdsBySkus->execute($skus); + $configurableProductIds = []; + foreach ($childrenIds as $childId) { + $configurableProductIds[] = $this->type->getParentIdsByChild($childId); + } + $configurableProductIds = array_merge(...$configurableProductIds); + $configurableSkus = $configurableProductIds ? $this->getSkusByProductIds->execute($configurableProductIds) : []; + $areConfigurableProductsSalable = $this->areProductsSalable->execute($configurableSkus, $stockId); + foreach ($areConfigurableProductsSalable as $salableResult) { + if ($salableResult->isSalable() !== $this->getIndexSalabilityStatus($salableResult->getSku(), $stockId)) { + $configurableData[$salableResult->getSku()] = $salableResult->isSalable(); + } + } + + return array_merge($result, $configurableData); + } + + /** + * Get current index is_salable value. + * + * @param string $sku + * @param int $stockId + * @return bool|null + */ + private function getIndexSalabilityStatus(string $sku, int $stockId): ?bool + { + try { + $data = $this->getStockItemData->execute($sku, $stockId); + $isSalable = $data ? (bool)$data[GetStockItemDataInterface::IS_SALABLE] : false; + } catch (LocalizedException $e) { + $this->logger->error($e->getLogMessage()); + return null; + } + + return $isSalable; + } +} diff --git a/InventoryConfigurableProductIndexer/composer.json b/InventoryConfigurableProductIndexer/composer.json index 7cddd52ebb9e..1bfa284d0bf5 100644 --- a/InventoryConfigurableProductIndexer/composer.json +++ b/InventoryConfigurableProductIndexer/composer.json @@ -5,10 +5,12 @@ "php": "~7.3.0||~7.4.0", "magento/framework": "*", "magento/module-catalog": "*", + "magento/module-configurable-product": "*", "magento/module-inventory-api": "*", "magento/module-inventory-catalog-api": "*", "magento/module-inventory-indexer": "*", - "magento/module-inventory-multi-dimensional-indexer-api": "*" + "magento/module-inventory-multi-dimensional-indexer-api": "*", + "magento/module-inventory-sales-api": "*" }, "suggest": { "magento/module-inventory": "*" diff --git a/InventoryConfigurableProductIndexer/etc/di.xml b/InventoryConfigurableProductIndexer/etc/di.xml index 9def048d6c77..3e614edc3e4f 100644 --- a/InventoryConfigurableProductIndexer/etc/di.xml +++ b/InventoryConfigurableProductIndexer/etc/di.xml @@ -32,4 +32,10 @@ Magento\Inventory\Model\ResourceModel\StockSourceLink::TABLE_NAME_STOCK_SOURCE_LINK + + + + + + diff --git a/InventoryElasticsearch/Plugin/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider/StockedProductFilterByInventoryStock.php b/InventoryElasticsearch/Plugin/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider/StockedProductFilterByInventoryStock.php index 00e38f931433..66f4f82b570d 100644 --- a/InventoryElasticsearch/Plugin/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider/StockedProductFilterByInventoryStock.php +++ b/InventoryElasticsearch/Plugin/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider/StockedProductFilterByInventoryStock.php @@ -7,13 +7,11 @@ namespace Magento\InventoryElasticsearch\Plugin\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; -use Magento\CatalogInventory\Api\Data\StockStatusInterface; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory; use Magento\CatalogInventory\Api\StockStatusRepositoryInterface; use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; use Magento\Framework\App\ResourceConnection; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Model\StockIndexTableNameResolverInterface; use Magento\InventorySalesApi\Model\StockByWebsiteIdResolverInterface; use Magento\Store\Api\StoreRepositoryInterface; @@ -53,11 +51,6 @@ class StockedProductFilterByInventoryStock */ private $stockStatusRepository; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @var StoreRepositoryInterface */ @@ -70,7 +63,6 @@ class StockedProductFilterByInventoryStock * @param StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver * @param StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory * @param StockStatusRepositoryInterface $stockStatusRepository - * @param DefaultStockProviderInterface $defaultStockProvider * @param StoreRepositoryInterface $storeRepository */ public function __construct( @@ -80,7 +72,6 @@ public function __construct( StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver, StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory, StockStatusRepositoryInterface $stockStatusRepository, - DefaultStockProviderInterface $defaultStockProvider, StoreRepositoryInterface $storeRepository ) { $this->stockConfiguration = $stockConfiguration; @@ -89,7 +80,6 @@ public function __construct( $this->stockByWebsiteIdResolver = $stockByWebsiteIdResolver; $this->stockStatusCriteriaFactory = $stockStatusCriteriaFactory; $this->stockStatusRepository = $stockStatusRepository; - $this->defaultStockProvider = $defaultStockProvider; $this->storeRepository = $storeRepository; } @@ -114,13 +104,7 @@ public function beforePrepareProductIndex( $store = $this->storeRepository->getById($storeId); $stock = $this->stockByWebsiteIdResolver->execute((int)$store->getWebsiteId()); $stockId = $stock->getStockId(); - - if ($this->defaultStockProvider->getId() === $stockId) { - $stockStatuses = $this->getStockStatusesFromDefaultStock($productIds); - } else { - $stockStatuses = $this->getStockStatusesFromCustomStock($productIds, $stockId); - } - + $stockStatuses = $this->getStockStatusesFromStock($productIds, $stockId); $indexData = array_intersect_key($indexData, $stockStatuses); } @@ -131,27 +115,6 @@ public function beforePrepareProductIndex( ]; } - /** - * Get product stock statuses on default stock. - * - * @param array $productIds - * @return array - */ - private function getStockStatusesFromDefaultStock(array $productIds): array - { - $stockStatusCriteria = $this->stockStatusCriteriaFactory->create(); - $stockStatusCriteria->setProductsFilter($productIds); - $stockStatusCollection = $this->stockStatusRepository->getList($stockStatusCriteria); - $stockStatuses = $stockStatusCollection->getItems(); - - return array_filter( - $stockStatuses, - function (StockStatusInterface $stockStatus) { - return StockStatusInterface::STATUS_IN_STOCK === (int)$stockStatus->getStockStatus(); - } - ); - } - /** * Get product stock statuses on custom stock. * @@ -159,7 +122,7 @@ function (StockStatusInterface $stockStatus) { * @param int $stockId * @return array */ - private function getStockStatusesFromCustomStock(array $productIds, int $stockId): array + private function getStockStatusesFromStock(array $productIds, int $stockId): array { $stockTable = $this->stockIndexTableNameResolver->execute($stockId); $connection = $this->resourceConnection->getConnection(); diff --git a/InventoryElasticsearch/composer.json b/InventoryElasticsearch/composer.json index bc435956cb52..478629f005bf 100644 --- a/InventoryElasticsearch/composer.json +++ b/InventoryElasticsearch/composer.json @@ -6,7 +6,6 @@ "magento/framework": "*", "magento/module-catalog-inventory": "*", "magento/module-catalog-search": "*", - "magento/module-inventory-catalog-api": "*", "magento/module-inventory-indexer": "*", "magento/module-inventory-sales-api": "*", "magento/module-store": "*" diff --git a/InventoryGroupedProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php b/InventoryGroupedProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php index 3413b9b3b47a..31465b58ae64 100644 --- a/InventoryGroupedProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php +++ b/InventoryGroupedProduct/Test/Integration/Order/PlaceOrderOnDefaultStockTest.php @@ -10,6 +10,9 @@ use Magento\Framework\Exception\LocalizedException; use Magento\InventorySales\Test\Integration\Order\PlaceOrderOnDefaultStockTest as PlaceOrderTest; +/** + * @magentoDbIsolation disabled + */ class PlaceOrderOnDefaultStockTest extends PlaceOrderTest { /** diff --git a/InventoryGroupedProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php b/InventoryGroupedProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php index cd2b4ddbf1c4..5bea0831fce1 100644 --- a/InventoryGroupedProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php +++ b/InventoryGroupedProduct/Test/Integration/SalesQuoteItem/AddSalesQuoteItemOnDefaultStockTest.php @@ -19,6 +19,9 @@ use PHPUnit\Framework\TestCase; use Magento\Framework\DataObject\Factory as DataObjectFactory; +/** + * @magentoDbIsolation disabled + */ class AddSalesQuoteItemOnDefaultStockTest extends TestCase { /** diff --git a/InventoryGroupedProductIndexer/Indexer/SourceItem/SourceItemIndexer.php b/InventoryGroupedProductIndexer/Indexer/SourceItem/SourceItemIndexer.php index ff27210d6f29..17142a6df648 100644 --- a/InventoryGroupedProductIndexer/Indexer/SourceItem/SourceItemIndexer.php +++ b/InventoryGroupedProductIndexer/Indexer/SourceItem/SourceItemIndexer.php @@ -8,13 +8,15 @@ namespace Magento\InventoryGroupedProductIndexer\Indexer\SourceItem; use Magento\Framework\App\ResourceConnection; +use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryMultiDimensionalIndexerApi\Model\Alias; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexHandlerInterface; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexNameBuilder; use Magento\InventoryMultiDimensionalIndexerApi\Model\IndexStructureInterface; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; -use Magento\InventoryIndexer\Indexer\InventoryIndexer; +/** + * Grouped product source item indexer. + */ class SourceItemIndexer { /** @@ -47,11 +49,6 @@ class SourceItemIndexer */ private $siblingSkuListInStockProvider; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @param ResourceConnection $resourceConnection * @param IndexNameBuilder $indexNameBuilder @@ -59,7 +56,6 @@ class SourceItemIndexer * @param IndexStructureInterface $indexStructure * @param IndexDataBySkuListProvider $indexDataBySkuListProvider * @param SiblingSkuListInStockProvider $siblingSkuListInStockProvider - * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( ResourceConnection $resourceConnection, @@ -67,8 +63,7 @@ public function __construct( IndexHandlerInterface $indexHandler, IndexStructureInterface $indexStructure, IndexDataBySkuListProvider $indexDataBySkuListProvider, - SiblingSkuListInStockProvider $siblingSkuListInStockProvider, - DefaultStockProviderInterface $defaultStockProvider + SiblingSkuListInStockProvider $siblingSkuListInStockProvider ) { $this->resourceConnection = $resourceConnection; $this->indexNameBuilder = $indexNameBuilder; @@ -76,10 +71,11 @@ public function __construct( $this->indexDataBySkuListProvider = $indexDataBySkuListProvider; $this->indexStructure = $indexStructure; $this->siblingSkuListInStockProvider = $siblingSkuListInStockProvider; - $this->defaultStockProvider = $defaultStockProvider; } /** + * Reindex given source items. + * * @param array $sourceItemIds */ public function executeList(array $sourceItemIds) @@ -88,10 +84,6 @@ public function executeList(array $sourceItemIds) foreach ($skuListInStockList as $skuListInStock) { $stockId = $skuListInStock->getStockId(); - - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } $skuList = $skuListInStock->getSkuList(); $mainIndexName = $this->indexNameBuilder diff --git a/InventoryGroupedProductIndexer/Indexer/Stock/StockIndexer.php b/InventoryGroupedProductIndexer/Indexer/Stock/StockIndexer.php index 5cded3aa5306..c812611c7040 100644 --- a/InventoryGroupedProductIndexer/Indexer/Stock/StockIndexer.php +++ b/InventoryGroupedProductIndexer/Indexer/Stock/StockIndexer.php @@ -10,7 +10,6 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\StateException; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\Stock\GetAllStockIds; use Magento\InventoryIndexer\Indexer\Stock\PrepareIndexDataForClearingIndex; @@ -57,11 +56,6 @@ class StockIndexer */ private $indexTableSwitcher; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @var PrepareIndexDataForClearingIndex */ @@ -76,7 +70,6 @@ class StockIndexer * @param IndexNameBuilder $indexNameBuilder * @param IndexDataByStockIdProvider $indexDataByStockIdProvider * @param IndexTableSwitcherInterface $indexTableSwitcher - * @param DefaultStockProviderInterface $defaultStockProvider * @param PrepareIndexDataForClearingIndex|null $prepareIndexDataForClearingIndex */ public function __construct( @@ -86,7 +79,6 @@ public function __construct( IndexNameBuilder $indexNameBuilder, IndexDataByStockIdProvider $indexDataByStockIdProvider, IndexTableSwitcherInterface $indexTableSwitcher, - DefaultStockProviderInterface $defaultStockProvider, PrepareIndexDataForClearingIndex $prepareIndexDataForClearingIndex = null ) { $this->getAllStockIds = $getAllStockIds; @@ -95,7 +87,6 @@ public function __construct( $this->indexNameBuilder = $indexNameBuilder; $this->indexDataByStockIdProvider = $indexDataByStockIdProvider; $this->indexTableSwitcher = $indexTableSwitcher; - $this->defaultStockProvider = $defaultStockProvider; $this->prepareIndexDataForClearingIndex = $prepareIndexDataForClearingIndex ?: ObjectManager::getInstance() ->get(PrepareIndexDataForClearingIndex::class); } @@ -134,10 +125,6 @@ public function executeRow(int $stockId): void public function executeList(array $stockIds): void { foreach ($stockIds as $stockId) { - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } - $mainIndexName = $this->indexNameBuilder ->setIndexId(InventoryIndexer::INDEXER_ID) ->addDimension('stock_', (string)$stockId) diff --git a/InventoryGroupedProductIndexer/composer.json b/InventoryGroupedProductIndexer/composer.json index 7c746bd140b9..18fe1d252e3f 100644 --- a/InventoryGroupedProductIndexer/composer.json +++ b/InventoryGroupedProductIndexer/composer.json @@ -6,7 +6,6 @@ "magento/framework": "*", "magento/module-catalog": "*", "magento/module-inventory-api": "*", - "magento/module-inventory-catalog-api": "*", "magento/module-inventory-indexer": "*", "magento/module-inventory-multi-dimensional-indexer-api": "*", "magento/module-grouped-product": "*" diff --git a/InventoryImportExport/Test/Integration/Model/Export/SourcesTest.php b/InventoryImportExport/Test/Integration/Model/Export/SourcesTest.php index 9959aab207fc..baad856a9cc3 100644 --- a/InventoryImportExport/Test/Integration/Model/Export/SourcesTest.php +++ b/InventoryImportExport/Test/Integration/Model/Export/SourcesTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +/** + * @magentoDbIsolation disabled + */ class SourcesTest extends TestCase { /** diff --git a/InventoryIndexer/Indexer/SelectBuilder.php b/InventoryIndexer/Indexer/SelectBuilder.php index ac1beea684bb..13af262568d3 100644 --- a/InventoryIndexer/Indexer/SelectBuilder.php +++ b/InventoryIndexer/Indexer/SelectBuilder.php @@ -18,7 +18,7 @@ use Magento\InventorySales\Model\ResourceModel\IsStockItemSalableCondition\GetIsStockItemSalableConditionInterface; /** - * Select builder + * Inventory select builder. */ class SelectBuilder { @@ -53,6 +53,8 @@ public function __construct( } /** + * Build inventory select for given stock. + * * @param int $stockId * @return Select */ @@ -79,12 +81,13 @@ public function execute(int $stockId): Select [] ); + $isSalableExpression = new \Zend_Db_Expr($this->getIsStockItemSalableCondition->execute($select)); $select->from( ['source_item' => $sourceItemTable], [ SourceItemInterface::SKU, IndexStructure::QUANTITY => 'SUM(' . $quantityExpression . ')', - IndexStructure::IS_SALABLE => $this->getIsStockItemSalableCondition->execute($select), + IndexStructure::IS_SALABLE => $isSalableExpression ] ) ->where('source_item.' . SourceItemInterface::SOURCE_CODE . ' IN (?)', $sourceCodes) diff --git a/InventoryIndexer/Indexer/SourceItem/Strategy/Sync.php b/InventoryIndexer/Indexer/SourceItem/Strategy/Sync.php index 85851a5942e7..54c9427356dc 100644 --- a/InventoryIndexer/Indexer/SourceItem/Strategy/Sync.php +++ b/InventoryIndexer/Indexer/SourceItem/Strategy/Sync.php @@ -8,7 +8,6 @@ namespace Magento\InventoryIndexer\Indexer\SourceItem\Strategy; use Magento\Framework\App\ResourceConnection; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\SourceItem\GetSkuListInStock; use Magento\InventoryIndexer\Indexer\SourceItem\IndexDataBySkuListProvider; @@ -53,11 +52,6 @@ class Sync */ private $stockIndexer; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * $indexStructure is reserved name for construct variable (in index internal mechanism) * @@ -67,7 +61,6 @@ class Sync * @param IndexDataBySkuListProvider $indexDataBySkuListProvider * @param IndexNameBuilder $indexNameBuilder * @param StockIndexer $stockIndexer - * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( GetSkuListInStock $getSkuListInStockToUpdate, @@ -75,8 +68,7 @@ public function __construct( IndexHandlerInterface $indexHandler, IndexDataBySkuListProvider $indexDataBySkuListProvider, IndexNameBuilder $indexNameBuilder, - StockIndexer $stockIndexer, - DefaultStockProviderInterface $defaultStockProvider + StockIndexer $stockIndexer ) { $this->getSkuListInStock = $getSkuListInStockToUpdate; $this->indexStructure = $indexStructureHandler; @@ -84,7 +76,6 @@ public function __construct( $this->indexDataBySkuListProvider = $indexDataBySkuListProvider; $this->indexNameBuilder = $indexNameBuilder; $this->stockIndexer = $stockIndexer; - $this->defaultStockProvider = $defaultStockProvider; } /** @@ -92,16 +83,12 @@ public function __construct( * * @param int[] $sourceItemIds */ - public function executeList(array $sourceItemIds) : void + public function executeList(array $sourceItemIds): void { $skuListInStockList = $this->getSkuListInStock->execute($sourceItemIds); foreach ($skuListInStockList as $skuListInStock) { $stockId = $skuListInStock->getStockId(); - if ($this->defaultStockProvider->getId() === $stockId) { - continue; - } - $skuList = $skuListInStock->getSkuList(); $mainIndexName = $this->indexNameBuilder @@ -134,7 +121,7 @@ public function executeList(array $sourceItemIds) : void * * @return void */ - public function executeFull() : void + public function executeFull(): void { $this->stockIndexer->executeFull(); } @@ -145,7 +132,7 @@ public function executeFull() : void * @param int $sourceItemId * @return void */ - public function executeRow(int $sourceItemId) : void + public function executeRow(int $sourceItemId): void { $this->executeList([$sourceItemId]); } diff --git a/InventoryIndexer/Indexer/Stock/Strategy/Sync.php b/InventoryIndexer/Indexer/Stock/Strategy/Sync.php index fb5d6506a320..6ae098e783f6 100644 --- a/InventoryIndexer/Indexer/Stock/Strategy/Sync.php +++ b/InventoryIndexer/Indexer/Stock/Strategy/Sync.php @@ -7,7 +7,6 @@ namespace Magento\InventoryIndexer\Indexer\Stock\Strategy; use Magento\Framework\App\ResourceConnection; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\InventoryIndexer; use Magento\InventoryIndexer\Indexer\Stock\GetAllStockIds; use Magento\InventoryIndexer\Indexer\Stock\IndexDataProviderByStockId; @@ -52,11 +51,6 @@ class Sync */ private $indexTableSwitcher; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * $indexStructure is reserved name for construct variable in index internal mechanism * @@ -66,7 +60,6 @@ class Sync * @param IndexNameBuilder $indexNameBuilder * @param IndexDataProviderByStockId $indexDataProviderByStockId * @param IndexTableSwitcherInterface $indexTableSwitcher - * @param DefaultStockProviderInterface $defaultStockProvider */ public function __construct( GetAllStockIds $getAllStockIds, @@ -74,8 +67,7 @@ public function __construct( IndexHandlerInterface $indexHandler, IndexNameBuilder $indexNameBuilder, IndexDataProviderByStockId $indexDataProviderByStockId, - IndexTableSwitcherInterface $indexTableSwitcher, - DefaultStockProviderInterface $defaultStockProvider + IndexTableSwitcherInterface $indexTableSwitcher ) { $this->getAllStockIds = $getAllStockIds; $this->indexStructure = $indexStructureHandler; @@ -83,7 +75,6 @@ public function __construct( $this->indexNameBuilder = $indexNameBuilder; $this->indexDataProviderByStockId = $indexDataProviderByStockId; $this->indexTableSwitcher = $indexTableSwitcher; - $this->defaultStockProvider = $defaultStockProvider; } /** @@ -117,10 +108,6 @@ public function executeRow(int $stockId): void public function executeList(array $stockIds): void { foreach ($stockIds as $stockId) { - if ($this->defaultStockProvider->getId() === (int)$stockId) { - continue; - } - $replicaIndexName = $this->indexNameBuilder ->setIndexId(InventoryIndexer::INDEXER_ID) ->addDimension('stock_', (string)$stockId) diff --git a/InventoryIndexer/Model/IsProductSalable.php b/InventoryIndexer/Model/IsProductSalable.php index 608029118a02..9c06aefc9b52 100644 --- a/InventoryIndexer/Model/IsProductSalable.php +++ b/InventoryIndexer/Model/IsProductSalable.php @@ -7,6 +7,7 @@ namespace Magento\InventoryIndexer\Model; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Exception\LocalizedException; use Magento\InventorySalesApi\Api\IsProductSalableInterface; use Magento\InventorySalesApi\Model\GetStockItemDataInterface; @@ -21,21 +22,30 @@ class IsProductSalable implements IsProductSalableInterface * @var GetStockItemDataInterface */ private $getStockItemData; + /** * @var LoggerInterface */ private $logger; + /** + * @var ScopeConfigInterface + */ + private $config; + /** * @param GetStockItemDataInterface $getStockItemData + * @param ScopeConfigInterface $config * @param LoggerInterface $logger */ public function __construct( GetStockItemDataInterface $getStockItemData, + ScopeConfigInterface $config, LoggerInterface $logger ) { $this->getStockItemData = $getStockItemData; $this->logger = $logger; + $this->config = $config; } /** @@ -44,8 +54,9 @@ public function __construct( public function execute(string $sku, int $stockId): bool { try { + $showOutOfStock = (int)$this->config->getValue('cataloginventory/options/show_out_of_stock'); $stockItem = $this->getStockItemData->execute($sku, $stockId); - $isSalable = (bool)($stockItem[GetStockItemDataInterface::IS_SALABLE] ?? false); + $isSalable = $showOutOfStock ? true : (bool)($stockItem[GetStockItemDataInterface::IS_SALABLE] ?? false); } catch (LocalizedException $exception) { $this->logger->warning( sprintf( diff --git a/InventoryIndexer/Model/Queue/UpdateIndexSalabilityStatus/IndexProcessor/GetDataForUpdate.php b/InventoryIndexer/Model/Queue/UpdateIndexSalabilityStatus/IndexProcessor/GetDataForUpdate.php new file mode 100644 index 000000000000..348c1b170bd3 --- /dev/null +++ b/InventoryIndexer/Model/Queue/UpdateIndexSalabilityStatus/IndexProcessor/GetDataForUpdate.php @@ -0,0 +1,71 @@ +getStockItemData = $getStockItemData; + } + + /** + * Build data for index update. + * + * @param IsProductSalableResultInterface[] $salabilityData + * @param int $stockId + * @return bool[] - ['sku' => bool] + */ + public function execute(array $salabilityData, int $stockId): array + { + $data = []; + foreach ($salabilityData as $isProductSalableResult) { + $currentStatus = $this->getIndexSalabilityStatus($isProductSalableResult->getSku(), $stockId); + if ($isProductSalableResult->isSalable() != $currentStatus && $currentStatus !== null) { + $data[$isProductSalableResult->getSku()] = $isProductSalableResult->isSalable(); + } + } + + return $data; + } + + /** + * Get current index is_salable value. + * + * @param string $sku + * @param int $stockId + * + * @return bool|null + */ + private function getIndexSalabilityStatus(string $sku, int $stockId): ?bool + { + try { + $data = $this->getStockItemData->execute($sku, $stockId); + $isSalable = $data ? (bool)$data[GetStockItemDataInterface::IS_SALABLE] : false; + } catch (LocalizedException $e) { + $isSalable = null; + } + + return $isSalable; + } +} diff --git a/InventoryIndexer/Model/ResourceModel/GetProductIdsByStockIds.php b/InventoryIndexer/Model/ResourceModel/GetProductIdsByStockIds.php index e8e3a32cfb9a..6cadf43106a8 100644 --- a/InventoryIndexer/Model/ResourceModel/GetProductIdsByStockIds.php +++ b/InventoryIndexer/Model/ResourceModel/GetProductIdsByStockIds.php @@ -8,7 +8,6 @@ namespace Magento\InventoryIndexer\Model\ResourceModel; use Magento\Framework\App\ResourceConnection; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; use Magento\InventoryIndexer\Indexer\IndexStructure; use Magento\InventoryIndexer\Model\StockIndexTableNameResolverInterface; @@ -27,11 +26,6 @@ class GetProductIdsByStockIds */ private $stockIndexTableNameResolver; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @var string */ @@ -40,17 +34,14 @@ class GetProductIdsByStockIds /** * @param ResourceConnection $resource * @param StockIndexTableNameResolverInterface $stockIndexTableNameResolver - * @param DefaultStockProviderInterface $defaultStockProvider * @param string $productTableName */ public function __construct( ResourceConnection $resource, StockIndexTableNameResolverInterface $stockIndexTableNameResolver, - DefaultStockProviderInterface $defaultStockProvider, string $productTableName ) { $this->resource = $resource; - $this->defaultStockProvider = $defaultStockProvider; $this->stockIndexTableNameResolver = $stockIndexTableNameResolver; $this->productTableName = $productTableName; } @@ -65,11 +56,9 @@ public function execute(array $stockIds): array { $productIds = [[]]; foreach ($stockIds as $stockId) { - if ($this->defaultStockProvider->getId() === (int)$stockId) { - continue; - } $stockIndexTableName = $this->stockIndexTableNameResolver->execute($stockId); $connection = $this->resource->getConnection(); + $sql = $connection->select() ->from(['stock_index' => $stockIndexTableName], []) ->join( diff --git a/InventoryIndexer/Model/ResourceModel/GetStockItemData.php b/InventoryIndexer/Model/ResourceModel/GetStockItemData.php index ddc4917c6715..d703481d5958 100644 --- a/InventoryIndexer/Model/ResourceModel/GetStockItemData.php +++ b/InventoryIndexer/Model/ResourceModel/GetStockItemData.php @@ -9,11 +9,10 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\LocalizedException; +use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface; +use Magento\InventoryIndexer\Indexer\IndexStructure; use Magento\InventoryIndexer\Model\StockIndexTableNameResolverInterface; use Magento\InventorySalesApi\Model\GetStockItemDataInterface; -use Magento\InventoryIndexer\Indexer\IndexStructure; -use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface; -use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface; /** * @inheritdoc @@ -30,11 +29,6 @@ class GetStockItemData implements GetStockItemDataInterface */ private $stockIndexTableNameResolver; - /** - * @var DefaultStockProviderInterface - */ - private $defaultStockProvider; - /** * @var GetProductIdsBySkusInterface */ @@ -43,18 +37,15 @@ class GetStockItemData implements GetStockItemDataInterface /** * @param ResourceConnection $resource * @param StockIndexTableNameResolverInterface $stockIndexTableNameResolver - * @param DefaultStockProviderInterface $defaultStockProvider * @param GetProductIdsBySkusInterface $getProductIdsBySkus */ public function __construct( ResourceConnection $resource, StockIndexTableNameResolverInterface $stockIndexTableNameResolver, - DefaultStockProviderInterface $defaultStockProvider, GetProductIdsBySkusInterface $getProductIdsBySkus ) { $this->resource = $resource; $this->stockIndexTableNameResolver = $stockIndexTableNameResolver; - $this->defaultStockProvider = $defaultStockProvider; $this->getProductIdsBySkus = $getProductIdsBySkus; } @@ -65,34 +56,19 @@ public function execute(string $sku, int $stockId): ?array { $connection = $this->resource->getConnection(); $select = $connection->select(); + $stockItemTableName = $this->stockIndexTableNameResolver->execute($stockId); + $select->from( + $stockItemTableName, + [ + GetStockItemDataInterface::QUANTITY => IndexStructure::QUANTITY, + GetStockItemDataInterface::IS_SALABLE => IndexStructure::IS_SALABLE, + ] + )->where(IndexStructure::SKU . ' = ?', $sku); - if ($this->defaultStockProvider->getId() === $stockId) { - $productId = current($this->getProductIdsBySkus->execute([$sku])); - $stockItemTableName = $this->resource->getTableName('cataloginventory_stock_status'); - $select->from( - $stockItemTableName, - [ - GetStockItemDataInterface::QUANTITY => 'qty', - GetStockItemDataInterface::IS_SALABLE => 'stock_status', - ] - )->where('product_id = ?', $productId); - + try { return $connection->fetchRow($select) ?: null; - } else { - $stockItemTableName = $this->stockIndexTableNameResolver->execute($stockId); - $select->from( - $stockItemTableName, - [ - GetStockItemDataInterface::QUANTITY => IndexStructure::QUANTITY, - GetStockItemDataInterface::IS_SALABLE => IndexStructure::IS_SALABLE, - ] - )->where(IndexStructure::SKU . ' = ?', $sku); - - try { - return $connection->fetchRow($select) ?: null; - } catch (\Exception $e) { - throw new LocalizedException(__('Could not receive Stock Item data'), $e); - } + } catch (\Exception $e) { + throw new LocalizedException(__('Could not receive Stock Item data'), $e); } } } diff --git a/InventoryIndexer/etc/communication.xml b/InventoryIndexer/etc/communication.xml index f1e38ed2bbd4..b97f171a8316 100644 --- a/InventoryIndexer/etc/communication.xml +++ b/InventoryIndexer/etc/communication.xml @@ -6,13 +6,13 @@ */ --> + + + - - - diff --git a/InventoryIndexer/etc/queue_publisher.xml b/InventoryIndexer/etc/queue_publisher.xml index 6c655d0858d6..7500e484259d 100644 --- a/InventoryIndexer/etc/queue_publisher.xml +++ b/InventoryIndexer/etc/queue_publisher.xml @@ -6,13 +6,13 @@ */ --> - + - + - + diff --git a/InventoryIndexer/etc/queue_topology.xml b/InventoryIndexer/etc/queue_topology.xml index 3437ff521905..ef83ac53cfea 100644 --- a/InventoryIndexer/etc/queue_topology.xml +++ b/InventoryIndexer/etc/queue_topology.xml @@ -6,9 +6,9 @@ */ --> - + + - diff --git a/InventorySales/Model/IsProductSalableCondition/IsSalableWithReservationsCondition.php b/InventorySales/Model/IsProductSalableCondition/IsSalableWithReservationsCondition.php index cc8dee8b5ba2..be9dfdb59999 100644 --- a/InventorySales/Model/IsProductSalableCondition/IsSalableWithReservationsCondition.php +++ b/InventorySales/Model/IsProductSalableCondition/IsSalableWithReservationsCondition.php @@ -71,17 +71,16 @@ public function __construct( */ public function execute(string $sku, int $stockId): bool { + $productType = $this->getProductTypesBySkus->execute([$sku])[$sku]; + if (false === $this->isSourceItemManagementAllowedForProductType->execute($productType)) { + return true; + } $stockItemData = $this->getStockItemData->execute($sku, $stockId); if (null === $stockItemData) { // Sku is not assigned to Stock return false; } - $productType = $this->getProductTypesBySkus->execute([$sku])[$sku]; - if (false === $this->isSourceItemManagementAllowedForProductType->execute($productType)) { - return (bool)$stockItemData[GetStockItemDataInterface::IS_SALABLE]; - } - /** @var StockItemConfigurationInterface $stockItemConfiguration */ $stockItemConfiguration = $this->getStockItemConfiguration->execute($sku, $stockId); $qtyWithReservation = $stockItemData[GetStockItemDataInterface::QUANTITY] + diff --git a/InventorySales/Model/ResourceModel/IsStockItemSalableCondition/IsStockItemSalableConditionChain.php b/InventorySales/Model/ResourceModel/IsStockItemSalableCondition/IsStockItemSalableConditionChain.php index fb93eb8a3b5c..d549cfd60795 100644 --- a/InventorySales/Model/ResourceModel/IsStockItemSalableCondition/IsStockItemSalableConditionChain.php +++ b/InventorySales/Model/ResourceModel/IsStockItemSalableCondition/IsStockItemSalableConditionChain.php @@ -7,6 +7,7 @@ namespace Magento\InventorySales\Model\ResourceModel\IsStockItemSalableCondition; +use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; use Magento\Framework\Exception\LocalizedException; @@ -26,14 +27,20 @@ class IsStockItemSalableConditionChain implements GetIsStockItemSalableCondition */ private $resourceConnection; + /** + * @var StockConfigurationInterface + */ + private $configuration; + /** * @param ResourceConnection $resourceConnection + * @param StockConfigurationInterface $configuration * @param array $conditions - * * @throws LocalizedException */ public function __construct( ResourceConnection $resourceConnection, + StockConfigurationInterface $configuration, array $conditions = [] ) { foreach ($conditions as $getIsSalableCondition) { @@ -45,15 +52,15 @@ public function __construct( } $this->resourceConnection = $resourceConnection; $this->conditions = $conditions; + $this->configuration = $configuration; } /** * @inheritdoc - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute(Select $select): string { - if (empty($this->conditions)) { + if (empty($this->conditions) || !$this->configuration->getManageStock()) { return '1'; } diff --git a/InventorySales/Test/Integration/GetStockItemData/BackorderConditionTest.php b/InventorySales/Test/Integration/GetStockItemData/BackorderConditionTest.php index 8ba8afeb0caa..e2f1f188387c 100644 --- a/InventorySales/Test/Integration/GetStockItemData/BackorderConditionTest.php +++ b/InventorySales/Test/Integration/GetStockItemData/BackorderConditionTest.php @@ -185,7 +185,7 @@ public function backordersGlobalEnabledDataProvider(): array return [ ['SKU-1', 10, [GetStockItemDataInterface::QUANTITY => 8.5, GetStockItemDataInterface::IS_SALABLE => 1]], ['SKU-2', 10, null], - ['SKU-3', 10, [GetStockItemDataInterface::QUANTITY => 0, GetStockItemDataInterface::IS_SALABLE => 1]], + ['SKU-3', 10, [GetStockItemDataInterface::QUANTITY => 0, GetStockItemDataInterface::IS_SALABLE => 0]], ]; } @@ -230,7 +230,7 @@ public function backordersEnabledDataProvider(): array 10, StockItemConfigurationInterface::BACKORDERS_YES_NONOTIFY, [ - GetStockItemDataInterface::QUANTITY => 0, GetStockItemDataInterface::IS_SALABLE => 1 + GetStockItemDataInterface::QUANTITY => 0, GetStockItemDataInterface::IS_SALABLE => 0 ] ], [ @@ -238,7 +238,7 @@ public function backordersEnabledDataProvider(): array 10, StockItemConfigurationInterface::BACKORDERS_YES_NOTIFY, [ - GetStockItemDataInterface::QUANTITY => 0, GetStockItemDataInterface::IS_SALABLE => 1 + GetStockItemDataInterface::QUANTITY => 0, GetStockItemDataInterface::IS_SALABLE => 0 ] ], ]; @@ -271,7 +271,6 @@ private function setStockItemBackorders(string $sku, int $backordersStatus): voi $stockItemSearchCriteria->setProductsFilter($product->getId()); $stockItemsCollection = $this->stockItemRepository->getList($stockItemSearchCriteria); - /** @var StockItemInterface $legacyStockItem */ $legacyStockItem = current($stockItemsCollection->getItems()); $legacyStockItem->setBackorders($backordersStatus); $legacyStockItem->setUseConfigBackorders(false); diff --git a/InventorySales/Test/Integration/Stock/GetProductSalableQtyTest.php b/InventorySales/Test/Integration/Stock/GetProductSalableQtyTest.php index 11e0e6f226fa..1ee21419101b 100644 --- a/InventorySales/Test/Integration/Stock/GetProductSalableQtyTest.php +++ b/InventorySales/Test/Integration/Stock/GetProductSalableQtyTest.php @@ -7,8 +7,12 @@ namespace Magento\InventorySales\Test\Integration\Stock; -use Magento\InventoryReservationsApi\Model\CleanupReservationsInterface; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; +use Magento\InventoryApi\Api\GetSourceItemsBySkuInterface; +use Magento\InventoryApi\Api\SourceItemsSaveInterface; +use Magento\InventoryConfiguration\Model\GetLegacyStockItem; use Magento\InventoryReservationsApi\Model\AppendReservationsInterface; +use Magento\InventoryReservationsApi\Model\CleanupReservationsInterface; use Magento\InventoryReservationsApi\Model\ReservationBuilderInterface; use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface; use Magento\TestFramework\Helper\Bootstrap; @@ -119,4 +123,82 @@ public function testGetProductQuantityIfReservationsArePresent() $this->reservationBuilder->setStockId(10)->setSku('SKU-1')->setQuantity(3.5)->build(), ]); } + + /** + * Verify 'Out of stock' source items will show '0' salable qty in case global 'out of stock' threshold is negative. + * + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stocks.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stock_source_links.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/source_items.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryIndexer/Test/_files/reindex_inventory.php + * + * @magentoConfigFixture default_store cataloginventory/item_options/min_qty -10 + * @magentoConfigFixture default_store cataloginventory/item_options/backorders 1 + * + * @magentoDbIsolation disabled + */ + public function testGetSalableQuantityWithGlobalBackordersAndOutOfStockSourceItems() + { + $sku = 'SKU-2'; + self::assertEquals(15, $this->getProductSalableQty->execute($sku, 20)); + $this->setSourceItemsToOutOfStock($sku); + self::assertEquals(0, $this->getProductSalableQty->execute($sku, 20)); + } + + /** + * Verify 'Out of stock' source items will show '0' salable qty if stock item 'out of stock' threshold is negative. + * + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/products.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/sources.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stocks.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/stock_source_links.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryApi/Test/_files/source_items.php + * @magentoDataFixture ../../../../app/code/Magento/InventoryIndexer/Test/_files/reindex_inventory.php + * + * @magentoDbIsolation disabled + */ + public function testGetSalableQuantityWithBackordersAndOutOfStockSourceItems() + { + $sku = 'SKU-2'; + $this->enableBackorders(); + self::assertEquals(15, $this->getProductSalableQty->execute($sku, 20)); + $this->setSourceItemsToOutOfStock($sku); + self::assertEquals(0, $this->getProductSalableQty->execute($sku, 20)); + } + + /** + * Enable backorders and negative 'out or stock' threshold for stock item. + * + * @return void + */ + private function enableBackorders(): void + { + $getLegacyItem = Bootstrap::getObjectManager()->get(GetLegacyStockItem::class); + $stockItemRepository = Bootstrap::getObjectManager()->get(StockItemRepositoryInterface::class); + $legacyStockItem = $getLegacyItem->execute('SKU-2'); + $legacyStockItem->setBackorders(1); + $legacyStockItem->setUseConfigBackorders(false); + $legacyStockItem->setMinQty(-10); + $legacyStockItem->setUseConfigMinQty(false); + $stockItemRepository->save($legacyStockItem); + } + + /** + * Set source items for given product 'out of stock' status. + * + * @param string $sku + * @return void + */ + private function setSourceItemsToOutOfStock(string $sku): void + { + $sourceItemsSave = Bootstrap::getObjectManager()->get(SourceItemsSaveInterface::class); + $getSourceItems = Bootstrap::getObjectManager()->get(GetSourceItemsBySkuInterface::class); + $sourceItems = $getSourceItems->execute($sku); + foreach ($sourceItems as $sourceItem) { + $sourceItem->setStatus(0); + } + $sourceItemsSave->execute($sourceItems); + } } diff --git a/InventorySetupFixtureGenerator/Plugin/Setup/Model/FixtureGenerator/EntityGeneratorFactory/UpdateCustomTableMapPlugin.php b/InventorySetupFixtureGenerator/Plugin/Setup/Model/FixtureGenerator/EntityGeneratorFactory/UpdateCustomTableMapPlugin.php index 11349256c774..e9a13d247cbc 100644 --- a/InventorySetupFixtureGenerator/Plugin/Setup/Model/FixtureGenerator/EntityGeneratorFactory/UpdateCustomTableMapPlugin.php +++ b/InventorySetupFixtureGenerator/Plugin/Setup/Model/FixtureGenerator/EntityGeneratorFactory/UpdateCustomTableMapPlugin.php @@ -22,6 +22,13 @@ class UpdateCustomTableMapPlugin */ private $sourceItems = []; + /** + * Processed stock statuses. + * + * @var array + */ + private $stockStatuses = []; + /** * Inject inventory_source_item table data to FixtureGenerator\EntityGeneratorFactory arguments. * @@ -49,6 +56,21 @@ public function beforeCreate( return $binds; }, ]; + $data['customTableMap']['inventory_stock_1'] = [ + 'entity_id_field' => EntityGenerator::SKIP_ENTITY_ID_BINDING, + 'handler' => function ($productId, $entityNumber, $fixture, $binds) { + foreach ($binds as &$bind) { + $sku = $fixture['sku']($productId, $entityNumber); + if (in_array($sku, $this->stockStatuses)) { + return []; + } + $bind['sku'] = $sku; + $this->stockStatuses[] = $sku; + } + + return $binds; + }, + ]; return [$data]; } diff --git a/dev/tests/integration/_files/Magento/TestModuleInventoryStateCache/Plugin/Catalog/Model/ProductRepository/GetNonCachedProductsPlugin.php b/dev/tests/integration/_files/Magento/TestModuleInventoryStateCache/Plugin/Catalog/Model/ProductRepository/GetNonCachedProductsPlugin.php new file mode 100644 index 000000000000..3b712ddd9cfc --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleInventoryStateCache/Plugin/Catalog/Model/ProductRepository/GetNonCachedProductsPlugin.php @@ -0,0 +1,40 @@ + + + + + + + diff --git a/dev/tests/integration/_files/Magento/TestModuleInventoryStateCache/etc/frontend/di.xml b/dev/tests/integration/_files/Magento/TestModuleInventoryStateCache/etc/frontend/di.xml index 30c34c0c4002..b2db5d70ab16 100644 --- a/dev/tests/integration/_files/Magento/TestModuleInventoryStateCache/etc/frontend/di.xml +++ b/dev/tests/integration/_files/Magento/TestModuleInventoryStateCache/etc/frontend/di.xml @@ -10,4 +10,8 @@ + + +