Steps by steps:
1. Went to admin page
2. Set some SKUs to `out of stock` status
3. Went to detail page
4. Selected color: Mellow Mauve
5. Expected result: Sizes S, M, L are disabled
Actual result: Sizes S, M, L still enable
6. Tried to add to cart product Mellow Mauve (S, M, L)
7. Got the message: There are no source items with the in stock status
SOLUTION 1:
The issue is resolved by disabling Manage Stock for parent product. Edit product -> Advanced Inventory -> Manage Stock -> set No
SOLUTION 2:
Front end Product shows OUT OF STOCK after we changed product to IN STOCK in admin page
Follow the patch
diff --git a/vendor/magento/module-catalog/Model/ResourceModel/GetProductTypeById.php b/vendor/magento/module-catalog/Model/ResourceModel/GetProductTypeById.php new file mode 100644 index 000000000000..bd62fedb457a --- /dev/null +++ b/vendor/magento/module-catalog/Model/ResourceModel/GetProductTypeById.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare (strict_types=1); +namespace Magento\Catalog\Model\ResourceModel; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\App\ResourceConnection; + +/** + * Get product type ID by product ID. + */ +class GetProductTypeById +{ + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @param ResourceConnection $resource + */ + public function __construct( + ResourceConnection $resource + ) { + $this->resource = $resource; + } + + /** + * Retrieve product type by its product ID + * + * @param int $productId + * @return string + */ + public function execute(int $productId): string + { + $connection = $this->resource->getConnection(); + $productTable = $this->resource->getTableName('catalog_product_entity'); + + $select = $connection->select() + ->from( + $productTable, + ProductInterface::TYPE_ID + )->where('entity_id = ?', $productId); + + $result = $connection->fetchOne($select); + return $result ?: ''; + } +} diff --git a/vendor/magento/module-configurable-product/Model/Inventory/ChangeParentStockStatus.php b/vendor/magento/module-configurable-product/Model/Inventory/ChangeParentStockStatus.php index 9bb4659b31db..4ad15ea905f0 100644 --- a/vendor/magento/module-configurable-product/Model/Inventory/ChangeParentStockStatus.php +++ b/vendor/magento/module-configurable-product/Model/Inventory/ChangeParentStockStatus.php @@ -106,6 +106,7 @@ private function processStockForParent(int $productId): void if ($this->isNeedToUpdateParent($parentStockItem, $childrenIsInStock)) { $parentStockItem->setIsInStock($childrenIsInStock); $parentStockItem->setStockStatusChangedAuto(1); + $parentStockItem->setStockStatusChangedAutomaticallyFlag(true); $this->stockItemRepository->save($parentStockItem); } } diff --git a/vendor/magento/module-configurable-product/Model/Plugin/UpdateStockChangedAuto.php b/vendor/magento/module-configurable-product/Model/Plugin/UpdateStockChangedAuto.php new file mode 100644 index 000000000000..c5a0cd5eae7f --- /dev/null +++ b/vendor/magento/module-configurable-product/Model/Plugin/UpdateStockChangedAuto.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare (strict_types=1); +namespace Magento\ConfigurableProduct\Model\Plugin; + +use Magento\Catalog\Model\ResourceModel\GetProductTypeById; +use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as ItemResourceModel; +use Magento\Framework\Model\AbstractModel as StockItem; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Updates stock_status_changed_auto setting for configurable product when it was saved manually + */ +class UpdateStockChangedAuto +{ + /** + * @var GetProductTypeById + */ + private $getProductTypeById; + + /** + * @param GetProductTypeById $getProductTypeById + */ + public function __construct(GetProductTypeById $getProductTypeById) + { + $this->getProductTypeById = $getProductTypeById; + } + + /** + * Updates stock_status_changed_auto for configurable product + * + * @param ItemResourceModel $subject + * @param StockItem $stockItem + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSave(ItemResourceModel $subject, StockItem $stockItem): void + { + if (!$stockItem->getIsInStock() && + !$stockItem->hasStockStatusChangedAutomaticallyFlag() && + $this->getProductTypeById->execute($stockItem->getProductId()) == Configurable::TYPE_CODE + ) { + $stockItem->setStockStatusChangedAuto(0); + } + } +} diff --git a/vendor/magento/module-configurable-product/etc/di.xml b/vendor/magento/module-configurable-product/etc/di.xml index 270e8ec74609..16e3aaafc9cb 100644 --- a/vendor/magento/module-configurable-product/etc/di.xml +++ b/vendor/magento/module-configurable-product/etc/di.xml @@ -280,4 +280,7 @@ <argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\Json</argument> </arguments> </type> + <type name="Magento\CatalogInventory\Model\ResourceModel\Stock\Item"> + <plugin name="updateStockChangedAuto" type="Magento\ConfigurableProduct\Model\Plugin\UpdateStockChangedAuto" /> + </type> </config> diff --git a/vendor/magento/module-inventory-configurable-product/Plugin/CatalogInventory/UpdateSourceItemAtLegacyStockItemSavePlugin.php b/vendor/magento/module-inventory-configurable-product/Plugin/CatalogInventory/UpdateSourceItemAtLegacyStockItemSavePlugin.php new file mode 100644 index 00000000000..6674af98b6c --- /dev/null +++ b/vendor/magento/module-inventory-configurable-product/Plugin/CatalogInventory/UpdateSourceItemAtLegacyStockItemSavePlugin.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\InventoryConfigurableProduct\Plugin\CatalogInventory; + +use Magento\Catalog\Model\ResourceModel\GetProductTypeById; +use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as ItemResourceModel; +use Magento\Framework\Model\AbstractModel as StockItem; +use Magento\InventoryCatalog\Model\ResourceModel\SetDataToLegacyStockStatus; +use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\CatalogInventory\Model\Stock; +use Magento\InventorySalesApi\Api\AreProductsSalableInterface; +use Magento\InventoryConfiguration\Model\GetLegacyStockItem; + +/** + * Class provides after Plugin on Magento\CatalogInventory\Model\ResourceModel\Stock\Item::save + * to update legacy stock status for configurable product + */ +class UpdateSourceItemAtLegacyStockItemSavePlugin +{ + /** + * @var GetProductTypeById + */ + private $getProductTypeById; + + /** + * @var SetDataToLegacyStockStatus + */ + private $setDataToLegacyStockStatus; + + /** + * @var GetSkusByProductIdsInterface + */ + private $getSkusByProductIds; + + /** + * @var Configurable + */ + private $configurableType; + + /** + * @var AreProductsSalableInterface + */ + private $areProductsSalable; + + /** + * @var GetLegacyStockItem + */ + private $getLegacyStockItem; + + /** + * @param GetProductTypeById $getProductTypeById + * @param SetDataToLegacyStockStatus $setDataToLegacyStockStatus + * @param GetSkusByProductIdsInterface $getSkusByProductIds + * @param Configurable $configurableType + * @param AreProductsSalableInterface $areProductsSalable + * @param GetLegacyStockItem $getLegacyStockItem + */ + public function __construct( + GetProductTypeById $getProductTypeById, + SetDataToLegacyStockStatus $setDataToLegacyStockStatus, + GetSkusByProductIdsInterface $getSkusByProductIds, + Configurable $configurableType, + AreProductsSalableInterface $areProductsSalable, + GetLegacyStockItem $getLegacyStockItem + ) { + $this->getProductTypeById = $getProductTypeById; + $this->setDataToLegacyStockStatus = $setDataToLegacyStockStatus; + $this->getSkusByProductIds = $getSkusByProductIds; + $this->configurableType = $configurableType; + $this->areProductsSalable = $areProductsSalable; + $this->getLegacyStockItem = $getLegacyStockItem; + } + + /** + * Update source item for legacy stock of a configurable product + * + * @param ItemResourceModel $subject + * @param ItemResourceModel $result + * @param StockItem $stockItem + * @return void + * @throws Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterSave(ItemResourceModel $subject, ItemResourceModel $result, StockItem $stockItem): void + { + if ($stockItem->getIsInStock() && + $this->getProductTypeById->execute($stockItem->getProductId()) === Configurable::TYPE_CODE + ) { + $productSku = $this->getSkusByProductIds + ->execute([$stockItem->getProductId()])[$stockItem->getProductId()]; + + if ($stockItem->getStockStatusChangedAuto() || + ($this->stockStatusChange($productSku) && $this->hasChildrenInStock($stockItem->getProductId())) + ) { + $this->setDataToLegacyStockStatus->execute( + $productSku, + (float) $stockItem->getQty(), + Stock::STOCK_IN_STOCK + ); + } + } + } + + /** + * Checks if configurable product stock item status was changed + * + * @param string $sku + * @return bool + */ + private function stockStatusChange(string $sku): bool + { + return $this->getLegacyStockItem->execute($sku)->getIsInStock() == Stock::STOCK_OUT_OF_STOCK; + } + + /** + * Checks if configurable has salable options + * + * @param int $productId + * @return bool + */ + private function hasChildrenInStock(int $productId): bool + { + $childrenIds = $this->configurableType->getChildrenIds($productId); + if (empty($childrenIds)) { + return false; + } + $skus = $this->getSkusByProductIds->execute(array_shift($childrenIds)); + $areSalableResults = $this->areProductsSalable->execute($skus, Stock::DEFAULT_STOCK_ID); + foreach ($areSalableResults as $productSalable) { + if ($productSalable->isSalable() === true) { + return true; + } + } + + return false; + } +} diff --git a/vendor/magento/module-inventory-configurable-product/composer.json b/vendor/magento/module-inventory-configurable-product/composer.json index 3855b9d6d11..adca1ede518 100644 --- a/vendor/magento/module-inventory-configurable-product/composer.json +++ b/vendor/magento/module-inventory-configurable-product/composer.json @@ -11,6 +11,8 @@ "php": "~7.4.0||~8.1.0", "magento/framework": "*", "magento/module-catalog": "*", + "magento/module-inventory-catalog": "*", + "magento/module-inventory-configuration": "*", "magento/module-inventory-sales-api": "1.2.*", "magento/module-inventory-catalog-api": "1.3.*", "magento/module-inventory-indexer": "2.1.*", diff --git a/vendor/magento/module-inventory-configurable-product/etc/di.xml b/vendor/magento/module-inventory-configurable-product/etc/di.xml index 7b32be4f885..a3fc8ce2038 100644 --- a/vendor/magento/module-inventory-configurable-product/etc/di.xml +++ b/vendor/magento/module-inventory-configurable-product/etc/di.xml @@ -40,4 +40,8 @@ <plugin name="update_parent_configurable_product_stock_status_in_legacy_stock" type="Magento\InventoryConfigurableProduct\Plugin\InventoryApi\UpdateParentStockStatusInLegacyStockPlugin"/> </type> + <type name="Magento\CatalogInventory\Model\ResourceModel\Stock\Item"> + <plugin name="update_source_stock_for_configurable_product" + type="Magento\InventoryConfigurableProduct\Plugin\CatalogInventory\UpdateSourceItemAtLegacyStockItemSavePlugin" /> + </type> </config>
SOLUTION 3:
Follow the patch
diff --git a/vendor/magento/module-swatches/view/base/web/js/swatch-renderer.js b/vendor/magento/module-swatches/view/base/web/js/swatch-renderer.js index 6782a87b02a9..06897c449734 100644 --- a/vendor/magento/module-swatches/view/base/web/js/swatch-renderer.js +++ b/vendor/magento/module-swatches/view/base/web/js/swatch-renderer.js @@ -496,6 +496,27 @@ define([ $widget._EmulateSelected($widget._getSelectedAttributes()); }, + disableSwatchForOutOfStockProducts: function () { + let $widget = this, container = this.element; + + $.each(this.options.jsonConfig.attributes, function () { + let item = this; + + if ($widget.options.jsonConfig.canDisplayShowOutOfStockStatus) { + let salableProducts = $widget.options.jsonConfig.salable[item.id], + swatchOptions = container.find('.swatch-option'); + + swatchOptions.each(function (key, value) { + let optionId = $(value).data('option-id'); + + if (!salableProducts.hasOwnProperty(optionId)) { + $(value).attr('disabled', true).addClass('disabled'); + } + }); + } + }); + }, + /** * Render swatch options by part of config * @@ -886,6 +907,7 @@ define([ .attr('disabled', true) .addClass('disabled') .attr('tabindex', '-1'); + this.disableSwatchForOutOfStockProducts(); }, /** diff --git a/vendor/magento/module-inventory-catalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptUpdateStockStatusBySkuPlugin.php b/vendor/magento/module-inventory-catalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptUpdateStockStatusBySkuPlugin.php index afe66fea814..b2ab3d2b5f8 100644 --- a/vendor/magento/module-inventory-catalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptUpdateStockStatusBySkuPlugin.php +++ b/vendor/magento/module-inventory-catalog/Plugin/CatalogInventory/Api/StockRegistry/AdaptUpdateStockStatusBySkuPlugin.php @@ -11,6 +11,7 @@ use Magento\CatalogInventory\Model\Stock; use Magento\InventoryCatalog\Model\ResourceModel\SetDataToLegacyStockStatus; use Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface; +use Magento\InventoryConfiguration\Model\GetLegacyStockItem; use Magento\InventoryConfiguration\Model\LegacyStockItem\CacheStorage; use Magento\InventoryConfigurationApi\Api\GetStockItemConfigurationInterface; use Magento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface; @@ -42,25 +43,33 @@ class AdaptUpdateStockStatusBySkuPlugin */ private $legacyStockItemCacheStorage; + /** + * @var GetLegacyStockItem + */ + private $getLegacyStockItem; + /** * @param SetDataToLegacyStockStatus $setDataToLegacyStockStatus * @param GetProductTypesBySkusInterface $getProductTypesBySkus * @param IsSourceItemManagementAllowedForProductTypeInterface $isSourceItemManagementAllowedForProductType * @param GetStockItemConfigurationInterface $getStockItemConfiguration * @param CacheStorage $legacyStockItemCacheStorage + * @param GetLegacyStockItem $getLegacyStockItem */ public function __construct( SetDataToLegacyStockStatus $setDataToLegacyStockStatus, GetProductTypesBySkusInterface $getProductTypesBySkus, IsSourceItemManagementAllowedForProductTypeInterface $isSourceItemManagementAllowedForProductType, GetStockItemConfigurationInterface $getStockItemConfiguration, - CacheStorage $legacyStockItemCacheStorage + CacheStorage $legacyStockItemCacheStorage, + GetLegacyStockItem $getLegacyStockItem ) { $this->setDataToLegacyStockStatus = $setDataToLegacyStockStatus; $this->getProductTypesBySkus = $getProductTypesBySkus; $this->isSourceItemManagementAllowedForProductType = $isSourceItemManagementAllowedForProductType; $this->getStockItemConfiguration = $getStockItemConfiguration; $this->legacyStockItemCacheStorage = $legacyStockItemCacheStorage; + $this->getLegacyStockItem = $getLegacyStockItem; } /** @@ -82,21 +91,23 @@ public function afterUpdateStockItemBySku( // Remove cache to get updated legacy stock item on the next request. $this->legacyStockItemCacheStorage->delete($productSku); + $updatedStockItem = $this->getLegacyStockItem->execute($productSku); + $productType = $this->getProductTypesBySkus->execute([$productSku])[$productSku]; $stockItemConfiguration = $this->getStockItemConfiguration->execute($productSku, Stock::DEFAULT_STOCK_ID); if ($stockItemConfiguration->isManageStock() === false || $stockItemConfiguration->isUseConfigManageStock() === false ) { - $this->setDataToLegacyStockStatus->execute($productSku, (float)$stockItem->getQty(), 1); + $this->setDataToLegacyStockStatus->execute($productSku, (float)$updatedStockItem->getQty(), 1); } else { if ($this->isSourceItemManagementAllowedForProductType->execute($productType) - && $stockItem->getQty() !== null + && $updatedStockItem->getQty() !== null ) { $this->setDataToLegacyStockStatus->execute( $productSku, - (float)$stockItem->getQty(), - $stockItem->getIsInStock() + (float)$updatedStockItem->getQty(), + $updatedStockItem->getIsInStock() ); } }
No comments:
Post a Comment