In the Magento 1 Enterprise Rewards module, there is a bug that affects page load time and server performance during the creation of a credit memo. In the case of a large store with large Reward Points history, it can actually prevent you from creating a credit memo. This problem affects orders placed from guests – customers that do not have a customer_id.
Nature of the Issue
When creating a credit memo while the Magento 1 Enterprise Reward Points module is installed and enabled, Magento will load all of the reward points history for the customer. It then iterates through this history, looking for this order in the Reward Points history so that it knows how to correctly calculate points during the refund.
This all happens in this file/method:
1 2 3 | app/code/core/Enterprise/Reward/Model/Observer.php public function creditmemoSaveAfter(Varien_Event_Observer $observer) Line 755 |
Reward Points history is saved based on a reward_id, which is correlated to a customer_id in the enterprise_reward table.
1 2 3 4 5 6 7 8 9 10 11 | mysql> DESCRIBE enterprise_reward; +-----------------------+----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------------+----------------------+------+-----+---------+----------------+ | reward_id | int(10) unsigned | NO | PRI | NULL | auto_increment | | customer_id | int(10) unsigned | NO | MUL | 0 | | | website_id | smallint(5) unsigned | YES | MUL | NULL | | | points_balance | int(10) unsigned | NO | | 0 | | | website_currency_code | varchar(3) | YES | | NULL | | +-----------------------+----------------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec) |
Here is where Magento looks up the Reward Points history:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # app/code/core/Enterprise/Reward/Model/Observer.php # Lines 798 - 809 // Void reward points granted for refunded amount if there was any $customerId = $order->getCustomerId(); $websiteId = Mage::app()->getStore($order->getStoreId())->getWebsiteId(); // @var $rewardHistoryCollection Enterprise_Reward_Model_Resource_Reward_History_Collection $rewardHistoryCollection = Mage::getModel('enterprise_reward/reward_history')->getCollection(); $rewardHistoryCollection->addCustomerFilter($customerId) ->addWebsiteFilter($websiteId) // nothing to void if reward points are expired already ->addFilter('main_table.is_expired', 0) // void points acquired for purchase only ->addFilter('main_table.action', Enterprise_Reward_Model_Reward::REWARD_ACTION_ORDER_EXTRA); |
The issue comes in then when the customer is a guest and thus does not have a customer_id. This line:
1 2 | $rewardHistoryCollection->addCustomerFilter($customerId) ->addWebsiteFilter($websiteId) |
calls:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # app/code/core/Enterprise/Reward/Model/Resource/Reward/History/Collection.php # public function addCustomerFilter($customerId) # Line 107 /** * Join reward table to filter history by customer id * * @param string $customerId * @return Enterprise_Reward_Model_Resource_Reward_History_Collection */ public function addCustomerFilter($customerId) { if ($customerId) { $this->_joinReward(); $this->getSelect()->where('reward_table.customer_id = ?', $customerId); } return $this; } |
If the $customerId is null, the join is never added to the query, thus resulting in ALL of the Reward Points history for ALL customers being returned.
1 | SELECT `main_table`.* FROM `enterprise_reward_history` AS `main_table` WHERE (main_table.website_id = '1') |
Depending on the size of your Reward Points history this can be a large issue. I’ve noticed that Magento will not return a page when it is too large because the server is asked to process far too much data. It can throw an error in the traffic logs similar to this:
1 | PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 8208 bytes) in /var/www/vhosts/examplesite.com/html/app/code/core/Enterprise/Reward/Model/Reward/History.php on line 196, referer: https://examplesite.com/sales_order/view/order_id/123456/ |
Bug Fix
The solution to this issue is to ensure that even if $customerId is null (guest customer) that this join still is called. We can $customerId = 0 as that customer would never exist in a Magento store.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * Join reward table to filter history by customer id * * @param string $customerId * @return Enterprise_Reward_Model_Resource_Reward_History_Collection */ public function addCustomerFilter($customerId) { /* Added to core */ if( !$customerId ) { $customerId = 0; } if ($customerId OR $customerId === 0) { /* Changed from core */ $this->_joinReward(); $this->getSelect()->where('reward_table.customer_id = ?', $customerId); } return $this; } |
This bug fix sets $customerId = 0, thus the join will have something to look for.
Also the if statement has to be modified as a value of 0 would fail.
The end result is that the query resulting from this change will look like:
1 2 | SELECT `main_table`.*, `reward_table`.`customer_id`, `reward_table`.`points_balance` AS `points_balance_total` FROM `enterprise_reward_history` AS `main_table` INNER JOIN `enterprise_reward` AS `reward_table` ON reward_table.reward_id = main_table.reward_id WHERE (reward_table.customer_id = 0) AND (main_table.website_id = '1') |
This should return zero results (as opposed to ALL results) and your server will hum right along just fine from there.
Bug Fix Via an Extend
As always, DO NOT modify core code – instead extend this module and properly fix this bug through the app/code/local directory. NEVER modify core code in a production environment!
Other Areas Using This Function
For clarity, there are a few other areas of the website that use this same function. These areas have been taken into account with this bug fix and are not affected by this change.
1 2 3 4 5 | grep -rn addCustomerFilter app/code/core/Enterprise/Reward/ app/code/core/Enterprise/Reward/Model/Resource/Reward/History/Collection.php:107: public function addCustomerFilter($customerId) app/code/core/Enterprise/Reward/Model/Observer.php:804: $rewardHistoryCollection->addCustomerFilter($customerId) app/code/core/Enterprise/Reward/Block/Adminhtml/Customer/Edit/Tab/Reward/History/Grid.php:57: ->addCustomerFilter($this->getCustomerId()) app/code/core/Enterprise/Reward/Block/Customer/Reward/History.php:147: ->addCustomerFilter(Mage::getSingleton('customer/session')->getCustomerId()) |
app/code/core/Enterprise/Reward/Model/Resource/Reward/History/Collection.php
Usage: The function with the bug that is modified with the proposed fix above
app/code/core/Enterprise/Reward/Model/Observer.php
Usage: Called after saving a credit memo.
app/code/core/Enterprise/Reward/Block/Adminhtml/Customer/Edit/Tab/Reward/History/Grid.php
Usage: In the Magento admin, displays the customers Reward Points history.
app/code/core/Enterprise/Reward/Block/Customer/Reward/History.php
Usage: On the frontend, displays the customers Reward Points history.