Skip to content

Commit d4edadd

Browse files
bubasumarostyslav-hymon
authored andcommitted
MC-33313: [Magento Cloud] - Error when assign customer to a group
1 parent 1014a94 commit d4edadd

File tree

2 files changed

+143
-63
lines changed

2 files changed

+143
-63
lines changed

app/code/Magento/Quote/Model/Quote/Address/Total/Subtotal.php

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
use Magento\Quote\Model\Quote\Address\Item as AddressItem;
1010
use Magento\Quote\Model\Quote\Item;
1111

12+
/**
13+
* Address total collector model
14+
*/
1215
class Subtotal extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal
1316
{
1417
/**
@@ -89,40 +92,35 @@ protected function _initItem($address, $item)
8992
} else {
9093
$quoteItem = $item;
9194
}
92-
$product = $quoteItem->getProduct();
93-
$product->setCustomerGroupId($quoteItem->getQuote()->getCustomerGroupId());
94-
95-
/**
96-
* Quote super mode flag mean what we work with quote without restriction
97-
*/
98-
if ($item->getQuote()->getIsSuperMode()) {
99-
if (!$product) {
100-
return false;
101-
}
102-
} else {
103-
if (!$product || !$product->isVisibleInCatalog()) {
104-
return false;
95+
$valid = false;
96+
if ($quoteItem) {
97+
$product = $quoteItem->getProduct();
98+
/**
99+
* Quote super mode flag mean what we work with quote without restriction
100+
*/
101+
if ($product && ($item->getQuote()->getIsSuperMode() || $product->isVisibleInCatalog())) {
102+
$product->setCustomerGroupId($quoteItem->getQuote()->getCustomerGroupId());
103+
$quoteItem->setConvertedPrice(null);
104+
$originalPrice = $product->getPrice();
105+
if ($quoteItem->getParentItem() && $quoteItem->isChildrenCalculated()) {
106+
$finalPrice = $quoteItem->getParentItem()->getProduct()->getPriceModel()->getChildFinalPrice(
107+
$quoteItem->getParentItem()->getProduct(),
108+
$quoteItem->getParentItem()->getQty(),
109+
$product,
110+
$quoteItem->getQty()
111+
);
112+
$this->_calculateRowTotal($item, $finalPrice, $originalPrice);
113+
} elseif (!$quoteItem->getParentItem()) {
114+
$finalPrice = $product->getFinalPrice($quoteItem->getQty());
115+
$this->_calculateRowTotal($item, $finalPrice, $originalPrice);
116+
$this->_addAmount($item->getRowTotal());
117+
$this->_addBaseAmount($item->getBaseRowTotal());
118+
$address->setTotalQty($address->getTotalQty() + $item->getQty());
119+
}
120+
$valid = true;
105121
}
106122
}
107-
108-
$quoteItem->setConvertedPrice(null);
109-
$originalPrice = $product->getPrice();
110-
if ($quoteItem->getParentItem() && $quoteItem->isChildrenCalculated()) {
111-
$finalPrice = $quoteItem->getParentItem()->getProduct()->getPriceModel()->getChildFinalPrice(
112-
$quoteItem->getParentItem()->getProduct(),
113-
$quoteItem->getParentItem()->getQty(),
114-
$product,
115-
$quoteItem->getQty()
116-
);
117-
$this->_calculateRowTotal($item, $finalPrice, $originalPrice);
118-
} elseif (!$quoteItem->getParentItem()) {
119-
$finalPrice = $product->getFinalPrice($quoteItem->getQty());
120-
$this->_calculateRowTotal($item, $finalPrice, $originalPrice);
121-
$this->_addAmount($item->getRowTotal());
122-
$this->_addBaseAmount($item->getBaseRowTotal());
123-
$address->setTotalQty($address->getTotalQty() + $item->getQty());
124-
}
125-
return true;
123+
return $valid;
126124
}
127125

128126
/**
@@ -147,7 +145,7 @@ protected function _calculateRowTotal($item, $finalPrice, $originalPrice)
147145
* Remove item
148146
*
149147
* @param Address $address
150-
* @param AddressItem|Item $item
148+
* @param AddressItem|Item $item
151149
* @return $this
152150
*/
153151
protected function _removeItem($address, $item)

app/code/Magento/Quote/Test/Unit/Model/Quote/Address/Total/SubtotalTest.php

Lines changed: 112 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,59 @@
55
*/
66
namespace Magento\Quote\Test\Unit\Model\Quote\Address\Total;
77

8+
use Magento\Catalog\Api\Data\ProductExtensionInterface;
9+
use Magento\Catalog\Model\Product;
10+
use Magento\Catalog\Model\Product\Type\Price;
11+
use Magento\CatalogInventory\Model\StockRegistry;
12+
use Magento\Framework\Pricing\PriceCurrencyInterface;
13+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
14+
use Magento\Quote\Api\Data\ShippingAssignmentInterface;
15+
use Magento\Quote\Api\Data\ShippingInterface;
16+
use Magento\Quote\Model\Quote;
17+
use Magento\Quote\Model\Quote\Address;
18+
use Magento\Quote\Model\Quote\Address\Item as AddressItem;
19+
use Magento\Quote\Model\Quote\Address\Total;
20+
use Magento\Quote\Model\Quote\Address\Total\Subtotal;
21+
use Magento\Quote\Model\Quote\Item;
22+
use Magento\Store\Model\Store;
23+
use PHPUnit\Framework\MockObject\MockObject;
24+
use PHPUnit\Framework\TestCase;
25+
use PHPUnit_Framework_MockObject_MockObject;
26+
827
/**
9-
* Class SubtotalTest
10-
* @package Magento\Quote\Model\Quote\Address\Total
11-
* TODO refactor me
28+
* Test address total collector model.
1229
*
1330
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1431
*/
15-
class SubtotalTest extends \PHPUnit\Framework\TestCase
32+
class SubtotalTest extends TestCase
1633
{
1734
/**
18-
* @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
35+
* @var ObjectManager
1936
*/
2037
protected $objectManager;
2138

2239
/**
23-
* @var \Magento\Quote\Model\Quote\Address\Total\Subtotal
40+
* @var Subtotal
2441
*/
2542
protected $subtotalModel;
2643

27-
/** @var \PHPUnit_Framework_MockObject_MockObject */
44+
/** @var MockObject */
2845
protected $stockItemMock;
2946

3047
/**
31-
* @var \PHPUnit_Framework_MockObject_MockObject
48+
* @var MockObject
3249
*/
3350
protected $stockRegistry;
3451

3552
protected function setUp()
3653
{
37-
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
54+
$this->objectManager = new ObjectManager($this);
3855
$this->subtotalModel = $this->objectManager->getObject(
39-
\Magento\Quote\Model\Quote\Address\Total\Subtotal::class
56+
Subtotal::class
4057
);
4158

4259
$this->stockRegistry = $this->createPartialMock(
43-
\Magento\CatalogInventory\Model\StockRegistry::class,
60+
StockRegistry::class,
4461
['getStockItem', '__wakeup']
4562
);
4663
$this->stockItemMock = $this->createPartialMock(
@@ -78,39 +95,39 @@ public function testCollect($price, $originalPrice, $itemHasParent, $expectedPri
7895
{
7996
$this->stockRegistry->expects($this->any())->method('getStockItem')->willReturn($this->stockItemMock);
8097

81-
$priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class)->getMock();
98+
$priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class)->getMock();
8299
$convertedPrice = 1231313;
83100
// @TODO this is a wrong test and it does not check methods. Any digital value will be correct
84101
$priceCurrency->expects($this->any())->method('convert')->willReturn(1231313);
85102

86-
/** @var \Magento\Quote\Model\Quote\Item|\PHPUnit_Framework_MockObject_MockObject $quoteItem */
103+
/** @var Item|PHPUnit_Framework_MockObject_MockObject $quoteItem */
87104
$quoteItem = $this->objectManager->getObject(
88-
\Magento\Quote\Model\Quote\Item::class,
105+
Item::class,
89106
[
90107
'stockRegistry' => $this->stockRegistry,
91108
'priceCurrency' => $priceCurrency,
92109
]
93110
);
94-
/** @var \Magento\Quote\Model\Quote\Address|\PHPUnit_Framework_MockObject_MockObject $address */
111+
/** @var Address|MockObject $address */
95112
$address = $this->createPartialMock(
96-
\Magento\Quote\Model\Quote\Address::class,
113+
Address::class,
97114
['setTotalQty', 'getTotalQty', 'removeItem', 'getQuote']
98115
);
99116

100-
/** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */
101-
$product = $this->createMock(\Magento\Catalog\Model\Product::class);
117+
/** @var Product|MockObject $product */
118+
$product = $this->createMock(Product::class);
102119
$product->expects($this->any())->method('getPrice')->will($this->returnValue($originalPrice));
103120

104-
/** @var \Magento\Quote\Model\Quote|\PHPUnit_Framework_MockObject_MockObject $quote */
105-
$quote = $this->createMock(\Magento\Quote\Model\Quote::class);
106-
$store = $this->objectManager->getObject(\Magento\Store\Model\Store::class);
121+
/** @var Quote|MockObject $quote */
122+
$quote = $this->createMock(Quote::class);
123+
$store = $this->objectManager->getObject(Store::class);
107124
$store->setCurrentCurrency('');
108125

109-
$store = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getWebsiteId']);
126+
$store = $this->createPartialMock(Store::class, ['getWebsiteId']);
110127
$store->expects($this->any())->method('getWebsiteId')->willReturn(10);
111128
$product->expects($this->any())->method('getStore')->willReturn($store);
112129
$product->expects($this->any())->method('isVisibleInCatalog')->will($this->returnValue(true));
113-
$extensionAttribute = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtensionInterface::class)
130+
$extensionAttribute = $this->getMockBuilder(ProductExtensionInterface::class)
114131
->setMethods(['getStockItem'])
115132
->disableOriginalConstructor()
116133
->getMockForAbstractClass();
@@ -123,28 +140,28 @@ public function testCollect($price, $originalPrice, $itemHasParent, $expectedPri
123140

124141
$parentQuoteItem = false;
125142
if ($itemHasParent) {
126-
$parentQuoteItem = $this->createMock(\Magento\Quote\Model\Quote\Item::class);
143+
$parentQuoteItem = $this->createMock(Item::class);
127144
$parentQuoteItem->expects($this->any())->method('getProduct')->will($this->returnValue($product));
128145
}
129146
$quoteItem->setParentItem($parentQuoteItem);
130147
//This value will be overwritten
131148
$quoteItem->setConvertedPrice(10);
132149

133-
$priceModel = $this->createMock(\Magento\Catalog\Model\Product\Type\Price::class);
150+
$priceModel = $this->createMock(Price::class);
134151
$priceModel->expects($this->any())->method('getChildFinalPrice')->willReturn($price);
135152
$product->expects($this->any())->method('getPriceModel')->willReturn($priceModel);
136153
$product->expects($this->any())->method('getFinalPrice')->willReturn($price);
137154

138-
$shipping = $this->createMock(\Magento\Quote\Api\Data\ShippingInterface::class);
155+
$shipping = $this->createMock(ShippingInterface::class);
139156
$shipping->expects($this->exactly(2))->method('getAddress')->willReturn($address);
140157
$address->expects($this->at(0))->method('setTotalQty')->with(0);
141158
$address->expects($this->any())->method('getTotalQty')->willReturn(0);
142-
$shippingAssignmentMock = $this->createMock(\Magento\Quote\Api\Data\ShippingAssignmentInterface::class);
159+
$shippingAssignmentMock = $this->createMock(ShippingAssignmentInterface::class);
143160
$shippingAssignmentMock->expects($this->exactly(2))->method('getShipping')->willReturn($shipping);
144161
$shippingAssignmentMock->expects($this->once())->method('getItems')->willReturn([$quoteItem]);
145162

146163
$total = $this->createPartialMock(
147-
\Magento\Quote\Model\Quote\Address\Total::class,
164+
Total::class,
148165
['setBaseVirtualAmount', 'setVirtualAmount']
149166
);
150167
$total->expects($this->once())->method('setBaseVirtualAmount')->willReturnSelf();
@@ -166,10 +183,75 @@ public function testFetch()
166183
'value' => 100
167184
];
168185

169-
$quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
170-
$totalMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address\Total::class, ['getSubtotal']);
186+
$quoteMock = $this->createMock(Quote::class);
187+
$totalMock = $this->createPartialMock(Total::class, ['getSubtotal']);
171188
$totalMock->expects($this->once())->method('getSubtotal')->willReturn(100);
172189

173190
$this->assertEquals($expectedResult, $this->subtotalModel->fetch($quoteMock, $totalMock));
174191
}
192+
193+
/**
194+
* Test that invalid items are not collected
195+
*/
196+
public function testCollectWithInvalidItems()
197+
{
198+
$addressItemId = 38203;
199+
$addressQuoteItemId = 7643;
200+
$storeId = 1;
201+
$quote = $this->createPartialMock(
202+
Quote::class,
203+
[
204+
'getItemsCollection',
205+
]
206+
);
207+
$quote->setData(
208+
[
209+
'store_id' => $storeId
210+
]
211+
);
212+
$quoteItem = $this->createPartialMock(
213+
Item::class,
214+
[]
215+
);
216+
$quoteItem->setQuote($quote);
217+
$quote->method('getItemsCollection')
218+
->willReturn([$quoteItem]);
219+
$address = $this->createPartialMock(
220+
Address::class,
221+
[
222+
'removeItem',
223+
'getQuote'
224+
]
225+
);
226+
$address->method('getQuote')
227+
->willReturn($quote);
228+
$address->expects($this->once())
229+
->method('removeItem')
230+
->with($addressItemId);
231+
$addressItem = $this->createPartialMock(
232+
AddressItem::class,
233+
[
234+
'getId',
235+
'getQuoteItemId'
236+
]
237+
);
238+
$addressItem->setAddress($address);
239+
$addressItem->method('getId')
240+
->willReturn($addressItemId);
241+
$addressItem->method('getQuoteItemId')
242+
->willReturn($addressQuoteItemId);
243+
$shipping = $this->createMock(ShippingInterface::class);
244+
$shipping->method('getAddress')
245+
->willReturn($address);
246+
$shippingAssignmentMock = $this->createMock(ShippingAssignmentInterface::class);
247+
$shippingAssignmentMock->method('getShipping')
248+
->willReturn($shipping);
249+
$shippingAssignmentMock->method('getItems')
250+
->willReturn([$addressItem]);
251+
$total = $this->createPartialMock(
252+
Total::class,
253+
[]
254+
);
255+
$this->subtotalModel->collect($quote, $shippingAssignmentMock, $total);
256+
}
175257
}

0 commit comments

Comments
 (0)