Skip to content

Commit 15990d2

Browse files
committed
Merge remote-tracking branch 'origin/MC-33313' into 2.4.1-develop-pr25
2 parents cdf14f6 + 7213d20 commit 15990d2

File tree

2 files changed

+130
-64
lines changed

2 files changed

+130
-64
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: 99 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<?php
2-
32
/**
43
* Copyright © Magento, Inc. All rights reserved.
54
* See COPYING.txt for license details.
@@ -11,22 +10,23 @@
1110
use Magento\Catalog\Api\Data\ProductExtensionInterface;
1211
use Magento\Catalog\Model\Product;
1312
use Magento\Catalog\Model\Product\Type\Price;
14-
use Magento\CatalogInventory\Model\Stock\Item;
1513
use Magento\CatalogInventory\Model\StockRegistry;
1614
use Magento\Framework\Pricing\PriceCurrencyInterface;
1715
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
1816
use Magento\Quote\Api\Data\ShippingAssignmentInterface;
1917
use Magento\Quote\Api\Data\ShippingInterface;
2018
use Magento\Quote\Model\Quote;
2119
use Magento\Quote\Model\Quote\Address;
20+
use Magento\Quote\Model\Quote\Address\Item as AddressItem;
2221
use Magento\Quote\Model\Quote\Address\Total;
2322
use Magento\Quote\Model\Quote\Address\Total\Subtotal;
23+
use Magento\Quote\Model\Quote\Item;
2424
use Magento\Store\Model\Store;
2525
use PHPUnit\Framework\MockObject\MockObject;
2626
use PHPUnit\Framework\TestCase;
2727

2828
/**
29-
* TODO refactor me
29+
* Test address total collector model.
3030
*
3131
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
3232
*/
@@ -53,10 +53,18 @@ class SubtotalTest extends TestCase
5353
protected function setUp(): void
5454
{
5555
$this->objectManager = new ObjectManager($this);
56-
$this->subtotalModel = $this->objectManager->getObject(Subtotal::class);
56+
$this->subtotalModel = $this->objectManager->getObject(
57+
Subtotal::class
58+
);
5759

58-
$this->stockRegistry = $this->createPartialMock(StockRegistry::class, ['getStockItem']);
59-
$this->stockItemMock = $this->createPartialMock(Item::class, ['getIsInStock']);
60+
$this->stockRegistry = $this->createPartialMock(
61+
StockRegistry::class,
62+
['getStockItem', '__wakeup']
63+
);
64+
$this->stockItemMock = $this->createPartialMock(
65+
\Magento\CatalogInventory\Model\Stock\Item::class,
66+
['getIsInStock', '__wakeup']
67+
);
6068
}
6169

6270
/**
@@ -88,30 +96,28 @@ public function testCollect($price, $originalPrice, $itemHasParent, $expectedPri
8896
{
8997
$this->stockRegistry->expects($this->any())->method('getStockItem')->willReturn($this->stockItemMock);
9098

91-
$priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class)
92-
->getMock();
99+
$priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class)->getMock();
93100
$convertedPrice = 1231313;
94101
// @TODO this is a wrong test and it does not check methods. Any digital value will be correct
95102
$priceCurrency->expects($this->any())->method('convert')->willReturn(1231313);
96103

97-
/** @var \Magento\Quote\Model\Quote\Item|MockObject $quoteItem */
104+
/** @var Item|MockObject $quoteItem */
98105
$quoteItem = $this->objectManager->getObject(
99-
\Magento\Quote\Model\Quote\Item::class,
106+
Item::class,
100107
[
101108
'stockRegistry' => $this->stockRegistry,
102109
'priceCurrency' => $priceCurrency,
103110
]
104111
);
105112
/** @var Address|MockObject $address */
106-
$address = $this->getMockBuilder(Address::class)
107-
->addMethods(['setTotalQty', 'getTotalQty'])
108-
->onlyMethods(['removeItem', 'getQuote'])
109-
->disableOriginalConstructor()
110-
->getMock();
113+
$address = $this->createPartialMock(
114+
Address::class,
115+
['setTotalQty', 'getTotalQty', 'removeItem', 'getQuote']
116+
);
111117

112118
/** @var Product|MockObject $product */
113119
$product = $this->createMock(Product::class);
114-
$product->expects($this->any())->method('getPrice')->willReturn($originalPrice);
120+
$product->expects($this->any())->method('getPrice')->will($this->returnValue($originalPrice));
115121

116122
/** @var Quote|MockObject $quote */
117123
$quote = $this->createMock(Quote::class);
@@ -121,22 +127,22 @@ public function testCollect($price, $originalPrice, $itemHasParent, $expectedPri
121127
$store = $this->createPartialMock(Store::class, ['getWebsiteId']);
122128
$store->expects($this->any())->method('getWebsiteId')->willReturn(10);
123129
$product->expects($this->any())->method('getStore')->willReturn($store);
124-
$product->expects($this->any())->method('isVisibleInCatalog')->willReturn(true);
130+
$product->expects($this->any())->method('isVisibleInCatalog')->will($this->returnValue(true));
125131
$extensionAttribute = $this->getMockBuilder(ProductExtensionInterface::class)
126132
->setMethods(['getStockItem'])
127133
->disableOriginalConstructor()
128134
->getMockForAbstractClass();
129135
$extensionAttribute->expects($this->atLeastOnce())
130136
->method('getStockItem')
131-
->willReturn($this->stockItemMock);
137+
->will($this->returnValue($this->stockItemMock));
132138
$product->expects($this->atLeastOnce())->method('getExtensionAttributes')->willReturn($extensionAttribute);
133-
$quote->expects($this->any())->method('getStore')->willReturn($store);
139+
$quote->expects($this->any())->method('getStore')->will($this->returnValue($store));
134140
$quoteItem->setProduct($product)->setQuote($quote);
135141

136142
$parentQuoteItem = false;
137143
if ($itemHasParent) {
138-
$parentQuoteItem = $this->createMock(\Magento\Quote\Model\Quote\Item::class);
139-
$parentQuoteItem->expects($this->any())->method('getProduct')->willReturn($product);
144+
$parentQuoteItem = $this->createMock(Item::class);
145+
$parentQuoteItem->expects($this->any())->method('getProduct')->will($this->returnValue($product));
140146
}
141147
$quoteItem->setParentItem($parentQuoteItem);
142148
//This value will be overwritten
@@ -147,18 +153,18 @@ public function testCollect($price, $originalPrice, $itemHasParent, $expectedPri
147153
$product->expects($this->any())->method('getPriceModel')->willReturn($priceModel);
148154
$product->expects($this->any())->method('getFinalPrice')->willReturn($price);
149155

150-
$shipping = $this->getMockForAbstractClass(ShippingInterface::class);
156+
$shipping = $this->createMock(ShippingInterface::class);
151157
$shipping->expects($this->exactly(2))->method('getAddress')->willReturn($address);
152158
$address->expects($this->at(0))->method('setTotalQty')->with(0);
153159
$address->expects($this->any())->method('getTotalQty')->willReturn(0);
154-
$shippingAssignmentMock = $this->getMockForAbstractClass(ShippingAssignmentInterface::class);
160+
$shippingAssignmentMock = $this->createMock(ShippingAssignmentInterface::class);
155161
$shippingAssignmentMock->expects($this->exactly(2))->method('getShipping')->willReturn($shipping);
156162
$shippingAssignmentMock->expects($this->once())->method('getItems')->willReturn([$quoteItem]);
157163

158-
$total = $this->getMockBuilder(Total::class)
159-
->addMethods(['setBaseVirtualAmount', 'setVirtualAmount'])
160-
->disableOriginalConstructor()
161-
->getMock();
164+
$total = $this->createPartialMock(
165+
Total::class,
166+
['setBaseVirtualAmount', 'setVirtualAmount']
167+
);
162168
$total->expects($this->once())->method('setBaseVirtualAmount')->willReturnSelf();
163169
$total->expects($this->once())->method('setVirtualAmount')->willReturnSelf();
164170

@@ -179,12 +185,74 @@ public function testFetch()
179185
];
180186

181187
$quoteMock = $this->createMock(Quote::class);
182-
$totalMock = $this->getMockBuilder(Total::class)
183-
->addMethods(['getSubtotal'])
184-
->disableOriginalConstructor()
185-
->getMock();
188+
$totalMock = $this->createPartialMock(Total::class, ['getSubtotal']);
186189
$totalMock->expects($this->once())->method('getSubtotal')->willReturn(100);
187190

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

0 commit comments

Comments
 (0)