Skip to content

Commit 18c6d26

Browse files
committed
Merge remote-tracking branch 'origin/MC-18457' into 2.4-develop-pr1
2 parents 29d2f6c + 5f43369 commit 18c6d26

File tree

8 files changed

+314
-4
lines changed

8 files changed

+314
-4
lines changed

app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ public function __construct(
6363
parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
6464
}
6565

66+
/**
67+
* Check subtotal for allowed free shipping
68+
*
69+
* @param RateRequest $request
70+
*
71+
* @return bool
72+
*/
73+
private function isFreeShippingRequired(RateRequest $request): bool
74+
{
75+
$minSubtotal = $request->getPackageValueWithDiscount();
76+
if ($request->getBaseSubtotalWithDiscountInclTax()
77+
&& $this->getConfigFlag('tax_including')) {
78+
$minSubtotal = $request->getBaseSubtotalWithDiscountInclTax();
79+
}
80+
81+
return $minSubtotal >= $this->getConfigData('free_shipping_subtotal');
82+
}
83+
6684
/**
6785
* FreeShipping Rates Collector
6886
*
@@ -80,10 +98,7 @@ public function collectRates(RateRequest $request)
8098

8199
$this->_updateFreeMethodQuote($request);
82100

83-
if ($request->getFreeShipping() || $request->getPackageValueWithDiscount() >= $this->getConfigData(
84-
'free_shipping_subtotal'
85-
)
86-
) {
101+
if ($request->getFreeShipping() || $this->isFreeShippingRequired($request)) {
87102
/** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */
88103
$method = $this->_rateMethodFactory->create();
89104

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="StorefrontFreeShippingDisplayWithInclTaxOptionTest">
12+
<annotations>
13+
<features value="Shipping"/>
14+
<stories value="Offline Shipping Methods"/>
15+
<title value="Free Shipping Minimum Order Amount Excluding/Including Tax options"/>
16+
<description value="Free Shipping Minimum Order Amount Excluding/Including Tax options"/>
17+
<severity value="AVERAGE"/>
18+
<testCaseId value="MC-20613"/>
19+
<useCaseId value="MC-18457"/>
20+
<group value="shipping"/>
21+
</annotations>
22+
<before>
23+
<createData entity="SimpleProduct2" stepKey="createSimpleProduct">
24+
<field key="price">100.00</field>
25+
</createData>
26+
<!-- Enable free shipping method -->
27+
<createData entity="FreeShippinMethodConfig" stepKey="enableFreeShippingMethod"/>
28+
<createData entity="setFreeShippingSubtotal" stepKey="setFreeShippingSubtotal"/>
29+
<createData entity="SetTaxIncluding" stepKey="setTaxIncluding"/>
30+
<!-- Tax configuration (Store>Configuration; Sales>Tax) -->
31+
<createData entity="Tax_Config_CA" stepKey="configureTaxForCA"/>
32+
<createData entity="defaultTaxRule" stepKey="createTaxRule"/>
33+
</before>
34+
<after>
35+
<!-- Disable free shipping method -->
36+
<createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/>
37+
<createData entity="setFreeShippingSubtotalToDefault" stepKey="setFreeShippingSubtotalToDefault"/>
38+
<createData entity="SetTaxIncludingToDefault" stepKey="setTaxIncludingToDefault"/>
39+
<deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/>
40+
<createData entity="DefaultTaxConfig" stepKey="resetTaxConfiguration"/>
41+
<deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/>
42+
</after>
43+
<!-- Add simple product to cart -->
44+
<actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart">
45+
<argument name="product" value="$$createSimpleProduct$$"/>
46+
</actionGroup>
47+
<!-- Assert that taxes are applied correctly for CA -->
48+
<amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/>
49+
<waitForPageLoad stepKey="waitForCart"/>
50+
<waitForElementVisible selector="{{CheckoutPaymentSection.tax}}" stepKey="waitForOverviewVisible"/>
51+
<waitForElement time="30" selector="{{CheckoutCartSummarySection.estimateShippingAndTaxForm}}" stepKey="waitForEstimateShippingAndTaxForm"/>
52+
<waitForElement time="30" selector="{{CheckoutCartSummarySection.shippingMethodForm}}" stepKey="waitForShippingMethodForm"/>
53+
<conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" />
54+
<selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUSCountry"/>
55+
<waitForPageLoad stepKey="waitForSelectCountry"/>
56+
<selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="California" stepKey="selectCaliforniaRegion"/>
57+
<waitForPageLoad stepKey="waitForSelectRegion"/>
58+
<see selector="{{CheckoutPaymentSection.tax}}" userInput="$8.25" stepKey="seeTaxForCA"/>
59+
<!-- See available Free Shipping option -->
60+
<actionGroup ref="StorefrontAssertShippingMethodPresentInCartActionGroup" stepKey="assertShippingMethodLabel">
61+
<argument name="shippingMethod" value="{{freeTitleDefault.value}}"/>
62+
</actionGroup>
63+
<!-- Change State to New York -->
64+
<selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_NY.state}}" stepKey="selectAnotherState"/>
65+
<waitForPageLoad stepKey="waitForShippingMethodLoad"/>
66+
<dontSee selector="{{CheckoutCartSummarySection.shippingMethodLabel}}" userInput="{{freeTitleDefault.value}}" stepKey="assertShippingMethodIsNotPresentInCart"/>
67+
</test>
68+
</tests>
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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\OfflineShipping\Test\Unit\Model\Carrier;
9+
10+
use Magento\Quote\Model\Quote\Address\RateResult\Method;
11+
use Magento\Shipping\Model\Rate\Result;
12+
use Magento\OfflineShipping\Model\Carrier\Freeshipping;
13+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
14+
use Magento\Quote\Model\Quote\Address\RateResult\MethodFactory;
15+
use Magento\Shipping\Model\Rate\ResultFactory;
16+
use Magento\Framework\App\Config\ScopeConfigInterface;
17+
use Magento\Quote\Model\Quote\Address\RateRequest;
18+
use Magento\Store\Model\ScopeInterface;
19+
use PHPUnit\Framework\TestCase;
20+
use PHPUnit\Framework\MockObject\MockObject;
21+
use Magento\Quote\Model\Quote\Item as QuoteItem;
22+
use PHPUnit\Framework\MockObject\Matcher\InvokedCount;
23+
24+
/**
25+
* Class for test free shipping
26+
*
27+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
28+
*/
29+
class FreeshippingTest extends TestCase
30+
{
31+
/**
32+
* @var Freeshipping
33+
*/
34+
private $model;
35+
36+
/**
37+
* @var ScopeConfigInterface|MockObject
38+
*/
39+
private $scopeConfigMock;
40+
41+
/**
42+
* @var ResultFactory|MockObject
43+
*/
44+
private $resultFactoryMock;
45+
46+
/**
47+
* @var MethodFactory|MockObject
48+
*/
49+
private $methodFactoryMock;
50+
51+
/**
52+
* @var ObjectManager
53+
*/
54+
private $helper;
55+
56+
/**
57+
* @inheritdoc
58+
*/
59+
protected function setUp()
60+
{
61+
$this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config::class)
62+
->disableOriginalConstructor()
63+
->getMock();
64+
65+
$this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class)
66+
->disableOriginalConstructor()
67+
->setMethods(['create'])
68+
->getMock();
69+
70+
$this->methodFactoryMock = $this
71+
->getMockBuilder(MethodFactory::class)
72+
->disableOriginalConstructor()
73+
->setMethods(['create'])
74+
->getMock();
75+
76+
$this->helper = new ObjectManager($this);
77+
$this->model = $this->helper->getObject(
78+
Freeshipping::class,
79+
[
80+
'scopeConfig' => $this->scopeConfigMock,
81+
'_rateResultFactory' => $this->resultFactoryMock,
82+
'_rateMethodFactory' => $this->methodFactoryMock,
83+
]
84+
);
85+
}
86+
87+
/**
88+
* Test for collect rate free shipping with tax options
89+
*
90+
* @param int $subtotalInclTax
91+
* @param int $minOrderAmount
92+
* @param int $packageValueWithDiscount
93+
* @param int $baseSubtotalWithDiscountInclTax
94+
* @param InvokedCount $expectedCallAppend
95+
*
96+
* @return void
97+
* @dataProvider freeShippingWithSubtotalTaxDataProvider
98+
*/
99+
public function testCollectRatesFreeShippingWithTaxOptions(
100+
int $subtotalInclTax,
101+
int $minOrderAmount,
102+
int $packageValueWithDiscount,
103+
int $baseSubtotalWithDiscountInclTax,
104+
InvokedCount $expectedCallAppend
105+
): void {
106+
/** @var RateRequest|MockObject $request */
107+
$request = $this->getMockBuilder(RateRequest::class)
108+
->disableOriginalConstructor()
109+
->setMethods(
110+
[
111+
'getAllItems',
112+
'getPackageQty',
113+
'getFreeShipping',
114+
'getBaseSubtotalWithDiscountInclTax',
115+
'getPackageValueWithDiscount',
116+
]
117+
)
118+
->getMock();
119+
$item = $this->getMockBuilder(QuoteItem::class)
120+
->disableOriginalConstructor()
121+
->getMock();
122+
$this->scopeConfigMock->expects($this->at(0))
123+
->method('isSetFlag')
124+
->willReturn(true);
125+
$this->scopeConfigMock->expects($this->at(1))
126+
->method('isSetFlag')
127+
->with(
128+
'carriers/freeshipping/tax_including',
129+
ScopeInterface::SCOPE_STORE,
130+
null
131+
)
132+
->willReturn($subtotalInclTax);
133+
$this->scopeConfigMock->expects($this->at(2))
134+
->method('getValue')
135+
->with(
136+
'carriers/freeshipping/free_shipping_subtotal',
137+
ScopeInterface::SCOPE_STORE,
138+
null
139+
)
140+
->willReturn($minOrderAmount);
141+
$method = $this->getMockBuilder(Method::class)
142+
->disableOriginalConstructor()
143+
->setMethods(['setCarrier', 'setCarrierTitle', 'setMethod', 'setMethodTitle', 'setPrice', 'setCost'])
144+
->getMock();
145+
$resultModel = $this->getMockBuilder(Result::class)
146+
->disableOriginalConstructor()
147+
->setMethods(['append'])
148+
->getMock();
149+
$this->resultFactoryMock->method('create')
150+
->willReturn($resultModel);
151+
$request->method('getPackageValueWithDiscount')
152+
->willReturn($packageValueWithDiscount);
153+
$request->method('getAllItems')
154+
->willReturn([$item]);
155+
$request->method('getFreeShipping')
156+
->willReturn(false);
157+
$request->method('getBaseSubtotalWithDiscountInclTax')
158+
->willReturn($baseSubtotalWithDiscountInclTax);
159+
$this->methodFactoryMock->method('create')->willReturn($method);
160+
161+
$resultModel->expects($expectedCallAppend)
162+
->method('append')
163+
->with($method);
164+
165+
$this->model->collectRates($request);
166+
}
167+
168+
/**
169+
* @return array
170+
*/
171+
public function freeShippingWithSubtotalTaxDataProvider(): array
172+
{
173+
return [
174+
[
175+
'subtotalInclTax' => 1,
176+
'minOrderAmount' => 10,
177+
'packageValueWithDiscount' => 8,
178+
'baseSubtotalWithDiscountInclTax' => 15,
179+
'expectedCallAppend' => $this->once(),
180+
181+
],
182+
[
183+
'subtotalInclTax' => 1,
184+
'minOrderAmount' => 20,
185+
'packageValueWithDiscount' => 8,
186+
'baseSubtotalWithDiscountInclTax' => 15,
187+
'expectedCallAppend' => $this->never(),
188+
189+
],
190+
[
191+
'subtotalInclTax' => 0,
192+
'minOrderAmount' => 10,
193+
'packageValueWithDiscount' => 8,
194+
'baseSubtotalWithDiscountInclTax' => 15,
195+
'expectedCallAppend' => $this->never(),
196+
197+
],
198+
];
199+
}
200+
}

app/code/Magento/OfflineShipping/etc/adminhtml/system.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@
127127
<label>Minimum Order Amount</label>
128128
<validate>validate-number validate-zero-or-greater</validate>
129129
</field>
130+
<field id="tax_including" translate="label" sortOrder="5" type="select" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
131+
<label>Include Tax to Amount</label>
132+
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
133+
</field>
130134
<field id="name" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
131135
<label>Method Name</label>
132136
</field>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,7 @@ public function requestShippingRates(\Magento\Quote\Model\Quote\Item\AbstractIte
10221022
$request->setLimitCarrier($this->getLimitCarrier());
10231023
$baseSubtotalInclTax = $this->getBaseSubtotalTotalInclTax();
10241024
$request->setBaseSubtotalInclTax($baseSubtotalInclTax);
1025+
$request->setBaseSubtotalWithDiscountInclTax($this->getBaseSubtotalWithDiscount() + $this->getBaseTaxAmount());
10251026

10261027
$result = $this->_rateCollector->create()->collectRates($request)->getResult();
10271028

app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<requiredEntity type="title">freeTitleDefault</requiredEntity>
2727
<requiredEntity type="name">freeNameDefault</requiredEntity>
2828
<requiredEntity type="free_shipping_subtotal">freeShippingSubtotalDefault</requiredEntity>
29+
<requiredEntity type="tax_including">TaxIncludingDefault</requiredEntity>
2930
<requiredEntity type="specificerrmsg">freeSpecificerrmsgDefault</requiredEntity>
3031
<requiredEntity type="sallowspecific">freeSallowspecificDefault</requiredEntity>
3132
<requiredEntity type="specificcountry">freeSpecificcountryDefault</requiredEntity>
@@ -44,6 +45,9 @@
4445
<entity name="freeShippingSubtotalDefault" type="free_shipping_subtotal">
4546
<data key="value" />
4647
</entity>
48+
<entity name="TaxIncludingDefault" type="tax_including">
49+
<data key="value">0</data>
50+
</entity>
4751
<entity name="freeSpecificerrmsgDefault" type="specificerrmsg">
4852
<data key="value">This shipping method is not available. To use this shipping method, please contact us.</data>
4953
</entity>
@@ -66,6 +70,17 @@
6670
<entity name="freeShippingSubtotal" type="free_shipping_subtotal">
6771
<data key="value">101</data>
6872
</entity>
73+
<!--Set Free Shipping "Include Tax to Amount" to "Yes"-->
74+
<entity name="SetTaxIncluding" type="free_shipping_method">
75+
<requiredEntity type="tax_including">TaxIncluding</requiredEntity>
76+
</entity>
77+
<entity name="TaxIncluding" type="tax_including">
78+
<data key="value">1</data>
79+
</entity>
80+
<!--Set to default Free Shipping "Include Tax to Amount"-->
81+
<entity name="SetTaxIncludingToDefault" type="free_shipping_method">
82+
<requiredEntity type="tax_including">TaxIncludingDefault</requiredEntity>
83+
</entity>
6984
<!--Set to default Free Shipping Subtotal-->
7085
<entity name="setFreeShippingSubtotalToDefault" type="free_shipping_method">
7186
<requiredEntity type="free_shipping_subtotal">freeShippingSubtotalDefault</requiredEntity>

app/code/Magento/Shipping/Test/Mftf/Metadata/shipping_methods-meta.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666
<object key="free_shipping_subtotal" dataType="free_shipping_subtotal">
6767
<field key="value">string</field>
6868
</object>
69+
<object key="tax_including" dataType="tax_including">
70+
<field key="value">boolean</field>
71+
</object>
6972
<object key="specificerrmsg" dataType="specificerrmsg">
7073
<field key="value">string</field>
7174
</object>

0 commit comments

Comments
 (0)