Skip to content

Commit 1ca6ef5

Browse files
MC-37718: Grouped product remains In Stock On Mass Update
1 parent 51b53be commit 1ca6ef5

File tree

7 files changed

+169
-139
lines changed

7 files changed

+169
-139
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<!-- Update Product Name and Description attribute -->
12+
<actionGroup name="AdminMassUpdateProductQtyAndStockStatusActionGroup">
13+
<arguments>
14+
<argument name="attributes"/>
15+
</arguments>
16+
<click selector="{{AdminProductGridSection.productGridCheckboxOnRow('1')}}" stepKey="clickCheckbox"/>
17+
<click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickDropdown"/>
18+
<click selector="{{AdminProductGridSection.bulkActionOption('Update attributes')}}" stepKey="clickOption"/>
19+
<waitForPageLoad stepKey="waitForUploadPage"/>
20+
<seeInCurrentUrl url="{{ProductAttributesEditPage.url}}" stepKey="seeAttributePageEditUrl"/>
21+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.inventory}}" stepKey="openInvetoryTab"/>
22+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.changeQty}}" stepKey="uncheckChangeQty"/>
23+
<fillField selector="{{AdminUpdateAttributesAdvancedInventorySection.qty}}" userInput="{{attributes.qty}}" stepKey="fillFieldName"/>
24+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.changeStockAvailability}}" stepKey="uncheckChangeStockAvailability"/>
25+
<selectOption selector="{{AdminUpdateAttributesAdvancedInventorySection.stockAvailability}}" userInput="{{attributes.stockAvailability}}" stepKey="selectStatus"/>
26+
<click selector="{{AdminUpdateAttributesSection.saveButton}}" stepKey="save"/>
27+
<waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitVisibleSuccessMessage"/>
28+
<see selector="{{AdminMessagesSection.success}}" userInput="Message is added to queue" stepKey="seeSuccessMessage"/>
29+
</actionGroup>
30+
</actionGroups>

app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMassUpdateData.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,12 @@
1212
<data key="name" unique="suffix">New Bundle Product Name</data>
1313
<data key="description" unique="suffix">This is the description</data>
1414
</entity>
15+
<entity name="UpdateAttributeQtyAndStockToInStock" type="productAttributeMassUpdate">
16+
<data key="qty">10</data>
17+
<data key="stockAvailability">In Stock</data>
18+
</entity>
19+
<entity name="UpdateAttributeQtyAndStockToOutOfStock" type="productAttributeMassUpdate">
20+
<data key="qty">0</data>
21+
<data key="stockAvailability">Out of Stock</data>
22+
</entity>
1523
</entities>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
10+
<section name="AdminUpdateAttributesAdvancedInventorySection">
11+
<element name="inventory" type="button" selector="#attributes_update_tabs_inventory"/>
12+
<element name="changeQty" type="checkbox" selector="#inventory_qty_checkbox"/>
13+
<element name="qty" type="input" selector="#inventory_qty"/>
14+
<element name="changeStockAvailability" type="checkbox" selector="#inventory_stock_availability_checkbox"/>
15+
<element name="stockAvailability" type="select" selector="//select[@name='inventory[is_in_stock]']"/>
16+
</section>
17+
</sections>

app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
*/
66
namespace Magento\CatalogInventory\Plugin;
77

8+
use Magento\Catalog\Api\ProductRepositoryInterface;
89
use Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save;
10+
use Magento\Catalog\Model\Product;
911
use Magento\CatalogInventory\Api\Data\StockItemInterface;
12+
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;
1013

1114
/**
12-
* MassUpdate product attribute.
15+
* Around plugin for MassUpdate product attribute via product grid.
16+
*
1317
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1418
*/
1519
class MassUpdateProductAttribute
@@ -49,6 +53,15 @@ class MassUpdateProductAttribute
4953
*/
5054
private $messageManager;
5155

56+
/**
57+
* @var ParentItemProcessorInterface[]
58+
*/
59+
private $parentItemProcessors;
60+
61+
/**
62+
* @var ProductRepositoryInterface
63+
*/
64+
private $productRepository;
5265
/**
5366
* @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor
5467
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
@@ -57,6 +70,8 @@ class MassUpdateProductAttribute
5770
* @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration
5871
* @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper
5972
* @param \Magento\Framework\Message\ManagerInterface $messageManager
73+
* @param ProductRepositoryInterface $productRepository
74+
* @param ParentItemProcessorInterface[] $parentItemProcessors
6075
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
6176
*/
6277
public function __construct(
@@ -66,7 +81,9 @@ public function __construct(
6681
\Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository,
6782
\Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration,
6883
\Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper,
69-
\Magento\Framework\Message\ManagerInterface $messageManager
84+
\Magento\Framework\Message\ManagerInterface $messageManager,
85+
ProductRepositoryInterface $productRepository,
86+
array $parentItemProcessors = []
7087
) {
7188
$this->stockIndexerProcessor = $stockIndexerProcessor;
7289
$this->dataObjectHelper = $dataObjectHelper;
@@ -75,6 +92,8 @@ public function __construct(
7592
$this->stockConfiguration = $stockConfiguration;
7693
$this->attributeHelper = $attributeHelper;
7794
$this->messageManager = $messageManager;
95+
$this->productRepository = $productRepository;
96+
$this->parentItemProcessors = $parentItemProcessors;
7897
}
7998

8099
/**
@@ -145,6 +164,7 @@ private function addConfigSettings($inventoryData)
145164
private function updateInventoryInProducts($productIds, $websiteId, $inventoryData): void
146165
{
147166
foreach ($productIds as $productId) {
167+
$product = $this->productRepository->getById($productId);
148168
$stockItemDo = $this->stockRegistry->getStockItem($productId, $websiteId);
149169
if (!$stockItemDo->getProductId()) {
150170
$inventoryData['product_id'] = $productId;
@@ -153,7 +173,21 @@ private function updateInventoryInProducts($productIds, $websiteId, $inventoryDa
153173
$this->dataObjectHelper->populateWithArray($stockItemDo, $inventoryData, StockItemInterface::class);
154174
$stockItemDo->setItemId($stockItemId);
155175
$this->stockItemRepository->save($stockItemDo);
176+
$this->processParents($product);
156177
}
157178
$this->stockIndexerProcessor->reindexList($productIds);
158179
}
180+
181+
/**
182+
* Process stock data for parent products
183+
*
184+
* @param Product $product
185+
* @return void
186+
*/
187+
private function processParents(Product $product): void
188+
{
189+
foreach ($this->parentItemProcessors as $processor) {
190+
$processor->process($product);
191+
}
192+
}
159193
}

app/code/Magento/GroupedProduct/Model/Inventory/ParentItemProcessor.php

Lines changed: 6 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -7,77 +7,26 @@
77

88
namespace Magento\GroupedProduct\Model\Inventory;
99

10-
use Magento\Catalog\Api\Data\ProductInterface;
11-
use Magento\Framework\EntityManager\MetadataPool;
12-
use Magento\GroupedProduct\Model\Product\Type\Grouped;
1310
use Magento\Catalog\Api\Data\ProductInterface as Product;
14-
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
15-
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
16-
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1711
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;
18-
use Magento\CatalogInventory\Api\Data\StockItemInterface;
19-
use Magento\GroupedProduct\Model\ResourceModel\Product\Link;
20-
use Magento\Framework\App\ResourceConnection;
2112

2213
/**
2314
* Process parent stock item for grouped product
2415
*/
2516
class ParentItemProcessor implements ParentItemProcessorInterface
2617
{
2718
/**
28-
* @var Grouped
19+
* @var ChangeParentStockStatus
2920
*/
30-
private $groupedType;
21+
private $changeParentStockStatus;
3122

3223
/**
33-
* @var StockItemRepositoryInterface
34-
*/
35-
private $stockItemRepository;
36-
37-
/**
38-
* @var StockConfigurationInterface
39-
*/
40-
private $stockConfiguration;
41-
42-
/**
43-
* @var StockItemCriteriaInterfaceFactory
44-
*/
45-
private $criteriaInterfaceFactory;
46-
47-
/**
48-
* Product metadata pool
49-
*
50-
* @var MetadataPool
51-
*/
52-
private $metadataPool;
53-
54-
/**
55-
* @var ResourceConnection
56-
*/
57-
private $resource;
58-
59-
/**
60-
* @param Grouped $groupedType
61-
* @param StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory
62-
* @param StockItemRepositoryInterface $stockItemRepository
63-
* @param StockConfigurationInterface $stockConfiguration
64-
* @param ResourceConnection $resource
65-
* @param MetadataPool $metadataPool
24+
* @param ChangeParentStockStatus $changeParentStockStatus
6625
*/
6726
public function __construct(
68-
Grouped $groupedType,
69-
StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory,
70-
StockItemRepositoryInterface $stockItemRepository,
71-
StockConfigurationInterface $stockConfiguration,
72-
ResourceConnection $resource,
73-
MetadataPool $metadataPool
27+
ChangeParentStockStatus $changeParentStockStatus
7428
) {
75-
$this->groupedType = $groupedType;
76-
$this->criteriaInterfaceFactory = $criteriaInterfaceFactory;
77-
$this->stockConfiguration = $stockConfiguration;
78-
$this->stockItemRepository = $stockItemRepository;
79-
$this->resource = $resource;
80-
$this->metadataPool = $metadataPool;
29+
$this->changeParentStockStatus = $changeParentStockStatus;
8130
}
8231

8332
/**
@@ -88,86 +37,6 @@ public function __construct(
8837
*/
8938
public function process(Product $product)
9039
{
91-
$parentIds = $this->getParentEntityIdsByChild($product->getId());
92-
foreach ($parentIds as $productId) {
93-
$this->processStockForParent((int)$productId);
94-
}
95-
}
96-
97-
/**
98-
* Change stock item for parent product depending on children stock items
99-
*
100-
* @param int $productId
101-
* @return void
102-
*/
103-
private function processStockForParent(int $productId)
104-
{
105-
$criteria = $this->criteriaInterfaceFactory->create();
106-
$criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId());
107-
$criteria->setProductsFilter($productId);
108-
$stockItemCollection = $this->stockItemRepository->getList($criteria);
109-
$allItems = $stockItemCollection->getItems();
110-
if (empty($allItems)) {
111-
return;
112-
}
113-
$parentStockItem = array_shift($allItems);
114-
$groupedChildrenIds = $this->groupedType->getChildrenIds($productId);
115-
$criteria->setProductsFilter($groupedChildrenIds);
116-
$stockItemCollection = $this->stockItemRepository->getList($criteria);
117-
$allItems = $stockItemCollection->getItems();
118-
119-
$groupedChildrenIsInStock = false;
120-
121-
foreach ($allItems as $childItem) {
122-
if ($childItem->getIsInStock() === true) {
123-
$groupedChildrenIsInStock = true;
124-
break;
125-
}
126-
}
127-
128-
if ($this->isNeedToUpdateParent($parentStockItem, $groupedChildrenIsInStock)) {
129-
$parentStockItem->setIsInStock($groupedChildrenIsInStock);
130-
$parentStockItem->setStockStatusChangedAuto(1);
131-
$this->stockItemRepository->save($parentStockItem);
132-
}
133-
}
134-
135-
/**
136-
* Check is parent item should be updated
137-
*
138-
* @param StockItemInterface $parentStockItem
139-
* @param bool $childrenIsInStock
140-
* @return bool
141-
*/
142-
private function isNeedToUpdateParent(StockItemInterface $parentStockItem, bool $childrenIsInStock): bool
143-
{
144-
return $parentStockItem->getIsInStock() !== $childrenIsInStock &&
145-
($childrenIsInStock === false || $parentStockItem->getStockStatusChangedAuto());
146-
}
147-
148-
/**
149-
* Retrieve parent ids array by child id
150-
*
151-
* @param int $childId
152-
* @return string[]
153-
*/
154-
private function getParentEntityIdsByChild($childId)
155-
{
156-
$select = $this->resource->getConnection()
157-
->select()
158-
->from(['l' => $this->resource->getTableName('catalog_product_link')], [])
159-
->join(
160-
['e' => $this->resource->getTableName('catalog_product_entity')],
161-
'e.' .
162-
$this->metadataPool->getMetadata(ProductInterface::class)->getLinkField() . ' = l.product_id',
163-
['e.entity_id']
164-
)
165-
->where('l.linked_product_id = ?', $childId)
166-
->where(
167-
'link_type_id = ?',
168-
Link::LINK_TYPE_GROUPED
169-
);
170-
171-
return $this->resource->getConnection()->fetchCol($select);
40+
$this->changeParentStockStatus->execute((int)$product->getId());
17241
}
17342
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="UpdateStockStatusGroupedProductTest">
12+
<annotations>
13+
<features value="GroupedProduct"/>
14+
<stories value="Create/Edit grouped product in Admin"/>
15+
<title value="Stock status of grouped product after changing quantity of child product should be changed"/>
16+
<description value="Change stock of grouped product after changing quantity of child product"/>
17+
<severity value="MAJOR"/>
18+
<testCaseId value="MC-38057"/>
19+
<useCaseId value="MC-37718"/>
20+
<group value="GroupedProduct"/>
21+
</annotations>
22+
<before>
23+
<!--Create simple and grouped product-->
24+
<createData entity="SimpleProduct2" stepKey="createFirstSimpleProduct"/>
25+
<createData entity="ApiGroupedProduct" stepKey="createGroupedProduct"/>
26+
<createData entity="OneSimpleProductLink" stepKey="addProductOne">
27+
<requiredEntity createDataKey="createGroupedProduct"/>
28+
<requiredEntity createDataKey="createFirstSimpleProduct"/>
29+
</createData>
30+
<magentoCron stepKey="runCronIndex" groups="index"/>
31+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
32+
</before>
33+
<after>
34+
<!--Delete created data-->
35+
<deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/>
36+
<deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/>
37+
<!--Admin logout-->
38+
<actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/>
39+
</after>
40+
<!--1.Open product grid page and choose "Update attributes" and set product stock status to "Out of Stock"-->
41+
<amOnPage url="{{AdminProductIndexPage.url}}" stepKey="openProductGridFirstTime"/>
42+
<waitForPageLoad stepKey="waitForProductGridFirstTime"/>
43+
<actionGroup ref="AdminMassUpdateProductQtyAndStockStatusActionGroup" stepKey="setProductToOutOfStock">
44+
<argument name="attributes" value="UpdateAttributeQtyAndStockToOutOfStock"/>
45+
</actionGroup>
46+
<!--2.Run cron for updating stock status of parent product-->
47+
<magentoCron stepKey="runAllCronJobs"/>
48+
<!--3.Check stock status of grouped product. Stock status should be "Out of Stock"-->
49+
<actionGroup ref="AssertAdminProductStockStatusActionGroup" stepKey="checkProductOutOfStock">
50+
<argument name="productId" value="$$createGroupedProduct.id$$"/>
51+
<argument name="stockStatus" value="Out of Stock"/>
52+
</actionGroup>
53+
<!--4.Open product grid page choose "Update attributes" and set product stock status to "In Stock"-->
54+
<amOnPage url="{{AdminProductIndexPage.url}}" stepKey="openProductGridSecondTime"/>
55+
<waitForPageLoad stepKey="waitForProductGridSecondTime"/>
56+
<actionGroup ref="AdminMassUpdateProductQtyAndStockStatusActionGroup" stepKey="returnProductToInStock">
57+
<argument name="attributes" value="UpdateAttributeQtyAndStockToInStock"/>
58+
</actionGroup>
59+
<!--5.Check stock status of grouped product. Stock status should be "In Stock"-->
60+
<actionGroup ref="AssertAdminProductStockStatusActionGroup" stepKey="checkProductInStock">
61+
<argument name="productId" value="$$createGroupedProduct.id$$"/>
62+
<argument name="stockStatus" value="In Stock"/>
63+
</actionGroup>
64+
</test>
65+
</tests>

0 commit comments

Comments
 (0)