Skip to content

Commit c3f5cd7

Browse files
committed
MAGETWO-67024: Sorting by price ignores Catalog Price Rule for Simple product with required custom option
1 parent 0ad656d commit c3f5cd7

File tree

11 files changed

+242
-252
lines changed

11 files changed

+242
-252
lines changed

app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php

Lines changed: 122 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -153,52 +153,43 @@ protected function _prepareBundlePriceByType($priceType, $entityIds = null)
153153
$specialPrice = $this->_addAttributeToSelect($select, 'special_price', "e.$linkField", 'cs.store_id');
154154
$specialFrom = $this->_addAttributeToSelect($select, 'special_from_date', "e.$linkField", 'cs.store_id');
155155
$specialTo = $this->_addAttributeToSelect($select, 'special_to_date', "e.$linkField", 'cs.store_id');
156-
$curentDate = new \Zend_Db_Expr('cwd.website_date');
157-
158-
$specialExpr = $connection->getCheckSql(
159-
$connection->getCheckSql(
160-
$specialFrom . ' IS NULL',
161-
'1',
162-
$connection->getCheckSql($specialFrom . ' <= ' . $curentDate, '1', '0')
163-
) . " > 0 AND " . $connection->getCheckSql(
164-
$specialTo . ' IS NULL',
165-
'1',
166-
$connection->getCheckSql($specialTo . ' >= ' . $curentDate, '1', '0')
167-
) . " > 0 AND {$specialPrice} > 0 AND {$specialPrice} < 100 ",
168-
$specialPrice,
169-
'0'
170-
);
156+
$currentDate = new \Zend_Db_Expr('cwd.website_date');
171157

172-
$tierExpr = new \Zend_Db_Expr("tp.min_price");
158+
$specialFromDate = $connection->getDatePartSql($specialFrom);
159+
$specialToDate = $connection->getDatePartSql($specialTo);
160+
$specialFromExpr = "{$specialFrom} IS NULL OR {$specialFromDate} <= {$currentDate}";
161+
$specialToExpr = "{$specialTo} IS NULL OR {$specialToDate} >= {$currentDate}";
162+
$specialExpr = "{$specialPrice} IS NOT NULL AND {$specialPrice} > 0 AND {$specialPrice} < 100"
163+
. " AND {$specialFromExpr} AND {$specialToExpr}";
164+
$tierExpr = new \Zend_Db_Expr('tp.min_price');
173165

174166
if ($priceType == \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED) {
175-
$finalPrice = $connection->getCheckSql(
176-
$specialExpr . ' > 0',
177-
'ROUND(' . $price . ' * (' . $specialExpr . ' / 100), 4)',
178-
$price
167+
$specialPriceExpr = $connection->getCheckSql(
168+
$specialExpr,
169+
'ROUND(' . $price . ' * (' . $specialPrice . ' / 100), 4)',
170+
'NULL'
179171
);
180172
$tierPrice = $connection->getCheckSql(
181173
$tierExpr . ' IS NOT NULL',
182-
'ROUND(' . $price . ' - ' . '(' . $price . ' * (' . $tierExpr . ' / 100)), 4)',
174+
'ROUND((1 - ' . $tierExpr . ' / 100) * ' . $price . ', 4)',
183175
'NULL'
184176
);
185-
186-
$finalPrice = $connection->getCheckSql(
187-
"{$tierPrice} < {$finalPrice}",
188-
$tierPrice,
189-
$finalPrice
190-
);
177+
$finalPrice = $connection->getLeastSql([
178+
$price,
179+
$connection->getIfNullSql($specialPriceExpr, $price),
180+
$connection->getIfNullSql($tierPrice, $price),
181+
]);
191182
} else {
192-
$finalPrice = new \Zend_Db_Expr("0");
183+
$finalPrice = new \Zend_Db_Expr('0');
193184
$tierPrice = $connection->getCheckSql($tierExpr . ' IS NOT NULL', '0', 'NULL');
194185
}
195186

196187
$select->columns(
197188
[
198189
'price_type' => new \Zend_Db_Expr($priceType),
199-
'special_price' => $specialExpr,
190+
'special_price' => $connection->getCheckSql($specialExpr, $specialPrice, '0'),
200191
'tier_percent' => $tierExpr,
201-
'orig_price' => $connection->getCheckSql($price . ' IS NULL', '0', $price),
192+
'orig_price' => $connection->getIfNullSql($price, '0'),
202193
'price' => $finalPrice,
203194
'min_price' => $finalPrice,
204195
'max_price' => $finalPrice,
@@ -246,63 +237,29 @@ protected function _calculateBundleOptionPrice()
246237
$this->_prepareBundleOptionTable();
247238

248239
$select = $connection->select()->from(
249-
['i' => $this->_getBundleSelectionTable()],
240+
$this->_getBundleSelectionTable(),
250241
['entity_id', 'customer_group_id', 'website_id', 'option_id']
251242
)->group(
252-
['entity_id', 'customer_group_id', 'website_id', 'option_id', 'is_required', 'group_type']
253-
)->columns(
243+
['entity_id', 'customer_group_id', 'website_id', 'option_id']
244+
);
245+
$minPrice = $connection->getCheckSql('is_required = 1', 'price', 'NULL');
246+
$tierPrice = $connection->getCheckSql('is_required = 1', 'tier_price', 'NULL');
247+
$select->columns(
254248
[
255-
'min_price' => $connection->getCheckSql('i.is_required = 1', 'MIN(i.price)', '0'),
256-
'alt_price' => $connection->getCheckSql('i.is_required = 0', 'MIN(i.price)', '0'),
257-
'max_price' => $connection->getCheckSql('i.group_type = 1', 'SUM(i.price)', 'MAX(i.price)'),
258-
'tier_price' => $connection->getCheckSql('i.is_required = 1', 'MIN(i.tier_price)', '0'),
259-
'alt_tier_price' => $connection->getCheckSql('i.is_required = 0', 'MIN(i.tier_price)', '0'),
249+
'min_price' => new \Zend_Db_Expr('MIN(' . $minPrice . ')'),
250+
'alt_price' => new \Zend_Db_Expr('MIN(price)'),
251+
'max_price' => $connection->getCheckSql('group_type = 0', 'MAX(price)', 'SUM(price)'),
252+
'tier_price' => new \Zend_Db_Expr('MIN(' . $tierPrice . ')'),
253+
'alt_tier_price' => new \Zend_Db_Expr('MIN(tier_price)'),
260254
]
261255
);
262256

263257
$query = $select->insertFromSelect($this->_getBundleOptionTable());
264258
$connection->query($query);
265259

266260
$this->_prepareDefaultFinalPriceTable();
267-
268-
$minPrice = new \Zend_Db_Expr(
269-
$connection->getCheckSql('SUM(io.min_price) = 0', 'MIN(io.alt_price)', 'SUM(io.min_price)') . ' + i.price'
270-
);
271-
$maxPrice = new \Zend_Db_Expr("SUM(io.max_price) + i.price");
272-
$tierPrice = $connection->getCheckSql(
273-
'MIN(i.tier_percent) IS NOT NULL',
274-
$connection->getCheckSql(
275-
'SUM(io.tier_price) = 0',
276-
'SUM(io.alt_tier_price)',
277-
'SUM(io.tier_price)'
278-
) . ' + MIN(i.tier_price)',
279-
'NULL'
280-
);
281-
282-
$select = $connection->select()->from(
283-
['io' => $this->_getBundleOptionTable()],
284-
['entity_id', 'customer_group_id', 'website_id']
285-
)->join(
286-
['i' => $this->_getBundlePriceTable()],
287-
'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' .
288-
' AND i.website_id = io.website_id',
289-
[]
290-
)->group(
291-
['io.entity_id', 'io.customer_group_id', 'io.website_id', 'i.tax_class_id', 'i.orig_price', 'i.price']
292-
)->columns(
293-
[
294-
'i.tax_class_id',
295-
'orig_price' => 'i.orig_price',
296-
'price' => 'i.price',
297-
'min_price' => $minPrice,
298-
'max_price' => $maxPrice,
299-
'tier_price' => $tierPrice,
300-
'base_tier' => 'MIN(i.base_tier)',
301-
]
302-
);
303-
304-
$query = $select->insertFromSelect($this->_getDefaultFinalPriceTable());
305-
$connection->query($query);
261+
$this->applyBundlePrice();
262+
$this->applyBundleOptionPrice();
306263

307264
return $this;
308265
}
@@ -348,33 +305,33 @@ protected function _calculateBundleSelectionPrice($priceType)
348305
'ROUND(i.base_tier - (i.base_tier * (' . $selectionPriceValue . ' / 100)),4)',
349306
$connection->getCheckSql(
350307
'i.tier_percent > 0',
351-
'ROUND(' .
352-
$selectionPriceValue .
353-
' - (' .
354-
$selectionPriceValue .
355-
' * (i.tier_percent / 100)),4)',
308+
'ROUND((1 - i.tier_percent / 100) * ' . $selectionPriceValue . ',4)',
356309
$selectionPriceValue
357310
)
358311
) . ' * bs.selection_qty',
359312
'NULL'
360313
);
361314

362-
$priceExpr = new \Zend_Db_Expr(
363-
$connection->getCheckSql("{$tierExpr} < {$priceExpr}", $tierExpr, $priceExpr)
364-
);
315+
$priceExpr = $connection->getLeastSql([
316+
$priceExpr,
317+
$connection->getIfNullSql($tierExpr, $priceExpr),
318+
]);
365319
} else {
366-
$priceExpr = new \Zend_Db_Expr(
367-
$connection->getCheckSql(
368-
'i.special_price > 0 AND i.special_price < 100',
369-
'ROUND(idx.min_price * (i.special_price / 100), 4)',
370-
'idx.min_price'
371-
) . ' * bs.selection_qty'
320+
$price = 'idx.min_price * bs.selection_qty';
321+
$specialExpr = $connection->getCheckSql(
322+
'i.special_price > 0 AND i.special_price < 100',
323+
'ROUND(' . $price . ' * (i.special_price / 100), 4)',
324+
$price
372325
);
373326
$tierExpr = $connection->getCheckSql(
374-
'i.base_tier IS NOT NULL',
375-
'ROUND(idx.min_price * (i.base_tier / 100), 4)* bs.selection_qty',
327+
'i.tier_percent IS NOT NULL',
328+
'ROUND((1 - i.tier_percent / 100) * ' . $price . ', 4)',
376329
'NULL'
377330
);
331+
$priceExpr = $connection->getLeastSql([
332+
$specialExpr,
333+
$connection->getIfNullSql($tierExpr, $price),
334+
]);
378335
}
379336

380337
$linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
@@ -508,4 +465,76 @@ protected function _prepareTierPriceIndex($entityIds = null)
508465

509466
return $this;
510467
}
468+
469+
/**
470+
* Create bundle price.
471+
*
472+
* @return void
473+
*/
474+
private function applyBundlePrice() : void
475+
{
476+
$select = $this->getConnection()->select();
477+
$select->from(
478+
$this->_getBundlePriceTable(),
479+
[
480+
'entity_id',
481+
'customer_group_id',
482+
'website_id',
483+
'tax_class_id',
484+
'orig_price',
485+
'price',
486+
'min_price',
487+
'max_price',
488+
'tier_price',
489+
'base_tier',
490+
]
491+
);
492+
493+
$query = $select->insertFromSelect($this->_getDefaultFinalPriceTable());
494+
$this->getConnection()->query($query);
495+
}
496+
497+
/**
498+
* Make insert/update bundle option price.
499+
*
500+
* @return void
501+
*/
502+
private function applyBundleOptionPrice() : void
503+
{
504+
$connection = $this->getConnection();
505+
506+
$subSelect = $connection->select()->from(
507+
$this->_getBundleOptionTable(),
508+
[
509+
'entity_id',
510+
'customer_group_id',
511+
'website_id',
512+
'min_price' => new \Zend_Db_Expr('SUM(min_price)'),
513+
'alt_price' => new \Zend_Db_Expr('MIN(alt_price)'),
514+
'max_price' => new \Zend_Db_Expr('SUM(max_price)'),
515+
'tier_price' => new \Zend_Db_Expr('SUM(tier_price)'),
516+
'alt_tier_price' => new \Zend_Db_Expr('MIN(alt_tier_price)'),
517+
]
518+
)->group(
519+
['entity_id', 'customer_group_id', 'website_id']
520+
);
521+
522+
$minPrice = 'i.min_price + ' . $connection->getIfNullSql('io.min_price', '0');
523+
$tierPrice = 'i.tier_price + ' . $connection->getIfNullSql('io.tier_price', '0');
524+
$select = $connection->select()->join(
525+
['io' => $subSelect],
526+
'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' .
527+
' AND i.website_id = io.website_id',
528+
[]
529+
)->columns(
530+
[
531+
'min_price' => $connection->getCheckSql("{$minPrice} = 0", 'io.alt_price', $minPrice),
532+
'max_price' => new \Zend_Db_Expr('io.max_price + i.max_price'),
533+
'tier_price' => $connection->getCheckSql("{$tierPrice} = 0", 'io.alt_tier_price', $tierPrice),
534+
]
535+
);
536+
537+
$query = $select->crossUpdateFromSelect(['i' => $this->_getDefaultFinalPriceTable()]);
538+
$connection->query($query);
539+
}
511540
}

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class DefaultPrice extends AbstractIndexer implements PriceInterface
7171
* @param \Magento\Framework\Event\ManagerInterface $eventManager
7272
* @param \Magento\Framework\Module\Manager $moduleManager
7373
* @param string|null $connectionName
74-
* @param null|IndexTableStructureFactory $indexTableStructureFactory
74+
* @param IndexTableStructureFactory $indexTableStructureFactory
7575
* @param PriceModifierInterface[] $priceModifiers
7676
*/
7777
public function __construct(
@@ -536,13 +536,18 @@ protected function _applyCustomOption()
536536
$finalPriceTable = $this->_getDefaultFinalPriceTable();
537537
$coaTable = $this->_getCustomOptionAggregateTable();
538538
$copTable = $this->_getCustomOptionPriceTable();
539+
$metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
539540

540541
$this->_prepareCustomOptionAggregateTable();
541542
$this->_prepareCustomOptionPriceTable();
542543

543544
$select = $connection->select()->from(
544545
['i' => $finalPriceTable],
545546
['entity_id', 'customer_group_id', 'website_id']
547+
)->join(
548+
['e' => $this->getTable('catalog_product_entity')],
549+
'e.entity_id = i.entity_id',
550+
[]
546551
)->join(
547552
['cw' => $this->getTable('store_website')],
548553
'cw.website_id = i.website_id',
@@ -557,7 +562,7 @@ protected function _applyCustomOption()
557562
[]
558563
)->join(
559564
['o' => $this->getTable('catalog_product_option')],
560-
'o.product_id = i.entity_id',
565+
'o.product_id = e.' . $metadata->getLinkField(),
561566
['option_id']
562567
)->join(
563568
['ot' => $this->getTable('catalog_product_option_type_value')],
@@ -610,6 +615,10 @@ protected function _applyCustomOption()
610615
$select = $connection->select()->from(
611616
['i' => $finalPriceTable],
612617
['entity_id', 'customer_group_id', 'website_id']
618+
)->join(
619+
['e' => $this->getTable('catalog_product_entity')],
620+
'e.entity_id = i.entity_id',
621+
[]
613622
)->join(
614623
['cw' => $this->getTable('store_website')],
615624
'cw.website_id = i.website_id',
@@ -624,7 +633,7 @@ protected function _applyCustomOption()
624633
[]
625634
)->join(
626635
['o' => $this->getTable('catalog_product_option')],
627-
'o.product_id = i.entity_id',
636+
'o.product_id = e.' . $metadata->getLinkField(),
628637
['option_id']
629638
)->join(
630639
['opd' => $this->getTable('catalog_product_option_price')],
@@ -641,13 +650,13 @@ protected function _applyCustomOption()
641650

642651
$minPriceRound = new \Zend_Db_Expr("ROUND(i.price * ({$optPriceValue} / 100), 4)");
643652
$priceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $minPriceRound);
644-
$minPrice = $connection->getCheckSql("{$priceExpr} > 0 AND o.is_require > 1", $priceExpr, 0);
653+
$minPrice = $connection->getCheckSql("{$priceExpr} > 0 AND o.is_require = 1", $priceExpr, 0);
645654

646655
$maxPrice = $priceExpr;
647656

648657
$tierPriceRound = new \Zend_Db_Expr("ROUND(i.base_tier * ({$optPriceValue} / 100), 4)");
649658
$tierPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $tierPriceRound);
650-
$tierPriceValue = $connection->getCheckSql("{$tierPriceExpr} > 0 AND o.is_require > 0", $tierPriceExpr, 0);
659+
$tierPriceValue = $connection->getCheckSql("{$tierPriceExpr} > 0 AND o.is_require = 1", $tierPriceExpr, 0);
651660
$tierPrice = $connection->getCheckSql("i.base_tier IS NOT NULL", $tierPriceValue, "NULL");
652661

653662
$select->columns(

dev/tests/integration/testsuite/Magento/Bundle/Model/Product/BundlePriceAbstract.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/**
1010
* Abstract class for testing bundle prices
11+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1112
*/
1213
abstract class BundlePriceAbstract extends \PHPUnit\Framework\TestCase
1314
{
@@ -34,6 +35,13 @@ protected function setUp()
3435
$this->productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
3536
$this->productCollectionFactory =
3637
$this->objectManager->create(\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class);
38+
39+
$scopeConfig = $this->objectManager->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class);
40+
$scopeConfig->setValue(
41+
\Magento\CatalogInventory\Model\Configuration::XML_PATH_SHOW_OUT_OF_STOCK,
42+
true,
43+
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
44+
);
3745
}
3846

3947
/**

0 commit comments

Comments
 (0)