Skip to content

Commit 1e53f17

Browse files
authored
Merge branch '2.4-develop' into AC-14307
2 parents 5e192df + f821084 commit 1e53f17

File tree

15 files changed

+1241
-182
lines changed

15 files changed

+1241
-182
lines changed

app/code/Magento/CatalogInventoryGraphQl/Model/Resolver/QuantityResolver.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

88
namespace Magento\CatalogInventoryGraphQl\Model\Resolver;
99

1010
use Magento\Catalog\Api\ProductRepositoryInterface;
1111
use Magento\Catalog\Model\Product;
12-
use Magento\CatalogInventory\Model\StockState;
1312
use Magento\CatalogInventory\Model\Config\Source\NotAvailableMessage;
1413
use Magento\Framework\App\Config\ScopeConfigInterface;
1514
use Magento\Framework\Exception\LocalizedException;
@@ -36,16 +35,16 @@ class QuantityResolver implements ResolverInterface
3635
private const CONFIG_PATH_NOT_AVAILABLE_MESSAGE = "cataloginventory/options/not_available_message";
3736

3837
/**
38+
* QuantityResolver Constructor
39+
*
3940
* @param ProductRepositoryInterface $productRepositoryInterface
4041
* @param ScopeConfigInterface $scopeConfig
41-
* @param StockState $stockState
4242
* @param ProductStock $productStock
4343
*/
4444
public function __construct(
4545
private readonly ProductRepositoryInterface $productRepositoryInterface,
4646
private readonly ScopeConfigInterface $scopeConfig,
47-
private readonly StockState $stockState,
48-
private readonly ProductStock $productStock,
47+
private readonly ProductStock $productStock
4948
) {
5049
}
5150

@@ -69,7 +68,7 @@ public function resolve(
6968
}
7069

7170
if (isset($value['cart_item']) && $value['cart_item'] instanceof Item) {
72-
return $this->productStock->getProductAvailableStock($value['cart_item']);
71+
return $this->productStock->getSaleableQtyByCartItem($value['cart_item'], null);
7372
}
7473

7574
if (!isset($value['model'])) {
@@ -82,6 +81,7 @@ public function resolve(
8281
if ($product->getTypeId() === self::PRODUCT_TYPE_CONFIGURABLE) {
8382
$product = $this->productRepositoryInterface->get($product->getSku());
8483
}
85-
return $this->stockState->getStockQty($product->getId());
84+
85+
return $this->productStock->getSaleableQty($product, null);
8686
}
8787
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\Quote\Model;
77

@@ -1563,6 +1563,12 @@ public function removeItem($itemId)
15631563
$item = $this->getItemById($itemId);
15641564

15651565
if ($item) {
1566+
1567+
// Remove gift message from cart
1568+
if ((int)$this->getItemsCount() === 1) {
1569+
$this->setGiftMessageId(0);
1570+
}
1571+
15661572
$item->setQuote($this);
15671573
$item->isDeleted(true);
15681574
if ($item->getHasChildren()) {
@@ -1596,6 +1602,10 @@ public function removeAllItems()
15961602
$item->isDeleted(true);
15971603
}
15981604
}
1605+
1606+
// Remove gift message from cart
1607+
$this->setGiftMessageId(0);
1608+
15991609
return $this;
16001610
}
16011611

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

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,10 @@ private function getLowestStockValueOfBundleProduct(Item $cartItem): float
202202
* Returns the lowest stock value of bundle product
203203
*
204204
* @param Item $cartItem
205-
* @param float $thresholdQty
205+
* @param float|null $thresholdQty
206206
* @return float
207207
*/
208-
private function getLowestSaleableQtyOfBundleProduct(Item $cartItem, float $thresholdQty): float
208+
private function getLowestSaleableQtyOfBundleProduct(Item $cartItem, ?float $thresholdQty): float
209209
{
210210
$bundleStock = [];
211211
foreach ($cartItem->getQtyOptions() as $qtyOption) {
@@ -231,7 +231,20 @@ public function getProductSaleableQty(Item $cartItem): float
231231
if ($thresholdQty === 0.0) {
232232
return $this->getProductAvailableStock($cartItem);
233233
}
234-
234+
235+
return $this->getSaleableQtyByCartItem($cartItem, $thresholdQty);
236+
}
237+
238+
/**
239+
* Returns the saleable qty value by cart item
240+
*
241+
* @param Item $cartItem
242+
* @param float|null $thresholdQty
243+
* @return float
244+
* @throws NoSuchEntityException
245+
*/
246+
public function getSaleableQtyByCartItem(Item $cartItem, ?float $thresholdQty): float
247+
{
235248
if ($cartItem->getProductType() === self::PRODUCT_TYPE_BUNDLE) {
236249
return $this->getLowestSaleableQtyOfBundleProduct($cartItem, $thresholdQty);
237250
}
@@ -248,15 +261,18 @@ public function getProductSaleableQty(Item $cartItem): float
248261
* Get product saleable qty when "Catalog > Inventory > Stock Options > Only X left Threshold" is greater than 0
249262
*
250263
* @param ProductInterface $product
251-
* @param float $thresholdQty
264+
* @param float|null $thresholdQty
252265
* @return float
253266
*/
254-
private function getSaleableQty(ProductInterface $product, float $thresholdQty): float
267+
public function getSaleableQty(ProductInterface $product, ?float $thresholdQty): float
255268
{
256-
$stockItem = $this->stockRegistry->getStockItem($product->getId());
257269
$stockStatus = $this->stockRegistry->getStockStatus($product->getId(), $product->getStore()->getWebsiteId());
258-
$stockCurrentQty = $stockStatus->getQty();
259-
$stockLeft = $stockCurrentQty - $stockItem->getMinQty();
260-
return ($stockCurrentQty >= 0 && $stockLeft <= $thresholdQty) ? (float)$stockCurrentQty : 0.0;
270+
$stockQty = (float)$stockStatus->getQty();
271+
if ($thresholdQty === null) {
272+
return $stockQty;
273+
}
274+
$stockLeft = $stockQty - $this->stockRegistry->getStockItem($product->getId())->getMinQty();
275+
276+
return ($stockQty >= 0 && $stockLeft <= $thresholdQty) ? $stockQty : 0.0;
261277
}
262278
}

app/code/Magento/QuoteGraphQl/Model/Resolver/EstimateTotals.php

Lines changed: 39 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,75 +8,80 @@
88
namespace Magento\QuoteGraphQl\Model\Resolver;
99

1010
use Magento\Checkout\Api\Data\TotalsInformationInterface;
11-
use Magento\Checkout\Api\Data\TotalsInformationInterfaceFactory;
1211
use Magento\Checkout\Api\TotalsInformationManagementInterface;
1312
use Magento\Framework\Exception\NoSuchEntityException;
1413
use Magento\Framework\GraphQl\Config\Element\Field;
1514
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
1615
use Magento\Framework\GraphQl\Query\ResolverInterface;
1716
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1817
use Magento\Quote\Api\CartRepositoryInterface;
19-
use Magento\Quote\Api\Data\AddressInterface;
2018
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
21-
use Magento\Quote\Model\Quote\Address;
22-
use Magento\Quote\Model\Quote\AddressFactory;
19+
use Magento\QuoteGraphQl\Model\Cart\AssignShippingMethodToCart;
2320
use Magento\QuoteGraphQl\Model\ErrorMapper;
21+
use Magento\QuoteGraphQl\Model\TotalsBuilder;
22+
use Psr\Log\LoggerInterface;
2423

2524
/**
2625
* Apply address and shipping method to totals estimate and return the quote
2726
*/
2827
class EstimateTotals implements ResolverInterface
2928
{
3029
/**
30+
* EstimateTotals Constructor
31+
*
3132
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
3233
* @param CartRepositoryInterface $cartRepository
33-
* @param AddressFactory $addressFactory
3434
* @param TotalsInformationManagementInterface $totalsInformationManagement
35-
* @param TotalsInformationInterfaceFactory $totalsInformationFactory
3635
* @param ErrorMapper $errorMapper
36+
* @param AssignShippingMethodToCart $assignShippingMethodToCart
37+
* @param LoggerInterface $logger
38+
* @param TotalsBuilder $totalsBuilder
3739
*/
3840
public function __construct(
3941
private readonly MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
4042
private readonly CartRepositoryInterface $cartRepository,
41-
private readonly AddressFactory $addressFactory,
4243
private readonly TotalsInformationManagementInterface $totalsInformationManagement,
43-
private readonly TotalsInformationInterfaceFactory $totalsInformationFactory,
44-
private readonly ErrorMapper $errorMapper
44+
private readonly ErrorMapper $errorMapper,
45+
private readonly AssignShippingMethodToCart $assignShippingMethodToCart,
46+
private readonly LoggerInterface $logger,
47+
private readonly TotalsBuilder $totalsBuilder
4548
) {
4649
}
4750

4851
/**
4952
* @inheritdoc
50-
*
51-
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
5253
*/
5354
public function resolve(Field $field, $context, ResolveInfo $info, ?array $value = null, ?array $args = null)
5455
{
55-
if (empty($args['input']['cart_id'])) {
56+
$input = $args['input'] ?? [];
57+
58+
if (empty($input['cart_id'])) {
5659
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
5760
}
5861

5962
try {
60-
$cartId = $this->maskedQuoteIdToQuoteId->execute($args['input']['cart_id']);
63+
$cartId = $this->maskedQuoteIdToQuoteId->execute($input['cart_id']);
6164
} catch (NoSuchEntityException $exception) {
6265
throw new GraphQlInputException(
6366
__(
6467
'Could not find a cart with ID "%masked_id"',
6568
[
66-
'masked_id' => $args['input']['cart_id']
69+
'masked_id' => $input['cart_id']
6770
]
6871
),
6972
$exception,
7073
$this->errorMapper->getErrorMessageId('Could not find a cart with ID')
7174
);
7275
}
7376

74-
if (empty($args['input']['address']['country_code'])) {
77+
$addressData = $input['address'] ?? [];
78+
if (empty($addressData['country_code'])) {
7579
throw new GraphQlInputException(__('Required parameter "country_code" is missing'));
7680
}
7781

78-
$data = $this->getTotalsInformation($args['input']);
79-
$this->totalsInformationManagement->calculate($cartId, $data);
82+
$totalsInfo = $this->totalsBuilder->execute($addressData, $input['shipping_method'] ?? []);
83+
$this->totalsInformationManagement->calculate($cartId, $totalsInfo);
84+
$this->updateShippingMethod($totalsInfo, $cartId);
8085

8186
return [
8287
'cart' => [
@@ -86,41 +91,26 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value
8691
}
8792

8893
/**
89-
* Retrieve an instance of totals information based on input data
94+
* Update shipping method if provided
9095
*
91-
* @param array $input
92-
* @return TotalsInformationInterface
96+
* @param TotalsInformationInterface $totalsInfo
97+
* @param int $cartId
98+
* @return void
99+
* @throws GraphQlInputException
93100
*/
94-
private function getTotalsInformation(array $input): TotalsInformationInterface
101+
private function updateShippingMethod(TotalsInformationInterface $totalsInfo, int $cartId): void
95102
{
96-
$data = [TotalsInformationInterface::ADDRESS => $this->getAddress($input['address'])];
97-
98-
$shippingMethod = $input['shipping_method'] ?? [];
99-
100-
if (isset($shippingMethod['carrier_code']) && isset($shippingMethod['method_code'])) {
101-
$data[TotalsInformationInterface::SHIPPING_CARRIER_CODE] = $shippingMethod['carrier_code'];
102-
$data[TotalsInformationInterface::SHIPPING_METHOD_CODE] = $shippingMethod['method_code'];
103+
try {
104+
if ($totalsInfo->getShippingCarrierCode() && $totalsInfo->getShippingMethodCode()) {
105+
$this->assignShippingMethodToCart->execute(
106+
$this->cartRepository->get($cartId),
107+
$totalsInfo->getAddress(),
108+
$totalsInfo->getShippingCarrierCode(),
109+
$totalsInfo->getShippingMethodCode()
110+
);
111+
}
112+
} catch (NoSuchEntityException $e) {
113+
$this->logger->error($e->getMessage());
103114
}
104-
105-
return $this->totalsInformationFactory->create(['data' => $data]);
106-
}
107-
108-
/**
109-
* Retrieve an instance of address based on address data
110-
*
111-
* @param array $data
112-
* @return AddressInterface
113-
*/
114-
private function getAddress(array $data): AddressInterface
115-
{
116-
/** @var Address $address */
117-
$address = $this->addressFactory->create();
118-
$address->setCountryId($data['country_code']);
119-
$address->setRegion($data['region'][AddressInterface::KEY_REGION] ?? null);
120-
$address->setRegionId($data['region'][AddressInterface::KEY_REGION_ID] ?? null);
121-
$address->setRegionCode($data['region'][AddressInterface::KEY_REGION_CODE] ?? null);
122-
$address->setPostcode($data[AddressInterface::KEY_POSTCODE] ?? null);
123-
124-
return $address;
125115
}
126116
}

0 commit comments

Comments
 (0)