Skip to content

Commit 0760690

Browse files
committed
Minicart Subtotal is doubled if quantity is updated
1 parent 2a774fc commit 0760690

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Multishipping\Model\Cart\Controller;
9+
10+
use Magento\Checkout\Controller\Sidebar\UpdateItemQty;
11+
use Magento\Checkout\Model\Session;
12+
use Magento\Customer\Api\AddressRepositoryInterface;
13+
use Magento\Framework\App\RequestInterface;
14+
use Magento\Framework\Exception\LocalizedException;
15+
use Magento\Multishipping\Model\DisableMultishipping;
16+
use Magento\Quote\Api\CartRepositoryInterface;
17+
use Magento\Quote\Model\Quote;
18+
19+
/**
20+
* Cleans shipping addresses and item assignments after MultiShipping flow
21+
*/
22+
class MiniCartPlugin
23+
{
24+
/**
25+
* @var CartRepositoryInterface
26+
*/
27+
private $cartRepository;
28+
29+
/**
30+
* @var Session
31+
*/
32+
private $checkoutSession;
33+
34+
/**
35+
* @var AddressRepositoryInterface
36+
*/
37+
private $addressRepository;
38+
39+
/**
40+
* @var DisableMultishipping
41+
*/
42+
private $disableMultishipping;
43+
44+
/**
45+
* @param CartRepositoryInterface $cartRepository
46+
* @param Session $checkoutSession
47+
* @param AddressRepositoryInterface $addressRepository
48+
* @param DisableMultishipping $disableMultishipping
49+
*/
50+
public function __construct(
51+
CartRepositoryInterface $cartRepository,
52+
Session $checkoutSession,
53+
AddressRepositoryInterface $addressRepository,
54+
DisableMultishipping $disableMultishipping
55+
) {
56+
$this->cartRepository = $cartRepository;
57+
$this->checkoutSession = $checkoutSession;
58+
$this->addressRepository = $addressRepository;
59+
$this->disableMultishipping = $disableMultishipping;
60+
}
61+
62+
/**
63+
* Cleans shipping addresses and item assignments after MultiShipping flow
64+
*
65+
* @param UpdateItemQty $subject
66+
* @param RequestInterface $request
67+
* @return void
68+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
69+
* @throws LocalizedException
70+
*/
71+
public function beforeDispatch(UpdateItemQty $subject, RequestInterface $request)
72+
{
73+
/** @var Quote $quote */
74+
$quote = $this->checkoutSession->getQuote();
75+
$isMultipleShippingAddressesPresent = $quote->isMultipleShippingAddresses();
76+
if ($isMultipleShippingAddressesPresent || $this->isDisableMultishippingRequired($request, $quote)) {
77+
$this->disableMultishipping->execute($quote);
78+
foreach ($quote->getAllShippingAddresses() as $address) {
79+
$quote->removeAddress($address->getId());
80+
}
81+
82+
$shippingAddress = $quote->getShippingAddress();
83+
$defaultShipping = $quote->getCustomer()->getDefaultShipping();
84+
if ($defaultShipping) {
85+
$defaultCustomerAddress = $this->addressRepository->getById($defaultShipping);
86+
$shippingAddress->importCustomerAddressData($defaultCustomerAddress);
87+
}
88+
if ($isMultipleShippingAddressesPresent) {
89+
$this->checkoutSession->setMultiShippingAddressesFlag(true);
90+
}
91+
$quote->setTotalsCollectedFlag(false);
92+
$this->cartRepository->save($quote);
93+
} elseif ($this->disableMultishipping->execute($quote) && $this->isVirtualItemInQuote($quote)) {
94+
$quote->setTotalsCollectedFlag(false);
95+
$this->cartRepository->save($quote);
96+
}
97+
}
98+
99+
/**
100+
* Checks whether quote has virtual items
101+
*
102+
* @param Quote $quote
103+
* @return bool
104+
*/
105+
private function isVirtualItemInQuote(Quote $quote): bool
106+
{
107+
$items = $quote->getItems();
108+
if (!empty($items)) {
109+
foreach ($items as $item) {
110+
if ($item->getIsVirtual()) {
111+
return true;
112+
}
113+
}
114+
}
115+
116+
return false;
117+
}
118+
119+
/**
120+
* Check if we have to disable multishipping mode depends on the request action name
121+
*
122+
* We should not disable multishipping mode if we are adding a new product item to the existing quote
123+
*
124+
* @param RequestInterface $request
125+
* @param Quote $quote
126+
* @return bool
127+
*/
128+
private function isDisableMultishippingRequired(RequestInterface $request, Quote $quote): bool
129+
{
130+
return $request->getActionName() !== "add" && $quote->getIsMultiShipping();
131+
}
132+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Multishipping\Test\Unit\Model\Cart\Controller;
9+
10+
use Magento\Checkout\Controller\Sidebar\UpdateItemQty;
11+
use Magento\Checkout\Model\Session;
12+
use Magento\Customer\Api\AddressRepositoryInterface;
13+
use Magento\Customer\Api\Data\AddressInterface;
14+
use Magento\Customer\Api\Data\CustomerInterface;
15+
use Magento\Framework\App\RequestInterface;
16+
use Magento\Framework\Exception\LocalizedException;
17+
use Magento\Multishipping\Model\Cart\Controller\MiniCartPlugin;
18+
use Magento\Multishipping\Model\DisableMultishipping;
19+
use Magento\Quote\Api\CartRepositoryInterface;
20+
use Magento\Quote\Model\Quote;
21+
use Magento\Quote\Model\Quote\Address;
22+
use PHPUnit\Framework\MockObject\MockObject;
23+
use PHPUnit\Framework\TestCase;
24+
25+
/**
26+
* Test shipping addresses and item assignments after MultiShipping flow
27+
*
28+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
29+
*/
30+
class MiniCartPluginTest extends TestCase
31+
{
32+
/**
33+
* @var MiniCartPlugin
34+
*/
35+
private $model;
36+
37+
/**
38+
* @var MockObject
39+
*/
40+
private $cartRepositoryMock;
41+
42+
/**
43+
* @var MockObject
44+
*/
45+
private $checkoutSessionMock;
46+
47+
/**
48+
* @var MockObject
49+
*/
50+
private $addressRepositoryMock;
51+
52+
protected function setUp(): void
53+
{
54+
$this->cartRepositoryMock = $this->getMockForAbstractClass(CartRepositoryInterface::class);
55+
$this->checkoutSessionMock = $this->createMock(Session::class);
56+
$this->addressRepositoryMock = $this->getMockForAbstractClass(AddressRepositoryInterface::class);
57+
$disableMultishippingMock = $this->createMock(DisableMultishipping::class);
58+
$this->model = new MiniCartPlugin(
59+
$this->cartRepositoryMock,
60+
$this->checkoutSessionMock,
61+
$this->addressRepositoryMock,
62+
$disableMultishippingMock
63+
);
64+
}
65+
66+
/**
67+
* Test cart plugin
68+
*
69+
* @param string $actionName
70+
* @param int $addressId
71+
* @param int $customerAddressId
72+
* @param bool $isMultiShippingAddresses
73+
* @throws LocalizedException
74+
* @dataProvider getDataDataProvider
75+
*/
76+
public function testBeforeDispatch(
77+
string $actionName,
78+
int $addressId,
79+
int $customerAddressId,
80+
bool $isMultiShippingAddresses
81+
): void {
82+
$requestMock = $this->getMockForAbstractClass(RequestInterface::class);
83+
$quoteMock = $this->createPartialMock(Quote::class, [
84+
'isMultipleShippingAddresses',
85+
'getAllShippingAddresses',
86+
'removeAddress',
87+
'getShippingAddress',
88+
'getCustomer'
89+
]);
90+
$requestMock->method('getActionName')
91+
->willReturn($actionName);
92+
$this->checkoutSessionMock->method('getQuote')
93+
->willReturn($quoteMock);
94+
95+
$addressMock = $this->createMock(Address::class);
96+
$addressMock->method('getId')
97+
->willReturn($addressId);
98+
99+
$quoteMock->method('isMultipleShippingAddresses')
100+
->willReturn($isMultiShippingAddresses);
101+
$quoteMock->method('getAllShippingAddresses')
102+
->willReturn([$addressMock]);
103+
$quoteMock->method('removeAddress')
104+
->with($addressId)->willReturnSelf();
105+
106+
$shippingAddressMock = $this->createMock(Address::class);
107+
$quoteMock->method('getShippingAddress')
108+
->willReturn($shippingAddressMock);
109+
$customerMock = $this->getMockForAbstractClass(CustomerInterface::class);
110+
$quoteMock->method('getCustomer')
111+
->willReturn($customerMock);
112+
$customerMock->method('getDefaultShipping')
113+
->willReturn($customerAddressId);
114+
115+
$customerAddressMock = $this->getMockForAbstractClass(AddressInterface::class);
116+
$this->addressRepositoryMock->method('getById')
117+
->with($customerAddressId)
118+
->willReturn($customerAddressMock);
119+
120+
$shippingAddressMock->method('importCustomerAddressData')
121+
->with($customerAddressMock)
122+
->willReturnSelf();
123+
124+
$this->cartRepositoryMock->expects($this->any())
125+
->method('save')
126+
->with($quoteMock);
127+
128+
$this->model->beforeDispatch(
129+
$this->createMock(UpdateItemQty::class),
130+
$requestMock
131+
);
132+
}
133+
134+
/**
135+
* @return array
136+
*/
137+
public function getDataDataProvider()
138+
{
139+
return [
140+
'test with `add` action and multi shipping address enabled' => ['add', 100, 200, true],
141+
'test with `add` action and multi shipping address disabled' => ['add', 100, 200, false],
142+
'test with `edit` action and multi shipping address disabled' => ['add', 110, 200, false]
143+
];
144+
}
145+
}

0 commit comments

Comments
 (0)