Skip to content

Commit b34ae19

Browse files
committed
Merge remote-tracking branch 'origin/MC-37249' into 2.4-develop-pr41
2 parents 21a6730 + 74ce657 commit b34ae19

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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\Framework\EntityManager\MetadataPool;
12+
use Magento\GroupedProduct\Model\Product\Type\Grouped;
13+
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;
17+
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;
21+
22+
/**
23+
* Process parent stock item for grouped product
24+
*/
25+
class ParentItemProcessor implements ParentItemProcessorInterface
26+
{
27+
/**
28+
* @var Grouped
29+
*/
30+
private $groupedType;
31+
32+
/**
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
66+
*/
67+
public function __construct(
68+
Grouped $groupedType,
69+
StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory,
70+
StockItemRepositoryInterface $stockItemRepository,
71+
StockConfigurationInterface $stockConfiguration,
72+
ResourceConnection $resource,
73+
MetadataPool $metadataPool
74+
) {
75+
$this->groupedType = $groupedType;
76+
$this->criteriaInterfaceFactory = $criteriaInterfaceFactory;
77+
$this->stockConfiguration = $stockConfiguration;
78+
$this->stockItemRepository = $stockItemRepository;
79+
$this->resource = $resource;
80+
$this->metadataPool = $metadataPool;
81+
}
82+
83+
/**
84+
* Process parent products
85+
*
86+
* @param Product $product
87+
* @return void
88+
*/
89+
public function process(Product $product)
90+
{
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);
172+
}
173+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,11 @@
105105
</argument>
106106
</arguments>
107107
</type>
108+
<type name="Magento\CatalogInventory\Observer\SaveInventoryDataObserver">
109+
<arguments>
110+
<argument name="parentItemProcessorPool" xsi:type="array">
111+
<item name="grouped" xsi:type="object"> Magento\GroupedProduct\Model\Inventory\ParentItemProcessor</item>
112+
</argument>
113+
</arguments>
114+
</type>
108115
</config>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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\ProductRepositoryInterface;
11+
use Magento\Catalog\Model\Product;
12+
use Magento\CatalogInventory\Api\StockRegistryInterface;
13+
use Magento\CatalogInventory\Model\Stock\StockItemRepository;
14+
use PHPUnit\Framework\TestCase;
15+
use Magento\TestFramework\Helper\Bootstrap;
16+
use Magento\Framework\ObjectManagerInterface;
17+
18+
/**
19+
* Test stock status parent product
20+
*/
21+
class ParentItemProcessorTest extends TestCase
22+
{
23+
/**
24+
* @var ObjectManagerInterface
25+
*/
26+
protected $objectManager;
27+
28+
/**
29+
* @inheritDoc
30+
*/
31+
protected function setUp(): void
32+
{
33+
$this->objectManager = Bootstrap::getObjectManager();
34+
}
35+
36+
/**
37+
* Test stock status parent product if children are out of stock
38+
*
39+
* @magentoDataFixture Magento/GroupedProduct/_files/product_grouped_with_simple_out_of_stock.php
40+
*
41+
* @return void
42+
*/
43+
public function testOutOfStockParentProduct(): void
44+
{
45+
$productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
46+
/** @var Product $product */
47+
$product = $productRepository->get('simple_100000001');
48+
$product->setStockData(['qty' => 0, 'is_in_stock' => 0]);
49+
$productRepository->save($product);
50+
/** @var StockItemRepository $stockItemRepository */
51+
$stockItemRepository = $this->objectManager->create(StockItemRepository::class);
52+
/** @var StockRegistryInterface $stockRegistry */
53+
$stockRegistry = $this->objectManager->create(StockRegistryInterface::class);
54+
$stockItem = $stockRegistry->getStockItemBySku('grouped');
55+
$stockItem = $stockItemRepository->get($stockItem->getItemId());
56+
57+
$this->assertEquals(false, $stockItem->getIsInStock());
58+
}
59+
}

0 commit comments

Comments
 (0)