13 use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructureFactory;
30 private $indexTableStructureFactory;
35 private $tableMaintainer;
40 private $metadataPool;
50 private $fullReindexAction;
55 private $connectionName;
67 private $dimensionToFieldMapper = [
75 private $basePriceModifier;
80 private $joinAttributeProcessor;
85 private $eventManager;
90 private $moduleManager;
107 IndexTableStructureFactory $indexTableStructureFactory,
113 \
Magento\Framework\Event\ManagerInterface $eventManager,
114 \
Magento\Framework\Module\Manager $moduleManager,
115 $fullReindexAction =
false,
116 $connectionName =
'indexer' 118 $this->indexTableStructureFactory = $indexTableStructureFactory;
119 $this->tableMaintainer = $tableMaintainer;
120 $this->connectionName = $connectionName;
121 $this->metadataPool = $metadataPool;
123 $this->fullReindexAction = $fullReindexAction;
124 $this->basePriceModifier = $basePriceModifier;
125 $this->joinAttributeProcessor = $joinAttributeProcessor;
126 $this->eventManager = $eventManager;
138 $this->tableMaintainer->createMainTmpTable($dimensions);
140 $temporaryPriceTable = $this->indexTableStructureFactory->create([
141 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions),
142 'entityField' =>
'entity_id',
143 'customerGroupField' =>
'customer_group_id',
144 'websiteField' =>
'website_id',
145 'taxClassField' =>
'tax_class_id',
146 'originalPriceField' =>
'price',
147 'finalPriceField' =>
'final_price',
148 'minPriceField' =>
'min_price',
149 'maxPriceField' =>
'max_price',
150 'tierPriceField' =>
'tier_price',
153 $entityIds = iterator_to_array($entityIds);
155 $this->prepareTierPriceIndex($dimensions, $entityIds);
157 $this->prepareBundlePriceTable();
159 $this->prepareBundlePriceByType(
165 $this->prepareBundlePriceByType(
171 $this->calculateBundleOptionPrice($temporaryPriceTable, $dimensions);
173 $this->basePriceModifier->modifyPrice($temporaryPriceTable, $entityIds);
181 private function getBundlePriceTable()
183 return $this->getTable(
'catalog_product_index_price_bundle_tmp');
191 private function getBundleSelectionTable()
193 return $this->getTable(
'catalog_product_index_price_bundle_sel_tmp');
201 private function getBundleOptionTable()
203 return $this->getTable(
'catalog_product_index_price_bundle_opt_tmp');
211 private function prepareBundlePriceTable()
213 $this->getConnection()->delete($this->getBundlePriceTable());
222 private function prepareBundleSelectionTable()
224 $this->getConnection()->delete($this->getBundleSelectionTable());
233 private function prepareBundleOptionTable()
235 $this->getConnection()->delete($this->getBundleOptionTable());
249 private function prepareBundlePriceByType(
$priceType, array $dimensions, $entityIds =
null)
251 $connection = $this->getConnection();
252 $select = $connection->select()->from(
253 [
'e' => $this->getTable(
'catalog_product_entity')],
256 [
'cg' => $this->getTable(
'customer_group')],
263 [
'customer_group_id']
265 [
'pw' => $this->getTable(
'catalog_product_website')],
266 'pw.product_id = e.entity_id',
269 [
'cwd' => $this->getTable(
'catalog_product_index_website')],
270 'pw.website_id = cwd.website_id',
274 [
'tp' => $this->getTable(
'catalog_product_index_tier_price')],
275 'tp.entity_id = e.entity_id AND tp.website_id = pw.website_id' .
276 ' AND tp.customer_group_id = cg.customer_group_id',
283 foreach ($dimensions as $dimension) {
284 if (!isset($this->dimensionToFieldMapper[$dimension->getName()])) {
285 throw new \LogicException(
286 'Provided dimension is not valid for Price indexer: ' . $dimension->getName()
289 $select->where($this->dimensionToFieldMapper[$dimension->getName()] .
' = ?', $dimension->getValue());
293 if ($this->moduleManager->isEnabled(
'Magento_Tax')) {
294 $taxClassId = $this->joinAttributeProcessor->process(
$select,
'tax_class_id');
296 $taxClassId = new \Zend_Db_Expr(
'0');
303 [
'tax_class_id' => $connection->getCheckSql($taxClassId .
' IS NOT NULL', $taxClassId, 0)]
309 $price = $this->joinAttributeProcessor->process(
$select,
'price');
310 $specialPrice = $this->joinAttributeProcessor->process(
$select,
'special_price');
311 $specialFrom = $this->joinAttributeProcessor->process(
$select,
'special_from_date');
312 $specialTo = $this->joinAttributeProcessor->process(
$select,
'special_to_date');
313 $currentDate = new \Zend_Db_Expr(
'cwd.website_date');
315 $specialFromDate = $connection->getDatePartSql($specialFrom);
316 $specialToDate = $connection->getDatePartSql($specialTo);
317 $specialFromExpr =
"{$specialFrom} IS NULL OR {$specialFromDate} <= {$currentDate}";
318 $specialToExpr =
"{$specialTo} IS NULL OR {$specialToDate} >= {$currentDate}";
319 $specialExpr =
"{$specialPrice} IS NOT NULL AND {$specialPrice} > 0 AND {$specialPrice} < 100" 320 .
" AND {$specialFromExpr} AND {$specialToExpr}";
321 $tierExpr = new \Zend_Db_Expr(
'tp.min_price');
324 $specialPriceExpr = $connection->getCheckSql(
326 'ROUND(' .
$price .
' * (' . $specialPrice .
' / 100), 4)',
329 $tierPrice = $connection->getCheckSql(
330 $tierExpr .
' IS NOT NULL',
331 'ROUND((1 - ' . $tierExpr .
' / 100) * ' .
$price .
', 4)',
334 $finalPrice = $connection->getLeastSql([
336 $connection->getIfNullSql($specialPriceExpr,
$price),
337 $connection->getIfNullSql($tierPrice,
$price),
340 $finalPrice = new \Zend_Db_Expr(
'0');
341 $tierPrice = $connection->getCheckSql($tierExpr .
' IS NOT NULL',
'0',
'NULL');
347 'special_price' => $connection->getCheckSql($specialExpr, $specialPrice,
'0'),
348 'tier_percent' => $tierExpr,
349 'orig_price' => $connection->getIfNullSql(
$price,
'0'),
350 'price' => $finalPrice,
351 'min_price' => $finalPrice,
352 'max_price' => $finalPrice,
353 'tier_price' => $tierPrice,
354 'base_tier' => $tierPrice,
358 if ($entityIds !==
null) {
359 $select->where(
'e.entity_id IN(?)', $entityIds);
365 $this->eventManager->dispatch(
366 'catalog_product_prepare_index_select',
371 'store_field' =>
new \
Zend_Db_Expr(
'cwd.default_store_id')
375 $query =
$select->insertFromSelect($this->getBundlePriceTable());
376 $connection->query(
$query);
388 private function calculateBundleOptionPrice($priceTable, $dimensions)
390 $connection = $this->getConnection();
392 $this->prepareBundleSelectionTable();
396 $this->prepareBundleOptionTable();
398 $select = $connection->select()->from(
399 $this->getBundleSelectionTable(),
400 [
'entity_id',
'customer_group_id',
'website_id',
'option_id']
402 [
'entity_id',
'customer_group_id',
'website_id',
'option_id']
404 $minPrice = $connection->getCheckSql(
'is_required = 1',
'price',
'NULL');
405 $tierPrice = $connection->getCheckSql(
'is_required = 1',
'tier_price',
'NULL');
408 'min_price' =>
new \
Zend_Db_Expr(
'MIN(' . $minPrice .
')'),
410 'max_price' => $connection->getCheckSql(
'group_type = 0',
'MAX(price)',
'SUM(price)'),
411 'tier_price' => new \Zend_Db_Expr(
'MIN(' . $tierPrice .
')'),
412 'alt_tier_price' => new \Zend_Db_Expr(
'MIN(tier_price)'),
416 $query =
$select->insertFromSelect($this->getBundleOptionTable());
417 $connection->query(
$query);
419 $this->getConnection()->delete($priceTable->getTableName());
420 $this->applyBundlePrice($priceTable);
421 $this->applyBundleOptionPrice($priceTable);
433 private function calculateBundleSelectionPrice($dimensions,
$priceType)
435 $connection = $this->getConnection();
438 $selectionPriceValue = $connection->getCheckSql(
439 'bsp.selection_price_value IS NULL',
440 'bs.selection_price_value',
441 'bsp.selection_price_value' 443 $selectionPriceType = $connection->getCheckSql(
444 'bsp.selection_price_type IS NULL',
445 'bs.selection_price_type',
446 'bsp.selection_price_type' 448 $priceExpr = new \Zend_Db_Expr(
449 $connection->getCheckSql(
450 $selectionPriceType .
' = 1',
451 'ROUND(i.price * (' . $selectionPriceValue .
' / 100),4)',
452 $connection->getCheckSql(
453 'i.special_price > 0 AND i.special_price < 100',
454 'ROUND(' . $selectionPriceValue .
' * (i.special_price / 100),4)',
457 ) .
'* bs.selection_qty' 460 $tierExpr = $connection->getCheckSql(
461 'i.base_tier IS NOT NULL',
462 $connection->getCheckSql(
463 $selectionPriceType .
' = 1',
464 'ROUND(i.base_tier - (i.base_tier * (' . $selectionPriceValue .
' / 100)),4)',
465 $connection->getCheckSql(
466 'i.tier_percent > 0',
467 'ROUND((1 - i.tier_percent / 100) * ' . $selectionPriceValue .
',4)',
470 ) .
' * bs.selection_qty',
474 $priceExpr = $connection->getLeastSql([
476 $connection->getIfNullSql($tierExpr, $priceExpr),
479 $price =
'idx.min_price * bs.selection_qty';
480 $specialExpr = $connection->getCheckSql(
481 'i.special_price > 0 AND i.special_price < 100',
482 'ROUND(' .
$price .
' * (i.special_price / 100), 4)',
485 $tierExpr = $connection->getCheckSql(
486 'i.tier_percent IS NOT NULL',
487 'ROUND((1 - i.tier_percent / 100) * ' .
$price .
', 4)',
490 $priceExpr = $connection->getLeastSql([
492 $connection->getIfNullSql($tierExpr,
$price),
496 $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
497 $linkField = $metadata->getLinkField();
498 $select = $connection->select()->from(
499 [
'i' => $this->getBundlePriceTable()],
500 [
'entity_id',
'customer_group_id',
'website_id']
502 [
'parent_product' => $this->getTable(
'catalog_product_entity')],
503 'parent_product.entity_id = i.entity_id',
506 [
'bo' => $this->getTable(
'catalog_product_bundle_option')],
507 "bo.parent_id = parent_product.$linkField",
510 [
'bs' => $this->getTable(
'catalog_product_bundle_selection')],
511 'bs.option_id = bo.option_id',
514 [
'bsp' => $this->getTable(
'catalog_product_bundle_selection_price')],
515 'bs.selection_id = bsp.selection_id AND bsp.website_id = i.website_id',
518 [
'idx' => $this->getMainTable($dimensions)],
519 'bs.product_id = idx.entity_id AND i.customer_group_id = idx.customer_group_id' .
520 ' AND i.website_id = idx.website_id',
523 [
'e' => $this->getTable(
'catalog_product_entity')],
524 'bs.product_id = e.entity_id AND e.required_options=0',
531 'group_type' => $connection->getCheckSql(
"bo.type = 'select' OR bo.type = 'radio'",
'0',
'1'),
532 'is_required' =>
'bo.required',
533 'price' => $priceExpr,
534 'tier_price' => $tierExpr,
538 $query =
$select->insertFromSelect($this->getBundleSelectionTable());
539 $connection->query(
$query);
550 private function prepareTierPriceIndex($dimensions, $entityIds)
552 $connection = $this->getConnection();
553 $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
554 $linkField = $metadata->getLinkField();
556 $select = $connection->select()->from(
557 [
'i' => $this->getTable(
'catalog_product_index_tier_price')],
560 [
'e' => $this->getTable(
'catalog_product_entity')],
561 "i.entity_id=e.entity_id",
568 $connection->query(
$query);
570 $select = $connection->select()->from(
571 [
'tp' => $this->getTable(
'catalog_product_entity_tier_price')],
574 [
'e' => $this->getTable(
'catalog_product_entity')],
575 "tp.{$linkField} = e.{$linkField}",
578 [
'cg' => $this->getTable(
'customer_group')],
579 'tp.all_groups = 1 OR (tp.all_groups = 0 AND tp.customer_group_id = cg.customer_group_id)',
580 [
'customer_group_id']
582 [
'pw' => $this->getTable(
'store_website')],
583 'tp.website_id = 0 OR tp.website_id = pw.website_id',
593 [
'e.entity_id',
'cg.customer_group_id',
'pw.website_id']
596 if (!empty($entityIds)) {
597 $select->where(
'e.entity_id IN(?)', $entityIds);
599 foreach ($dimensions as $dimension) {
600 if (!isset($this->dimensionToFieldMapper[$dimension->getName()])) {
601 throw new \LogicException(
602 'Provided dimension is not valid for Price indexer: ' . $dimension->getName()
605 $select->where($this->dimensionToFieldMapper[$dimension->getName()] .
' = ?', $dimension->getValue());
608 $query =
$select->insertFromSelect($this->getTable(
'catalog_product_index_tier_price'));
609 $connection->query(
$query);
618 private function applyBundlePrice($priceTable): void
620 $select = $this->getConnection()->select();
622 $this->getBundlePriceTable(),
636 $query =
$select->insertFromSelect($priceTable->getTableName());
637 $this->getConnection()->query(
$query);
646 private function applyBundleOptionPrice($priceTable): void
648 $connection = $this->getConnection();
650 $subSelect = $connection->select()->from(
651 $this->getBundleOptionTable(),
660 'alt_tier_price' =>
new \
Zend_Db_Expr(
'MIN(alt_tier_price)'),
663 [
'entity_id',
'customer_group_id',
'website_id']
666 $minPrice =
'i.min_price + ' . $connection->getIfNullSql(
'io.min_price',
'0');
667 $tierPrice =
'i.tier_price + ' . $connection->getIfNullSql(
'io.tier_price',
'0');
668 $select = $connection->select()->join(
669 [
'io' => $subSelect],
670 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' .
671 ' AND i.website_id = io.website_id',
675 'min_price' => $connection->getCheckSql(
"{$minPrice} = 0",
'io.alt_price', $minPrice),
676 'max_price' => new \Zend_Db_Expr(
'io.max_price + i.max_price'),
677 'tier_price' => $connection->getCheckSql(
"{$tierPrice} = 0",
'io.alt_tier_price', $tierPrice),
681 $query =
$select->crossUpdateFromSelect([
'i' => $priceTable->getTableName()]);
682 $connection->query(
$query);
691 private function getMainTable($dimensions)
693 if ($this->fullReindexAction) {
694 return $this->tableMaintainer->getMainReplicaTable($dimensions);
696 return $this->tableMaintainer->getMainTable($dimensions);
705 private function getConnection(): \
Magento\Framework\DB\Adapter\AdapterInterface
707 if ($this->connection ===
null) {
708 $this->connection = $this->resource->getConnection($this->connectionName);
711 return $this->connection;
722 return $this->resource->getTableName(
$tableName, $this->connectionName);
executeByDimensions(array $dimensions, \Traversable $entityIds)
__construct(IndexTableStructureFactory $indexTableStructureFactory, TableMaintainer $tableMaintainer, MetadataPool $metadataPool, \Magento\Framework\App\ResourceConnection $resource, BasePriceModifier $basePriceModifier, JoinAttributeProcessor $joinAttributeProcessor, \Magento\Framework\Event\ManagerInterface $eventManager, \Magento\Framework\Module\Manager $moduleManager, $fullReindexAction=false, $connectionName='indexer')