Skip to content

Commit 474ccad

Browse files
committed
MAGETWO-57023: [GitHub] Sorting configurable products by price doesn't work when simple product has special_price #4778
2 parents d3afaa9 + e9d8fa0 commit 474ccad

File tree

6 files changed

+274
-98
lines changed

6 files changed

+274
-98
lines changed

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,30 @@ protected function _prepareFinalPriceData($entityIds = null)
235235
* @param string|null $type product type, all if null
236236
* @return $this
237237
* @throws \Magento\Framework\Exception\LocalizedException
238-
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
239238
*/
240239
protected function prepareFinalPriceDataForType($entityIds, $type)
241240
{
242241
$this->_prepareDefaultFinalPriceTable();
242+
243+
$select = $this->getSelect($entityIds, $type);
244+
$query = $select->insertFromSelect($this->_getDefaultFinalPriceTable(), [], false);
245+
$this->getConnection()->query($query);
246+
return $this;
247+
}
248+
249+
/**
250+
* Forms Select for collecting price related data for final price index table
251+
* Next types of prices took into account: default, special, tier price
252+
* Moved to protected for possible reusing
253+
*
254+
* @param int|array $entityIds Ids for filtering output result
255+
* @param string|null $type Type for filtering output result by specified product type (all if null)
256+
* @return \Magento\Framework\DB\Select
257+
* @throws \Magento\Framework\Exception\LocalizedException
258+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
259+
*/
260+
protected function getSelect($entityIds = null, $type = null)
261+
{
243262
$metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
244263
$connection = $this->getConnection();
245264
$select = $connection->select()->from(
@@ -371,10 +390,7 @@ protected function prepareFinalPriceDataForType($entityIds, $type)
371390
'store_field' => new \Zend_Db_Expr('cs.store_id'),
372391
]
373392
);
374-
375-
$query = $select->insertFromSelect($this->_getDefaultFinalPriceTable(), [], false);
376-
$connection->query($query);
377-
return $this;
393+
return $select;
378394
}
379395

380396
/**

app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php

Lines changed: 20 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -87,63 +87,14 @@ public function reindexEntity($entityIds)
8787
protected function reindex($entityIds = null)
8888
{
8989
if ($this->hasEntity() || !empty($entityIds)) {
90-
if (!empty($entityIds)) {
91-
$allEntityIds = $this->getRelatedProducts($entityIds);
92-
$this->prepareFinalPriceDataForType($allEntityIds, null);
93-
} else {
94-
$this->_prepareFinalPriceData($entityIds);
95-
}
90+
$this->prepareFinalPriceDataForType($entityIds, $this->getTypeId());
9691
$this->_applyCustomOption();
97-
$this->_applyConfigurableOption($entityIds);
92+
$this->_applyConfigurableOption();
9893
$this->_movePriceDataToIndexTable($entityIds);
9994
}
10095
return $this;
10196
}
10297

103-
/**
104-
* Get related product
105-
*
106-
* @param int[] $entityIds
107-
* @return int[]
108-
*/
109-
private function getRelatedProducts($entityIds)
110-
{
111-
$metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class);
112-
$select = $this->getConnection()->select()->union(
113-
[
114-
$this->getConnection()->select()
115-
->from(
116-
['e' => $this->getTable('catalog_product_entity')],
117-
'e.entity_id'
118-
)->join(
119-
['cpsl' => $this->getTable('catalog_product_super_link')],
120-
'cpsl.parent_id = e.' . $metadata->getLinkField(),
121-
[]
122-
)->where(
123-
'e.entity_id IN (?)',
124-
$entityIds
125-
),
126-
$this->getConnection()->select()
127-
->from(
128-
['cpsl' => $this->getTable('catalog_product_super_link')],
129-
'cpsl.product_id'
130-
)->join(
131-
['e' => $this->getTable('catalog_product_entity')],
132-
'cpsl.parent_id = e.' . $metadata->getLinkField(),
133-
[]
134-
)->where(
135-
'e.entity_id IN (?)',
136-
$entityIds
137-
),
138-
$this->getConnection()->select()
139-
->from($this->getTable('catalog_product_super_link'), 'product_id')
140-
->where('product_id in (?)', $entityIds),
141-
]
142-
);
143-
144-
return array_map('intval', $this->getConnection()->fetchCol($select));
145-
}
146-
14798
/**
14899
* Retrieve table name for custom option temporary aggregation data
149100
*
@@ -199,57 +150,33 @@ protected function _applyConfigurableOption()
199150
$connection = $this->getConnection();
200151
$coaTable = $this->_getConfigurableOptionAggregateTable();
201152
$copTable = $this->_getConfigurableOptionPriceTable();
153+
$linkField = $metadata->getLinkField();
202154

203155
$this->_prepareConfigurableOptionAggregateTable();
204156
$this->_prepareConfigurableOptionPriceTable();
205157

206-
$statusAttribute = $this->_getAttribute(ProductInterface::STATUS);
207-
$linkField = $metadata->getLinkField();
208-
209-
$select = $connection->select()->from(
210-
['i' => $this->_getDefaultFinalPriceTable()],
211-
[]
212-
)->join(
213-
['e' => $this->getTable('catalog_product_entity')],
214-
'e.entity_id = i.entity_id',
215-
['parent_id' => 'e.entity_id']
216-
)->join(
158+
$subSelect = $this->getSelect();
159+
$subSelect->join(
217160
['l' => $this->getTable('catalog_product_super_link')],
218-
'l.parent_id = e.' . $linkField,
219-
['product_id']
220-
)->columns(
221-
['customer_group_id', 'website_id'],
222-
'i'
161+
'l.product_id = e.entity_id',
162+
[]
223163
)->join(
224164
['le' => $this->getTable('catalog_product_entity')],
225-
'le.entity_id = l.product_id',
226-
[]
227-
)->where(
228-
'le.required_options=0'
229-
)->joinLeft(
230-
['status_global_attr' => $statusAttribute->getBackendTable()],
231-
"status_global_attr.{$linkField} = le.{$linkField}"
232-
. ' AND status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId()
233-
. ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID,
234-
[]
235-
)->joinLeft(
236-
['status_attr' => $statusAttribute->getBackendTable()],
237-
"status_attr.{$linkField} = le.{$linkField}"
238-
. ' AND status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId()
239-
. ' AND status_attr.store_id = ' . $this->storeResolver->getCurrentStoreId(),
240-
[]
241-
)->where(
242-
'IFNULL(status_attr.value, status_global_attr.value) = ?',
243-
Status::STATUS_ENABLED
244-
)->group(
245-
['e.entity_id', 'i.customer_group_id', 'i.website_id', 'l.product_id']
165+
'le.' . $linkField . ' = l.parent_id',
166+
['parent_id' => 'entity_id']
246167
);
247-
$priceColumn = $this->_addAttributeToSelect($select, 'price', 'le.' . $linkField, 0, null, true);
248-
$tierPriceColumn = $connection->getIfNullSql('MIN(i.tier_price)', 'NULL');
249168

250-
$select->columns(
251-
['price' => $priceColumn, 'tier_price' => $tierPriceColumn]
252-
);
169+
$select = $connection->select();
170+
$select
171+
->from(['sub' => new \Zend_Db_Expr('(' . (string)$subSelect . ')')], '')
172+
->columns([
173+
'sub.parent_id',
174+
'sub.entity_id',
175+
'sub.customer_group_id',
176+
'sub.website_id',
177+
'sub.price',
178+
'sub.tier_price'
179+
]);
253180

254181
$query = $select->insertFromSelect($coaTable);
255182
$connection->query($query);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
use Magento\Catalog\Model\Indexer\Product\Price\Processor;
7+
use Magento\TestFramework\Helper\Bootstrap;
8+
9+
$indexerProcessor = Bootstrap::getObjectManager()->get(Processor::class);
10+
$indexerProcessor->getIndexer()->setScheduled(true);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
use Magento\Catalog\Model\Indexer\Product\Price\Processor;
7+
use Magento\TestFramework\Helper\Bootstrap;
8+
9+
$indexerProcessor = Bootstrap::getObjectManager()->get(Processor::class);
10+
$indexerProcessor->getIndexer()->setScheduled(false);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\ConfigurableProduct\Pricing\Price;
7+
8+
use Magento\Catalog\Api\Data\ProductInterface;
9+
use Magento\Catalog\Api\ProductRepositoryInterface;
10+
use Magento\Catalog\Model\Indexer\Product\Price\Processor;
11+
use Magento\Catalog\Model\Product;
12+
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
13+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
14+
use Magento\TestFramework\Helper\Bootstrap;
15+
16+
class SpecialPriceIndexerTest extends \PHPUnit_Framework_TestCase
17+
{
18+
/**
19+
* @var ProductRepositoryInterface
20+
*/
21+
private $productRepository;
22+
23+
/**
24+
* @var CollectionFactory
25+
*/
26+
private $productCollectionFactory;
27+
28+
/**
29+
* @var Processor
30+
*/
31+
private $indexerProcessor;
32+
33+
protected function setUp()
34+
{
35+
$this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
36+
$this->productCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class);
37+
$this->indexerProcessor = Bootstrap::getObjectManager()->get(Processor::class);
38+
}
39+
40+
/**
41+
* Use collection to check data in index
42+
* Do not use magentoDbIsolation because index statement changing "tears" transaction (triggers creating)
43+
*
44+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
45+
* @magentoDataFixture Magento/Catalog/_files/enable_price_index_schedule.php
46+
*/
47+
public function testFullReindexIfChildHasSpecialPrice()
48+
{
49+
$specialPrice = 2;
50+
/** @var Product $childProduct */
51+
$childProduct = $this->productRepository->get('simple_10', true);
52+
$childProduct->setData('special_price', $specialPrice);
53+
$this->productRepository->save($childProduct);
54+
55+
/** @var ProductCollection $collection */
56+
$collection = $this->productCollectionFactory->create();
57+
$collection
58+
->addPriceData()
59+
->addFieldToFilter(ProductInterface::SKU, 'configurable');
60+
61+
/** @var Product[] $items */
62+
$items = array_values($collection->getItems());
63+
self::assertEquals(10, $items[0]->getData('min_price'));
64+
65+
$this->indexerProcessor->reindexAll();
66+
67+
/** @var ProductCollection $collection */
68+
$collection = $this->productCollectionFactory->create();
69+
$collection
70+
->addPriceData()
71+
->addFieldToFilter(ProductInterface::SKU, 'configurable');
72+
73+
/** @var Product $item */
74+
$item = $collection->getFirstItem();
75+
self::assertEquals($specialPrice, $item->getData('min_price'));
76+
}
77+
78+
/**
79+
* Use collection to check data in index
80+
*
81+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
82+
*/
83+
public function testOnSaveIndexationIfChildHasSpecialPrice()
84+
{
85+
$specialPrice = 2;
86+
/** @var Product $childProduct */
87+
$childProduct = $this->productRepository->get('simple_10', true);
88+
$childProduct->setData('special_price', $specialPrice);
89+
$this->productRepository->save($childProduct);
90+
91+
/** @var ProductCollection $collection */
92+
$collection = $this->productCollectionFactory->create();
93+
$collection
94+
->addPriceData()
95+
->addFieldToFilter(ProductInterface::SKU, 'configurable');
96+
97+
/** @var Product $item */
98+
$item = $collection->getFirstItem();
99+
self::assertEquals($specialPrice, $item->getData('min_price'));
100+
}
101+
}

0 commit comments

Comments
 (0)