Skip to content

Commit 98b5efe

Browse files
committed
ACP2E-445: [Magento Cloud] Issue with indexing / price type attribute display in search result layered navigation
1 parent b1f87bf commit 98b5efe

File tree

2 files changed

+221
-40
lines changed
  • app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav
  • dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend

2 files changed

+221
-40
lines changed

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav;
77

88
use Magento\Catalog\Api\Data\ProductInterface;
9+
use Magento\Framework\DB\Select;
10+
use Magento\Store\Model\Store;
11+
use Zend_Db;
912

1013
/**
1114
* Catalog Product Eav Decimal Attributes Indexer resource model
@@ -46,37 +49,72 @@ protected function _prepareIndex($entityIds = null, $attributeId = null)
4649
return $this;
4750
}
4851

49-
$productIdField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
50-
$productValueExpression = $connection->getCheckSql('pds.value_id > 0', 'pds.value', 'pdd.value');
52+
$select = $connection->select()
53+
->union(
54+
[
55+
$this->getSelect($attrIds, $entityIds),
56+
$this->getSelect($attrIds, $entityIds, true)
57+
]
58+
);
5159

52-
$select = $connection->select()->from(
53-
['pdd' => $this->getTable('catalog_product_entity_decimal')],
54-
[]
55-
)->join(
56-
['cs' => $this->getTable('store')],
57-
'',
58-
[]
59-
)->joinLeft(
60-
['pds' => $this->getTable('catalog_product_entity_decimal')],
61-
sprintf(
62-
'pds.%s = pdd.%s AND pds.attribute_id = pdd.attribute_id'.' AND pds.store_id=cs.store_id',
63-
$productIdField,
64-
$productIdField
65-
),
66-
[]
67-
)->joinLeft(
60+
$query = $select->insertFromSelect($idxTable);
61+
$connection->query($query);
62+
63+
return $this;
64+
}
65+
66+
/**
67+
* Generate select with attribute values
68+
*
69+
* @param array $attributeIds
70+
* @param array|null $entityIds
71+
* @param bool $storeValuesOnly
72+
* @return Select
73+
* @throws \Exception
74+
*/
75+
private function getSelect(array $attributeIds, ?array $entityIds, bool $storeValuesOnly = false): Select
76+
{
77+
$productIdField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
78+
$connection = $this->getConnection();
79+
$select = $connection->select()
80+
->from(
81+
['cs' => $this->getTable('store')],
82+
[]
83+
);
84+
if ($storeValuesOnly) {
85+
$select->join(
86+
['pdd' => $this->getTable('catalog_product_entity_decimal')],
87+
'pdd.store_id = cs.store_id',
88+
[]
89+
);
90+
$productValueExpression = 'pdd.value';
91+
} else {
92+
$select->join(
93+
['pdd' => $this->getTable('catalog_product_entity_decimal')],
94+
'pdd.store_id=' . Store::DEFAULT_STORE_ID,
95+
[]
96+
)->joinLeft(
97+
['pds' => $this->getTable('catalog_product_entity_decimal')],
98+
sprintf(
99+
'pds.%s = pdd.%s AND pds.attribute_id = pdd.attribute_id' . ' AND pds.store_id=cs.store_id',
100+
$productIdField,
101+
$productIdField
102+
),
103+
[]
104+
);
105+
$productValueExpression = $connection->getCheckSql('pds.value_id > 0', 'pds.value', 'pdd.value');
106+
}
107+
$select->joinLeft(
68108
['cpe' => $this->getTable('catalog_product_entity')],
69109
"cpe.{$productIdField} = pdd.{$productIdField}",
70110
[]
71-
)->where(
72-
'pdd.store_id=?',
73-
\Magento\Store\Model\Store::DEFAULT_STORE_ID
74111
)->where(
75112
'cs.store_id!=?',
76-
\Magento\Store\Model\Store::DEFAULT_STORE_ID
113+
Store::DEFAULT_STORE_ID
77114
)->where(
78115
'pdd.attribute_id IN(?)',
79-
$attrIds
116+
$attributeIds,
117+
Zend_Db::INT_TYPE
80118
)->where(
81119
"{$productValueExpression} IS NOT NULL"
82120
)->columns(
@@ -105,7 +143,11 @@ protected function _prepareIndex($entityIds = null, $attributeId = null)
105143
);
106144

107145
if ($entityIds !== null) {
108-
$select->where('cpe.entity_id IN(?)', $entityIds);
146+
$select->where(
147+
'cpe.entity_id IN(?)',
148+
$entityIds,
149+
Zend_Db::INT_TYPE
150+
);
109151
}
110152

111153
/**
@@ -121,10 +163,7 @@ protected function _prepareIndex($entityIds = null, $attributeId = null)
121163
]
122164
);
123165

124-
$query = $select->insertFromSelect($idxTable);
125-
$connection->query($query);
126-
127-
return $this;
166+
return $select;
128167
}
129168

130169
/**

dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/PriceTest.php

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Magento\Catalog\Model\ResourceModel\Product;
1010
use Magento\Catalog\Observer\SwitchPriceAttributeScopeOnConfigChange;
1111
use Magento\Framework\App\Config\ReinitableConfigInterface;
12+
use Magento\Store\Model\Store;
1213
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
1314

1415
/**
@@ -49,7 +50,7 @@ protected function setUp(): void
4950
$reinitiableConfig = $this->objectManager->get(ReinitableConfigInterface::class);
5051
$reinitiableConfig->setValue(
5152
'catalog/price/scope',
52-
\Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE
53+
Store::PRICE_SCOPE_WEBSITE
5354
);
5455
$observer = $this->objectManager->get(\Magento\Framework\Event\Observer::class);
5556
$this->objectManager->get(SwitchPriceAttributeScopeOnConfigChange::class)
@@ -113,8 +114,8 @@ public function testSetScope()
113114
*/
114115
public function testAfterSave()
115116
{
116-
/** @var \Magento\Store\Model\Store $store */
117-
$store = $this->objectManager->create(\Magento\Store\Model\Store::class);
117+
/** @var Store $store */
118+
$store = $this->objectManager->create(Store::class);
118119
$globalStoreId = $store->load('admin')->getId();
119120
$product = $this->productRepository->get('simple');
120121
$product->setPrice('9.99');
@@ -136,9 +137,9 @@ public function testAfterSave()
136137
*/
137138
public function testAfterSaveWithDifferentStores()
138139
{
139-
/** @var \Magento\Store\Model\Store $store */
140+
/** @var Store $store */
140141
$store = $this->objectManager->create(
141-
\Magento\Store\Model\Store::class
142+
Store::class
142143
);
143144
$globalStoreId = $store->load('admin')->getId();
144145
$secondStoreId = $this->fixtures->get('store2')->getId();
@@ -175,9 +176,9 @@ public function testAfterSaveWithDifferentStores()
175176
*/
176177
public function testAfterSaveWithSameCurrency()
177178
{
178-
/** @var \Magento\Store\Model\Store $store */
179+
/** @var Store $store */
179180
$store = $this->objectManager->create(
180-
\Magento\Store\Model\Store::class
181+
Store::class
181182
);
182183
$globalStoreId = $store->load('admin')->getId();
183184
$secondStoreId = $store->load('fixture_second_store')->getId();
@@ -214,9 +215,9 @@ public function testAfterSaveWithSameCurrency()
214215
*/
215216
public function testAfterSaveWithUseDefault()
216217
{
217-
/** @var \Magento\Store\Model\Store $store */
218+
/** @var Store $store */
218219
$store = $this->objectManager->create(
219-
\Magento\Store\Model\Store::class
220+
Store::class
220221
);
221222
$globalStoreId = $store->load('admin')->getId();
222223
$secondStoreId = $store->load('fixture_second_store')->getId();
@@ -266,9 +267,9 @@ public function testAfterSaveWithUseDefault()
266267
*/
267268
public function testAfterSaveForWebsitesWithDifferentCurrencies()
268269
{
269-
/** @var \Magento\Store\Model\Store $store */
270+
/** @var Store $store */
270271
$store = $this->objectManager->create(
271-
\Magento\Store\Model\Store::class
272+
Store::class
272273
);
273274

274275
/** @var \Magento\Directory\Model\ResourceModel\Currency $rate */
@@ -322,12 +323,153 @@ public static function tearDownAfterClass(): void
322323
);
323324
$reinitiableConfig->setValue(
324325
'catalog/price/scope',
325-
\Magento\Store\Model\Store::PRICE_SCOPE_GLOBAL
326+
Store::PRICE_SCOPE_GLOBAL
326327
);
327328
$observer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
328329
\Magento\Framework\Event\Observer::class
329330
);
330331
\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(SwitchPriceAttributeScopeOnConfigChange::class)
331332
->execute($observer);
332333
}
334+
335+
/**
336+
* @magentoDataFixture Magento\Store\Test\Fixture\Website as:website2
337+
* @magentoDataFixture Magento\Store\Test\Fixture\Group with:{"website_id":"$website2.id$"} as:store_group2
338+
* @magentoDataFixture Magento\Store\Test\Fixture\Store with:{"store_group_id":"$store_group2.id$"} as:store2
339+
* @magentoDataFixture Magento\Store\Test\Fixture\Store with:{"store_group_id":"$store_group2.id$"} as:store3
340+
* @magentoDataFixture Magento\Catalog\Test\Fixture\Attribute as:attr1
341+
* @magentoDataFixture Magento\Catalog\Test\Fixture\Attribute as:attr2
342+
* @magentoDataFixture Magento\Catalog\Test\Fixture\Attribute as:attr3
343+
* @magentoDataFixture Magento\Catalog\Test\Fixture\Product with:{"website_ids":[1, "$website2.id"]} as:product
344+
* @magentoDataFixtureDataProvider {"attr1":{"frontend_input":"price","is_filterable":1}}
345+
* @magentoDataFixtureDataProvider {"attr2":{"frontend_input":"price","is_filterable":1}}
346+
* @magentoDataFixtureDataProvider {"attr3":{"frontend_input":"price","is_filterable":1}}
347+
* @magentoConfigFixture current_store catalog/price/scope 1
348+
* @magentoDbIsolation disabled
349+
* @magentoAppArea adminhtml
350+
* @dataProvider saveCustomPriceAttributeDataProvider
351+
*/
352+
public function testSaveCustomPriceAttribute(
353+
array $attributes,
354+
array $updates,
355+
array $expectedValues,
356+
array $expectedIndexValues
357+
) {
358+
$storeIds['admin'] = $this->objectManager->create(Store::class)->load('admin')->getId();
359+
$storeIds['default'] = $this->objectManager->create(Store::class)->load('default')->getId();
360+
$storeIds['store2'] = $this->fixtures->get('store2')->getId();
361+
$storeIds['store3'] = $this->fixtures->get('store3')->getId();
362+
$storeNames = array_flip($storeIds);
363+
$productSku = $this->fixtures->get('product')->getSku();
364+
$productId = $this->fixtures->get('product')->getId();
365+
366+
foreach ($updates as $name => $scopes) {
367+
$attributeCode = $this->fixtures->get($name)->getAttributeCode();
368+
foreach ($scopes as $storeName => $storeValue) {
369+
$product = $this->productRepository->get($productSku, true, $storeIds[$storeName], true);
370+
$product->setData($attributeCode, $storeValue);
371+
$this->productResource->save($product);
372+
}
373+
}
374+
375+
$actualValues = [];
376+
foreach ($attributes as $name) {
377+
$attributeCode = $this->fixtures->get($name)->getAttributeCode();
378+
foreach ($storeIds as $storeName => $storeId) {
379+
$product = $this->productRepository->get($productSku, false, $storeId, true);
380+
$actualValues[$name][$storeName] = $product->getData($attributeCode);
381+
}
382+
}
383+
384+
$this->assertEquals($expectedValues, $actualValues);
385+
386+
$connection = $this->productResource->getConnection();
387+
388+
$actualIndexValues = [];
389+
foreach ($attributes as $name) {
390+
$attributeId = $this->fixtures->get($name)->getId();
391+
$select = $connection->select()
392+
->from(
393+
$connection->getTableName('catalog_product_index_eav_decimal'),
394+
[
395+
'store_id',
396+
'value'
397+
]
398+
)
399+
->where(
400+
'entity_id = ?',
401+
$productId,
402+
)
403+
->where(
404+
'attribute_id = ?',
405+
$attributeId
406+
);
407+
$actualIndexValues[$name] = [];
408+
foreach ($connection->fetchPairs($select) as $storeId => $storeValue) {
409+
$actualIndexValues[$name][$storeNames[$storeId]] = $storeValue;
410+
}
411+
}
412+
413+
$this->assertEquals($expectedIndexValues, $actualIndexValues);
414+
}
415+
416+
/**
417+
* @return array[]
418+
*/
419+
public function saveCustomPriceAttributeDataProvider(): array
420+
{
421+
return [
422+
[
423+
'attributes' => ['attr1', 'attr2', 'attr3'],
424+
'set' => [
425+
'attr1' => [
426+
'admin' => 9,
427+
],
428+
'attr2' => [
429+
'admin' => 7,
430+
'store2' => 3.5,
431+
],
432+
'attr3' => [
433+
'store3' => 15,
434+
]
435+
],
436+
'expectedValues' =>[
437+
'attr1' => [
438+
'admin' => 9,
439+
'default' => 9,
440+
'store2' => 9,
441+
'store3' => 9,
442+
],
443+
'attr2' => [
444+
'admin' => 7,
445+
'default' => 7,
446+
'store2' => 3.5,
447+
'store3' => 3.5,
448+
],
449+
'attr3' => [
450+
'admin' => null,
451+
'default' => null,
452+
'store2' => 15,
453+
'store3' => 15,
454+
]
455+
],
456+
'expectedIndexValues' => [
457+
'attr1' => [
458+
'default' => 9,
459+
'store2' => 9,
460+
'store3' => 9,
461+
],
462+
'attr2' => [
463+
'default' => 7,
464+
'store2' => 3.5,
465+
'store3' => 3.5,
466+
],
467+
'attr3' => [
468+
'store2' => 15,
469+
'store3' => 15,
470+
]
471+
]
472+
]
473+
];
474+
}
333475
}

0 commit comments

Comments
 (0)