Skip to content

Commit 4fb5da8

Browse files
committed
#22856: Catalog price rules are not working with custom options as expected.
1 parent a8f3b9e commit 4fb5da8

File tree

2 files changed

+301
-15
lines changed

2 files changed

+301
-15
lines changed

app/code/Magento/Catalog/Pricing/Price/CalculateCustomOptionCatalogRule.php

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,31 @@ public function execute(
5353
float $optionPriceValue,
5454
bool $isPercent
5555
): float {
56-
$basePrice = $this->getGetBasePriceWithOutCatalogRules($product);
57-
if ($isPercent) {
58-
$optionPrice = $basePrice * $optionPriceValue / 100;
59-
} else {
60-
$optionPrice = $optionPriceValue;
61-
}
62-
63-
$totalPriceModified = $this->priceModifier->modifyPrice(
64-
$basePrice + $optionPrice,
65-
$product
66-
);
67-
$basePriceModified = $this->priceModifier->modifyPrice(
68-
$basePrice,
56+
$regularPrice = (float)$product->getPriceInfo()
57+
->getPrice(RegularPrice::PRICE_CODE)
58+
->getValue();
59+
$catalogRulePrice = $this->priceModifier->modifyPrice(
60+
$regularPrice,
6961
$product
7062
);
71-
$price = $totalPriceModified - $basePriceModified;
63+
$basePriceWithOutCatalogRules = (float)$this->getGetBasePriceWithOutCatalogRules($product);
64+
// Apply catalog price rules to product options only if catalog price rules are applied to product.
65+
if ($catalogRulePrice < $basePriceWithOutCatalogRules) {
66+
$optionPrice = $this->getOptionPriceWithoutPriceRule($optionPriceValue, $isPercent, $regularPrice);
67+
$totalCatalogRulePrice = $this->priceModifier->modifyPrice(
68+
$regularPrice + $optionPrice,
69+
$product
70+
);
71+
$finalOptionPrice = $totalCatalogRulePrice - $catalogRulePrice;
72+
} else {
73+
$finalOptionPrice = $this->getOptionPriceWithoutPriceRule(
74+
$optionPriceValue,
75+
$isPercent,
76+
$this->getGetBasePriceWithOutCatalogRules($product)
77+
);
78+
}
7279

73-
return $this->priceCurrency->convertAndRound($price);
80+
return $this->priceCurrency->convertAndRound($finalOptionPrice);
7481
}
7582

7683
/**
@@ -96,4 +103,17 @@ private function getGetBasePriceWithOutCatalogRules(Product $product): float
96103

97104
return $basePrice ?? $product->getPrice();
98105
}
106+
107+
/**
108+
* Calculate option price without catalog price rule discount.
109+
*
110+
* @param float $optionPriceValue
111+
* @param bool $isPercent
112+
* @param float $basePrice
113+
* @return float
114+
*/
115+
private function getOptionPriceWithoutPriceRule(float $optionPriceValue, bool $isPercent, float $basePrice): float
116+
{
117+
return $isPercent ? $basePrice * $optionPriceValue / 100 : $optionPriceValue;
118+
}
99119
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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\Catalog\Test\Unit\Pricing\Price;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product\PriceModifier\Composite as PriceModifier;
12+
use Magento\Catalog\Pricing\Price\CalculateCustomOptionCatalogRule;
13+
use Magento\Catalog\Pricing\Price\RegularPrice;
14+
use Magento\Catalog\Pricing\Price\SpecialPrice;
15+
use Magento\CatalogRule\Pricing\Price\CatalogRulePrice;
16+
use Magento\Directory\Model\PriceCurrency;
17+
use Magento\Framework\Pricing\PriceInfo\Base;
18+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
19+
use PHPUnit\Framework\MockObject\MockObject;
20+
use PHPUnit\Framework\TestCase;
21+
22+
/**
23+
* Test for CalculateCustomOptionCatalogRule class.
24+
*/
25+
class CalculateCustomOptionCatalogRuleTest extends TestCase
26+
{
27+
/**
28+
* @var Product|MockObject
29+
*/
30+
private $saleableItemMock;
31+
32+
/**
33+
* @var RegularPrice|MockObject
34+
*/
35+
private $regularPriceMock;
36+
37+
/**
38+
* @var SpecialPrice|MockObject
39+
*/
40+
private $specialPriceMock;
41+
42+
/**
43+
* @var CatalogRulePrice|MockObject
44+
*/
45+
private $catalogRulePriceMock;
46+
47+
/**
48+
* @var PriceModifier|MockObject
49+
*/
50+
private $priceModifierMock;
51+
52+
/**
53+
* @var CalculateCustomOptionCatalogRule
54+
*/
55+
private $calculateCustomOptionCatalogRule;
56+
57+
/**
58+
* @inheritdoc
59+
*/
60+
protected function setUp()
61+
{
62+
$objectManager = new ObjectManager($this);
63+
$this->saleableItemMock = $this->createMock(Product::class);
64+
$this->regularPriceMock = $this->createMock(RegularPrice::class);
65+
$this->specialPriceMock = $this->createMock(SpecialPrice::class);
66+
$this->catalogRulePriceMock = $this->createMock(CatalogRulePrice::class);
67+
$priceInfoMock = $this->createMock(Base::class);
68+
$this->saleableItemMock->expects($this->any())
69+
->method('getPriceInfo')
70+
->willReturn($priceInfoMock);
71+
$this->regularPriceMock->expects($this->any())
72+
->method('getPriceCode')
73+
->willReturn(RegularPrice::PRICE_CODE);
74+
$this->specialPriceMock->expects($this->any())
75+
->method('getPriceCode')
76+
->willReturn(SpecialPrice::PRICE_CODE);
77+
$this->catalogRulePriceMock->expects($this->any())
78+
->method('getPriceCode')
79+
->willReturn(CatalogRulePrice::PRICE_CODE);
80+
$priceInfoMock->expects($this->any())
81+
->method('getPrices')
82+
->willReturn(
83+
[
84+
'regular_price' => $this->regularPriceMock,
85+
'special_price' => $this->specialPriceMock,
86+
'catalog_rule_price' => $this->catalogRulePriceMock
87+
]
88+
);
89+
$priceInfoMock->expects($this->any())
90+
->method('getPrice')
91+
->willReturnMap(
92+
[
93+
['regular_price', $this->regularPriceMock],
94+
['special_price', $this->specialPriceMock],
95+
['catalog_rule_price', $this->catalogRulePriceMock],
96+
]
97+
);
98+
$priceCurrencyMock = $this->createMock(PriceCurrency::class);
99+
$priceCurrencyMock->expects($this->any())
100+
->method('convertAndRound')
101+
->willReturnArgument(0);
102+
$this->priceModifierMock = $this->createMock(PriceModifier::class);
103+
104+
$this->calculateCustomOptionCatalogRule = $objectManager->getObject(
105+
CalculateCustomOptionCatalogRule::class,
106+
[
107+
'priceCurrency' => $priceCurrencyMock,
108+
'priceModifier' => $this->priceModifierMock,
109+
]
110+
);
111+
}
112+
113+
/**
114+
* Tests correct option price calculation with different catalog rules and special prices combination.
115+
*
116+
* @dataProvider executeDataProvider
117+
* @param array $prices
118+
* @param float $catalogRulePriceModifier
119+
* @param float $optionPriceValue
120+
* @param bool $isPercent
121+
* @param float $expectedResult
122+
*/
123+
public function testExecute(
124+
array $prices,
125+
float $catalogRulePriceModifier,
126+
float $optionPriceValue,
127+
bool $isPercent,
128+
float $expectedResult
129+
) {
130+
$this->regularPriceMock->expects($this->any())
131+
->method('getValue')
132+
->willReturn($prices['regularPriceValue']);
133+
$this->specialPriceMock->expects($this->any())
134+
->method('getValue')
135+
->willReturn($prices['specialPriceValue']);
136+
$this->priceModifierMock->expects($this->any())
137+
->method('modifyPrice')
138+
->willReturnCallback(
139+
function ($price) use ($catalogRulePriceModifier) {
140+
return $price * $catalogRulePriceModifier;
141+
}
142+
);
143+
144+
$finalPrice = $this->calculateCustomOptionCatalogRule->execute(
145+
$this->saleableItemMock,
146+
$optionPriceValue,
147+
$isPercent
148+
);
149+
150+
$this->assertSame($expectedResult, $finalPrice);
151+
}
152+
153+
/**
154+
* Data provider for testExecute.
155+
*
156+
* "Active" means this price type has biggest discount, so other prices doesn't count.
157+
*
158+
* @return array
159+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
160+
*/
161+
public function executeDataProvider(): array
162+
{
163+
return [
164+
'No special price, no catalog price rules, fixed option price' => [
165+
'prices' => [
166+
'regularPriceValue' => 1000,
167+
'specialPriceValue' => 1000,
168+
],
169+
'catalogRulePriceModifier' => 1.0,
170+
'optionPriceValue' => 100.0,
171+
'isPercent' => false,
172+
'expectedResult' => 100.0
173+
],
174+
'No special price, no catalog price rules, percent option price' => [
175+
'prices' => [
176+
'regularPriceValue' => 1000,
177+
'specialPriceValue' => 1000,
178+
],
179+
'catalogRulePriceModifier' => 1.0,
180+
'optionPriceValue' => 100.0,
181+
'isPercent' => true,
182+
'expectedResult' => 1000.0
183+
],
184+
'No special price, catalog price rule set, fixed option price' => [
185+
'prices' => [
186+
'regularPriceValue' => 1000,
187+
'specialPriceValue' => 1000,
188+
],
189+
'catalogRulePriceModifier' => 0.9,
190+
'optionPriceValue' => 100.0,
191+
'isPercent' => false,
192+
'expectedResult' => 90.0
193+
],
194+
'No special price, catalog price rule set, percent option price' => [
195+
'prices' => [
196+
'regularPriceValue' => 1000,
197+
'specialPriceValue' => 1000,
198+
],
199+
'catalogRulePriceModifier' => 0.9,
200+
'optionPriceValue' => 100.0,
201+
'isPercent' => true,
202+
'expectedResult' => 900.0
203+
],
204+
'Special price set, no catalog price rule, fixed option price' => [
205+
'prices' => [
206+
'regularPriceValue' => 1000,
207+
'specialPriceValue' => 900,
208+
],
209+
'catalogRulePriceModifier' => 1.0,
210+
'optionPriceValue' => 100.0,
211+
'isPercent' => false,
212+
'expectedResult' => 100.0
213+
],
214+
'Special price set, no catalog price rule, percent option price' => [
215+
'prices' => [
216+
'regularPriceValue' => 1000,
217+
'specialPriceValue' => 900,
218+
],
219+
'catalogRulePriceModifier' => 1.0,
220+
'optionPriceValue' => 100.0,
221+
'isPercent' => true,
222+
'expectedResult' => 900.0
223+
],
224+
'Special price set and active, catalog price rule set, fixed option price' => [
225+
'prices' => [
226+
'regularPriceValue' => 1000,
227+
'specialPriceValue' => 800,
228+
],
229+
'catalogRulePriceModifier' => 0.9,
230+
'optionPriceValue' => 100.0,
231+
'isPercent' => false,
232+
'expectedResult' => 100.0
233+
],
234+
'Special price set and active, catalog price rule set, percent option price' => [
235+
'prices' => [
236+
'regularPriceValue' => 1000,
237+
'specialPriceValue' => 800,
238+
],
239+
'catalogRulePriceModifier' => 0.9,
240+
'optionPriceValue' => 100.0,
241+
'isPercent' => true,
242+
'expectedResult' => 800.0
243+
],
244+
'Special price set, catalog price rule set and active, fixed option price' => [
245+
'prices' => [
246+
'regularPriceValue' => 1000,
247+
'specialPriceValue' => 950,
248+
],
249+
'catalogRulePriceModifier' => 0.9,
250+
'optionPriceValue' => 100.0,
251+
'isPercent' => false,
252+
'expectedResult' => 90.0
253+
],
254+
'Special price set, catalog price rule set and active, percent option price' => [
255+
'prices' => [
256+
'regularPriceValue' => 1000,
257+
'specialPriceValue' => 950,
258+
],
259+
'catalogRulePriceModifier' => 0.9,
260+
'optionPriceValue' => 100.0,
261+
'isPercent' => true,
262+
'expectedResult' => 900.0
263+
],
264+
];
265+
}
266+
}

0 commit comments

Comments
 (0)