Skip to content

Commit 9ecad58

Browse files
Merge pull request #361 from magento-l3/L3-PR-2023-09-06
L3 pr 2023 09 06
2 parents b74144e + a731891 commit 9ecad58

File tree

13 files changed

+900
-57
lines changed

13 files changed

+900
-57
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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\InventoryCatalog\Plugin\CatalogInventory\Observer\ParentItemProcessor;
9+
10+
use Closure;
11+
use Magento\Catalog\Api\Data\ProductInterface;
12+
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;
13+
use Magento\InventoryCatalogApi\Model\IsSingleSourceModeInterface;
14+
15+
/**
16+
* Process composite product stock status.
17+
*/
18+
class SkipParentItemProcessorOnMultipleSourceMode
19+
{
20+
/**
21+
* @var IsSingleSourceModeInterface
22+
*/
23+
private $isSingleSourceMode;
24+
25+
/**
26+
* @param IsSingleSourceModeInterface $isSingleSourceMode
27+
*/
28+
public function __construct(
29+
IsSingleSourceModeInterface $isSingleSourceMode
30+
) {
31+
$this->isSingleSourceMode = $isSingleSourceMode;
32+
}
33+
34+
/**
35+
* Process composite product stock status considering source mode.
36+
*
37+
* @param ParentItemProcessorInterface $subject
38+
* @param Closure $proceed
39+
* @param ProductInterface $product
40+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
41+
*/
42+
public function aroundProcess(
43+
ParentItemProcessorInterface $subject,
44+
Closure $proceed,
45+
ProductInterface $product
46+
): void {
47+
if ($this->isSingleSourceMode->execute()) {
48+
$proceed($product);
49+
}
50+
}
51+
}

InventoryCatalog/etc/di.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,7 @@
198198
<type name="Magento\Catalog\Model\ResourceModel\Product\Collection">
199199
<plugin name="outOfStockSorting" type="Magento\InventoryCatalog\Plugin\Catalog\Model\ResourceModel\Product\CollectionPlugin"/>
200200
</type>
201+
<type name="Magento\CatalogInventory\Observer\ParentItemProcessorInterface">
202+
<plugin name="skip_parent_stock_processors" type="Magento\InventoryCatalog\Plugin\CatalogInventory\Observer\ParentItemProcessor\SkipParentItemProcessorOnMultipleSourceMode"/>
203+
</type>
201204
</config>
Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,63 +5,68 @@
55
*/
66
declare(strict_types=1);
77

8-
namespace Magento\InventoryConfigurableProduct\Plugin\CatalogInventory\Observer\ParentItemProcessor;
8+
namespace Magento\InventoryConfigurableProduct\Plugin\Model;
99

10-
use Closure;
11-
use Magento\Catalog\Api\Data\ProductInterface;
1210
use Magento\CatalogInventory\Api\StockRegistryInterface;
13-
use Magento\CatalogInventory\Observer\ParentItemProcessorInterface;
1411
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
12+
use Magento\Framework\Exception\NoSuchEntityException;
13+
use Magento\Inventory\Model\SourceItem\Command\Handler\SourceItemsSaveHandler;
14+
use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface;
1515
use Magento\InventoryCatalogApi\Model\GetSkusByProductIdsInterface;
1616
use Magento\InventoryCatalogApi\Model\IsSingleSourceModeInterface;
1717
use Magento\InventoryIndexer\Model\ResourceModel\UpdateLegacyStockStatus;
1818

19-
/**
20-
* Process configurable product stock status.
21-
*/
22-
class AdaptParentItemProcessorPlugin
19+
class UpdateParentStockStatus
2320
{
2421
/**
2522
* @var IsSingleSourceModeInterface
2623
*/
27-
private $isSingleSourceMode;
24+
private IsSingleSourceModeInterface $isSingleSourceMode;
2825

2926
/**
3027
* @var GetSkusByProductIdsInterface
3128
*/
32-
private $getSkusByProductIds;
29+
private GetSkusByProductIdsInterface $getSkusByProductIds;
30+
31+
/**
32+
* @var GetProductIdsBySkusInterface
33+
*/
34+
private GetProductIdsBySkusInterface $getProductIdsBySkus;
3335

3436
/**
3537
* @var Configurable
3638
*/
37-
private $configurableType;
39+
private Configurable $configurableType;
3840

3941
/**
4042
* @var UpdateLegacyStockStatus
4143
*/
42-
private $updateLegacyStockStatus;
44+
private UpdateLegacyStockStatus $updateLegacyStockStatus;
4345

4446
/**
4547
* @var StockRegistryInterface
4648
*/
47-
private $stockRegistry;
49+
private StockRegistryInterface $stockRegistry;
4850

4951
/**
5052
* @param IsSingleSourceModeInterface $isSingleSourceMode
5153
* @param GetSkusByProductIdsInterface $getSkusByProductIds
54+
* @param GetProductIdsBySkusInterface $getProductIdsBySkus
5255
* @param Configurable $configurableType
5356
* @param UpdateLegacyStockStatus $updateLegacyStockStatus
5457
* @param StockRegistryInterface $stockRegistry
5558
*/
5659
public function __construct(
5760
IsSingleSourceModeInterface $isSingleSourceMode,
5861
GetSkusByProductIdsInterface $getSkusByProductIds,
62+
GetProductIdsBySkusInterface $getProductIdsBySkus,
5963
Configurable $configurableType,
6064
UpdateLegacyStockStatus $updateLegacyStockStatus,
6165
StockRegistryInterface $stockRegistry
6266
) {
6367
$this->isSingleSourceMode = $isSingleSourceMode;
6468
$this->getSkusByProductIds = $getSkusByProductIds;
69+
$this->getProductIdsBySkus = $getProductIdsBySkus;
6570
$this->configurableType = $configurableType;
6671
$this->updateLegacyStockStatus = $updateLegacyStockStatus;
6772
$this->stockRegistry = $stockRegistry;
@@ -70,20 +75,21 @@ public function __construct(
7075
/**
7176
* Process configurable product stock status considering source mode.
7277
*
73-
* @param ParentItemProcessorInterface $subject
74-
* @param Closure $proceed
75-
* @param ProductInterface $product
78+
* @param SourceItemsSaveHandler $subject
79+
* @param void $result
80+
* @param array $sourceItems
81+
* @return void
82+
* @throws NoSuchEntityException
7683
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
7784
*/
78-
public function aroundProcess(
79-
ParentItemProcessorInterface $subject,
80-
Closure $proceed,
81-
ProductInterface $product
85+
public function afterExecute(
86+
SourceItemsSaveHandler $subject,
87+
$result,
88+
array $sourceItems
8289
): void {
83-
if ($this->isSingleSourceMode->execute()) {
84-
$proceed($product);
85-
} else {
86-
$parentIds = $this->configurableType->getParentIdsByChild($product->getId());
90+
if (!$this->isSingleSourceMode->execute()) {
91+
$productIds = $this->getProductIds($sourceItems);
92+
$parentIds = $this->configurableType->getParentIdsByChild($productIds);
8793
$skus = $this->getSkusByProductIds->execute($parentIds);
8894

8995
$dataForUpdate = [];
@@ -93,7 +99,31 @@ public function aroundProcess(
9399
$dataForUpdate[$skus[$parentId]] = true;
94100
}
95101
}
96-
$this->updateLegacyStockStatus->execute($dataForUpdate);
102+
if (count($dataForUpdate)) {
103+
$this->updateLegacyStockStatus->execute($dataForUpdate);
104+
}
105+
}
106+
}
107+
108+
/**
109+
* Get product ids
110+
*
111+
* @param array $sourceItems
112+
* @return array
113+
*/
114+
private function getProductIds(array $sourceItems): array
115+
{
116+
$productIds = [];
117+
foreach ($sourceItems as $sourceItem) {
118+
if ($sourceItem->getStatus()) {
119+
try {
120+
$sku = $sourceItem->getSku();
121+
$productIds[$sku] ??= (int) $this->getProductIdsBySkus->execute([$sku])[$sku];
122+
} catch (NoSuchEntityException $e) {
123+
continue;
124+
}
125+
}
97126
}
127+
return $productIds;
98128
}
99129
}

InventoryConfigurableProduct/Test/Api/ConfigurableProductShouldBeInStockWhenChildProductInStockTest.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,31 @@
88
namespace Magento\InventoryConfigurableProduct\Test\Api;
99

1010
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
12+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
13+
use Magento\Catalog\Test\Fixture\Product;
1114
use Magento\CatalogInventory\Api\StockRegistryInterface;
15+
use Magento\CatalogInventory\Helper\Stock;
16+
use Magento\ConfigurableProduct\Test\Fixture\Attribute;
17+
use Magento\ConfigurableProduct\Test\Fixture\Product as ConfigurableProductFixture;
1218
use Magento\Framework\Api\SearchCriteria;
1319
use Magento\Framework\ObjectManagerInterface;
1420
use Magento\Framework\Webapi\Rest\Request;
1521
use Magento\InventoryApi\Api\Data\SourceItemInterface;
22+
use Magento\InventoryApi\Test\Fixture\Source;
1623
use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface;
1724
use Magento\Store\Model\StoreManagerInterface;
25+
use Magento\TestFramework\Fixture\AppArea;
26+
use Magento\TestFramework\Fixture\DataFixture;
27+
use Magento\TestFramework\Fixture\DataFixtureStorage;
28+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
1829
use Magento\TestFramework\Helper\Bootstrap;
1930
use Magento\TestFramework\TestCase\WebapiAbstract;
2031

2132
/**
2233
* Test validation on add source to child product of configurable product.
34+
*
35+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2336
*/
2437
class ConfigurableProductShouldBeInStockWhenChildProductInStockTest extends WebapiAbstract
2538
{
@@ -60,6 +73,11 @@ class ConfigurableProductShouldBeInStockWhenChildProductInStockTest extends Weba
6073
*/
6174
private $storeCodeBefore;
6275

76+
/**
77+
* @var DataFixtureStorage
78+
*/
79+
private $fixtures;
80+
6381
/**
6482
* @var array[]
6583
*/
@@ -82,6 +100,12 @@ class ConfigurableProductShouldBeInStockWhenChildProductInStockTest extends Weba
82100
SourceItemInterface::QUANTITY => 0,
83101
SourceItemInterface::STATUS => SourceItemInterface::STATUS_OUT_OF_STOCK,
84102
],
103+
[
104+
SourceItemInterface::SOURCE_CODE => 'default',
105+
SourceItemInterface::SKU => self::CONFIGURABLE_CHILD_PRODUCT_SKU2,
106+
SourceItemInterface::QUANTITY => 0,
107+
SourceItemInterface::STATUS => SourceItemInterface::STATUS_OUT_OF_STOCK,
108+
],
85109
];
86110
/**
87111
* @inheritdoc
@@ -94,6 +118,7 @@ protected function setUp(): void
94118
$this->getProductIdsBySkus = $this->objectManager->get(GetProductIdsBySkusInterface::class);
95119
$this->storeManager = $this->objectManager->get(StoreManagerInterface::class);
96120
$this->storeCodeBefore = $this->storeManager->getStore()->getCode();
121+
$this->fixtures = DataFixtureStorageManager::getStorage();
97122
}
98123

99124
protected function tearDown(): void
@@ -130,6 +155,70 @@ public function testConfigurableProductIsInStockAfterSave()
130155
self::assertEquals(0, $actualData['items'][0]['quantity']);
131156
}
132157

158+
/**
159+
* Test if configurable product back in stock if child product is in stock again
160+
*
161+
*/
162+
#[
163+
AppArea('frontend'),
164+
DataFixture(Source::class, as: 'src'),
165+
DataFixture(Attribute::class, ['options' => [['label' => 'option', 'sort_order' => 0]]], as:'attribute'),
166+
DataFixture(Product::class, as: 'simple'),
167+
DataFixture(
168+
ConfigurableProductFixture::class,
169+
['_options' => ['$attribute$'], '_links' => ['$simple$']],
170+
as: 'configurable'
171+
)
172+
]
173+
public function testConfigurableProductIsInStockOnDefaultSourceAfterSave()
174+
{
175+
$simpleProduct = $this->fixtures->get('simple');
176+
$configurableProduct = $this->fixtures->get('configurable');
177+
178+
$collection = $this->getLayerProductCollection($configurableProduct->getSku());
179+
self::assertEquals(1, $collection->count());
180+
181+
$this->addSourceItems([
182+
[
183+
SourceItemInterface::SOURCE_CODE => 'default',
184+
SourceItemInterface::SKU => $simpleProduct->getSku(),
185+
SourceItemInterface::QUANTITY => 0,
186+
SourceItemInterface::STATUS => SourceItemInterface::STATUS_OUT_OF_STOCK
187+
]
188+
]);
189+
190+
$collection = $this->getLayerProductCollection($configurableProduct->getSku());
191+
self::assertEquals(0, $collection->count());
192+
193+
$this->addSourceItems([
194+
[
195+
SourceItemInterface::SOURCE_CODE => 'default',
196+
SourceItemInterface::SKU => $simpleProduct->getSku(),
197+
SourceItemInterface::QUANTITY => 100,
198+
SourceItemInterface::STATUS => SourceItemInterface::STATUS_IN_STOCK,
199+
]
200+
]);
201+
202+
$collection = $this->getLayerProductCollection($configurableProduct->getSku());
203+
self::assertEquals(1, $collection->count());
204+
}
205+
206+
/**
207+
* Get layer product collection for frontend
208+
*
209+
* @param string $sku
210+
* @return Collection
211+
*/
212+
private function getLayerProductCollection(string $sku): Collection
213+
{
214+
$collection = $this->objectManager->get(CollectionFactory::class)->create();
215+
$collection->addAttributeToFilter('sku', $sku)
216+
->addMinimalPrice()
217+
->addFinalPrice()
218+
->addTaxPercents();
219+
return $collection;
220+
}
221+
133222
/**
134223
* Add source items data for the configurable product
135224
*
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@
55
*/
66
declare(strict_types=1);
77

8-
namespace Magento\InventoryConfigurableProduct\Test\Integration\CatalogInventory\Observer\ParentItemProcessor;
8+
namespace Magento\InventoryConfigurableProduct\Test\Integration\CatalogInventory;
99

1010
use Magento\Catalog\Api\ProductRepositoryInterface;
11-
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
12-
use Magento\InventorySalesApi\Api\AreProductsSalableInterface;
1311
use Magento\Catalog\Test\Fixture\Product as ProductFixture;
12+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
1413
use Magento\ConfigurableProduct\Test\Fixture\Attribute as AttributeFixture;
1514
use Magento\ConfigurableProduct\Test\Fixture\Product as ConfigurableProductFixture;
1615
use Magento\InventoryApi\Test\Fixture\SourceItem as SourceItemFixture;
16+
use Magento\InventorySalesApi\Api\AreProductsSalableInterface;
1717
use Magento\TestFramework\Fixture\DataFixture;
1818
use Magento\TestFramework\Fixture\DataFixtureStorage;
1919
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
2020
use Magento\TestFramework\Fixture\DbIsolation;
2121
use Magento\TestFramework\Helper\Bootstrap;
2222
use PHPUnit\Framework\TestCase;
2323

24-
class AdaptParentItemProcessorPluginTest extends TestCase
24+
class ConfigurableProductShouldBeInStockWhenChildProductInStockTest extends TestCase
2525
{
2626
/**
2727
* @var DataFixtureStorage

InventoryConfigurableProduct/etc/di.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@
2424
<argument name="baseSelectProcessor" xsi:type="object">Magento\InventoryConfigurableProduct\Pricing\Price\LowestPriceOptionsProvider\StockStatusBaseSelectProcessor</argument>
2525
</arguments>
2626
</type>
27-
<type name="Magento\CatalogInventory\Observer\ParentItemProcessorInterface">
28-
<plugin name="adapt_parent_stock_processor" type="Magento\InventoryConfigurableProduct\Plugin\CatalogInventory\Observer\ParentItemProcessor\AdaptParentItemProcessorPlugin"/>
29-
</type>
3027
<type name="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsSelectBuilder">
3128
<arguments>
3229
<argument name="selectProcessor" xsi:type="object">Magento\InventoryConfigurableProduct\Pricing\Price\Indexer\BaseStockStatusSelectProcessor</argument>
@@ -56,4 +53,7 @@
5653
</argument>
5754
</arguments>
5855
</type>
56+
<type name="Magento\Inventory\Model\SourceItem\Command\Handler\SourceItemsSaveHandler">
57+
<plugin name="update_parent_stock_status" type="Magento\InventoryConfigurableProduct\Plugin\Model\UpdateParentStockStatus"/>
58+
</type>
5959
</config>

0 commit comments

Comments
 (0)