Skip to content

Commit f5fe281

Browse files
author
Oleksandr Iegorov
committed
MC-42652: GraphQL - Expected behavior of add product to cart when SKU already exists
1 parent 912857f commit f5fe281

File tree

2 files changed

+125
-5
lines changed

2 files changed

+125
-5
lines changed

app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
namespace Magento\QuoteGraphQl\Model\Cart;
99

1010
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
11-
use Magento\Framework\Message\MessageInterface;
1211
use Magento\Quote\Api\CartRepositoryInterface;
1312
use Magento\Quote\Model\Quote;
13+
use Magento\Framework\App\CacheInterface;
1414

1515
/**
1616
* Adding products to cart using GraphQL
@@ -27,16 +27,24 @@ class AddProductsToCart
2727
*/
2828
private $addProductToCart;
2929

30+
/**
31+
* @var CacheInterface
32+
*/
33+
private $cache;
34+
3035
/**
3136
* @param CartRepositoryInterface $cartRepository
3237
* @param AddSimpleProductToCart $addProductToCart
38+
* @param CacheInterface $cache
3339
*/
3440
public function __construct(
3541
CartRepositoryInterface $cartRepository,
36-
AddSimpleProductToCart $addProductToCart
42+
AddSimpleProductToCart $addProductToCart,
43+
CacheInterface $cache
3744
) {
3845
$this->cartRepository = $cartRepository;
3946
$this->addProductToCart = $addProductToCart;
47+
$this->cache = $cache;
4048
}
4149

4250
/**
@@ -50,10 +58,16 @@ public function __construct(
5058
*/
5159
public function execute(Quote $cart, array $cartItems): void
5260
{
61+
$ck = 'cart_processing_mutex_' . $cart->getId();
62+
while ($this->cache->load($ck) === '1') {
63+
// wait till other process working with the same cart complete
64+
usleep(rand (300, 600));
65+
}
66+
$this->cache->save('1', $ck, [], 1);
5367
foreach ($cartItems as $cartItemData) {
5468
$this->addProductToCart->execute($cart, $cartItemData);
5569
}
56-
5770
$this->cartRepository->save($cart);
71+
$this->cache->remove($ck);
5872
}
5973
}

app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,21 @@
99

1010
use Exception;
1111
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Catalog\Model\Product;
13+
use Magento\Framework\DataObject;
14+
use Magento\Quote\Model\Quote\Item;
15+
use Magento\Catalog\Model\Product\Type\AbstractType;
16+
use Magento\Framework\Model\Context;
17+
use Magento\Framework\Event\ManagerInterface;
18+
use Magento\Framework\Phrase;
19+
use Magento\Framework\Exception\LocalizedException;
1220
use Magento\Framework\Exception\NoSuchEntityException;
1321
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
1422
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
1523
use Magento\Quote\Model\Quote;
24+
use Magento\Quote\Model\Quote\Item\Processor;
1625
use Magento\QuoteGraphQl\Model\Cart\BuyRequest\BuyRequestBuilder;
26+
use Magento\Framework\App\CacheInterface;
1727

1828
/**
1929
* Add simple product to cart mutation
@@ -30,16 +40,33 @@ class AddSimpleProductToCart
3040
*/
3141
private $buyRequestBuilder;
3242

43+
/**
44+
* @var Processor
45+
*/
46+
private $itemProcessor;
47+
48+
/**
49+
* @var ManagerInterface
50+
*/
51+
private $eventManager;
52+
3353
/**
3454
* @param ProductRepositoryInterface $productRepository
3555
* @param BuyRequestBuilder $buyRequestBuilder
56+
* @param Processor $itemProcessor
57+
* @param Context $context
3658
*/
3759
public function __construct(
3860
ProductRepositoryInterface $productRepository,
39-
BuyRequestBuilder $buyRequestBuilder
61+
BuyRequestBuilder $buyRequestBuilder,
62+
Processor $itemProcessor,
63+
Context $context,
64+
CacheInterface $cache
4065
) {
4166
$this->productRepository = $productRepository;
4267
$this->buyRequestBuilder = $buyRequestBuilder;
68+
$this->itemProcessor = $itemProcessor;
69+
$this->eventManager = $context->getEventDispatcher();
4370
}
4471

4572
/**
@@ -62,7 +89,11 @@ public function execute(Quote $cart, array $cartItemData): void
6289
}
6390

6491
try {
65-
$result = $cart->addProduct($product, $this->buyRequestBuilder->build($cartItemData));
92+
$result = $this->addProductToCartWithConcurrency(
93+
$cart,
94+
$product,
95+
$this->buyRequestBuilder->build($cartItemData)
96+
);
6697
} catch (Exception $e) {
6798
throw new GraphQlInputException(
6899
__(
@@ -100,4 +131,79 @@ private function extractSku(array $cartItemData): string
100131
}
101132
return (string)$cartItemData['data']['sku'];
102133
}
134+
135+
/**
136+
* @param Quote $cart
137+
* @param Product $product
138+
* @param DataObject $request
139+
* @return Item
140+
* @throws LocalizedException
141+
*/
142+
private function addProductToCartWithConcurrency(Quote $cart, Product $product, DataObject $request) : Item
143+
{
144+
if (!$product->isSalable()) {
145+
throw new LocalizedException(
146+
__('Product that you are trying to add is not available.')
147+
);
148+
}
149+
$cartCandidates = $product->getTypeInstance()->prepareForCartAdvanced(
150+
$request,
151+
$product,
152+
AbstractType::PROCESS_MODE_FULL
153+
);
154+
if (is_string($cartCandidates) || $cartCandidates instanceof Phrase) {
155+
throw new LocalizedException((string)$cartCandidates);
156+
}
157+
if (!is_array($cartCandidates)) {
158+
$cartCandidates = [$cartCandidates];
159+
}
160+
$parentItem = null;
161+
$errors = [];
162+
$items = [];
163+
foreach ($cartCandidates as $candidate) {
164+
$stickWithinParent = $candidate->getParentProductId() ? $parentItem : null;
165+
$candidate->setStickWithinParent($stickWithinParent);
166+
$item = null;
167+
$itemsCollection = $cart->getItemsCollection(false);
168+
169+
foreach ($itemsCollection as $item) {
170+
if (!$item->isDeleted() && $item->representProduct($product)) {
171+
break;
172+
}
173+
}
174+
175+
if (!$item) {
176+
$item = $this->itemProcessor->init($candidate, $request);
177+
$item->setQuote($cart);
178+
$item->setOptions($candidate->getCustomOptions());
179+
$item->setProduct($candidate);
180+
$cart->addItem($item);
181+
}
182+
$items[] = $item;
183+
184+
if (!$parentItem) {
185+
$parentItem = $item;
186+
}
187+
if ($parentItem && $candidate->getParentProductId() && !$item->getParentItem()) {
188+
$item->setParentItem($parentItem);
189+
}
190+
191+
$this->itemProcessor->prepare($item, $request, $candidate);
192+
193+
if ($item->getHasError()) {
194+
$cart->deleteItem($item);
195+
foreach ($item->getMessage(false) as $message) {
196+
if (!in_array($message, $errors)) {
197+
$errors[] = $message;
198+
}
199+
}
200+
break;
201+
}
202+
}
203+
if (!empty($errors)) {
204+
throw new LocalizedException(__(implode("\n", $errors)));
205+
}
206+
$this->eventManager->dispatch('sales_quote_product_add_after', ['items' => $items]);
207+
return $parentItem;
208+
}
103209
}

0 commit comments

Comments
 (0)