Skip to content

Commit 85afb2a

Browse files
authored
Merge pull request #2332 from magento-tango/2.3-PR-4
MAGETWO-72861 Visual Merchandiser category edit performance issue - for 2.3 MAGETWO-70835 Fix Tier Price for One Product Unit
2 parents 548ef66 + b02bd16 commit 85afb2a

File tree

18 files changed

+787
-239
lines changed

18 files changed

+787
-239
lines changed

app/code/Magento/Catalog/Model/Category.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
* @method Category setUrlPath(string $urlPath)
3333
* @method Category getSkipDeleteChildren()
3434
* @method Category setSkipDeleteChildren(boolean $value)
35+
* @method Category setChangedProductIds(array $categoryIds) Set products ids that inserted or deleted for category
36+
* @method array getChangedProductIds() Get products ids that inserted or deleted for category
3537
*
3638
* @SuppressWarnings(PHPMD.LongVariable)
3739
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\Category\Product;
9+
10+
/**
11+
* Resolver to get product positions by ids assigned to specific category
12+
*/
13+
class PositionResolver extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
14+
{
15+
/**
16+
* Initialize resource model
17+
*
18+
* @return void
19+
*/
20+
protected function _construct()
21+
{
22+
$this->_init('catalog_product_entity', 'entity_id');
23+
}
24+
25+
/**
26+
* Get category product positions
27+
*
28+
* @param int $categoryId
29+
* @return array
30+
*/
31+
public function getPositions(int $categoryId): array
32+
{
33+
$connection = $this->getConnection();
34+
35+
$select = $connection->select()->from(
36+
['cpe' => $this->getTable('catalog_product_entity')],
37+
'entity_id'
38+
)->joinLeft(
39+
['ccp' => $this->getTable('catalog_category_product')],
40+
'ccp.product_id=cpe.entity_id'
41+
)->where(
42+
'ccp.category_id = ?',
43+
$categoryId
44+
)->order(
45+
'ccp.position ' . \Magento\Framework\DB\Select::SQL_ASC
46+
);
47+
48+
return array_flip($connection->fetchCol($select));
49+
}
50+
}

app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php

Lines changed: 69 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\Catalog\Model\Indexer\Product\Price;
77

8+
use Magento\Framework\App\ObjectManager;
9+
810
/**
911
* Abstract action reindex class
1012
*
@@ -71,9 +73,9 @@ abstract class AbstractAction
7173
protected $_indexers;
7274

7375
/**
74-
* @var \Magento\Catalog\Model\ResourceModel\Product
76+
* @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice
7577
*/
76-
private $productResource;
78+
private $tierPriceIndexResource;
7779

7880
/**
7981
* @param \Magento\Framework\App\Config\ScopeConfigInterface $config
@@ -84,6 +86,7 @@ abstract class AbstractAction
8486
* @param \Magento\Catalog\Model\Product\Type $catalogProductType
8587
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory
8688
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource
89+
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource
8790
*/
8891
public function __construct(
8992
\Magento\Framework\App\Config\ScopeConfigInterface $config,
@@ -93,7 +96,8 @@ public function __construct(
9396
\Magento\Framework\Stdlib\DateTime $dateTime,
9497
\Magento\Catalog\Model\Product\Type $catalogProductType,
9598
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory,
96-
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource
99+
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource,
100+
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource = null
97101
) {
98102
$this->_config = $config;
99103
$this->_storeManager = $storeManager;
@@ -104,6 +108,9 @@ public function __construct(
104108
$this->_indexerPriceFactory = $indexerPriceFactory;
105109
$this->_defaultIndexerResource = $defaultIndexerResource;
106110
$this->_connection = $this->_defaultIndexerResource->getConnection();
111+
$this->tierPriceIndexResource = $tierPriceIndexResource ?: ObjectManager::getInstance()->get(
112+
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice::class
113+
);
107114
}
108115

109116
/**
@@ -215,92 +222,8 @@ protected function _prepareWebsiteDateTable()
215222
*/
216223
protected function _prepareTierPriceIndex($entityIds = null)
217224
{
218-
$table = $this->_defaultIndexerResource->getTable('catalog_product_index_tier_price');
219-
$this->_emptyTable($table);
220-
if (empty($entityIds)) {
221-
return $this;
222-
}
223-
$linkField = $this->getProductIdFieldName();
224-
$priceAttribute = $this->getProductResource()->getAttribute('price');
225-
$baseColumns = [
226-
'cpe.entity_id',
227-
'tp.customer_group_id',
228-
'tp.website_id'
229-
];
230-
if ($linkField !== 'entity_id') {
231-
$baseColumns[] = 'cpe.' . $linkField;
232-
};
233-
$subSelect = $this->_connection->select()->from(
234-
['cpe' => $this->_defaultIndexerResource->getTable('catalog_product_entity')],
235-
array_merge_recursive(
236-
$baseColumns,
237-
[
238-
'min(tp.value) AS value',
239-
'min(tp.percentage_value) AS percentage_value'
240-
]
241-
)
242-
)->joinInner(
243-
['tp' => $this->_defaultIndexerResource->getTable(['catalog_product_entity', 'tier_price'])],
244-
'tp.' . $linkField . ' = cpe.' . $linkField,
245-
[]
246-
)->where("cpe.entity_id IN(?)", $entityIds)
247-
->where("tp.website_id != 0")
248-
->group(['cpe.entity_id', 'tp.customer_group_id', 'tp.website_id']);
249-
250-
$subSelect2 = $this->_connection->select()
251-
->from(
252-
['cpe' => $this->_defaultIndexerResource->getTable('catalog_product_entity')],
253-
array_merge_recursive(
254-
$baseColumns,
255-
[
256-
'MIN(ROUND(tp.value * cwd.rate, 4)) AS value',
257-
'MIN(ROUND(tp.percentage_value * cwd.rate, 4)) AS percentage_value'
258-
259-
]
260-
)
261-
)
262-
->joinInner(
263-
['tp' => $this->_defaultIndexerResource->getTable(['catalog_product_entity', 'tier_price'])],
264-
'tp.' . $linkField . ' = cpe.' . $linkField,
265-
[]
266-
)->join(
267-
['cw' => $this->_defaultIndexerResource->getTable('store_website')],
268-
true,
269-
[]
270-
)
271-
->joinInner(
272-
['cwd' => $this->_defaultIndexerResource->getTable('catalog_product_index_website')],
273-
'cw.website_id = cwd.website_id',
274-
[]
275-
)
276-
->where("cpe.entity_id IN(?)", $entityIds)
277-
->where("tp.website_id = 0")
278-
->group(
279-
['cpe.entity_id', 'tp.customer_group_id', 'tp.website_id']
280-
);
281-
282-
$unionSelect = $this->_connection->select()
283-
->union([$subSelect, $subSelect2], \Magento\Framework\DB\Select::SQL_UNION_ALL);
284-
$select = $this->_connection->select()
285-
->from(
286-
['b' => new \Zend_Db_Expr(sprintf('(%s)', $unionSelect->assemble()))],
287-
[
288-
'b.entity_id',
289-
'b.customer_group_id',
290-
'b.website_id',
291-
'MIN(IF(b.value = 0, product_price.value * (1 - b.percentage_value / 100), b.value))'
292-
]
293-
)
294-
->joinInner(
295-
['product_price' => $priceAttribute->getBackend()->getTable()],
296-
'b.' . $linkField . ' = product_price.' . $linkField,
297-
[]
298-
)
299-
->group(['b.entity_id', 'b.customer_group_id', 'b.website_id']);
300-
301-
$query = $select->insertFromSelect($table, [], false);
225+
$this->tierPriceIndexResource->reindexEntity((array) $entityIds);
302226

303-
$this->_connection->query($query);
304227
return $this;
305228
}
306229

@@ -391,30 +314,17 @@ protected function _emptyTable($table)
391314
*
392315
* @param array $changedIds
393316
* @return array Affected ids
394-
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
395317
*/
396318
protected function _reindexRows($changedIds = [])
397319
{
398320
$this->_emptyTable($this->_defaultIndexerResource->getIdxTable());
399321
$this->_prepareWebsiteDateTable();
400322

401-
$select = $this->_connection->select()->from(
402-
$this->_defaultIndexerResource->getTable('catalog_product_entity'),
403-
['entity_id', 'type_id']
404-
)->where(
405-
'entity_id IN(?)',
406-
$changedIds
407-
);
408-
$pairs = $this->_connection->fetchPairs($select);
409-
$byType = [];
410-
foreach ($pairs as $productId => $productType) {
411-
$byType[$productType][$productId] = $productId;
412-
}
413-
323+
$productsTypes = $this->getProductsTypes($changedIds);
414324
$compositeIds = [];
415325
$notCompositeIds = [];
416326

417-
foreach ($byType as $productType => $entityIds) {
327+
foreach ($productsTypes as $productType => $entityIds) {
418328
$indexer = $this->_getIndexer($productType);
419329
if ($indexer->getIsComposite()) {
420330
$compositeIds += $entityIds;
@@ -424,37 +334,21 @@ protected function _reindexRows($changedIds = [])
424334
}
425335

426336
if (!empty($notCompositeIds)) {
427-
$select = $this->_connection->select()->from(
428-
['l' => $this->_defaultIndexerResource->getTable('catalog_product_relation')],
429-
''
430-
)->join(
431-
['e' => $this->_defaultIndexerResource->getTable('catalog_product_entity')],
432-
'e.' . $this->getProductIdFieldName() . ' = l.parent_id',
433-
['e.entity_id as parent_id', 'type_id']
434-
)->where(
435-
'l.child_id IN(?)',
436-
$notCompositeIds
437-
);
438-
$pairs = $this->_connection->fetchPairs($select);
439-
foreach ($pairs as $productId => $productType) {
440-
if (!in_array($productId, $changedIds)) {
441-
$changedIds[] = (string) $productId;
442-
$byType[$productType][$productId] = $productId;
443-
$compositeIds[$productId] = $productId;
444-
}
445-
}
337+
$parentProductsTypes = $this->getParentProductsTypes($notCompositeIds);
338+
$productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes);
339+
$parentProductsIds = array_keys($parentProductsTypes);
340+
$compositeIds = $compositeIds + array_combine($parentProductsIds, $parentProductsIds);
341+
$changedIds = array_merge($changedIds, $parentProductsIds);
446342
}
447343

448344
if (!empty($compositeIds)) {
449345
$this->_copyRelationIndexData($compositeIds, $notCompositeIds);
450346
}
451347
$this->_prepareTierPriceIndex($compositeIds + $notCompositeIds);
452348

453-
$indexers = $this->getTypeIndexers();
454-
foreach ($indexers as $indexer) {
455-
if (!empty($byType[$indexer->getTypeId()])) {
456-
$indexer->reindexEntity($byType[$indexer->getTypeId()]);
457-
}
349+
foreach ($productsTypes as $productType => $entityIds) {
350+
$indexer = $this->_getIndexer($productType);
351+
$indexer->reindexEntity($entityIds);
458352
}
459353
$this->_syncData($changedIds);
460354

@@ -524,15 +418,56 @@ protected function getProductIdFieldName()
524418
}
525419

526420
/**
527-
* @return \Magento\Catalog\Model\ResourceModel\Product
528-
* @deprecated 101.1.0
421+
* Get products types.
422+
*
423+
* @param array $changedIds
424+
* @return array
425+
*/
426+
private function getProductsTypes(array $changedIds = [])
427+
{
428+
$select = $this->_connection->select()->from(
429+
$this->_defaultIndexerResource->getTable('catalog_product_entity'),
430+
['entity_id', 'type_id']
431+
);
432+
if ($changedIds) {
433+
$select->where('entity_id IN (?)', $changedIds);
434+
}
435+
$pairs = $this->_connection->fetchPairs($select);
436+
437+
$byType = [];
438+
foreach ($pairs as $productId => $productType) {
439+
$byType[$productType][$productId] = $productId;
440+
}
441+
442+
return $byType;
443+
}
444+
445+
/**
446+
* Get parent products types.
447+
*
448+
* @param array $productsIds
449+
* @return array
529450
*/
530-
private function getProductResource()
451+
private function getParentProductsTypes(array $productsIds)
531452
{
532-
if (null === $this->productResource) {
533-
$this->productResource = \Magento\Framework\App\ObjectManager::getInstance()
534-
->get(\Magento\Catalog\Model\ResourceModel\Product::class);
453+
$select = $this->_connection->select()->from(
454+
['l' => $this->_defaultIndexerResource->getTable('catalog_product_relation')],
455+
''
456+
)->join(
457+
['e' => $this->_defaultIndexerResource->getTable('catalog_product_entity')],
458+
'e.' . $this->getProductIdFieldName() . ' = l.parent_id',
459+
['e.entity_id as parent_id', 'type_id']
460+
)->where(
461+
'l.child_id IN(?)',
462+
$productsIds
463+
);
464+
$pairs = $this->_connection->fetchPairs($select);
465+
466+
$byType = [];
467+
foreach ($pairs as $productId => $productType) {
468+
$byType[$productType][$productId] = $productId;
535469
}
536-
return $this->productResource;
470+
471+
return $byType;
537472
}
538473
}

app/code/Magento/Catalog/Model/ResourceModel/Category.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,18 @@ protected function _saveCategoryProducts($category)
410410
* Update product positions in category
411411
*/
412412
if (!empty($update)) {
413+
$newPositions = [];
413414
foreach ($update as $productId => $position) {
414-
$where = ['category_id = ?' => (int)$id, 'product_id = ?' => (int)$productId];
415-
$bind = ['position' => (int)$position];
415+
$delta = $position - $oldProducts[$productId];
416+
if (!isset($newPositions[$delta])) {
417+
$newPositions[$delta] = [];
418+
}
419+
$newPositions[$delta][] = $productId;
420+
}
421+
422+
foreach ($newPositions as $delta => $productIds) {
423+
$bind = ['position' => new \Zend_Db_Expr("position + ({$delta})")];
424+
$where = ['category_id = ?' => (int)$id, 'product_id IN (?)' => $productIds];
416425
$connection->update($this->getCategoryProductTable(), $bind, $where);
417426
}
418427
}
@@ -423,6 +432,8 @@ protected function _saveCategoryProducts($category)
423432
'catalog_category_change_products',
424433
['category' => $category, 'product_ids' => $productIds]
425434
);
435+
436+
$category->setChangedProductIds($productIds);
426437
}
427438

428439
if (!empty($insert) || !empty($update) || !empty($delete)) {

0 commit comments

Comments
 (0)