Skip to content

Commit 0aefc4b

Browse files
authored
LYNX-541: Introduce InsufficientStockError type
1 parent 6e8cf30 commit 0aefc4b

File tree

12 files changed

+539
-193
lines changed

12 files changed

+539
-193
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* ADOBE CONFIDENTIAL
5+
* ___________________
6+
*
7+
* Copyright 2024 Adobe
8+
* All Rights Reserved.
9+
*
10+
* NOTICE: All information contained herein is, and remains
11+
* the property of Adobe and its suppliers, if any. The intellectual
12+
* and technical concepts contained herein are proprietary to Adobe
13+
* and its suppliers and are protected by all applicable intellectual
14+
* property laws, including trade secret and copyright laws.
15+
* Dissemination of this information or reproduction of this material
16+
* is strictly forbidden unless prior written permission is obtained
17+
* from Adobe.
18+
* ************************************************************************
19+
*/
20+
declare(strict_types=1);
21+
22+
namespace Magento\Quote\Api;
23+
24+
interface ErrorInterface
25+
{
26+
/**
27+
* Get error code
28+
*
29+
* @return string
30+
*/
31+
public function getCode(): string;
32+
33+
/**
34+
* Get cart item position
35+
*
36+
* @return int
37+
*/
38+
public function getCartItemPosition(): int;
39+
40+
/**
41+
* Get error message
42+
*
43+
* @return string
44+
*/
45+
public function getMessage(): string;
46+
}

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

Lines changed: 54 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\Quote\Model\Cart;
99

10+
use Magento\CatalogInventory\Api\StockRegistryInterface;
1011
use Magento\Framework\Exception\NoSuchEntityException;
1112
use Magento\Quote\Api\CartRepositoryInterface;
1213
use Magento\Quote\Model\Cart\BuyRequest\BuyRequestBuilder;
@@ -21,50 +22,22 @@
2122
*/
2223
class AddProductsToCart
2324
{
24-
/**
25-
* @var CartRepositoryInterface
26-
*/
27-
private $cartRepository;
28-
29-
/**
30-
* @var MaskedQuoteIdToQuoteIdInterface
31-
*/
32-
private $maskedQuoteIdToQuoteId;
33-
34-
/**
35-
* @var BuyRequestBuilder
36-
*/
37-
private $requestBuilder;
38-
39-
/**
40-
* @var ProductReaderInterface
41-
*/
42-
private $productReader;
43-
44-
/**
45-
* @var AddProductsToCartError
46-
*/
47-
private $error;
48-
4925
/**
5026
* @param CartRepositoryInterface $cartRepository
5127
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
5228
* @param BuyRequestBuilder $requestBuilder
5329
* @param ProductReaderInterface $productReader
54-
* @param AddProductsToCartError $addProductsToCartError
30+
* @param AddProductsToCartError $error
31+
* @param StockRegistryInterface $stockRegistry
5532
*/
5633
public function __construct(
57-
CartRepositoryInterface $cartRepository,
58-
MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
59-
BuyRequestBuilder $requestBuilder,
60-
ProductReaderInterface $productReader,
61-
AddProductsToCartError $addProductsToCartError
34+
private readonly CartRepositoryInterface $cartRepository,
35+
private readonly MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
36+
private readonly BuyRequestBuilder $requestBuilder,
37+
private readonly ProductReaderInterface $productReader,
38+
private readonly AddProductsToCartError $error,
39+
private readonly StockRegistryInterface $stockRegistry
6240
) {
63-
$this->cartRepository = $cartRepository;
64-
$this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId;
65-
$this->requestBuilder = $requestBuilder;
66-
$this->productReader = $productReader;
67-
$this->error = $addProductsToCartError;
6841
}
6942

7043
/**
@@ -128,7 +101,16 @@ function ($item) {
128101
);
129102
$this->productReader->loadProducts($skus, $cart->getStoreId());
130103
foreach ($cartItems as $cartItemPosition => $cartItem) {
131-
$errors = $this->addItemToCart($cart, $cartItem, $cartItemPosition);
104+
$product = $this->productReader->getProductBySku($cartItem->getSku());
105+
$stockItemQuantity = 0.0;
106+
if ($product) {
107+
$stockItem = $this->stockRegistry->getStockItem(
108+
$product->getId(),
109+
$cart->getStore()->getWebsiteId()
110+
);
111+
$stockItemQuantity = $stockItem->getQty() - $stockItem->getMinQty();
112+
}
113+
$errors = $this->addItemToCart($cart, $cartItem, $cartItemPosition, $stockItemQuantity);
132114
if ($errors) {
133115
$failedCartItems[$cartItemPosition] = $errors;
134116
}
@@ -143,42 +125,53 @@ function ($item) {
143125
* @param Quote $cart
144126
* @param Data\CartItem $cartItem
145127
* @param int $cartItemPosition
128+
* @param float $stockItemQuantity
146129
* @return array
147130
*/
148-
private function addItemToCart(Quote $cart, Data\CartItem $cartItem, int $cartItemPosition): array
149-
{
131+
private function addItemToCart(
132+
Quote $cart,
133+
Data\CartItem $cartItem,
134+
int $cartItemPosition,
135+
float $stockItemQuantity
136+
): array {
150137
$sku = $cartItem->getSku();
151138
$errors = [];
152139
$result = null;
153140

154141
if ($cartItem->getQuantity() <= 0) {
155142
$errors[] = $this->error->create(
156143
__('The product quantity should be greater than 0')->render(),
157-
$cartItemPosition
144+
$cartItemPosition,
145+
$stockItemQuantity
158146
);
159-
} else {
160-
$productBySku = $this->productReader->getProductBySku($sku);
161-
$product = isset($productBySku) ? clone $productBySku : null;
162-
if (!$product || !$product->isSaleable() || !$product->isAvailable()) {
163-
$errors[] = $this->error->create(
147+
}
148+
149+
$productBySku = $this->productReader->getProductBySku($sku);
150+
$product = isset($productBySku) ? clone $productBySku : null;
151+
152+
if (!$product || !$product->isSaleable() || !$product->isAvailable()) {
153+
return [
154+
$this->error->create(
164155
__('Could not find a product with SKU "%sku"', ['sku' => $sku])->render(),
165-
$cartItemPosition
166-
);
167-
} else {
168-
try {
169-
$result = $cart->addProduct($product, $this->requestBuilder->build($cartItem));
170-
} catch (\Throwable $e) {
171-
$errors[] = $this->error->create(
172-
__($e->getMessage())->render(),
173-
$cartItemPosition
174-
);
175-
}
176-
}
156+
$cartItemPosition,
157+
$stockItemQuantity
158+
)
159+
];
160+
}
177161

178-
if (is_string($result)) {
179-
foreach (array_unique(explode("\n", $result)) as $error) {
180-
$errors[] = $this->error->create(__($error)->render(), $cartItemPosition);
181-
}
162+
try {
163+
$result = $cart->addProduct($product, $this->requestBuilder->build($cartItem));
164+
} catch (\Throwable $e) {
165+
$errors[] = $this->error->create(
166+
__($e->getMessage())->render(),
167+
$cartItemPosition,
168+
$stockItemQuantity
169+
);
170+
}
171+
172+
if (is_string($result)) {
173+
foreach (array_unique(explode("\n", $result)) as $error) {
174+
$errors[] = $this->error->create(__($error)->render(), $cartItemPosition, $stockItemQuantity);
182175
}
183176
}
184177

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

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,42 @@
77

88
namespace Magento\Quote\Model\Cart;
99

10+
use Magento\Quote\Api\ErrorInterface;
11+
1012
/**
1113
* Create instances of errors on adding products to cart. Identify error code based on the message
1214
*/
1315
class AddProductsToCartError
1416
{
15-
/**#@+
16-
* Error message codes
17-
*/
18-
private const ERROR_PRODUCT_NOT_FOUND = 'PRODUCT_NOT_FOUND';
19-
private const ERROR_INSUFFICIENT_STOCK = 'INSUFFICIENT_STOCK';
20-
private const ERROR_NOT_SALABLE = 'NOT_SALABLE';
2117
private const ERROR_UNDEFINED = 'UNDEFINED';
22-
/**#@-*/
2318

2419
/**
25-
* List of error messages and codes.
20+
* @param array $errorMessageCodesMapper
2621
*/
27-
private const MESSAGE_CODES = [
28-
'Could not find a product with SKU' => self::ERROR_PRODUCT_NOT_FOUND,
29-
'The required options you selected are not available' => self::ERROR_NOT_SALABLE,
30-
'Product that you are trying to add is not available.' => self::ERROR_NOT_SALABLE,
31-
'This product is out of stock' => self::ERROR_INSUFFICIENT_STOCK,
32-
'There are no source items' => self::ERROR_NOT_SALABLE,
33-
'The fewest you may purchase is' => self::ERROR_INSUFFICIENT_STOCK,
34-
'The most you may purchase is' => self::ERROR_INSUFFICIENT_STOCK,
35-
'The requested qty is not available' => self::ERROR_INSUFFICIENT_STOCK,
36-
'Not enough items for sale' => self::ERROR_INSUFFICIENT_STOCK,
37-
'Only %s of %s available' => self::ERROR_INSUFFICIENT_STOCK,
38-
];
22+
public function __construct(
23+
private readonly array $errorMessageCodesMapper
24+
) {
25+
}
3926

4027
/**
4128
* Returns an error object
4229
*
4330
* @param string $message
4431
* @param int $cartItemPosition
32+
* @param float $stockItemQuantity
4533
* @return Data\Error
4634
*/
47-
public function create(string $message, int $cartItemPosition = 0): Data\Error
48-
{
49-
return new Data\Error(
35+
public function create(
36+
string $message,
37+
int $cartItemPosition = 0,
38+
float $stockItemQuantity = 0.0
39+
): ErrorInterface {
40+
41+
return new Data\InsufficientStockError(
5042
$message,
5143
$this->getErrorCode($message),
52-
$cartItemPosition
44+
$cartItemPosition,
45+
$stockItemQuantity
5346
);
5447
}
5548

@@ -62,7 +55,7 @@ public function create(string $message, int $cartItemPosition = 0): Data\Error
6255
private function getErrorCode(string $message): string
6356
{
6457
$message = preg_replace('/\d+/', '%s', $message);
65-
foreach (self::MESSAGE_CODES as $codeMessage => $code) {
58+
foreach ($this->errorMessageCodesMapper as $codeMessage => $code) {
6659
if (false !== stripos($message, $codeMessage)) {
6760
return $code;
6861
}

app/code/Magento/Quote/Model/Cart/Data/Error.php

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,39 @@
11
<?php
2-
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
2+
/************************************************************************
3+
* Copyright 2024 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
13+
* from Adobe.
14+
* ************************************************************************
515
*/
616
declare(strict_types=1);
717

818
namespace Magento\Quote\Model\Cart\Data;
919

20+
use Magento\Quote\Api\ErrorInterface;
21+
1022
/**
1123
* DTO represents error item
1224
*/
13-
class Error
25+
class Error implements ErrorInterface
1426
{
15-
/**
16-
* @var string
17-
*/
18-
private $message;
19-
20-
/**
21-
* @var string
22-
*/
23-
private $code;
24-
25-
/**
26-
* @var int
27-
*/
28-
private $cartItemPosition;
29-
3027
/**
3128
* @param string $message
3229
* @param string $code
3330
* @param int $cartItemPosition
3431
*/
35-
public function __construct(string $message, string $code, int $cartItemPosition)
36-
{
37-
$this->message = $message;
38-
$this->code = $code;
39-
$this->cartItemPosition = $cartItemPosition;
32+
public function __construct(
33+
private readonly string $message,
34+
private readonly string $code,
35+
private readonly int $cartItemPosition
36+
) {
4037
}
4138

4239
/**

0 commit comments

Comments
 (0)