How to detect if a price rule gets applied to a product

We were asked to write a simple module that makes it possible to display a rule-specific teaser on product pages when they are affected by catalog price rules.

So, our first question was: how can we even detect if a price rule is applied to a product?

The easiest Solutions

Model Mage_CatalogRule_Model_Rule offers two methods that seem quite interesting: loadProductRules and calcProductPriceRule.

loadProductRules will save any rule ids that are attached to the given product in the product’s collection:

// $_product is an instance of Mage_Catalog_Model_Product
$catalogRule = Mage::getModel('catalogrule/rule')->loadProductRules($_product);

$catalogRule will now contain an array: Array( [rule_id] => [irrelevant_number] ). If you want to know why we get an irrelevant number here, you can read up below.

While this is the fastest and easiest way to get any rules applied to our product, the most important thing to note is that it literally gives us *every* rule that is somehow connected. There are no filters applied which means we also get future and past rules and rules that are meant for any customer group or website. Depending on what you want to do this can be totally fine, of course.

Lets take a look at calcProductPriceRule. This handy method is returning our product’s price with any catalog rule applied to it. If there is no rule applied it will return NULL. This sounds great because we can use it do exactly what we need – detect whether a price rule gets applied to our product or not:

if( Mage::getModel('catalogrule/rule')->calcProductPriceRule($_product,$_product->getPrice()) ){
	echo 'Catalog price rule applied';
} else {
	echo 'No catalog price rule applied';
}

And yes, this one filters our rules by date, customer groups and website. The only downside of it is that it does more than we need it to do – which means it takes more resources than necessary.

So what are the alternatives?

An alternative way and also the way that we ended up going is reproducing what calcProductPriceRule does, but without actually calculating any prices. Essentially, it makes use of Mage_CatalogRule_Model_Resource_Rule::getRulesFromProduct and so will we:

Step 1: Set up your Model

class My_Module_Model_Catalogrule extends Mage_Core_Model_Abstract {
  /*
  * @param Mage_Catalog_Model_Product $product
  * @return array
  */
  public function getRulesFromProduct(Mage_Catalog_Model_Product $product)
  {
	  $productId  = $product->getId();
	  $storeId    = $product->getStoreId();
	  $websiteId  = Mage::app()->getStore($storeId)->getWebsiteId();
	  if ($product->hasCustomerGroupId()) {
		  $customerGroupId = $product->getCustomerGroupId();
	  } else {
		  $customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId();
	  }
	  $dateTs     = Mage::app()->getLocale()->date()->getTimestamp();
  
	  return Mage::getResourceModel('catalogrule/rule')->getRulesFromProduct($dateTs, $websiteId, $customerGroupId, $productId);	  
  }	
}

Our method returns an empty array if the product does not have any price rules applied.

Step 2: Set up your Block

class My_Module_Block_Catalog_Product_Catalogrule extends Mage_Catalog_Block_Product_Abstract {
	
	protected $_rules;
	public function getRules(){
		if( is_null($this->_rules) ){
			$catalogrule = Mage::getModel('my_module/catalogrule');
			if(($product = $this->getProduct()) && ($rules = $catalogrule->getRulesFromProduct($product))){
				$this->_rules = $rules;
			} else {
				$this->_rules = false;	
			}
		}
		return $this->_rules;
	}
	
	public function hasRuleApplied(){
		if( $this->getRules() ){
			return true;
		}
		return false;
	}	
}

Step 3: Set up your template

Now we can do stuff like this in our block’s template:

<?php if($this->hasRuleApplied()): ?>
    <?php foreach($this->getRules() as $rule): ?>
    <div class="pricerule-teaser">
		<strong><?php 
        switch($rule['action_operator']){
            case 'by_percent':
                echo $this->__('%s%% Discount', round($rule['action_amount']));
            break;
            case 'by_fixed':
                echo $this->__('%s Discount', Mage::helper('core')->currency($rule['action_amount'], true, false));
            break;	
            case 'to_percent':
                echo $this->__('Only %s%% of the original price', round($rule['action_amount']));
            break;	
            case 'to_fixed':
                echo $this->__('Only %s', Mage::helper('core')->currency($rule['action_amount'], true, false));
            break;	
        } ?></strong>
    	<?php echo $this->__('until the %s !', date('dS M', $rule['to_time'])) ?>
    </div>
    <?php endforeach; ?>
<?php endif; ?>

Read up below if you want a list of all the values our $rule array stores.

Well and that’s it! Let us know if you have questions or ideas for improvement.


What are these values for?

Method loadProductRules calles Mage_CatalogRule_Model_Resource_Rule::getProductRuleIds which runs following database query:

SELECT `catalogrule_product`.`rule_id` FROM `catalogrule_product` WHERE (product_id = '{product_id}')

This means we get a list of all price rules applied to this specific product – exactly what we want to do. However, `catalogrule_product` doesn’t store just one row per rule but one row for each customer group, that is allowed for this rule. So lets assume we allowed 3 customer groups to shop with rule #1, the query’s original fetch would be:
Array (
[0] => 1
[1] => 1
[2] => 1
)

Because that’s a kinda nonsensical array for us, getProductRuleIds is simply flipping the array. This way it returns Array( [1] => 2 ) instead (2 as the highest key value).

Which values does the $rule array store?

It doesn’t store many values – if you need more like he rule’s title or description you’d have to load the rule explicitly.

Array
(
[rule_product_id] => 5174
[rule_id] => 1
[from_time] => 1496707200
[to_time] => 1498089599
[customer_group_id] => 0
[product_id] => 708
[action_operator] => by_percent
[action_amount] => 10.0000
[action_stop] => 1
[sort_order] => 10
[website_id] => 1
[sub_simple_action] => by_percent
[sub_discount_amount] => 10.0000
)

Leave a Reply

Your email address will not be published. Required fields are marked *