Skip to content

Commit 6437db5

Browse files
authored
[Unsupported Products] Removing inventory requirement for bundle/group products (#491)
* Removing inventory check for unsupported products - these items in Magento do not have any inventory associated with them as they are logical groups, however stock status is still relevant as controls whether the items are displayed on the seller website. * Incorporating new unit tests and adding features for grouped product stock * Removing inventory check for unsupported products - these items in Magento do not have any inventory associated with them as they are logical groups, however stock status is still relevant as controls whether the items are displayed on the seller website. * Fixing review comments * Reformat * Fix test failure
1 parent 5af5f11 commit 6437db5

File tree

7 files changed

+635
-12
lines changed

7 files changed

+635
-12
lines changed

app/code/Meta/Catalog/Model/Product/Feed/Builder/Inventory.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
2727
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
2828

29-
class Inventory implements InventoryInterface
29+
class Inventory extends InventoryRequirements implements InventoryInterface
3030
{
3131
/**
3232
* @var StockItemRepositoryInterface
@@ -59,9 +59,9 @@ class Inventory implements InventoryInterface
5959
* @param SystemConfig $systemConfig
6060
*/
6161
public function __construct(
62-
StockItemRepositoryInterface $stockItemRepository,
62+
StockItemRepositoryInterface $stockItemRepository,
6363
StockItemCriteriaInterfaceFactory $stockItemCriteriaInterfaceFactory,
64-
SystemConfig $systemConfig
64+
SystemConfig $systemConfig
6565
) {
6666
$this->stockItemRepository = $stockItemRepository;
6767
$this->stockItemCriteriaInterfaceFactory = $stockItemCriteriaInterfaceFactory;
@@ -108,8 +108,9 @@ public function getAvailability(): string
108108
if ($inventory === self::UNMANAGED_STOCK_QTY) {
109109
return self::STATUS_IN_STOCK;
110110
}
111+
111112
return $this->productStock && $this->productStock->getIsInStock()
112-
&& ($inventory > 0)
113+
&& $this->meetsInventoryRequirementsToBeInStock($this->product)
113114
? self::STATUS_IN_STOCK : self::STATUS_OUT_OF_STOCK;
114115
}
115116

app/code/Meta/Catalog/Model/Product/Feed/Builder/InventoryInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* See the License for the specific language governing permissions and
1818
* limitations under the License.
1919
*/
20+
2021
namespace Meta\Catalog\Model\Product\Feed\Builder;
2122

2223
use Magento\Catalog\Model\Product;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Copyright (c) Meta Platforms, Inc. and affiliates.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace Meta\Catalog\Model\Product\Feed\Builder;
22+
23+
use Magento\Catalog\Model\Product;
24+
25+
abstract class InventoryRequirements implements InventoryInterface
26+
{
27+
/**
28+
* Validates if the item meets inventory requirements to be flagged as in stock.
29+
*
30+
* This will pass if either the item does not require inventory, or the available inventory is greater than zero.
31+
*
32+
* @param ?Product $product
33+
* @return bool
34+
*/
35+
public function meetsInventoryRequirementsToBeInStock(?Product $product): bool
36+
{
37+
if (!$product) {
38+
return false;
39+
}
40+
41+
if (!$this->isInventoryRequired($product)) {
42+
return true;
43+
}
44+
45+
return $this->getInventory() > 0;
46+
}
47+
48+
/**
49+
* Identifies if inventory is applicable and should be checked as part of generating item availability.
50+
* For some aggregate product types, such as grouped products and bundles, inventory isn't applicable.
51+
* Therefore, for these types of products, we should not incorporate inventory into stock status checks
52+
*
53+
* @param Product $product
54+
* @return bool
55+
*/
56+
private function isInventoryRequired(Product $product): bool
57+
{
58+
switch ($product->getTypeId()) {
59+
case 'bundle':
60+
case 'grouped':
61+
return false;
62+
default:
63+
return true;
64+
}
65+
}
66+
}

app/code/Meta/Catalog/Model/Product/Feed/Builder/MultiSourceInventory.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
use Magento\InventorySalesApi\Model\StockByWebsiteIdResolverInterface;
2828
use Meta\BusinessExtension\Model\System\Config as SystemConfig;
2929

30-
class MultiSourceInventory implements InventoryInterface
30+
class MultiSourceInventory extends InventoryRequirements implements InventoryInterface
3131
{
3232
/**
3333
* @var Product
@@ -77,11 +77,11 @@ class MultiSourceInventory implements InventoryInterface
7777
* @param GetIsManageStockForProduct $getIsManageStockForProduct
7878
*/
7979
public function __construct(
80-
IsProductSalableInterface $isProductSalableInterface,
81-
GetProductSalableQtyInterface $getProductSalableQtyInterface,
82-
SystemConfig $systemConfig,
80+
IsProductSalableInterface $isProductSalableInterface,
81+
GetProductSalableQtyInterface $getProductSalableQtyInterface,
82+
SystemConfig $systemConfig,
8383
StockByWebsiteIdResolverInterface $stockByWebsiteIdResolver,
84-
GetIsManageStockForProduct $getIsManageStockForProduct
84+
GetIsManageStockForProduct $getIsManageStockForProduct
8585
) {
8686
$this->isProductSalableInterface = $isProductSalableInterface;
8787
$this->getProductSalableQtyInterface = $getProductSalableQtyInterface;
@@ -151,7 +151,7 @@ public function isStockManagedForProduct(): bool
151151
*/
152152
public function initInventoryForProduct(Product $product): MultiSourceInventory
153153
{
154-
$websiteId = (int) $product->getStore()->getWebsiteId();
154+
$websiteId = (int)$product->getStore()->getWebsiteId();
155155
$stockId = $this->stockByWebsiteIdResolver->execute($websiteId)->getStockId();
156156
$this->product = $product;
157157
$this->stockStatus = $this->isInStock($product, $stockId);
@@ -170,7 +170,9 @@ public function getAvailability(): string
170170
if (!$this->isStockManagedForProduct()) {
171171
return self::STATUS_IN_STOCK;
172172
}
173-
return $this->getInventory() && $this->stockStatus ? self::STATUS_IN_STOCK : self::STATUS_OUT_OF_STOCK;
173+
174+
return $this->meetsInventoryRequirementsToBeInStock($this->product)
175+
&& $this->stockStatus ? self::STATUS_IN_STOCK : self::STATUS_OUT_OF_STOCK;
174176
}
175177

176178
/**
@@ -183,11 +185,13 @@ public function getInventory(): int
183185
if (!$this->product) {
184186
return 0;
185187
}
188+
186189
if (!$this->isStockManagedForProduct()) {
187190
return self::UNMANAGED_STOCK_QTY;
188191
}
192+
189193
$outOfStockThreshold = $this->systemConfig->getOutOfStockThreshold($this->product->getStoreId());
190-
$quantityAvailableForCatalog = (int) $this->stockQty - $outOfStockThreshold;
194+
$quantityAvailableForCatalog = (int)$this->stockQty - $outOfStockThreshold;
191195
return $quantityAvailableForCatalog > 0 ? $quantityAvailableForCatalog : 0;
192196
}
193197
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Copyright (c) Meta Platforms, Inc. and affiliates.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace Meta\Catalog\Test\Unit\Model\Product\Feed\Builder;
22+
23+
require_once __DIR__ . "/../../../../../../Model/Product/Feed/Builder/InventoryInterface.php";
24+
require_once __DIR__ . "/../../../../../../Model/Product/Feed/Builder/InventoryRequirements.php";
25+
26+
use Magento\Catalog\Model\Product;
27+
use Meta\Catalog\Model\Product\Feed\Builder\InventoryRequirements;
28+
use PHPUnit\Framework\TestCase;
29+
30+
class InventoryRequirementsTest extends TestCase
31+
{
32+
/**
33+
* @var Product
34+
*/
35+
private Product $product;
36+
37+
/**
38+
* Used to set the values before running a test
39+
*
40+
* @return void
41+
*/
42+
public function setUp(): void
43+
{
44+
$this->product = $this->createStub(Product::class);
45+
}
46+
47+
public function dataProvider(): array
48+
{
49+
return [
50+
'grouped products: no required inventory' => ['grouped', 0, true],
51+
'bundle products: no required inventory' => ['grouped', 0, true],
52+
'simple products: require inventory, zero inventory available,' => ['simple', 0, false],
53+
'simple products: require inventory, non-zero inventory available,' => ['simple', 1, true],
54+
];
55+
}
56+
57+
/**
58+
* @dataProvider dataProvider
59+
*/
60+
public function testProductsMeetInventoryRequirements(string $productTypeId, int $inventoryCount, bool $expected)
61+
{
62+
// Arrange
63+
$this->product->method('getTypeId')->willReturn($productTypeId);
64+
65+
$inventoryMethod = 'getInventory';
66+
$mock = $this->getMockForAbstractClass(InventoryRequirements::class);
67+
68+
$mock->expects($this->any())
69+
->method($inventoryMethod)
70+
->will($this->returnValue($inventoryCount));
71+
72+
// Act
73+
$meetsInventoryRequirements = $mock->meetsInventoryRequirementsToBeInStock($this->product);
74+
75+
// Assert
76+
$this->assertEquals($expected, $meetsInventoryRequirements);
77+
}
78+
}

0 commit comments

Comments
 (0)