Skip to content

Commit 0fd2b96

Browse files
Merge remote-tracking branch 'origin/MC-35490' into 2.4-develop-pr33
2 parents 42d85ba + 2f8035e commit 0fd2b96

File tree

5 files changed

+194
-12
lines changed

5 files changed

+194
-12
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,14 +368,20 @@ protected function _reindexRows($changedIds = [])
368368
$productsTypes = $this->getProductsTypes($changedIds);
369369
$parentProductsTypes = $this->getParentProductsTypes($changedIds);
370370

371-
$changedIds = array_merge($changedIds, ...array_values($parentProductsTypes));
371+
$changedIds = array_unique(array_merge($changedIds, ...array_values($parentProductsTypes)));
372372
$productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes);
373373

374374
if ($changedIds) {
375375
$this->deleteIndexData($changedIds);
376376
}
377-
foreach ($productsTypes as $productType => $entityIds) {
378-
$indexer = $this->_getIndexer($productType);
377+
378+
$typeIndexers = $this->getTypeIndexers();
379+
foreach ($typeIndexers as $productType => $indexer) {
380+
$entityIds = $productsTypes[$productType] ?? [];
381+
if (empty($entityIds)) {
382+
continue;
383+
}
384+
379385
if ($indexer instanceof DimensionalIndexerInterface) {
380386
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
381387
$this->tableMaintainer->createMainTmpTable($dimensions);

dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price;
77

88
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor;
910
use Magento\Catalog\Model\Product\Attribute\Source\Status;
1011
use Magento\Catalog\Model\ResourceModel\Product\Collection;
1112
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
@@ -18,7 +19,7 @@
1819
use Magento\Catalog\Api\Data\ProductInterface;
1920

2021
/**
21-
* Configurable test
22+
* Test reindex of configurable products
2223
*
2324
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2425
* @magentoAppArea adminhtml
@@ -64,7 +65,7 @@ protected function setUp(): void
6465
*/
6566
public function testGetProductFinalPriceIfOneOfChildIsDisabled(): void
6667
{
67-
$configurableProduct = $this->getConfigurableProductFromCollection();
68+
$configurableProduct = $this->getConfigurableProductFromCollection(1);
6869
$this->assertEquals(10, $configurableProduct->getMinimalPrice());
6970

7071
$childProduct = $this->productRepository->getById(10, false, null, true);
@@ -75,7 +76,7 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabled(): void
7576
$this->productRepository->save($childProduct);
7677
$this->storeManager->setCurrentStore($currentStoreId);
7778

78-
$configurableProduct = $this->getConfigurableProductFromCollection();
79+
$configurableProduct = $this->getConfigurableProductFromCollection(1);
7980
$this->assertEquals(20, $configurableProduct->getMinimalPrice());
8081
}
8182

@@ -93,7 +94,7 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabled(): void
9394
*/
9495
public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore(): void
9596
{
96-
$configurableProduct = $this->getConfigurableProductFromCollection();
97+
$configurableProduct = $this->getConfigurableProductFromCollection(1);
9798
$this->assertEquals(10, $configurableProduct->getMinimalPrice());
9899

99100
$childProduct = $this->productRepository->get('simple_10', false, null, true);
@@ -106,7 +107,7 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore(): void
106107
$this->productRepository->save($childProduct);
107108
$this->storeManager->setCurrentStore($currentStoreId);
108109

109-
$configurableProduct = $this->getConfigurableProductFromCollection();
110+
$configurableProduct = $this->getConfigurableProductFromCollection(1);
110111
$this->assertEquals(20, $configurableProduct->getMinimalPrice());
111112
}
112113

@@ -122,33 +123,56 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore(): void
122123
*/
123124
public function testGetProductMinimalPriceIfOneOfChildIsOutOfStock(): void
124125
{
125-
$configurableProduct = $this->getConfigurableProductFromCollection();
126+
$configurableProduct = $this->getConfigurableProductFromCollection(1);
126127
$this->assertEquals(10, $configurableProduct->getMinimalPrice());
127128

128129
$childProduct = $this->productRepository->getById(10, false, null, true);
129130
$stockItem = $childProduct->getExtensionAttributes()->getStockItem();
130131
$stockItem->setIsInStock(Stock::STOCK_OUT_OF_STOCK);
131132
$this->stockRepository->save($stockItem);
132133

133-
$configurableProduct = $this->getConfigurableProductFromCollection();
134+
$configurableProduct = $this->getConfigurableProductFromCollection(1);
134135
$this->assertEquals(20, $configurableProduct->getMinimalPrice());
135136
}
136137

138+
/**
139+
* @magentoDataFixture Magento/Catalog/_files/enable_price_index_schedule.php
140+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples.php
141+
* @magentoDbIsolation disabled
142+
*
143+
* @return void
144+
*/
145+
public function testReindexWithCorrectPriority()
146+
{
147+
$configurableProduct = $this->productRepository->get('configurable');
148+
$childProduct1 = $this->productRepository->get('simple_1');
149+
$childProduct2 = $this->productRepository->get('simple_2');
150+
$priceIndexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class);
151+
$priceIndexerProcessor->reindexList(
152+
[$configurableProduct->getId(), $childProduct1->getId(), $childProduct2->getId()],
153+
true
154+
);
155+
156+
$configurableProduct = $this->getConfigurableProductFromCollection($configurableProduct->getId());
157+
$this->assertEquals($childProduct1->getPrice(), $configurableProduct->getMinimalPrice());
158+
}
159+
137160
/**
138161
* Retrieve configurable product.
139162
* Returns Configurable product that was created by Magento/ConfigurableProduct/_files/product_configurable.php
140163
* fixture
141164
*
165+
* @param int $productId
142166
* @return ProductInterface
143167
*/
144-
private function getConfigurableProductFromCollection(): ProductInterface
168+
private function getConfigurableProductFromCollection(int $productId): ProductInterface
145169
{
146170
/** @var Collection $collection */
147171
$collection = Bootstrap::getObjectManager()->get(CollectionFactory::class)
148172
->create();
149173
/** @var ProductInterface $configurableProduct */
150174
$configurableProduct = $collection
151-
->addIdFilter([1])
175+
->addIdFilter([$productId])
152176
->addMinimalPrice()
153177
->load()
154178
->getFirstItem();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor;
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
12+
use Magento\Catalog\Model\Product\Type;
13+
use Magento\Catalog\Model\Product\Visibility;
14+
use Magento\Catalog\Setup\CategorySetup;
15+
use Magento\CatalogInventory\Model\Stock\Item;
16+
use Magento\ConfigurableProduct\Helper\Product\Options\Factory;
17+
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
18+
use Magento\Eav\Api\Data\AttributeOptionInterface;
19+
use Magento\Eav\Model\Config;
20+
use Magento\TestFramework\Helper\Bootstrap;
21+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
22+
23+
Bootstrap::getInstance()->reinitialize();
24+
25+
Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_attribute.php');
26+
27+
$installer = Bootstrap::getObjectManager()->create(CategorySetup::class);
28+
$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
29+
30+
$eavConfig = Bootstrap::getObjectManager()->get(Config::class);
31+
$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable');
32+
33+
$product = Bootstrap::getObjectManager()->create(Product::class);
34+
$product->setTypeId(Configurable::TYPE_CODE)
35+
->setAttributeSetId($attributeSetId)
36+
->setWebsiteIds([1])
37+
->setName('Configurable Product')
38+
->setSku('configurable')
39+
->setVisibility(Visibility::VISIBILITY_BOTH)
40+
->setStatus(Status::STATUS_ENABLED)
41+
->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]);
42+
$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
43+
$product = $productRepository->save($product);
44+
45+
$attributeValues = [];
46+
$associatedProductIds = [];
47+
/** @var AttributeOptionInterface[] $options */
48+
$options = $attribute->getOptions();
49+
array_shift($options); //remove the first option which is empty
50+
$productNumber = 0;
51+
foreach ($options as $option) {
52+
$productNumber++;
53+
54+
$childProduct = Bootstrap::getObjectManager()->create(Product::class);
55+
$childProduct->setTypeId(Type::TYPE_SIMPLE)
56+
->setAttributeSetId($attributeSetId)
57+
->setWebsiteIds([1])
58+
->setName('Configurable Option' . $option->getLabel())
59+
->setSku('simple_' . $productNumber)
60+
->setPrice($productNumber * 10)
61+
->setTestConfigurable($option->getValue())
62+
->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE)
63+
->setStatus(Status::STATUS_ENABLED)
64+
->setStockData(
65+
['use_config_manage_stock' => 1,'qty' => $productNumber * 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]
66+
);
67+
$childProduct = $productRepository->save($childProduct);
68+
69+
$stockItem = Bootstrap::getObjectManager()->create(Item::class);
70+
$stockItem->load($childProduct->getId(), 'product_id');
71+
if (!$stockItem->getProductId()) {
72+
$stockItem->setProductId($childProduct->getId());
73+
}
74+
$stockItem->setUseConfigManageStock(1);
75+
$stockItem->setQty($productNumber * 100);
76+
$stockItem->setIsQtyDecimal(0);
77+
$stockItem->setIsInStock(1);
78+
$stockItem->save();
79+
80+
$attributeValues[] = [
81+
'label' => 'test',
82+
'attribute_id' => $attribute->getId(),
83+
'value_index' => $option->getValue(),
84+
];
85+
$associatedProductIds[] = $childProduct->getId();
86+
}
87+
88+
$indexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class);
89+
$indexerProcessor->reindexList($associatedProductIds);
90+
91+
$optionsFactory = Bootstrap::getObjectManager()->create(Factory::class);
92+
$configurableOptions = $optionsFactory->create(
93+
[
94+
[
95+
'attribute_id' => $attribute->getId(),
96+
'code' => $attribute->getAttributeCode(),
97+
'label' => $attribute->getStoreLabel(),
98+
'position' => '0',
99+
'values' => $attributeValues,
100+
],
101+
]
102+
);
103+
$extensionConfigurableAttributes = $product->getExtensionAttributes();
104+
$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions);
105+
$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds);
106+
$product->setExtensionAttributes($extensionConfigurableAttributes);
107+
$product = $productRepository->save($product);
108+
109+
$indexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class);
110+
$indexerProcessor->reindexRow($product->getId());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\CatalogInventory\Model\Stock\Status;
10+
use Magento\Framework\Registry;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
13+
14+
$objectManager = Bootstrap::getObjectManager();
15+
16+
$registry = $objectManager->get(Registry::class);
17+
$registry->unregister('isSecureArea');
18+
$registry->register('isSecureArea', true);
19+
20+
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
21+
foreach (['simple_1', 'simple_2', 'configurable'] as $sku) {
22+
try {
23+
$product = $productRepository->get($sku, true);
24+
25+
$stockStatus = $objectManager->create(Status::class);
26+
$stockStatus->load($product->getEntityId(), 'product_id');
27+
$stockStatus->delete();
28+
29+
if ($product->getId()) {
30+
$productRepository->delete($product);
31+
}
32+
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
33+
//Product already removed
34+
}
35+
}
36+
37+
Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_attribute_rollback.php');
38+
39+
$registry->unregister('isSecureArea');
40+
$registry->register('isSecureArea', false);

dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
/**
1818
* Test for \Magento\ImportExport\Controller\Adminhtml\Export\File\Delete class.
19+
*
20+
* @magentoAppArea adminhtml
1921
*/
2022
class DeleteTest extends AbstractBackendController
2123
{

0 commit comments

Comments
 (0)