Skip to content

Commit 36b2198

Browse files
Merge remote-tracking branch '39722/bug/39479-grouped-product-qty-validation' into comprs_june
2 parents e8e4b1a + 188fae4 commit 36b2198

File tree

10 files changed

+295
-149
lines changed

10 files changed

+295
-149
lines changed

app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
<argument name="value" value="Yes"/>
4949
</actionGroup>
5050
<actionGroup ref="AdminFillAdvancedInventoryQtyActionGroup" stepKey="fillProductQty">
51-
<argument name="qty" value="5"/>
51+
<argument name="qty" value="8"/>
5252
</actionGroup>
5353
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="uncheckMiniQtyCheckBox"/>
5454
<actionGroup ref="AdminSetMinAllowedQtyForProductActionGroup" stepKey="fillMiniAllowedQty">
@@ -111,17 +111,17 @@
111111
<argument name="productQty" value="4"/>
112112
</actionGroup>
113113
<actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addAdditionalProductToCart">
114-
<argument name="productQty" value="1"/>
114+
<argument name="productQty" value="4"/>
115115
</actionGroup>
116116
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeSuccessSaveMessage"/>
117117
<seeElement selector="{{StorefrontMinicartSection.quantity(6)}}" stepKey="seeAddedProductQuantityInCart"/>
118118
<actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/>
119-
<executeJS function="return {{SimpleProduct.price}} * 5" stepKey="expectedCartSubtotal"/>
119+
<executeJS function="return {{SimpleProduct.price}} * 8" stepKey="expectedCartSubtotal"/>
120120
<actionGroup ref="AssertStorefrontMiniCartItemsActionGroup" stepKey="seeProductNameInMiniCart">
121121
<argument name="productName" value="{{SimpleProduct.name}}"/>
122122
<argument name="productPrice" value="{{SimpleProduct.price}}"/>
123123
<argument name="cartSubtotal" value="{$expectedCartSubtotal}" />
124-
<argument name="qty" value="5"/>
124+
<argument name="qty" value="8"/>
125125
</actionGroup>
126126
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeProductPriceInMiniCart"/>
127127
<comment userInput="Comment is added to preserve the step key for backward compatibility" stepKey="seeCheckOutButtonInMiniCart"/>

app/code/Magento/CatalogInventory/Block/Plugin/ProductView.php

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,42 @@
55
*/
66
namespace Magento\CatalogInventory\Block\Plugin;
77

8-
use Magento\CatalogInventory\Api\StockRegistryInterface;
8+
use Magento\Catalog\Block\Product\View;
9+
use Magento\CatalogInventory\Model\Product\QuantityValidator;
910

1011
class ProductView
1112
{
1213
/**
13-
* @var StockRegistryInterface
14+
* @var QuantityValidator
1415
*/
15-
private $stockRegistry;
16+
private $productQuantityValidator;
1617

1718
/**
18-
* @param StockRegistryInterface $stockRegistry
19+
* @param QuantityValidator $productQuantityValidator
1920
*/
2021
public function __construct(
21-
StockRegistryInterface $stockRegistry
22+
QuantityValidator $productQuantityValidator
2223
) {
23-
$this->stockRegistry = $stockRegistry;
24+
$this->productQuantityValidator = $productQuantityValidator;
2425
}
2526

2627
/**
2728
* Adds quantities validator.
2829
*
29-
* @param \Magento\Catalog\Block\Product\View $block
30+
* @param View $block
3031
* @param array $validators
3132
* @return array
3233
*/
3334
public function afterGetQuantityValidators(
34-
\Magento\Catalog\Block\Product\View $block,
35+
View $block,
3536
array $validators
3637
) {
37-
$stockItem = $this->stockRegistry->getStockItem(
38-
$block->getProduct()->getId(),
39-
$block->getProduct()->getStore()->getWebsiteId()
38+
return array_merge(
39+
$validators,
40+
$this->productQuantityValidator->getData(
41+
$block->getProduct()->getId(),
42+
$block->getProduct()->getStore()->getWebsiteId()
43+
)
4044
);
41-
42-
$params = [];
43-
if ($stockItem->getMaxSaleQty()) {
44-
$params['maxAllowed'] = (float)$stockItem->getMaxSaleQty();
45-
}
46-
if ($stockItem->getQtyIncrements() > 0) {
47-
$params['qtyIncrements'] = (float)$stockItem->getQtyIncrements();
48-
}
49-
$validators['validate-item-quantity'] = $params;
50-
51-
return $validators;
5245
}
5346
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogInventory\Model\Product;
9+
10+
use Magento\CatalogInventory\Api\StockRegistryInterface;
11+
12+
class QuantityValidator
13+
{
14+
/**
15+
* @param StockRegistryInterface $stockRegistry
16+
*/
17+
public function __construct(
18+
private readonly StockRegistryInterface $stockRegistry
19+
) {
20+
}
21+
22+
/**
23+
* To get quantity validators
24+
*
25+
* @param int $productId
26+
* @param int|null $websiteId
27+
*
28+
* @return array
29+
*/
30+
public function getData(int $productId, int|null $websiteId): array
31+
{
32+
$stockItem = $this->stockRegistry->getStockItem($productId, $websiteId);
33+
34+
if (!$stockItem) {
35+
return [];
36+
}
37+
38+
$params = [];
39+
$validators = [];
40+
$params['minAllowed'] = $stockItem->getMinSaleQty();
41+
if ($stockItem->getMaxSaleQty()) {
42+
$params['maxAllowed'] = $stockItem->getMaxSaleQty();
43+
}
44+
if ($stockItem->getQtyIncrements() > 0) {
45+
$params['qtyIncrements'] = (float) $stockItem->getQtyIncrements();
46+
}
47+
$validators['validate-item-quantity'] = $params;
48+
49+
return $validators;
50+
}
51+
}

app/code/Magento/CatalogInventory/Test/Unit/Block/Plugin/ProductViewTest.php

Lines changed: 0 additions & 94 deletions
This file was deleted.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogInventory\Test\Unit\Model\Product;
9+
10+
use Magento\CatalogInventory\Api\StockRegistryInterface;
11+
use Magento\CatalogInventory\Model\Product\QuantityValidator;
12+
use Magento\CatalogInventory\Api\Data\StockItemInterface;
13+
use PHPUnit\Framework\MockObject\MockObject;
14+
use PHPUnit\Framework\TestCase;
15+
16+
/**
17+
* @covers \Magento\CatalogInventory\Model\Product\QuantityValidator
18+
*/
19+
class QuantityValidatorTest extends TestCase
20+
{
21+
private const PRODUCT_ID = 42;
22+
private const WEBSITE_ID = 1;
23+
24+
/**
25+
* @var QuantityValidator
26+
*/
27+
private $quantityValidator;
28+
29+
/**
30+
* @var StockRegistryInterface|MockObject
31+
*/
32+
private $stockRegistry;
33+
34+
protected function setUp(): void
35+
{
36+
$this->stockRegistry = $this->createMock(StockRegistryInterface::class);
37+
38+
$this->quantityValidator = new QuantityValidator(
39+
$this->stockRegistry
40+
);
41+
}
42+
43+
public function testGetDataWithMinMaxAndIncrements(): void
44+
{
45+
$stockItem = $this->createMock(StockItemInterface::class);
46+
47+
$stockItem->method('getMinSaleQty')
48+
->willReturn(2.0);
49+
50+
$stockItem->method('getMaxSaleQty')
51+
->willReturn(10.0);
52+
53+
$stockItem->method('getQtyIncrements')
54+
->willReturn(2.0);
55+
56+
$this->stockRegistry->expects($this->once())
57+
->method('getStockItem')
58+
->with(self::PRODUCT_ID, self::WEBSITE_ID)
59+
->willReturn($stockItem);
60+
61+
$expected = [
62+
'validate-item-quantity' => [
63+
'minAllowed' => 2.0,
64+
'maxAllowed' => 10.0,
65+
'qtyIncrements' => 2.0
66+
]
67+
];
68+
69+
$result = $this->quantityValidator->getData(self::PRODUCT_ID, self::WEBSITE_ID);
70+
$this->assertEquals($expected, $result);
71+
}
72+
73+
public function testReturnsEmptyArrayForNonExistentProductOrWebsite(): void
74+
{
75+
$this->stockRegistry->expects($this->once())
76+
->method('getStockItem')
77+
->with(self::PRODUCT_ID, self::WEBSITE_ID)
78+
->willReturn(null);
79+
80+
$result = $this->quantityValidator->getData(self::PRODUCT_ID, self::WEBSITE_ID);
81+
$this->assertSame([], $result, 'Should return empty array when StockItem is not found');
82+
}
83+
84+
public function testHandlesNullValuesFromStockItem(): void
85+
{
86+
$stockItem = $this->createMock(StockItemInterface::class);
87+
$stockItem->method('getMinSaleQty')
88+
->willReturn(null);
89+
$stockItem->method('getMaxSaleQty')
90+
->willReturn(null);
91+
$stockItem->method('getQtyIncrements')
92+
->willReturn(null);
93+
94+
$this->stockRegistry->expects($this->once())
95+
->method('getStockItem')
96+
->with(self::PRODUCT_ID, self::WEBSITE_ID)
97+
->willReturn($stockItem);
98+
99+
$expected = [
100+
'validate-item-quantity' => [
101+
'minAllowed' => null
102+
],
103+
];
104+
$result = $this->quantityValidator->getData(self::PRODUCT_ID, self::WEBSITE_ID);
105+
$this->assertEquals($expected, $result);
106+
}
107+
108+
public function testHandlesInvalidValuesFromStockItem(): void
109+
{
110+
$stockItem = $this->createMock(StockItemInterface::class);
111+
$stockItem->method('getMinSaleQty')
112+
->willReturn('not-a-number');
113+
$stockItem->method('getMaxSaleQty')
114+
->willReturn(-5);
115+
$stockItem->method('getQtyIncrements')
116+
->willReturn(false);
117+
118+
$this->stockRegistry->expects($this->once())
119+
->method('getStockItem')
120+
->with(self::PRODUCT_ID, self::WEBSITE_ID)
121+
->willReturn($stockItem);
122+
123+
$expected = [
124+
'validate-item-quantity' => [
125+
'minAllowed' => 'not-a-number',
126+
'maxAllowed' => -5
127+
],
128+
];
129+
$result = $this->quantityValidator->getData(self::PRODUCT_ID, self::WEBSITE_ID);
130+
$this->assertEquals($expected, $result);
131+
}
132+
}

0 commit comments

Comments
 (0)