Skip to content

Commit 1707f2a

Browse files
author
Stanislav Idolov
committed
MC-41862: Stock status resets to saleable when the product is saved from Admin
1 parent bab395a commit 1707f2a

File tree

11 files changed

+194
-20
lines changed

11 files changed

+194
-20
lines changed

app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php

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

99
use Magento\Catalog\Model\ResourceModel\Product\Indexer\AbstractIndexer;
1010
use Magento\CatalogInventory\Model\Stock;
11+
use Magento\Framework\App\ObjectManager;
1112
use Magento\Framework\DB\Adapter\AdapterInterface;
1213
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1314
use Magento\CatalogInventory\Model\Indexer\Stock\Action\Full;
@@ -64,6 +65,11 @@ class DefaultStock extends AbstractIndexer implements StockInterface
6465
*/
6566
private $actionType;
6667

68+
/**
69+
* @var GetStatusExpression
70+
*/
71+
private $getStatusExpression;
72+
6773
/**
6874
* Class constructor
6975
*
@@ -72,16 +78,21 @@ class DefaultStock extends AbstractIndexer implements StockInterface
7278
* @param \Magento\Eav\Model\Config $eavConfig
7379
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
7480
* @param string $connectionName
81+
* @param \Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\GetStatusExpression|null $getStatusExpression
7582
*/
7683
public function __construct(
7784
\Magento\Framework\Model\ResourceModel\Db\Context $context,
7885
\Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy,
7986
\Magento\Eav\Model\Config $eavConfig,
8087
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
81-
$connectionName = null
88+
$connectionName = null,
89+
\Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\GetStatusExpression $getStatusExpression = null
8290
) {
8391
$this->_scopeConfig = $scopeConfig;
8492
parent::__construct($context, $tableStrategy, $eavConfig, $connectionName);
93+
$this->getStatusExpression = $getStatusExpression ?: ObjectManager::getInstance()->get(
94+
\Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\GetStatusExpression::class
95+
);
8596
}
8697

8798
/**
@@ -251,6 +262,10 @@ protected function _getStockStatusSelect($entityIds = null, $usePrimaryTable = f
251262
. ' AND mcpei.attribute_id = ' . $this->_getAttribute('status')->getId()
252263
. ' AND mcpei.value = ' . ProductStatus::STATUS_ENABLED,
253264
[]
265+
)->joinLeft(
266+
['css' => 'cataloginventory_stock_status'],
267+
'css.product_id = e.entity_id',
268+
[]
254269
)->columns(
255270
['qty' => $qtyExpr]
256271
)->where(
@@ -292,14 +307,14 @@ protected function _prepareIndexTable($entityIds = null)
292307
*/
293308
protected function _updateIndex($entityIds)
294309
{
295-
$this->deleteOldRecords($entityIds);
296310
$connection = $this->getConnection();
297311
$select = $this->_getStockStatusSelect($entityIds, true);
298312
$select = $this->getQueryProcessorComposite()->processQuery($select, $entityIds, true);
299313
$query = $connection->query($select);
300314

301315
$i = 0;
302316
$data = [];
317+
$savedEntityIds = [];
303318
while ($row = $query->fetch(\PDO::FETCH_ASSOC)) {
304319
$i++;
305320
$data[] = [
@@ -309,6 +324,7 @@ protected function _updateIndex($entityIds)
309324
'qty' => (double)$row['qty'],
310325
'stock_status' => (int)$row['status'],
311326
];
327+
$savedEntityIds[] = (int)$row['entity_id'];
312328
if ($i % 1000 == 0) {
313329
$this->_updateIndexTable($data);
314330
$data = [];
@@ -317,6 +333,7 @@ protected function _updateIndex($entityIds)
317333

318334
$this->_updateIndexTable($data);
319335

336+
$this->deleteOldRecords(array_diff($entityIds, $savedEntityIds));
320337
return $this;
321338
}
322339

@@ -376,21 +393,7 @@ public function getIdxTable($table = null)
376393
*/
377394
protected function getStatusExpression(AdapterInterface $connection, $isAggregate = false)
378395
{
379-
$isInStockExpression = $isAggregate ? 'MAX(cisi.is_in_stock)' : 'cisi.is_in_stock';
380-
if ($this->_isManageStock()) {
381-
$statusExpr = $connection->getCheckSql(
382-
'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0',
383-
1,
384-
$isInStockExpression
385-
);
386-
} else {
387-
$statusExpr = $connection->getCheckSql(
388-
'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1',
389-
$isInStockExpression,
390-
1
391-
);
392-
}
393-
return $statusExpr;
396+
return $this->getStatusExpression->execute($this->getTypeId(), $connection, $isAggregate);
394397
}
395398

396399
/**
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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\CatalogInventory\Model\ResourceModel\Indexer\Stock;
9+
10+
use InvalidArgumentException;
11+
use Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\StatusExpression\ExpressionInterface;
12+
use Magento\Framework\DB\Adapter\AdapterInterface;
13+
use Zend_Db_Expr;
14+
15+
class GetStatusExpression
16+
{
17+
/**
18+
* @var array
19+
*/
20+
private $statusExpressions;
21+
22+
/**
23+
* @param array $statusExpressions
24+
*/
25+
public function __construct(array $statusExpressions = [])
26+
{
27+
foreach ($statusExpressions as $expression) {
28+
if (!($expression instanceof ExpressionInterface)) {
29+
throw new InvalidArgumentException(
30+
'Expressions must implement '
31+
.'\Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\StatusExpression\ExpressionInterface'
32+
.' interface'
33+
);
34+
}
35+
}
36+
$this->statusExpressions = $statusExpressions;
37+
}
38+
39+
/**
40+
* Returns stock status expression for MySQL query.
41+
*
42+
* @param string $productType
43+
* @param AdapterInterface $connection
44+
* @param bool $isAggregate
45+
* @return Zend_Db_Expr|null
46+
*/
47+
public function execute(string $productType, AdapterInterface $connection, bool $isAggregate): ?Zend_Db_Expr
48+
{
49+
$expression = $this->statusExpressions[$productType] ?? $this->statusExpressions['default'];
50+
return $expression->getExpression($connection, $isAggregate);
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\StatusExpression;
8+
9+
use Magento\CatalogInventory\Model\Configuration;
10+
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
use Magento\Framework\DB\Adapter\AdapterInterface;
12+
use Magento\Store\Model\ScopeInterface;
13+
use Zend_Db_Expr;
14+
15+
class DefaultExpression implements ExpressionInterface
16+
{
17+
/**
18+
* @var ScopeConfigInterface
19+
*/
20+
private $scopeConfig;
21+
22+
/**
23+
* @param ScopeConfigInterface $scopeConfig
24+
*/
25+
public function __construct(ScopeConfigInterface $scopeConfig)
26+
{
27+
$this->scopeConfig = $scopeConfig;
28+
}
29+
30+
/**
31+
* Returns status expressions for MySQL query
32+
*
33+
* @ingeritdoc
34+
* @param AdapterInterface $connection
35+
* @param bool $isAggregate
36+
* @return Zend_Db_Expr
37+
*/
38+
public function getExpression(AdapterInterface $connection, bool $isAggregate): Zend_Db_Expr
39+
{
40+
$isManageStock = $this->scopeConfig->isSetFlag(
41+
Configuration::XML_PATH_MANAGE_STOCK,
42+
ScopeInterface::SCOPE_STORE
43+
);
44+
45+
$isInStockExpression = $isAggregate ? 'MAX(cisi.is_in_stock)' : 'cisi.is_in_stock';
46+
if ($isManageStock) {
47+
$statusExpr = $connection->getCheckSql(
48+
'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 0',
49+
1,
50+
$isInStockExpression
51+
);
52+
} else {
53+
$statusExpr = $connection->getCheckSql(
54+
'cisi.use_config_manage_stock = 0 AND cisi.manage_stock = 1',
55+
$isInStockExpression,
56+
1
57+
);
58+
}
59+
return $statusExpr;
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\StatusExpression;
8+
9+
use Magento\Framework\DB\Adapter\AdapterInterface;
10+
use Zend_Db_Expr;
11+
12+
/**
13+
* Interface for composite status expressions for MySQL query.
14+
*/
15+
interface ExpressionInterface
16+
{
17+
/**
18+
* Returns status expressions for MySQL query
19+
*
20+
* @param AdapterInterface $connection
21+
* @param bool $isAggregate
22+
* @return Zend_Db_Expr
23+
*/
24+
public function getExpression(AdapterInterface $connection, bool $isAggregate): Zend_Db_Expr;
25+
}

app/code/Magento/CatalogInventory/etc/di.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,11 @@
133133
<type name="Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save">
134134
<plugin name="massAction" type="Magento\CatalogInventory\Plugin\MassUpdateProductAttribute" />
135135
</type>
136+
<type name="Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\GetStatusExpression">
137+
<arguments>
138+
<argument name="statusExpressions" xsi:type="array">
139+
<item name="default" xsi:type="object">Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\StatusExpression\DefaultExpression</item>
140+
</argument>
141+
</arguments>
142+
</type>
136143
</config>

dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
class CategoryProductsVariantsTest extends GraphQlAbstract
1919
{
2020
/**
21+
* @magentoApiDataFixture Magento/Catalog/_files/reindex_catalog_inventory_stock.php
2122
* @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
2223
* @throws \Magento\Framework\Exception\NoSuchEntityException
2324
*/

dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/ReorderTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ protected function setUp(): void
5656
} catch (NoSuchEntityException $e) {
5757
}
5858
}
59-
6059
/**
6160
* @magentoApiDataFixture Magento/Sales/_files/customer_order_item_with_product_and_custom_options.php
6261
*/
@@ -188,15 +187,17 @@ public function testReorderWithLowStock()
188187
}
189188

190189
/**
191-
* Assert that simple product is not available.
190+
* Assert that simple product is not available
192191
*/
193192
private function assertProductNotAvailable()
194193
{
195194
$response = $this->makeReorderForDefaultCustomer();
196195
$expectedResponse = [
197196
'userInputErrors' => [
198197
[
199-
'path' => ['orderNumber'],
198+
'path' => [
199+
'orderNumber'
200+
],
200201
'code' => 'NOT_SALABLE',
201202
],
202203
],

dev/tests/integration/testsuite/Magento/Catalog/_files/products_list_rollback.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
declare(strict_types=1);
77

88
/** @var \Magento\Framework\Registry $registry */
9+
10+
use Magento\Framework\Indexer\IndexerRegistry;
11+
912
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
1013

1114
$registry->unregister('isSecureArea');
@@ -37,6 +40,9 @@
3740
//Product already removed
3841
}
3942

43+
\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(IndexerRegistry::class)
44+
->get(Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID)
45+
->reindexAll();
4046

4147
$registry->unregister('isSecureArea');
4248
$registry->register('isSecureArea', false);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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\CatalogInventory\Model\Indexer\Stock\Processor;
9+
use Magento\Framework\Indexer\IndexerRegistry;
10+
use Magento\TestFramework\Helper\Bootstrap;
11+
12+
Bootstrap::getObjectManager()->get(IndexerRegistry::class)->get(Processor::INDEXER_ID)->reindexAll();

dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public function testOutOfStockProductWithDisabledConfigView(): void
102102
}
103103

104104
/**
105+
* @magentoDataFixture Magento/Catalog/_files/reindex_catalog_inventory_stock.php
105106
* @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_category.php
106107
*
107108
* @return void

0 commit comments

Comments
 (0)