Skip to content

Commit 7d1761a

Browse files
committed
Merge branch 'MAGETWO-99941' of https://github.com/magento-tango/magento2ce into MPI_PR_2019_06_21
2 parents bcfe16f + 3f99d89 commit 7d1761a

File tree

6 files changed

+314
-1
lines changed

6 files changed

+314
-1
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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\Observer;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface as Product;
11+
12+
/**
13+
* Interface for processing parent items of complex product types
14+
*/
15+
interface ParentItemProcessorInterface
16+
{
17+
/**
18+
* Process stock for parent items
19+
*
20+
* @param Product $product
21+
* @return void
22+
*/
23+
public function process(Product $product);
24+
}

app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
use Magento\CatalogInventory\Api\StockRegistryInterface;
1414
use Magento\CatalogInventory\Model\StockItemValidator;
1515
use Magento\Framework\Event\Observer as EventObserver;
16+
use Magento\Framework\Exception\LocalizedException;
17+
use Magento\Framework\Exception\NoSuchEntityException;
1618

1719
/**
1820
* Saves stock data from a product to the Stock Item
@@ -39,6 +41,11 @@ class SaveInventoryDataObserver implements ObserverInterface
3941
*/
4042
private $stockItemValidator;
4143

44+
/**
45+
* @var ParentItemProcessorInterface[]
46+
*/
47+
private $parentItemProcessorPool;
48+
4249
/**
4350
* @var array
4451
*/
@@ -77,15 +84,18 @@ class SaveInventoryDataObserver implements ObserverInterface
7784
* @param StockConfigurationInterface $stockConfiguration
7885
* @param StockRegistryInterface $stockRegistry
7986
* @param StockItemValidator $stockItemValidator
87+
* @param ParentItemProcessorInterface[] $parentItemProcessorPool
8088
*/
8189
public function __construct(
8290
StockConfigurationInterface $stockConfiguration,
8391
StockRegistryInterface $stockRegistry,
84-
StockItemValidator $stockItemValidator = null
92+
StockItemValidator $stockItemValidator = null,
93+
array $parentItemProcessorPool = []
8594
) {
8695
$this->stockConfiguration = $stockConfiguration;
8796
$this->stockRegistry = $stockRegistry;
8897
$this->stockItemValidator = $stockItemValidator ?: ObjectManager::getInstance()->get(StockItemValidator::class);
98+
$this->parentItemProcessorPool = $parentItemProcessorPool;
8999
}
90100

91101
/**
@@ -96,10 +106,15 @@ public function __construct(
96106
*
97107
* @param EventObserver $observer
98108
* @return void
109+
* @throws LocalizedException
110+
* @throws NoSuchEntityException
99111
*/
100112
public function execute(EventObserver $observer)
101113
{
114+
/** @var Product $product */
102115
$product = $observer->getEvent()->getProduct();
116+
117+
/** @var Item $stockItem */
103118
$stockItem = $this->getStockItemToBeUpdated($product);
104119

105120
if ($product->getStockData() !== null) {
@@ -108,6 +123,7 @@ public function execute(EventObserver $observer)
108123
}
109124
$this->stockItemValidator->validate($product, $stockItem);
110125
$this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem);
126+
$this->processParents($product);
111127
}
112128

113129
/**
@@ -156,4 +172,17 @@ private function getStockData(Product $product)
156172
}
157173
return $stockData;
158174
}
175+
176+
/**
177+
* Process stock data for parent products
178+
*
179+
* @param Product $product
180+
* @return void
181+
*/
182+
private function processParents(Product $product)
183+
{
184+
foreach ($this->parentItemProcessorPool as $processor) {
185+
$processor->process($product);
186+
}
187+
}
159188
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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\ConfigurableProduct\Model\Inventory;
9+
10+
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
11+
use Magento\Catalog\Api\Data\ProductInterface as Product;
12+
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
13+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
14+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
15+
use Magento\CatalogInventory\Api\Data\StockItemInterface;
16+
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;
17+
18+
/**
19+
* Process parent stock item
20+
*/
21+
class ParentItemProcessor implements ParentItemProcessorInterface
22+
{
23+
/**
24+
* @var Configurable
25+
*/
26+
private $configurableType;
27+
28+
/**
29+
* @var StockItemCriteriaInterfaceFactory
30+
*/
31+
private $criteriaInterfaceFactory;
32+
33+
/**
34+
* @var StockItemRepositoryInterface
35+
*/
36+
private $stockItemRepository;
37+
38+
/**
39+
* @var StockConfigurationInterface
40+
*/
41+
private $stockConfiguration;
42+
43+
/**
44+
* @param Configurable $configurableType
45+
* @param StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory
46+
* @param StockItemRepositoryInterface $stockItemRepository
47+
* @param StockConfigurationInterface $stockConfiguration
48+
*/
49+
public function __construct(
50+
Configurable $configurableType,
51+
StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory,
52+
StockItemRepositoryInterface $stockItemRepository,
53+
StockConfigurationInterface $stockConfiguration
54+
) {
55+
$this->configurableType = $configurableType;
56+
$this->criteriaInterfaceFactory = $criteriaInterfaceFactory;
57+
$this->stockItemRepository = $stockItemRepository;
58+
$this->stockConfiguration = $stockConfiguration;
59+
}
60+
61+
/**
62+
* Process parent products
63+
*
64+
* @param Product $product
65+
* @return void
66+
*/
67+
public function process(Product $product)
68+
{
69+
$parentIds = $this->configurableType->getParentIdsByChild($product->getId());
70+
foreach ($parentIds as $productId) {
71+
$this->processStockForParent((int)$productId);
72+
}
73+
}
74+
75+
/**
76+
* Change stock item for parent product depending on children stock items
77+
*
78+
* @param int $productId
79+
* @return void
80+
*/
81+
private function processStockForParent(int $productId)
82+
{
83+
$criteria = $this->criteriaInterfaceFactory->create();
84+
$criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId());
85+
86+
$criteria->setProductsFilter($productId);
87+
$stockItemCollection = $this->stockItemRepository->getList($criteria);
88+
$allItems = $stockItemCollection->getItems();
89+
if (empty($allItems)) {
90+
return;
91+
}
92+
$parentStockItem = array_shift($allItems);
93+
94+
$childrenIds = $this->configurableType->getChildrenIds($productId);
95+
$criteria->setProductsFilter($childrenIds);
96+
$stockItemCollection = $this->stockItemRepository->getList($criteria);
97+
$allItems = $stockItemCollection->getItems();
98+
99+
$childrenIsInStock = false;
100+
101+
foreach ($allItems as $childItem) {
102+
if ($childItem->getIsInStock() === true) {
103+
$childrenIsInStock = true;
104+
break;
105+
}
106+
}
107+
108+
if ($this->isNeedToUpdateParent($parentStockItem, $childrenIsInStock)) {
109+
$parentStockItem->setIsInStock($childrenIsInStock);
110+
$parentStockItem->setStockStatusChangedAuto(1);
111+
$this->stockItemRepository->save($parentStockItem);
112+
}
113+
}
114+
115+
/**
116+
* Check is parent item should be updated
117+
*
118+
* @param StockItemInterface $parentStockItem
119+
* @param bool $childrenIsInStock
120+
* @return bool
121+
*/
122+
private function isNeedToUpdateParent(
123+
StockItemInterface $parentStockItem,
124+
bool $childrenIsInStock
125+
): bool {
126+
return $parentStockItem->getIsInStock() !== $childrenIsInStock &&
127+
($childrenIsInStock === false || $parentStockItem->getStockStatusChangedAuto());
128+
}
129+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,11 @@
255255
</argument>
256256
</arguments>
257257
</type>
258+
<type name="Magento\CatalogInventory\Observer\SaveInventoryDataObserver">
259+
<arguments>
260+
<argument name="parentItemProcessorPool" xsi:type="array">
261+
<item name="configurable" xsi:type="object"> Magento\ConfigurableProduct\Model\Inventory\ParentItemProcessor</item>
262+
</argument>
263+
</arguments>
264+
</type>
258265
</config>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\CatalogInventory\Observer;
10+
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
use PHPUnit\Framework\TestCase;
13+
use Magento\Catalog\Api\ProductRepositoryInterface;
14+
use Magento\Catalog\Api\Data\ProductExtensionInterface;
15+
use Magento\Catalog\Api\Data\ProductInterface;
16+
use Magento\CatalogInventory\Api\Data\StockItemInterface;
17+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
18+
use Magento\Framework\Exception\NoSuchEntityException;
19+
use Magento\Framework\Exception\InputException;
20+
use Magento\Framework\Exception\StateException;
21+
use Magento\Framework\Exception\CouldNotSaveException;
22+
23+
/**
24+
* Test for SaveInventoryDataObserver
25+
*/
26+
class SaveInventoryDataObserverTest extends TestCase
27+
{
28+
/**
29+
* @var ProductRepositoryInterface
30+
*/
31+
private $productRepository;
32+
33+
/**
34+
* @var StockItemRepositoryInterface
35+
*/
36+
private $stockItemRepository;
37+
38+
/**
39+
* @inheritDoc
40+
*/
41+
protected function setUp()
42+
{
43+
$this->productRepository = Bootstrap::getObjectManager()
44+
->get(ProductRepositoryInterface::class);
45+
$this->stockItemRepository = Bootstrap::getObjectManager()
46+
->get(StockItemRepositoryInterface::class);
47+
}
48+
49+
/**
50+
* Check that parent product will be out of stock
51+
*
52+
* @magentoAppArea adminhtml
53+
* @magentoAppIsolation enabled
54+
* @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
55+
* @magentoDataFixture Magento/CatalogInventory/_files/configurable_options_with_low_stock.php
56+
* @throws NoSuchEntityException
57+
* @throws InputException
58+
* @throws StateException
59+
* @throws CouldNotSaveException
60+
* @return void
61+
*/
62+
public function testAutoChangingIsInStockForParent()
63+
{
64+
/** @var ProductInterface $product */
65+
$product = $this->productRepository->get('simple_10');
66+
67+
/** @var ProductExtensionInterface $attributes*/
68+
$attributes = $product->getExtensionAttributes();
69+
70+
/** @var StockItemInterface $stockItem */
71+
$stockItem = $attributes->getStockItem();
72+
$stockItem->setQty(0);
73+
$stockItem->setIsInStock(false);
74+
$attributes->setStockItem($stockItem);
75+
$product->setExtensionAttributes($attributes);
76+
$this->productRepository->save($product);
77+
78+
/** @var ProductInterface $product */
79+
$parentProduct = $this->productRepository->get('configurable');
80+
81+
$parentProductStockItem = $this->stockItemRepository->get(
82+
$parentProduct->getExtensionAttributes()->getStockItem()->getItemId()
83+
);
84+
$this->assertSame(false, $parentProductStockItem->getIsInStock());
85+
}
86+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Data\ProductInterface;
9+
use Magento\CatalogInventory\Api\Data\StockItemInterface;
10+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
use Magento\Catalog\Api\ProductRepositoryInterface;
13+
14+
$objectManager = Bootstrap::getObjectManager();
15+
16+
/** @var ProductRepositoryInterface $productRepository */
17+
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
18+
19+
/** @var StockItemRepositoryInterface $stockItemRepository */
20+
$stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class);
21+
22+
/** @var ProductInterface $product */
23+
$product = $productRepository->get('simple_10');
24+
25+
/** @var StockItemInterface $stockItem */
26+
$stockItem = $product->getExtensionAttributes()->getStockItem();
27+
$stockItem->setIsInStock(true)
28+
->setQty(1);
29+
$stockItemRepository->save($stockItem);
30+
31+
/** @var ProductInterface $product */
32+
$product = $productRepository->get('simple_20');
33+
34+
/** @var StockItemInterface $stockItem */
35+
$stockItem = $product->getExtensionAttributes()->getStockItem();
36+
$stockItem->setIsInStock(false)
37+
->setQty(0);
38+
$stockItemRepository->save($stockItem);

0 commit comments

Comments
 (0)