Skip to content

Commit 332038e

Browse files
committed
MC-42187: Configurable products with multiple dropdown attributes, do not show out of stock options, regarless configuration
- Initial commit and adding test
1 parent b16e06c commit 332038e

File tree

6 files changed

+67
-11
lines changed

6 files changed

+67
-11
lines changed

app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
use Magento\Customer\Helper\Session\CurrentCustomer;
1414
use Magento\Customer\Model\Session;
1515
use Magento\Framework\App\ObjectManager;
16+
use Magento\Framework\Exception\NoSuchEntityException;
1617
use Magento\Framework\Locale\Format;
1718
use Magento\Framework\Pricing\PriceCurrencyInterface;
19+
use Magento\Store\Model\Store;
1820

1921
/**
2022
* Confugurable product view type
@@ -198,7 +200,8 @@ public function getAllowProducts()
198200
/**
199201
* Retrieve current store
200202
*
201-
* @return \Magento\Store\Model\Store
203+
* @return Store
204+
* @throws NoSuchEntityException
202205
*/
203206
public function getCurrentStore()
204207
{
@@ -239,6 +242,8 @@ public function getJsonConfig()
239242
'chooseText' => __('Choose an Option...'),
240243
'images' => $this->getOptionImages(),
241244
'index' => isset($options['index']) ? $options['index'] : [],
245+
'salable' => $options['salable'] ?? [],
246+
'canDisplayShowOutOfStockStatus' => $options['canDisplayShowOutOfStockStatus'] ?? false
242247
];
243248

244249
if ($currentProduct->hasPreconfiguredValues() && !empty($attributesData['defaultValues'])) {

app/code/Magento/ConfigurableProduct/Helper/Data.php

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
use Magento\Catalog\Model\Product\Image\UrlBuilder;
1010
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
11+
use Magento\Framework\App\Config\ScopeConfigInterface;
1112
use Magento\Framework\App\ObjectManager;
1213
use Magento\Catalog\Helper\Image as ImageHelper;
1314
use Magento\Catalog\Api\Data\ProductInterface;
@@ -33,14 +34,24 @@ class Data
3334
*/
3435
private $imageUrlBuilder;
3536

37+
/**
38+
* @var ScopeConfigInterface
39+
*/
40+
private $scopeConfig;
41+
3642
/**
3743
* @param ImageHelper $imageHelper
38-
* @param UrlBuilder $urlBuilder
44+
* @param UrlBuilder|null $urlBuilder
45+
* @param ScopeConfigInterface|null $scopeConfig
3946
*/
40-
public function __construct(ImageHelper $imageHelper, UrlBuilder $urlBuilder = null)
41-
{
47+
public function __construct(
48+
ImageHelper $imageHelper,
49+
UrlBuilder $urlBuilder = null,
50+
?ScopeConfigInterface $scopeConfig = null
51+
) {
4252
$this->imageHelper = $imageHelper;
4353
$this->imageUrlBuilder = $urlBuilder ?? ObjectManager::getInstance()->get(UrlBuilder::class);
54+
$this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class);
4455
}
4556

4657
/**
@@ -90,12 +101,20 @@ public function getOptions($currentProduct, $allowedProducts)
90101
$productAttribute = $attribute->getProductAttribute();
91102
$productAttributeId = $productAttribute->getId();
92103
$attributeValue = $product->getData($productAttribute->getAttributeCode());
93-
if ($product->isSalable()) {
104+
if ($this->canDisplayShowOutOfStockStatus()) {
105+
if ($product->isSalable()) {
106+
$options['salable'][$productAttributeId][$attributeValue][] = $productId;
107+
}
94108
$options[$productAttributeId][$attributeValue][] = $productId;
109+
} else {
110+
if ($product->isSalable()) {
111+
$options[$productAttributeId][$attributeValue][] = $productId;
112+
}
95113
}
96114
$options['index'][$productId][$productAttributeId] = $attributeValue;
97115
}
98116
}
117+
$options['canDisplayShowOutOfStockStatus'] = $this->canDisplayShowOutOfStockStatus();
99118
return $options;
100119
}
101120

@@ -111,4 +130,14 @@ public function getAllowAttributes($product)
111130
? $product->getTypeInstance()->getConfigurableAttributes($product)
112131
: [];
113132
}
133+
134+
/**
135+
* Returns if display out of stock status set or not in catalog inventory
136+
*
137+
* @return bool
138+
*/
139+
private function canDisplayShowOutOfStockStatus(): bool
140+
{
141+
return (bool) $this->scopeConfig->getValue('cataloginventory/options/show_out_of_stock');
142+
}
114143
}

app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,8 @@ private function getExpectedArray($productId, $amount, $priceQty, $percentage):
435435
'chooseText' => __('Choose an Option...'),
436436
'images' => [],
437437
'index' => [],
438+
'salable' => [],
439+
'canDisplayShowOutOfStockStatus' => false
438440
];
439441

440442
return $expectedArray;

app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Magento\Catalog\Model\Product\Image\UrlBuilder;
1414
use Magento\ConfigurableProduct\Helper\Data;
1515
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
16+
use Magento\Framework\App\Config\ScopeConfigInterface;
1617
use Magento\Framework\Data\Collection;
1718
use Magento\Framework\DataObject;
1819
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
@@ -41,19 +42,26 @@ class DataTest extends TestCase
4142
*/
4243
protected $imageUrlBuilder;
4344

45+
/**
46+
* @var ScopeConfigInterface|MockObject
47+
*/
48+
private $scopeConfigMock;
49+
4450
protected function setUp(): void
4551
{
4652
$objectManager = new ObjectManager($this);
4753
$this->imageUrlBuilder = $this->getMockBuilder(UrlBuilder::class)
4854
->disableOriginalConstructor()
4955
->getMock();
56+
$this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class);
5057
$this->_imageHelperMock = $this->createMock(Image::class);
5158
$this->_productMock = $this->createMock(Product::class);
5259
$this->_productMock->setTypeId(Configurable::TYPE_CODE);
5360
$this->_model = $objectManager->getObject(
5461
Data::class,
5562
[
56-
'_imageHelper' => $this->_imageHelperMock
63+
'_imageHelper' => $this->_imageHelperMock,
64+
'scopeConfig' => $this->scopeConfigMock
5765
]
5866
);
5967
$objectManager->setBackwardCompatibleProperty($this->_model, 'imageUrlBuilder', $this->imageUrlBuilder);
@@ -131,7 +139,9 @@ public function getOptionsDataProvider(): array
131139
);
132140
$provider = [];
133141
$provider[] = [
134-
[],
142+
[
143+
'canDisplayShowOutOfStockStatus' => false
144+
],
135145
[
136146
'allowed_products' => [],
137147
'current_product_mock' => $currentProductMock,
@@ -213,6 +223,7 @@ public function getOptionsDataProvider(): array
213223
'attribute_id_2' => [
214224
'attribute_code_value_2' => ['product_id_1', 'product_id_2'],
215225
],
226+
'canDisplayShowOutOfStockStatus' => false
216227
],
217228
[
218229
'allowed_products' => $allowedProducts,

app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,9 @@ define([
432432
allowedOptions = [],
433433
indexKey,
434434
allowedProductMinPrice,
435-
allowedProductsAllMinPrice;
435+
allowedProductsAllMinPrice,
436+
canDisplayOutOfStockProducts = false,
437+
filteredSalableProducts;
436438

437439
this._clearSelect(element);
438440
element.options[0] = new Option('', '');
@@ -506,11 +508,17 @@ define([
506508
options[i].allowedProducts = allowedProducts;
507509
element.options[index] = new Option(this._getOptionLabel(options[i]), options[i].id);
508510

511+
if (this.options.spConfig.canDisplayShowOutOfStockStatus) {
512+
filteredSalableProducts = $(this.options.spConfig.salable[attributeId][options[i].id]).
513+
filter(options[i].allowedProducts);
514+
canDisplayOutOfStockProducts = filteredSalableProducts.length === 0;
515+
}
516+
509517
if (typeof options[i].price !== 'undefined') {
510518
element.options[index].setAttribute('price', options[i].price);
511519
}
512520

513-
if (allowedProducts.length === 0) {
521+
if (allowedProducts.length === 0 || canDisplayOutOfStockProducts) {
514522
element.options[index].disabled = true;
515523
}
516524

app/code/Magento/ConfigurableProductGraphQl/Model/Options/ConfigurableOptionsMetadata.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ public function getAvailableSelections(ProductInterface $product, array $options
5454
$availableSelections = [];
5555

5656
foreach ($options as $attributeId => $option) {
57-
if ($attributeId === 'index' || isset($selectedOptions[$attributeId])) {
57+
if (in_array($attributeId, ['index','salable','canDisplayShowOutOfStockStatus'])
58+
|| isset($selectedOptions[$attributeId])) {
5859
continue;
5960
}
6061

6162
$availableSelections[] = $this->configurableOptionsFormatter->format(
6263
$attributes[$attributeId],
63-
$options[$attributeId] ?? []
64+
$option ?? []
6465
);
6566
}
6667

0 commit comments

Comments
 (0)