Skip to content

Commit 6fa94aa

Browse files
committed
Merge remote-tracking branch 'origin/MC-37718' into 2.4-develop-pr45
2 parents ae5eb40 + e6112b2 commit 6fa94aa

File tree

8 files changed

+355
-139
lines changed

8 files changed

+355
-139
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
<argument name="product"/>
16+
</arguments>
17+
<!--Filter product in product grid-->
18+
<amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageFirstTime"/>
19+
<waitForPageLoad stepKey="waitForProductGridPageLoad"/>
20+
<conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/>
21+
<click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/>
22+
<fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{product.name}}" stepKey="fillProductNameFilter"/>
23+
<fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillProductSkuFilter"/>
24+
<selectOption selector="{{AdminProductGridFilterSection.typeFilter}}" userInput="{{product.type_id}}" stepKey="selectionProductType"/>
25+
<click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/>
26+
<!--Select first product from grid and open mass action-->
27+
<click selector="{{AdminProductGridSection.productGridCheckboxOnRow('1')}}" stepKey="clickCheckbox"/>
28+
<click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickDropdown"/>
29+
<click selector="{{AdminProductGridSection.bulkActionOption('Update attributes')}}" stepKey="clickOption"/>
30+
<waitForPageLoad stepKey="waitForUploadPage"/>
31+
<seeInCurrentUrl url="{{ProductAttributesEditPage.url}}" stepKey="seeAttributePageEditUrl"/>
32+
<!--Update inventory attributes and save-->
33+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.inventory}}" stepKey="openInvetoryTab"/>
34+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.changeQty}}" stepKey="uncheckChangeQty"/>
35+
<fillField selector="{{AdminUpdateAttributesAdvancedInventorySection.qty}}" userInput="{{attributes.qty}}" stepKey="fillFieldName"/>
36+
<click selector="{{AdminUpdateAttributesAdvancedInventorySection.changeStockAvailability}}" stepKey="uncheckChangeStockAvailability"/>
37+
<selectOption selector="{{AdminUpdateAttributesAdvancedInventorySection.stockAvailability}}" userInput="{{attributes.stockAvailability}}" stepKey="selectStatus"/>
38+
<click selector="{{AdminUpdateAttributesSection.saveButton}}" stepKey="save"/>
39+
<waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitVisibleSuccessMessage"/>
40+
<see selector="{{AdminMessagesSection.success}}" userInput="Message is added to queue" stepKey="seeSuccessMessage"/>
41+
<amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageSecondTime"/>
42+
<waitForPageLoad stepKey="waitForProductGridPage"/>
43+
<conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersAfterMassAction"/>
44+
</actionGroup>
45+
</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: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\CatalogInventory\Plugin;
79

10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
812
use Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save;
913
use Magento\CatalogInventory\Api\Data\StockItemInterface;
14+
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;
1015

1116
/**
12-
* MassUpdate product attribute.
17+
* Around plugin for MassUpdate product attribute via product grid.
18+
*
1319
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1420
*/
1521
class MassUpdateProductAttribute
@@ -49,6 +55,15 @@ class MassUpdateProductAttribute
4955
*/
5056
private $messageManager;
5157

58+
/**
59+
* @var ParentItemProcessorInterface[]
60+
*/
61+
private $parentItemProcessorPool;
62+
63+
/**
64+
* @var ProductRepositoryInterface
65+
*/
66+
private $productRepository;
5267
/**
5368
* @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor
5469
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
@@ -57,6 +72,8 @@ class MassUpdateProductAttribute
5772
* @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration
5873
* @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper
5974
* @param \Magento\Framework\Message\ManagerInterface $messageManager
75+
* @param ProductRepositoryInterface $productRepository
76+
* @param ParentItemProcessorInterface[] $parentItemProcessorPool
6077
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
6178
*/
6279
public function __construct(
@@ -66,7 +83,9 @@ public function __construct(
6683
\Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository,
6784
\Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration,
6885
\Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper,
69-
\Magento\Framework\Message\ManagerInterface $messageManager
86+
\Magento\Framework\Message\ManagerInterface $messageManager,
87+
ProductRepositoryInterface $productRepository,
88+
array $parentItemProcessorPool = []
7089
) {
7190
$this->stockIndexerProcessor = $stockIndexerProcessor;
7291
$this->dataObjectHelper = $dataObjectHelper;
@@ -75,6 +94,8 @@ public function __construct(
7594
$this->stockConfiguration = $stockConfiguration;
7695
$this->attributeHelper = $attributeHelper;
7796
$this->messageManager = $messageManager;
97+
$this->productRepository = $productRepository;
98+
$this->parentItemProcessorPool = $parentItemProcessorPool;
7899
}
79100

80101
/**
@@ -145,6 +166,7 @@ private function addConfigSettings($inventoryData)
145166
private function updateInventoryInProducts($productIds, $websiteId, $inventoryData): void
146167
{
147168
foreach ($productIds as $productId) {
169+
$product = $this->productRepository->getById($productId);
148170
$stockItemDo = $this->stockRegistry->getStockItem($productId, $websiteId);
149171
if (!$stockItemDo->getProductId()) {
150172
$inventoryData['product_id'] = $productId;
@@ -153,7 +175,21 @@ private function updateInventoryInProducts($productIds, $websiteId, $inventoryDa
153175
$this->dataObjectHelper->populateWithArray($stockItemDo, $inventoryData, StockItemInterface::class);
154176
$stockItemDo->setItemId($stockItemId);
155177
$this->stockItemRepository->save($stockItemDo);
178+
$this->processParents($product);
156179
}
157180
$this->stockIndexerProcessor->reindexList($productIds);
158181
}
182+
183+
/**
184+
* Process stock data for parent products
185+
*
186+
* @param ProductInterface $product
187+
* @return void
188+
*/
189+
private function processParents(ProductInterface $product): void
190+
{
191+
foreach ($this->parentItemProcessorPool as $processor) {
192+
$processor->process($product);
193+
}
194+
}
159195
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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\GroupedProduct\Model\Inventory;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\CatalogInventory\Api\Data\StockItemInterface;
12+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
13+
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
14+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
15+
use Magento\Framework\App\ResourceConnection;
16+
use Magento\Framework\EntityManager\MetadataPool;
17+
use Magento\GroupedProduct\Model\Product\Type\Grouped;
18+
use Magento\GroupedProduct\Model\ResourceModel\Product\Link;
19+
20+
/**
21+
* Change stock status of grouped product by child product id
22+
*/
23+
class ChangeParentStockStatus
24+
{
25+
/**
26+
* @var Grouped
27+
*/
28+
private $groupedType;
29+
30+
/**
31+
* @var StockItemRepositoryInterface
32+
*/
33+
private $stockItemRepository;
34+
35+
/**
36+
* @var StockConfigurationInterface
37+
*/
38+
private $stockConfiguration;
39+
40+
/**
41+
* @var StockItemCriteriaInterfaceFactory
42+
*/
43+
private $criteriaInterfaceFactory;
44+
45+
/**
46+
* @var ResourceConnection
47+
*/
48+
private $resource;
49+
50+
/**
51+
* Product metadata pool
52+
*
53+
* @var MetadataPool
54+
*/
55+
private $metadataPool;
56+
57+
/**
58+
* @param Grouped $groupedType
59+
* @param StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory
60+
* @param StockItemRepositoryInterface $stockItemRepository
61+
* @param StockConfigurationInterface $stockConfiguration
62+
* @param ResourceConnection $resource
63+
* @param MetadataPool $metadataPool
64+
*/
65+
public function __construct(
66+
Grouped $groupedType,
67+
StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory,
68+
StockItemRepositoryInterface $stockItemRepository,
69+
StockConfigurationInterface $stockConfiguration,
70+
ResourceConnection $resource,
71+
MetadataPool $metadataPool
72+
) {
73+
$this->groupedType = $groupedType;
74+
$this->criteriaInterfaceFactory = $criteriaInterfaceFactory;
75+
$this->stockConfiguration = $stockConfiguration;
76+
$this->stockItemRepository = $stockItemRepository;
77+
$this->resource = $resource;
78+
$this->metadataPool = $metadataPool;
79+
}
80+
81+
/**
82+
* Change stock item for parent product depending on children stock items
83+
*
84+
* @param int $productId
85+
* @return void
86+
*/
87+
public function execute(int $productId): void
88+
{
89+
$parentIds = $this->getParentEntityIdsByChild($productId);
90+
foreach ($parentIds as $productId) {
91+
$this->changeParentStockStatus((int)$productId);
92+
}
93+
}
94+
95+
/**
96+
* Change stock status of grouped product
97+
*
98+
* @param int $productId
99+
* @return void
100+
*/
101+
private function changeParentStockStatus(int $productId): void
102+
{
103+
$criteria = $this->criteriaInterfaceFactory->create();
104+
$criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId());
105+
$criteria->setProductsFilter($productId);
106+
$stockItemCollection = $this->stockItemRepository->getList($criteria);
107+
$allItems = $stockItemCollection->getItems();
108+
if (empty($allItems)) {
109+
return;
110+
}
111+
$parentStockItem = array_shift($allItems);
112+
$groupedChildrenIds = $this->groupedType->getChildrenIds($productId);
113+
$criteria->setProductsFilter($groupedChildrenIds);
114+
$stockItemCollection = $this->stockItemRepository->getList($criteria);
115+
$allItems = $stockItemCollection->getItems();
116+
117+
$groupedChildrenIsInStock = false;
118+
119+
foreach ($allItems as $childItem) {
120+
if ($childItem->getIsInStock() === true) {
121+
$groupedChildrenIsInStock = true;
122+
break;
123+
}
124+
}
125+
126+
if ($this->isNeedToUpdateParent($parentStockItem, $groupedChildrenIsInStock)) {
127+
$parentStockItem->setIsInStock($groupedChildrenIsInStock);
128+
$parentStockItem->setStockStatusChangedAuto(1);
129+
$this->stockItemRepository->save($parentStockItem);
130+
}
131+
}
132+
133+
/**
134+
* Check is parent item should be updated
135+
*
136+
* @param StockItemInterface $parentStockItem
137+
* @param bool $childrenIsInStock
138+
* @return bool
139+
*/
140+
private function isNeedToUpdateParent(StockItemInterface $parentStockItem, bool $childrenIsInStock): bool
141+
{
142+
return $parentStockItem->getIsInStock() !== $childrenIsInStock &&
143+
($childrenIsInStock === false || $parentStockItem->getStockStatusChangedAuto());
144+
}
145+
146+
/**
147+
* Retrieve parent ids array by child id
148+
*
149+
* @param int $childId
150+
* @return array
151+
*/
152+
private function getParentEntityIdsByChild(int $childId): array
153+
{
154+
$select = $this->resource->getConnection()
155+
->select()
156+
->from(['l' => $this->resource->getTableName('catalog_product_link')], [])
157+
->join(
158+
['e' => $this->resource->getTableName('catalog_product_entity')],
159+
'e.' .
160+
$this->metadataPool->getMetadata(ProductInterface::class)->getLinkField() . ' = l.product_id',
161+
['e.entity_id']
162+
)
163+
->where('l.linked_product_id = ?', $childId)
164+
->where(
165+
'link_type_id = ?',
166+
Link::LINK_TYPE_GROUPED
167+
);
168+
169+
return $this->resource->getConnection()->fetchCol($select);
170+
}
171+
}

0 commit comments

Comments
 (0)