Skip to content

Commit fdd480c

Browse files
author
Prabhu Ram
committed
PWA-1655: [bug]: Broken Add to Cart for logged in users
- Fixed the bug and added tests
1 parent ad08ccf commit fdd480c

File tree

4 files changed

+163
-1
lines changed

4 files changed

+163
-1
lines changed

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

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@
77

88
namespace Magento\QuoteGraphQl\Model\Resolver;
99

10+
use Magento\CatalogInventory\Api\StockRegistryInterface;
1011
use Magento\Framework\App\ObjectManager;
1112
use Magento\Framework\Exception\CouldNotSaveException;
13+
use Magento\Framework\Exception\NoSuchEntityException;
1214
use Magento\Framework\GraphQl\Config\Element\Field;
1315
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
1416
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
1517
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
1618
use Magento\Framework\GraphQl\Query\ResolverInterface;
1719
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1820
use Magento\GraphQl\Model\Query\ContextInterface;
21+
use Magento\Quote\Api\CartItemRepositoryInterface;
1922
use Magento\Quote\Api\CartRepositoryInterface;
23+
use Magento\Quote\Api\Data\CartInterface;
24+
use Magento\Quote\Api\Data\CartItemInterface;
2025
use Magento\Quote\Model\Cart\CustomerCartResolver;
2126
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
2227
use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
@@ -48,24 +53,42 @@ class MergeCarts implements ResolverInterface
4853
*/
4954
private $quoteIdToMaskedQuoteId;
5055

56+
/**
57+
* @var CartItemRepositoryInterface
58+
*/
59+
private $cartItemRepository;
60+
61+
/**
62+
* @var StockRegistryInterface
63+
*/
64+
private $stockRegistry;
65+
5166
/**
5267
* @param GetCartForUser $getCartForUser
5368
* @param CartRepositoryInterface $cartRepository
5469
* @param CustomerCartResolver|null $customerCartResolver
5570
* @param QuoteIdToMaskedQuoteIdInterface|null $quoteIdToMaskedQuoteId
71+
* @param CartItemRepositoryInterface|null $cartItemRepository
72+
* @param StockRegistryInterface|null $stockRegistry
5673
*/
5774
public function __construct(
5875
GetCartForUser $getCartForUser,
5976
CartRepositoryInterface $cartRepository,
6077
CustomerCartResolver $customerCartResolver = null,
61-
QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedQuoteId = null
78+
QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedQuoteId = null,
79+
CartItemRepositoryInterface $cartItemRepository = null,
80+
StockRegistryInterface $stockRegistry = null
6281
) {
6382
$this->getCartForUser = $getCartForUser;
6483
$this->cartRepository = $cartRepository;
6584
$this->customerCartResolver = $customerCartResolver
6685
?: ObjectManager::getInstance()->get(CustomerCartResolver::class);
6786
$this->quoteIdToMaskedQuoteId = $quoteIdToMaskedQuoteId
6887
?: ObjectManager::getInstance()->get(QuoteIdToMaskedQuoteIdInterface::class);
88+
$this->cartItemRepository = $cartItemRepository
89+
?: ObjectManager::getInstance()->get(CartItemRepositoryInterface::class);
90+
$this->stockRegistry = $stockRegistry
91+
?: ObjectManager::getInstance()->get(StockRegistryInterface::class);
6992
}
7093

7194
/**
@@ -127,6 +150,13 @@ public function resolve(
127150
$currentUserId,
128151
$storeId
129152
);
153+
if ($this->validateFinalCartQuantities($customerCart, $guestCart)) {
154+
$guestCart = $this->getCartForUser->execute(
155+
$guestMaskedCartId,
156+
null,
157+
$storeId
158+
);
159+
}
130160
$customerCart->merge($guestCart);
131161
$guestCart->setIsActive(false);
132162
$this->cartRepository->save($customerCart);
@@ -135,4 +165,39 @@ public function resolve(
135165
'model' => $customerCart,
136166
];
137167
}
168+
169+
/**
170+
* Validate combined cart quantities to make sure they are within available stock
171+
*
172+
* @param CartInterface $customerCart
173+
* @param CartInterface $guestCart
174+
* @return bool
175+
*/
176+
private function validateFinalCartQuantities(CartInterface $customerCart, CartInterface $guestCart)
177+
{
178+
$modified = false;
179+
/** @var CartItemInterface $guestCartItem */
180+
foreach ($guestCart->getAllVisibleItems() as $guestCartItem) {
181+
foreach ($customerCart->getAllItems() as $customerCartItem) {
182+
if ($customerCartItem->compare($guestCartItem)) {
183+
$product = $customerCartItem->getProduct();
184+
$stockCurrentQty = $this->stockRegistry->getStockStatus(
185+
$product->getId(),
186+
$product->getStore()->getWebsiteId()
187+
)->getQty();
188+
if ($stockCurrentQty < $guestCartItem->getQty() + $customerCartItem->getQty()) {
189+
try {
190+
$this->cartItemRepository->deleteById($guestCart->getId(), $guestCartItem->getItemId());
191+
$modified = true;
192+
} catch (NoSuchEntityException $e) {
193+
continue;
194+
} catch (CouldNotSaveException $e) {
195+
continue;
196+
}
197+
}
198+
}
199+
}
200+
}
201+
return $modified;
202+
}
138203
}

dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,48 @@ public function testMergeGuestWithCustomerCart()
101101
self::assertEquals(1, $item2['quantity']);
102102
}
103103

104+
/**
105+
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
106+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
107+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
108+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product_with_100_qty.php
109+
*/
110+
public function testMergeGuestWithCustomerCartWithOutOfStockQuantity()
111+
{
112+
$customerQuote = $this->quoteFactory->create();
113+
$this->quoteResource->load($customerQuote, 'test_quote', 'reserved_order_id');
114+
115+
$guestQuote = $this->quoteFactory->create();
116+
$this->quoteResource->load(
117+
$guestQuote,
118+
'test_order_with_virtual_product_without_address',
119+
'reserved_order_id'
120+
);
121+
122+
$customerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$customerQuote->getId());
123+
$guestQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$guestQuote->getId());
124+
125+
$query = $this->getCartMergeMutation($guestQuoteMaskedId, $customerQuoteMaskedId);
126+
$mergeResponse = $this->graphQlMutation($query, [], '', $this->getHeaderMap());
127+
self::assertArrayHasKey('mergeCarts', $mergeResponse);
128+
$cartResponse = $mergeResponse['mergeCarts'];
129+
self::assertArrayHasKey('items', $cartResponse);
130+
self::assertCount(1, $cartResponse['items']);
131+
$cartResponse = $this->graphQlMutation(
132+
$this->getCartQuery($customerQuoteMaskedId),
133+
[],
134+
'',
135+
$this->getHeaderMap()
136+
);
137+
138+
self::assertArrayHasKey('cart', $cartResponse);
139+
self::assertArrayHasKey('items', $cartResponse['cart']);
140+
self::assertCount(1, $cartResponse['cart']['items']);
141+
$item1 = $cartResponse['cart']['items'][0];
142+
self::assertArrayHasKey('quantity', $item1);
143+
self::assertEquals(100, $item1['quantity']);
144+
}
145+
104146
/**
105147
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
106148
* @magentoApiDataFixture Magento/Customer/_files/customer.php
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Quote\Api\CartRepositoryInterface;
10+
use Magento\Quote\Model\QuoteFactory;
11+
use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
12+
use Magento\TestFramework\Helper\Bootstrap;
13+
14+
/** @var ProductRepositoryInterface $productRepository */
15+
$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
16+
/** @var QuoteFactory $quoteFactory */
17+
$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class);
18+
/** @var QuoteResource $quoteResource */
19+
$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
20+
/** @var CartRepositoryInterface $cartRepository */
21+
$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class);
22+
23+
$product = $productRepository->get('virtual-product');
24+
25+
$quote = $quoteFactory->create();
26+
$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
27+
$quote->addProduct($product, 100);
28+
$cartRepository->save($quote);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
use Magento\Framework\Exception\NoSuchEntityException;
9+
10+
\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize();
11+
12+
/** @var \Magento\Framework\Registry $registry */
13+
$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
14+
15+
$registry->unregister('isSecureArea');
16+
$registry->register('isSecureArea', true);
17+
18+
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
19+
$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
20+
->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
21+
try {
22+
$product = $productRepository->get('virtual-product', false, null, true);
23+
$productRepository->delete($product);
24+
} catch (NoSuchEntityException $e) {
25+
}
26+
$registry->unregister('isSecureArea');
27+
$registry->register('isSecureArea', false);

0 commit comments

Comments
 (0)