Skip to content

Commit ae293b3

Browse files
authored
LYNX-420: is_available attribute in CartItemInterface returns true even when salable stock is lower than the quantity of the product (#251)
1 parent 174fe83 commit ae293b3

File tree

3 files changed

+167
-97
lines changed

3 files changed

+167
-97
lines changed

app/code/Magento/QuoteGraphQl/Model/CartItem/ProductStock.php

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<?php
22
/**
3-
* Copyright 2023 Adobe
3+
* Copyright 2024 Adobe
44
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

88
namespace Magento\QuoteGraphQl\Model\CartItem;
99

1010
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\CatalogInventory\Model\StockState;
1112
use Magento\Catalog\Api\ProductRepositoryInterface;
1213
use Magento\Framework\Exception\NoSuchEntityException;
1314
use Magento\Quote\Model\Quote\Item;
15+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1416

1517
/**
1618
* Product Stock class to check availability of product
@@ -31,9 +33,13 @@ class ProductStock
3133
* ProductStock constructor
3234
*
3335
* @param ProductRepositoryInterface $productRepositoryInterface
36+
* @param StockState $stockState
37+
* @param StockConfigurationInterface $stockConfiguration
3438
*/
3539
public function __construct(
3640
private readonly ProductRepositoryInterface $productRepositoryInterface,
41+
private readonly StockState $stockState,
42+
private readonly StockConfigurationInterface $stockConfiguration
3743
) {
3844
}
3945

@@ -46,37 +52,30 @@ public function __construct(
4652
*/
4753
public function isProductAvailable(Item $cartItem): bool
4854
{
49-
$requestedQty = 0;
50-
$previousQty = 0;
5155
/**
5256
* @var ProductInterface $variantProduct
5357
* Configurable products cannot have stock, only its variants can. If the user adds a configurable product
5458
* using its SKU and the selected options, we need to get the variant it refers to from the quote.
5559
*/
5660
$variantProduct = null;
5761

58-
foreach ($cartItem->getQuote()->getItems() as $item) {
59-
if ($item->getItemId() !== $cartItem->getItemId()) {
60-
continue;
62+
if ($cartItem->getProductType() === self::PRODUCT_TYPE_CONFIGURABLE) {
63+
if ($cartItem->getChildren()[0] !== null) {
64+
$variantProduct = $this->productRepositoryInterface->get($cartItem->getSku());
6165
}
62-
if ($cartItem->getProductType() === self::PRODUCT_TYPE_CONFIGURABLE) {
63-
if ($cartItem->getChildren()[0] !== null) {
64-
$variantProduct = $this->productRepositoryInterface->get($item->getSku());
65-
}
66-
}
67-
$requestedQty = $item->getQtyToAdd() ?? $item->getQty();
68-
$previousQty = $item->getPreviousQty() ?? 0;
6966
}
67+
$requestedQty = $cartItem->getQtyToAdd() ?? $cartItem->getQty();
68+
$previousQty = $cartItem->getPreviousQty() ?? 0;
7069

7170
if ($cartItem->getProductType() === self::PRODUCT_TYPE_BUNDLE) {
7271
return $this->isStockAvailableBundle($cartItem, $previousQty, $requestedQty);
7372
}
7473

7574
$requiredItemQty = $requestedQty + $previousQty;
7675
if ($variantProduct !== null) {
77-
return $this->isStockQtyAvailable($variantProduct, $requiredItemQty);
76+
return $this->isStockQtyAvailable($variantProduct, $requestedQty, $requiredItemQty, $previousQty);
7877
}
79-
return $this->isStockQtyAvailable($cartItem->getProduct(), $requiredItemQty);
78+
return $this->isStockQtyAvailable($cartItem->getProduct(), $requestedQty, $requiredItemQty, $previousQty);
8079
}
8180

8281
/**
@@ -96,30 +95,36 @@ public function isStockAvailableBundle(Item $cartItem, int $previousQty, $reques
9695
if ($totalRequestedQty) {
9796
$requiredItemQty = $requiredItemQty * $totalRequestedQty;
9897
}
99-
if (!$this->isStockQtyAvailable($qtyOption->getProduct(), $requiredItemQty)) {
98+
if (!$this->isStockQtyAvailable($qtyOption->getProduct(), $requestedQty, $requiredItemQty, $previousQty)) {
10099
return false;
101100
}
102101
}
103102
return true;
104103
}
105104

106105
/**
107-
* Check if product is available in stock using quantity from Catalog Inventory Stock Item
106+
* Check if product is available in stock
108107
*
109108
* @param ProductInterface $product
109+
* @param float $itemQty
110110
* @param float $requiredQuantity
111-
* @throws NoSuchEntityException
111+
* @param float $prevQty
112112
* @return bool
113113
*/
114-
private function isStockQtyAvailable(ProductInterface $product, float $requiredQuantity): bool
115-
{
116-
$stockItem = $product->getExtensionAttributes()->getStockItem();
117-
if ($stockItem === null) {
118-
return true;
119-
}
120-
if ((int) $stockItem->getProductId() !== (int) $product->getId()) {
121-
throw new NoSuchEntityException(__('Stock item\'s product ID does not match requested product ID'));
122-
}
123-
return $stockItem->getQty() >= $requiredQuantity;
114+
private function isStockQtyAvailable(
115+
ProductInterface $product,
116+
float $itemQty,
117+
float $requiredQuantity,
118+
float $prevQty
119+
): bool {
120+
$stockStatus = $this->stockState->checkQuoteItemQty(
121+
$product->getId(),
122+
$itemQty,
123+
$requiredQuantity,
124+
$prevQty,
125+
$this->stockConfiguration->getDefaultScopeId()
126+
);
127+
128+
return ((bool) $stockStatus->getHasError()) === false;
124129
}
125130
}

dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,14 @@ public function testOnlyXLeftInStockConfigurableProduct(string $stockThresholdQt
203203
itemsV2 {
204204
items {
205205
uid
206-
product {
207-
name
208-
sku
209-
stock_status
210-
only_x_left_in_stock
211-
}
206+
... on ConfigurableCartItem {
207+
configured_variant {
208+
name
209+
sku
210+
stock_status
211+
only_x_left_in_stock
212+
}
213+
}
212214
}
213215
}
214216
}
@@ -219,7 +221,7 @@ public function testOnlyXLeftInStockConfigurableProduct(string $stockThresholdQt
219221
$responseDataObject = new DataObject($response);
220222
self::assertEquals(
221223
$expected,
222-
$responseDataObject->getData('cart/itemsV2/items/0/product/only_x_left_in_stock'),
224+
$responseDataObject->getData('cart/itemsV2/items/0/configured_variant/only_x_left_in_stock'),
223225
);
224226
}
225227

@@ -251,12 +253,7 @@ private function mutationAddConfigurableProduct(
251253
}]
252254
) {
253255
cart {
254-
items {
255-
is_available
256-
product {
257-
sku
258-
}
259-
}
256+
id
260257
}
261258
user_errors {
262259
code

0 commit comments

Comments
 (0)