Closed FadedOut closed 2 years ago
Hello @nthurston I ran into the exact same issues using MAgento 2.4.6-p3. Have you found a fix for this back in the day? Greetings Eike.
Hello @nthurston I ran into the exact same issues using MAgento 2.4.6-p3. Have you found a fix for this back in the day? Greetings Eike.
I still have this problem on 2.4.5-p7, even with 2.4-develop changes applied. I'm trying to figure out where this all goes wrong.
I have MSI enabled, with two different stock sources - both show incorrect on their respective website:
website 1: b2c stock website 2: b2b stock
Can believe this is so hard to get right.
I have managed to get this to work, my conclusion is that the isSalable() is not checking for any quantities on the salable stock. So if you make $product->isSalable() you always get true. This is not following best pratices, and is only used as a demonstration of the issue source.
My solution consists on only touching file vendor/magento/module-configurable-product/Helper/Data.php , the file below is from version 2.4.6-p4 , modified with the issue tag.
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\ConfigurableProduct\Helper;
use Magento\Catalog\Model\Product\Image\UrlBuilder;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Helper\Image as ImageHelper;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Image;
use Magento\Catalog\Api\ProductRepositoryInterface; /* Salable Issue */
use Magento\InventorySalesApi\Api\GetProductSalableQtyInterface; /* Salable Issue */
/**
* Class Data
*
* Helper class for getting options
* @api
* @since 100.0.2
*/
class Data
{
/**
* @var ImageHelper
*/
protected $imageHelper;
/**
* @var UrlBuilder
*/
private $imageUrlBuilder;
/**
* @var ScopeConfigInterface
*/
private $scopeConfig;
protected $productRepository; /* Salable Issue */
protected $getProductSalableQty; /* Salable Issue */
/**
* @param ImageHelper $imageHelper
* @param UrlBuilder|null $urlBuilder
* @param ScopeConfigInterface|null $scopeConfig
*/
public function __construct(
ImageHelper $imageHelper,
UrlBuilder $urlBuilder = null,
?ScopeConfigInterface $scopeConfig = null,
ProductRepositoryInterface $productRepository, /* Salable Issue */
GetProductSalableQtyInterface $getProductSalableQty /* Salable Issue */
) {
$this->imageHelper = $imageHelper;
$this->imageUrlBuilder = $urlBuilder ?? ObjectManager::getInstance()->get(UrlBuilder::class);
$this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class);
$this->productRepository = $productRepository; /* Salable Issue */
$this->getProductSalableQty = $getProductSalableQty; /* Salable Issue */
}
/**
* Retrieve collection of gallery images
*
* @param ProductInterface $product
* @return Image[]|null
*/
public function getGalleryImages(ProductInterface $product)
{
$images = $product->getMediaGalleryImages();
if ($images instanceof \Magento\Framework\Data\Collection) {
/** @var $image Image */
foreach ($images as $image) {
$smallImageUrl = $this->imageUrlBuilder
->getUrl($image->getFile(), 'product_page_image_small');
$image->setData('small_image_url', $smallImageUrl);
$mediumImageUrl = $this->imageUrlBuilder
->getUrl($image->getFile(), 'product_page_image_medium');
$image->setData('medium_image_url', $mediumImageUrl);
$largeImageUrl = $this->imageUrlBuilder
->getUrl($image->getFile(), 'product_page_image_large');
$image->setData('large_image_url', $largeImageUrl);
}
}
return $images;
}
/**
* Get Options for Configurable Product Options
*
* @param Product $currentProduct
* @param array $allowedProducts
* @return array
*/
public function getOptions($currentProduct, $allowedProducts)
{
$options = [];
$allowAttributes = $this->getAllowAttributes($currentProduct);
foreach ($allowedProducts as $product) {
$productId = $product->getId();
foreach ($allowAttributes as $attribute) {
$productAttribute = $attribute->getProductAttribute();
$productAttributeId = $productAttribute->getId();
$attributeValue = $product->getData($productAttribute->getAttributeCode());
/*if ($this->canDisplayShowOutOfStockStatus()) {
if ($product->isSalable()) {
$options['salable'][$productAttributeId][$attributeValue][] = $productId;
}
$options[$productAttributeId][$attributeValue][] = $productId;
} else {
if ($product->isSalable()) {
$options[$productAttributeId][$attributeValue][] = $productId;
}
}*/
/********************* Salable Issue ************************/
$product = $this->productRepository->get($product->getSku());
$stockItem = $product->getExtensionAttributes()->getStockItem();
if( $stockItem->getManageStock() && $this->getProductSalableQty->execute($product->getSku(), 1) > 0) {
$options['salable'][$productAttributeId][$attributeValue][] = $productId;
}
if( !$stockItem->getManageStock() ) {
$options['salable'][$productAttributeId][$attributeValue][] = $productId;
}
$options[$productAttributeId][$attributeValue][] = $productId;
/********************* Salable Issue ************************/
$options['index'][$productId][$productAttributeId] = $attributeValue;
}
}
$options['canDisplayShowOutOfStockStatus'] = $this->canDisplayShowOutOfStockStatus();
return $options;
}
/**
* Get allowed attributes
*
* @param Product $product
* @return array
*/
public function getAllowAttributes($product)
{
return ($product->getTypeId() == Configurable::TYPE_CODE)
? $product->getTypeInstance()->getConfigurableAttributes($product)
: [];
}
/**
* Returns if display out of stock status set or not in catalog inventory
*
* @return bool
*/
private function canDisplayShowOutOfStockStatus(): bool
{
return (bool) $this->scopeConfig->getValue('cataloginventory/options/show_out_of_stock');
}
}
I will try this asap, still have issues with configurables seemingly out of stock even though the simples have returned to a positive qty.
FIX: A quick non-official hack/fix was posted further below by @loic-paquin and @jas8522 here
Preconditions (*)
Steps to reproduce (*)
Expected result (*)
Actual result (*)
Product EAV index process error during indexation process: Deprecated Functionality: explode(): Passing null to parameter #2 ($string) of type string is deprecated in /home/********/public_html/vendor/magento/module-catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php on line 444
My previous installation of 2.4.3-p1 that I just upgraded from, this worked as intended (out of stock variations could not be selected/clicked. It showed a cross-through of the variation that was out of stock). It now does not do this, after upgrading to 2.4.4.
Below are screenshots of the product page as well as the admin/backend showing 0 for default stock quantity. This example is shown on the stock Magento Luma theme.
EDIT/UPDATE: The original report of EAV indexing (using CLI) failing might be related, was incorrect. It is not related and made no difference for the bug of "Product with Salable Qty of 0 shows 'In Stock' on product page" - they are separate issues.
Additional Information: https://github.com/magento/magento2/issues/35319#issuecomment-1101209973
Please provide Severity assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.