Skip to content

Commit c836b89

Browse files
committed
MAGETWO-93702: [2.3] User can place order when product changes status to Out of stock during checkout
1 parent 602ba15 commit c836b89

File tree

7 files changed

+249
-68
lines changed

7 files changed

+249
-68
lines changed

app/code/Magento/CatalogInventory/Model/Stock/Status.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ public function getQty()
106106
/**
107107
* @return int
108108
*/
109-
public function getStockStatus()
109+
public function getStockStatus(): int
110110
{
111-
return $this->getData(self::KEY_STOCK_STATUS);
111+
return (int)$this->getData(self::KEY_STOCK_STATUS);
112112
}
113113

114114
//@codeCoverageIgnoreEnd

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
namespace Magento\Quote\Model;
88

9-
use Magento\Framework\Exception\LocalizedException;
10-
use Magento\Quote\Model\Quote as QuoteEntity;
119
use Magento\Directory\Model\AllowedCountries;
1210
use Magento\Framework\App\ObjectManager;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\Message\Error;
13+
use Magento\Quote\Model\Quote as QuoteEntity;
1314
use Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage as OrderAmountValidationMessage;
1415
use Magento\Quote\Model\ValidationRules\QuoteValidationRuleInterface;
1516

@@ -72,18 +73,24 @@ public function validateQuoteAmount(QuoteEntity $quote, $amount)
7273
$quote->setHasError(true);
7374
$quote->addMessage(__('This item price or quantity is not valid for checkout.'));
7475
}
76+
7577
return $this;
7678
}
7779

7880
/**
79-
* Validate quote before submit
81+
* Validates quote before submit.
8082
*
8183
* @param Quote $quote
8284
* @return $this
83-
* @throws \Magento\Framework\Exception\LocalizedException
85+
* @throws LocalizedException
8486
*/
8587
public function validateBeforeSubmit(QuoteEntity $quote)
8688
{
89+
if ($quote->getHasError()) {
90+
$errors = $this->getQuoteErrors($quote);
91+
throw new LocalizedException(__($errors ?: 'Something went wrong. Please try to place the order again.'));
92+
}
93+
8794
foreach ($this->quoteValidationRule->validate($quote) as $validationResult) {
8895
if ($validationResult->isValid()) {
8996
continue;
@@ -101,4 +108,22 @@ public function validateBeforeSubmit(QuoteEntity $quote)
101108

102109
return $this;
103110
}
111+
112+
/**
113+
* Parses quote error messages and concatenates them into single string.
114+
*
115+
* @param Quote $quote
116+
* @return string
117+
*/
118+
private function getQuoteErrors(QuoteEntity $quote): string
119+
{
120+
$errors = array_map(
121+
function (Error $error) {
122+
return $error->getText();
123+
},
124+
$quote->getErrors()
125+
);
126+
127+
return implode(PHP_EOL, $errors);
128+
}
104129
}

dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,9 @@ public function testGetOptions()
581581
'4-2-radio' => 40000.00
582582
];
583583
foreach ($options as $option) {
584+
if (!$option->getValues()) {
585+
continue;
586+
}
584587
foreach ($option->getValues() as $value) {
585588
$this->assertEquals($expectedValue[$value->getSku()], floatval($value->getPrice()));
586589
}

dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,18 @@
8686
'sku' => '4-2-radio',
8787
],
8888
]
89-
]
89+
],
90+
[
91+
'previous_group' => 'text',
92+
'title' => 'Test Field',
93+
'type' => 'field',
94+
'is_require' => 1,
95+
'sort_order' => 0,
96+
'price' => 1,
97+
'price_type' => 'fixed',
98+
'sku' => '1-text',
99+
'max_characters' => 100,
100+
],
90101
];
91102

92103
$options = [];

dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111

1212
use Magento\Catalog\Api\ProductRepositoryInterface;
1313
use Magento\Checkout\Model\Session;
14+
use Magento\Checkout\Model\Session as CheckoutSession;
1415
use Magento\Customer\Model\ResourceModel\CustomerRepository;
1516
use Magento\Framework\Data\Form\FormKey;
17+
use Magento\Framework\Api\SearchCriteriaBuilder;
18+
use Magento\Quote\Model\Quote;
19+
use Magento\Quote\Api\CartRepositoryInterface;
1620
use Magento\TestFramework\Helper\Bootstrap;
1721
use Magento\TestFramework\Request;
1822
use Magento\Customer\Model\Session as CustomerSession;
@@ -25,15 +29,37 @@
2529
*/
2630
class CartTest extends \Magento\TestFramework\TestCase\AbstractController
2731
{
32+
/** @var CheckoutSession */
33+
private $checkoutSession;
34+
35+
/**
36+
* @inheritdoc
37+
*/
38+
protected function setUp()
39+
{
40+
parent::setUp();
41+
$this->checkoutSession = $this->_objectManager->get(CheckoutSession::class);
42+
$this->_objectManager->addSharedInstance($this->checkoutSession, CheckoutSession::class);
43+
}
44+
45+
/**
46+
* @inheritdoc
47+
*/
48+
protected function tearDown()
49+
{
50+
$this->_objectManager->removeSharedInstance(CheckoutSession::class);
51+
parent::tearDown();
52+
}
53+
2854
/**
2955
* Test for \Magento\Checkout\Controller\Cart::configureAction() with simple product
3056
*
3157
* @magentoDataFixture Magento/Checkout/_files/quote_with_simple_product.php
3258
*/
3359
public function testConfigureActionWithSimpleProduct()
3460
{
35-
/** @var $session \Magento\Checkout\Model\Session */
36-
$session = $this->_objectManager->create(\Magento\Checkout\Model\Session::class);
61+
/** @var $session CheckoutSession */
62+
$session = $this->_objectManager->create(CheckoutSession::class);
3763

3864
/** @var ProductRepositoryInterface $productRepository */
3965
$productRepository = $this->_objectManager->create(ProductRepositoryInterface::class);
@@ -63,19 +89,20 @@ public function testConfigureActionWithSimpleProduct()
6389
/**
6490
* Test for \Magento\Checkout\Controller\Cart::configureAction() with simple product and custom option
6591
*
66-
* @magentoDataFixture Magento/Checkout/_files/quote_with_simple_product_and_custom_option.php
92+
* @magentoDataFixture Magento/Checkout/_files/cart_with_simple_product_and_custom_options.php
6793
*/
6894
public function testConfigureActionWithSimpleProductAndCustomOption()
6995
{
70-
/** @var $session \Magento\Checkout\Model\Session */
71-
$session = $this->_objectManager->create(\Magento\Checkout\Model\Session::class);
96+
/** @var Quote $quote */
97+
$quote = $this->getQuote('test_order_item_with_custom_options');
98+
$this->checkoutSession->setQuoteId($quote->getId());
7299

73100
/** @var ProductRepositoryInterface $productRepository */
74101
$productRepository = $this->_objectManager->create(ProductRepositoryInterface::class);
75102
/** @var $product \Magento\Catalog\Model\Product */
76-
$product = $productRepository->get('simple');
103+
$product = $productRepository->get('simple_with_custom_options');
77104

78-
$quoteItem = $this->_getQuoteItemIdByProductId($session->getQuote(), $product->getId());
105+
$quoteItem = $this->_getQuoteItemIdByProductId($quote, $product->getId());
79106
$this->assertNotNull($quoteItem, 'Cannot get quote item for simple product with custom option');
80107

81108
$this->dispatch(
@@ -112,8 +139,8 @@ public function testConfigureActionWithSimpleProductAndCustomOption()
112139
*/
113140
public function testConfigureActionWithBundleProduct()
114141
{
115-
/** @var $session \Magento\Checkout\Model\Session */
116-
$session = $this->_objectManager->create(\Magento\Checkout\Model\Session::class);
142+
/** @var $session CheckoutSession */
143+
$session = $this->_objectManager->create(CheckoutSession::class);
117144

118145
/** @var ProductRepositoryInterface $productRepository */
119146
$productRepository = $this->_objectManager->create(ProductRepositoryInterface::class);
@@ -147,8 +174,8 @@ public function testConfigureActionWithBundleProduct()
147174
*/
148175
public function testConfigureActionWithDownloadableProduct()
149176
{
150-
/** @var $session \Magento\Checkout\Model\Session */
151-
$session = $this->_objectManager->create(\Magento\Checkout\Model\Session::class);
177+
/** @var $session CheckoutSession */
178+
$session = $this->_objectManager->create(CheckoutSession::class);
152179

153180
/** @var ProductRepositoryInterface $productRepository */
154181
$productRepository = $this->_objectManager->create(ProductRepositoryInterface::class);
@@ -201,8 +228,8 @@ public function testUpdatePostAction()
201228
$productId = $product->getId();
202229
$originalQuantity = 1;
203230
$updatedQuantity = 2;
204-
/** @var $checkoutSession \Magento\Checkout\Model\Session */
205-
$checkoutSession = $this->_objectManager->create(\Magento\Checkout\Model\Session::class);
231+
/** @var $checkoutSession CheckoutSession */
232+
$checkoutSession = $this->_objectManager->create(CheckoutSession::class);
206233
$quoteItem = $this->_getQuoteItemIdByProductId($checkoutSession->getQuote(), $productId);
207234

208235
/** @var FormKey $formKey */
@@ -235,6 +262,26 @@ public function testUpdatePostAction()
235262
$this->assertEquals($updatedQuantity, $quoteItem->getQty(), "Invalid quote item quantity");
236263
}
237264

265+
/**
266+
* Gets quote by reserved order id.
267+
*
268+
* @param string $reservedOrderId
269+
* @return Quote
270+
*/
271+
private function getQuote($reservedOrderId)
272+
{
273+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
274+
$searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class);
275+
$searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId)
276+
->create();
277+
278+
/** @var CartRepositoryInterface $quoteRepository */
279+
$quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class);
280+
$items = $quoteRepository->getList($searchCriteria)->getItems();
281+
282+
return array_pop($items);
283+
}
284+
238285
/**
239286
* Gets \Magento\Quote\Model\Quote\Item from \Magento\Quote\Model\Quote by product id
240287
*
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
use Magento\Catalog\Api\Data\ProductCustomOptionInterface;
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Catalog\Model\Product\Option;
10+
use Magento\Checkout\Model\Session;
11+
use Magento\Framework\DataObject;
12+
use Magento\Quote\Model\Quote;
13+
use Magento\TestFramework\Helper\Bootstrap;
14+
15+
require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_with_custom_options.php';
16+
17+
/** @var ProductRepositoryInterface $productRepository */
18+
$productRepository = Bootstrap::getObjectManager()
19+
->create(ProductRepositoryInterface::class);
20+
$product = $productRepository->get('simple_with_custom_options');
21+
22+
$options = [];
23+
/** @var $option Option */
24+
foreach ($product->getOptions() as $option) {
25+
switch ($option->getGroupByType()) {
26+
case ProductCustomOptionInterface::OPTION_GROUP_SELECT:
27+
$value = key($option->getValues());
28+
break;
29+
default:
30+
$value = 'test';
31+
break;
32+
}
33+
$options[$option->getId()] = $value;
34+
}
35+
36+
$requestInfo = new DataObject(['qty' => 1, 'options' => $options]);
37+
38+
/** @var $cart \Magento\Checkout\Model\Cart */
39+
$quote = Bootstrap::getObjectManager()->create(Quote::class);
40+
$quote->setReservedOrderId('test_order_item_with_custom_options');
41+
$quote->addProduct($product, $requestInfo);
42+
$quote->save();
43+
44+
/** @var $objectManager \Magento\TestFramework\ObjectManager */
45+
$objectManager = Bootstrap::getObjectManager();
46+
$objectManager->removeSharedInstance(Session::class);

0 commit comments

Comments
 (0)