I post our solution, after some working days
This module install a new attribute for catalog rules (price_source, which is a selector of price / special_price)
Then, we rewrite the needed classes to add special_price values when calculating the price when applying the catalog rules
app/code/Sinapsis/CatalogRule/etc/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\CatalogRule\Model\Indexer\ProductPriceCalculator" type="Sinapsis\CatalogRule\Model\Indexer\ProductPriceCalculator" />
<preference for="Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder" type="Sinapsis\CatalogRule\Model\Indexer\RuleProductsSelectBuilder" />
</config>
app/code/Sinapsis/CatalogRule/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="module.xsd">
<module name="Sinapsis_CatalogRule" setup_version="1.0.0">
</module>
</config>
app/code/Sinapsis/CatalogRule/Model/Indexer/ProductPriceCalculator.php
...
public function calculate($ruleData, $productData = null)
{
if ($productData !== null && isset($productData['rule_price'])) {
$productPrice = $productData['rule_price'];
} else {
// get price source from rule
$productPrice = $ruleData['default_price'];
if ($ruleData['price_source'] == 2) {
$productPrice = $ruleData['default_special_price'];
}
}
...
app/code/Sinapsis/CatalogRule/Model/Indexer/RuleProductsSelectBuilder.php
...
public function build(
$websiteId,
\Magento\Catalog\Model\Product $product = null,
$useAdditionalTable = false
) {
...
if ($product && $product->getEntityId()) {
$select->where('rp.product_id=?', $product->getEntityId());
}
// join price_source
$select->joinInner(
['catalogrule' => $this->resource->getTableName('catalogrule')],
'catalogrule.rule_id=rp.rule_id',
['price_source']
);
$linkField = $this->metadataPool
->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
->getLinkField();
$select->join(
['e' => $this->resource->getTableName('catalog_product_entity')],
sprintf('e.entity_id = rp.product_id'),
[]
);
/**
* Join default price and websites prices to result
*/
foreach (array('price', 'special_price') as $sourcePrice) {
$priceTableAlias = 'pp_default';
if ($sourcePrice == 'special_price') {
$priceTableAlias = 'sp_default';
}
$priceAttr = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $sourcePrice);
$priceTable = $priceAttr->getBackend()->getTable();
$attributeId = $priceAttr->getId();
$joinCondition = '%1$s.' . $linkField . '=e.' . $linkField . ' AND (%1$s.attribute_id='
. $attributeId
. ') and %1$s.store_id=%2$s';
$select->join(
[$priceTableAlias => $priceTable],
sprintf($joinCondition, $priceTableAlias, \Magento\Store\Model\Store::DEFAULT_STORE_ID),
[]
);
}
$website = $this->storeManager->getWebsite($websiteId);
$defaultGroup = $website->getDefaultGroup();
if ($defaultGroup instanceof \Magento\Store\Model\Group) {
$storeId = $defaultGroup->getDefaultStoreId();
} else {
$storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID;
}
$select->joinInner(
['product_website' => $this->resource->getTableName('catalog_product_website')],
'product_website.product_id=rp.product_id '
. 'AND product_website.website_id = rp.website_id '
. 'AND product_website.website_id='
. $websiteId,
[]
);
foreach (array('price', 'special_price') as $sourcePrice) {
$priceKey = 'pp';
if ($sourcePrice == 'special_price') {
$priceKey = 'sp';
}
$priceTableAlias = $priceKey . '_default';
$priceAttr = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $sourcePrice);
$priceTable = $priceAttr->getBackend()->getTable();
$tableAlias = $priceKey . $websiteId;
$select->joinLeft(
[$tableAlias => $priceTable],
sprintf($joinCondition, $tableAlias, $storeId),
[]
);
$select->columns([
'default_' . $sourcePrice => $connection->getIfNullSql($tableAlias . '.value', $priceTableAlias .'.value'),
]);
}
return $connection->query($select);
}
app/code/Sinapsis/CatalogRule/Model/PriceOptions.php
<?php
namespace Sinapsis\CatalogRule\Model;
use Magento\Framework\Data\OptionSourceInterface;
class PriceOptions implements OptionSourceInterface
{
public function toOptionArray()
{
return [
['label' => __('Price'), 'value' => '1'],
['label' => __('Special price'), 'value' => '2']
];
}
}
app/code/Sinapsis/CatalogRule/Setup/InstallSchema.php
<?php
namespace Sinapsis\CatalogRule\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
class InstallSchema implements InstallSchemaInterface
{
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$installer = $setup;
$installer->startSetup();
$installer->getConnection()->addColumn(
$installer->getTable('catalogrule'),
'price_source',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
'length' => 1,
'nullable' => false,
'default' => 1,
'comment' => 'Price Source'
]
);
$installer->endSetup();
}
}
app/code/Sinapsis/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<fieldset name="rule_information">
<field name="price_source" formElement="select">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="source" xsi:type="string">catalog_rule</item>
</item>
</argument>
<settings>
<dataType>text</dataType>
<label translate="true">Price Source</label>
<visible>true</visible>
<dataScope>price_source</dataScope>
<validation>
<rule name="required-entry" xsi:type="boolean">true</rule>
</validation>
</settings>
<formElements>
<select>
<settings>
<options class="Sinapsis\CatalogRule\Model\PriceOptions"/>
</settings>
</select>
</formElements>
</field>
</fieldset>
</form>
app/code/Sinapsis/CatalogRule/registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Sinapsis_CatalogRule',
__DIR__
);