7
7
namespace Magento \ConfigurableProduct \Pricing \Price ;
8
8
9
9
use Magento \Catalog \Model \Product ;
10
+ use Magento \ConfigurableProduct \Model \ConfigurableMaxPriceCalculator ;
10
11
use Magento \Framework \App \ObjectManager ;
11
12
use Magento \Framework \ObjectManager \ResetAfterRequestInterface ;
12
13
use Magento \Framework \Pricing \Price \AbstractPrice ;
@@ -54,12 +55,23 @@ class ConfigurableRegularPrice extends AbstractPrice implements
54
55
*/
55
56
private $ lowestPriceOptionsProvider ;
56
57
58
+ /**
59
+ * @var ConfigurableMaxPriceCalculator
60
+ */
61
+ private $ configurableMaxPriceCalculator ;
62
+
63
+ /**
64
+ * @var array<int, bool>
65
+ */
66
+ private $ equalFinalPriceCache = [];
67
+
57
68
/**
58
69
* @param \Magento\Framework\Pricing\SaleableInterface $saleableItem
59
70
* @param float $quantity
60
71
* @param \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator
61
72
* @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
62
73
* @param PriceResolverInterface $priceResolver
74
+ * @param ConfigurableMaxPriceCalculator $configurableMaxPriceCalculator
63
75
* @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider
64
76
*/
65
77
public function __construct (
@@ -68,12 +80,14 @@ public function __construct(
68
80
\Magento \Framework \Pricing \Adjustment \CalculatorInterface $ calculator ,
69
81
\Magento \Framework \Pricing \PriceCurrencyInterface $ priceCurrency ,
70
82
PriceResolverInterface $ priceResolver ,
83
+ ConfigurableMaxPriceCalculator $ configurableMaxPriceCalculator ,
71
84
?LowestPriceOptionsProviderInterface $ lowestPriceOptionsProvider = null
72
85
) {
73
86
parent ::__construct ($ saleableItem , $ quantity , $ calculator , $ priceCurrency );
74
87
$ this ->priceResolver = $ priceResolver ;
75
88
$ this ->lowestPriceOptionsProvider = $ lowestPriceOptionsProvider ?:
76
89
ObjectManager::getInstance ()->get (LowestPriceOptionsProviderInterface::class);
90
+ $ this ->configurableMaxPriceCalculator = $ configurableMaxPriceCalculator ;
77
91
}
78
92
79
93
/**
@@ -184,31 +198,71 @@ private function getConfigurableOptionsProvider()
184
198
public function _resetState (): void
185
199
{
186
200
$ this ->values = [];
201
+ $ this ->equalFinalPriceCache = [];
187
202
}
188
203
189
204
/**
190
- * Check whether Configurable Product has more than one child product and if their prices are equal
205
+ * Check whether Configurable Product have more than one children products
191
206
*
192
207
* @param SaleableInterface $product
193
208
* @return bool
209
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
210
+ * @SuppressWarnings(PHPMD.NPathComplexity)
194
211
*/
195
212
public function isChildProductsOfEqualPrices (SaleableInterface $ product ): bool
196
213
{
197
- // Get all child products of the configurable product
198
- $ childProducts = $ product ->getTypeInstance ()->getUsedProducts ($ product );
199
- if (count ($ childProducts ) <= 1 ) {
200
- return false ; // Not more than one child product
214
+ $ storeId = (int ) ($ product ->getStoreId () ?: 0 );
215
+ $ cacheKey = (int ) $ product ->getId () . ': ' . $ storeId ;
216
+ if (isset ($ this ->equalFinalPriceCache [$ cacheKey ])) {
217
+ return $ this ->equalFinalPriceCache [$ cacheKey ];
218
+ }
219
+
220
+ $ memoKey = '_children_final_prices_equal_store_ ' . $ storeId ;
221
+ $ memoized = $ product ->getData ($ memoKey );
222
+ if ($ memoized !== null ) {
223
+ return (bool ) $ memoized ;
201
224
}
202
225
203
- $ prices = [];
204
- foreach ($ childProducts as $ child ) {
205
- $ prices [] = $ child ->getFinalPrice ();
226
+ // Listing fast-path: if index fields are present, rely on them and avoid any child loading
227
+ $ minIndexed = $ product ->getData ('minimal_price ' );
228
+ $ maxIndexed = $ product ->getData ('max_price ' );
229
+ if (is_numeric ($ minIndexed ) && is_numeric ($ maxIndexed )) {
230
+ $ result = ((float )$ minIndexed === (float )$ maxIndexed );
231
+ $ this ->equalFinalPriceCache [$ cacheKey ] = $ result ;
232
+ $ product ->setData ($ memoKey , $ result );
233
+ return $ result ;
206
234
}
207
235
208
- $ minPrice = min ($ prices );
209
- $ maxPrice = max ($ prices );
236
+ $ children = $ product ->getTypeInstance ()->getUsedProducts ($ product );
237
+ $ firstFinal = null ;
238
+ $ saleableChildrenCount = 0 ;
239
+ $ allEqual = true ;
240
+ foreach ($ children as $ child ) {
241
+ if (!$ child ->isSalable ()) {
242
+ continue ;
243
+ }
244
+ $ saleableChildrenCount ++;
245
+ $ value = $ child ->getPriceInfo ()->getPrice ('final_price ' )->getAmount ()->getValue ();
246
+ if ($ firstFinal === null ) {
247
+ $ firstFinal = $ value ;
248
+ continue ;
249
+ }
250
+ if ($ value != $ firstFinal ) {
251
+ $ allEqual = false ;
252
+ break ;
253
+ }
254
+ }
255
+
256
+ if ($ saleableChildrenCount <= 1 || $ firstFinal === null || !$ allEqual ) {
257
+ $ product ->setData ($ memoKey , false );
258
+ $ this ->equalFinalPriceCache [$ cacheKey ] = false ;
259
+ return false ;
260
+ }
210
261
211
- // Return true only if all child prices are equal (min == max)
212
- return $ minPrice == $ maxPrice ;
262
+ // Guard against parent-level extra discounts (compute only when children are equal)
263
+ $ result = !($ product ->getFinalPrice () < $ firstFinal );
264
+ $ this ->equalFinalPriceCache [$ cacheKey ] = $ result ;
265
+ $ product ->setData ($ memoKey , $ result );
266
+ return $ result ;
213
267
}
214
268
}
0 commit comments