Skip to content

Commit 2e40569

Browse files
Merge branch '2.4-develop' into ACPT-1666
2 parents 43fc9d5 + 74b1871 commit 2e40569

File tree

6 files changed

+647
-1
lines changed

6 files changed

+647
-1
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Copyright 2023 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\Catalog\Test\Fixture;
18+
19+
use Magento\CatalogInventory\Api\StockRegistryInterface;
20+
use Magento\Framework\DataObject;
21+
use Magento\Framework\DataObjectFactory;
22+
use Magento\TestFramework\Fixture\Api\DataMerger;
23+
use Magento\TestFramework\Fixture\DataFixtureInterface;
24+
25+
class ProductStock implements DataFixtureInterface
26+
{
27+
private const DEFAULT_DATA = [
28+
'prod_id' => null,
29+
'prod_qty' => 1
30+
];
31+
32+
/**
33+
* @var DataObjectFactory
34+
*/
35+
protected DataObjectFactory $dataObjectFactory;
36+
37+
/**
38+
* @var StockRegistryInterface
39+
*/
40+
protected StockRegistryInterface $stockRegistry;
41+
42+
/**
43+
* @var DataMerger
44+
*/
45+
protected DataMerger $dataMerger;
46+
47+
/**
48+
* @param DataObjectFactory $dataObjectFactory
49+
* @param StockRegistryInterface $stockRegistry
50+
* @param DataMerger $dataMerger
51+
*/
52+
public function __construct(
53+
DataObjectFactory $dataObjectFactory,
54+
StockRegistryInterface $stockRegistry,
55+
DataMerger $dataMerger
56+
) {
57+
$this->dataObjectFactory = $dataObjectFactory;
58+
$this->stockRegistry = $stockRegistry;
59+
$this->dataMerger = $dataMerger;
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
* @param array $data Parameters. Same format as ProductStock::DEFAULT_DATA
65+
*/
66+
public function apply(array $data = []): ?DataObject
67+
{
68+
$data = $this->dataMerger->merge(self::DEFAULT_DATA, $data);
69+
$stockItem = $this->stockRegistry->getStockItem($data['prod_id']);
70+
$stockItem->setData('is_in_stock', 1);
71+
$stockItem->setData('qty', 90);
72+
$stockItem->setData('manage_stock', 1);
73+
$stockItem->save();
74+
75+
return $this->dataObjectFactory->create(['data' => [$data]]);
76+
}
77+
}

app/code/Magento/Quote/Model/Quote/Item.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem implements \Mage
139139
protected $_optionsByCode = [];
140140

141141
/**
142-
* Not Represent options
142+
* Not Represent option
143143
*
144144
* @var array
145145
*/
@@ -148,6 +148,7 @@ class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem implements \Mage
148148
/**
149149
* Flag stating that options were successfully saved
150150
*
151+
* @var bool
151152
*/
152153
protected $_flagOptionsSaved;
153154

@@ -176,6 +177,7 @@ class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem implements \Mage
176177
/**
177178
* @var \Magento\CatalogInventory\Api\StockRegistryInterface
178179
* @deprecated 101.0.0
180+
* @see nothing
179181
*/
180182
protected $stockRegistry;
181183

@@ -348,6 +350,7 @@ public function addQty($qty)
348350
if (!$this->getParentItem() || !$this->getId()) {
349351
$qty = $this->_prepareQty($qty);
350352
$this->setQtyToAdd($qty);
353+
$this->setPreviousQty($this->getQty());
351354
$this->setQty($this->getQty() + $qty);
352355
}
353356
return $this;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\QuoteGraphQl\Model\CartItem;
20+
21+
use Magento\CatalogInventory\Api\StockStatusRepositoryInterface;
22+
use Magento\Quote\Model\Quote\Item;
23+
24+
/**
25+
* Product Stock class to check availability of product
26+
*/
27+
class ProductStock
28+
{
29+
/**
30+
* Product type code
31+
*/
32+
private const PRODUCT_TYPE_BUNDLE = "bundle";
33+
34+
/**
35+
* ProductStock constructor
36+
*
37+
* @param StockStatusRepositoryInterface $stockStatusRepository
38+
*/
39+
public function __construct(
40+
private StockStatusRepositoryInterface $stockStatusRepository
41+
) {
42+
}
43+
44+
/**
45+
* Check item status available or unavailable
46+
*
47+
* @param Item $cartItem
48+
* @return bool
49+
*/
50+
public function isProductAvailable($cartItem): bool
51+
{
52+
$requestedQty = 0;
53+
$previousQty = 0;
54+
55+
foreach ($cartItem->getQuote()->getItems() as $item) {
56+
if ($item->getItemId() === $cartItem->getItemId()) {
57+
$requestedQty = $item->getQtyToAdd() ?? $item->getQty();
58+
$previousQty = $item->getPreviousQty() ?? 0;
59+
}
60+
}
61+
62+
if ($cartItem->getProductType() === self::PRODUCT_TYPE_BUNDLE) {
63+
$qtyOptions = $cartItem->getQtyOptions();
64+
$totalRequestedQty = $previousQty + $requestedQty;
65+
foreach ($qtyOptions as $qtyOption) {
66+
$productId = (int) $qtyOption->getProductId();
67+
$requiredItemQty = (float) $qtyOption->getValue();
68+
if ($totalRequestedQty) {
69+
$requiredItemQty = $requiredItemQty * $totalRequestedQty;
70+
}
71+
if (!$this->isStockAvailable($productId, $requiredItemQty)) {
72+
return false;
73+
}
74+
}
75+
} else {
76+
$requiredItemQty = $requestedQty + $previousQty;
77+
$productId = (int) $cartItem->getProduct()->getId();
78+
return $this->isStockAvailable($productId, $requiredItemQty);
79+
}
80+
return true;
81+
}
82+
83+
/**
84+
* Check if is required product available in stock
85+
*
86+
* @param int $productId
87+
* @param float $requiredQuantity
88+
* @return bool
89+
*/
90+
private function isStockAvailable(int $productId, float $requiredQuantity): bool
91+
{
92+
$stock = $this->stockStatusRepository->get($productId);
93+
return $stock->getQty() >= $requiredQuantity;
94+
}
95+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\QuoteGraphQl\Model\Resolver;
20+
21+
use Magento\Framework\Exception\LocalizedException;
22+
use Magento\Framework\GraphQl\Config\Element\Field;
23+
use Magento\Framework\GraphQl\Query\ResolverInterface;
24+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
25+
use Magento\QuoteGraphQl\Model\CartItem\ProductStock;
26+
use Magento\Quote\Model\Quote\Item;
27+
28+
/**
29+
* @inheritdoc
30+
*/
31+
class CheckProductStockAvailability implements ResolverInterface
32+
{
33+
/**
34+
* CheckProductStockAvailability constructor
35+
*
36+
* @param ProductStock $productStock
37+
*/
38+
public function __construct(
39+
private ProductStock $productStock
40+
) {
41+
}
42+
43+
/**
44+
* @inheritdoc
45+
*/
46+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
47+
{
48+
if (!isset($value['model'])) {
49+
throw new LocalizedException(__('"model" value should be specified'));
50+
}
51+
/** @var Item $cartItem */
52+
$cartItem = $value['model'];
53+
54+
return $this->productStock->isProductAvailable($cartItem);
55+
}
56+
}

app/code/Magento/QuoteGraphQl/etc/schema.graphqls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ interface CartItemInterface @typeResolver(class: "Magento\\QuoteGraphQl\\Model\\
360360
id: String! @deprecated(reason: "Use `uid` instead.")
361361
uid: ID! @doc(description: "The unique ID for a `CartItemInterface` object.")
362362
quantity: Float! @doc(description: "The quantity of this item in the cart.")
363+
is_available: Boolean! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CheckProductStockAvailability") @doc(description: "True if requested quantity is less than available stock, false otherwise.")
363364
prices: CartItemPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemPrices") @doc(description: "Contains details about the price of the item, including taxes and discounts.")
364365
product: ProductInterface! @doc(description: "Details about an item in the cart.")
365366
errors: [CartItemError!] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemErrors") @doc(description: "An array of errors encountered while loading the cart item")

0 commit comments

Comments
 (0)