Thursday, April 1, 2010

Create bulk discount rules

Problem:
Create bulk discount rules with unique coupon codes.
Details found here: http://www.magentocommerce.com/boards/viewthread/177465/
Possible solution:
This is not an extension, it's just a 'quick and dirty' script that I hope some day (when I have the time) will grow up and become an extension.

put the code below into a file (let's call it coupons.php) and follow the instructions below:

<?php
$mageFilename = 'app/Mage.php';

require_once $mageFilename;

Varien_Profiler::enable();

Mage::setIsDeveloperMode(true);

ini_set('display_errors', 1);

umask(0);
Mage::app('default');
Mage::register('isSecureArea', 1);
function generateUniqueId($length = null){
$rndId = crypt(uniqid(rand(),1)); 
$rndId = strip_tags(stripslashes($rndId)); 
$rndId = str_replace(array(".", "$"),"",$rndId); 
$rndId = strrev(str_replace("/","",$rndId));
if (!is_null($rndId)){
return strtoupper(substr($rndId, 0, $length));
} 
return strtoupper($rndId);
} 
function getAllCustomerGroups(){
//get all customer groups
$customerGroups = Mage::getModel('customer/group')->getCollection();
$groups = array();
foreach ($customerGroups as $group){
$groups[] = $group->getId();
}
return $groups;
}
function getAllWbsites(){
//get all wabsites
$websites = Mage::getModel('core/website')->getCollection();
$websiteIds = array();
foreach ($websites as $website){
$websiteIds[] = $website->getId();
}
return $websiteIds;
}
//read comments for each line
function generateRule(){
$uniqueId = generateUniqueId(10);
$rule = Mage::getModel('salesrule/rule');
$rule->setName($uniqueId);
$rule->setDescription('Generated by tzyganu');
$rule->setFromDate(date('Y-m-d'));//starting today
//$rule->setToDate('2011-01-01');//if you need an expiration date
$rule->setCouponCode($uniqueId);
$rule->setUsesPerCoupon(1);//number of allowed uses for this coupon
$rule->setUsesPerCustomer(1);//number of allowed uses for this coupon for each customer
$rule->setCustomerGroupIds(getAllCustomerGroups());//if you want only certain groups replace getAllCustomerGroups() with an array of desired ids 
$rule->setIsActive(1);
$rule->setStopRulesProcessing(0);//set to 1 if you want all other rules after this to not be processed
$rule->setIsRss(0);//set to 1 if you want this rule to be public in rss
$rule->setIsAdvanced(1);//have no idea what it means :)
$rule->setProductIds('');
$rule->setSortOrder(0);// order in which the rules will be applied

$rule->setSimpleAction('by_percent');
//all available discount types
//by_percent - Percent of product price discount
//by_fixed - Fixed amount discount
//cart_fixed - Fixed amount discount for whole cart
//buy_x_get_y - Buy X get Y free (discount amount is Y)

$rule->setDiscountAmount('20');//the discount amount/percent. if SimpleAction is by_percent this value must be <= 100
$rule->setDiscountQty(0);//Maximum Qty Discount is Applied to
$rule->setDiscountStep(0);//used for buy_x_get_y; This is X
$rule->setSimpleFreeShipping(0);//set to 1 for Free shipping
$rule->setApplyToShipping(1);//set to 0 if you don't want the rule to be applied to shipping
$rule->setWebsiteIds(getAllWbsites());//if you want only certain websites replace getAllWbsites() with an array of desired ids

$conditions = array();
$conditions[1] = array(
'type' => 'salesrule/rule_condition_combine',
'aggregator' => 'all',
'value' => "1", //[UPDATE] added quotes on the value. Thanks Aziz Rattani [/UPDATE]
'new_child' => ''
);
//the conditions above are for 'if all of these conditions are true'
//for if any one of the conditions is true set 'aggregator' to 'any'
//for if all of the conditions are false set 'value' to 0.
//for if any one of the conditions is false set 'aggregator' to 'any' and 'value' to 0
$conditions['1--1'] = Array
(
'type' => 'salesrule/rule_condition_address',
'attribute' => 'base_subtotal',
'operator' => '>=',
'value' => 200
);
//the constraints above are for 'Subtotal is equal or grater than 200'
//for 'equal or less than' set 'operator' to '<='... You get the idea other operators for numbers: '==', '!=', '>', '<'
//for 'is one of' set operator to '()';
//for 'is not one of' set operator to '!()';
//in this example the constraint is on the subtotal
//for other attributes you can change the value for 'attribute' to: 'total_qty', 'weight', 'payment_method', 'shipping_method', 'postcode', 'region', 'region_id', 'country_id'

//to add an other constraint on product attributes (not cart attributes like above) uncomment and change the following:
/*
$conditions['1--2'] = array
(
'type' => 'salesrule/rule_condition_product_found',//-> means 'if all of the following are true' - same rules as above for 'aggregator' and 'value'
//other values for type: 'salesrule/rule_condition_product_subselect' 'salesrule/rule_condition_combine'
'value' => 1,
'aggregator' => 'all',
'new_child' => '', 
);

$conditions['1--2--1'] = array
(
'type' => 'salesrule/rule_condition_product',
'attribute' => 'sku',
'operator' => '==',
'value' => '12',
);
*/
//$conditions['1--2--1'] means sku equals 12. For other constraints change 'attribute', 'operator'(see list above), 'value'

$rule->setData('conditions',$conditions);
$rule->loadPost($rule->getData());
$rule->save();
//[UPDATE]if you work with Mangento EE and you want to link banners to your rule uncomment the line of code below
//Mage::getResourceModel('enterprise_banner/banner')->bindBannersToSalesRule($rule->getId(), array(1,2));//the array(1,2, ...) is the array with all the banners you want to link to the rule.
//[/UPDATE]
}
?>


[UPDATE]. This was made for Magento 1.3 and lower. For versions >= 1.4 you have to add a new line
$rule->setCouponType(2);
Add it just before
$rule->save();
(Thanks Raj for pointing this out).
[/UPDATE]

[UPDATE 1.1]
For Magento 1.4 and later there are store labels for the discount rules.
To add this construct the following array
$labels = array();
$labels[0] = 'Default store label';//default store label
$labels[1] = 'Label for store with id 1';
//....
$labels[n] = 'Label for store with id n';
//add one line for each store view you have. The key is the store view ID

and before calling $rule->save(); add this line
$rule->setStoreLabels($labels);

(Thanks Sigmund for the update)
[/UPDATE 1.1]

Put the newly created file in the root of your magento installation (same level as index.php)
Walk through the file read all the comments and configure your discount settings (some programming skills are requred)
After everything is done add the following code at the end of the file (before php closing tag)

for ($i=1;$i<=200;$i++){//replace 200 with the number of coupons you want
generateRule();
}
[UPDATE 1.2]
If you want a nice script to import coupons using csv files with code, description and discount check this out: http://www.gielberkers.com/bulk-import-coupon-codes-in-magento/
Nice job Giel :). [/UPDATE 1.2]
call the page in you browser (http://your.magento.root/coupons.php) or run it from the command line.
Now check your admin for the new added rules.
PS (this does not work yet for 'Actions' tab).

30 comments:

  1. Thanks a lot for the great post! But you forgot to mention about the salesrule's label. ;)

    ReplyDelete
  2. Hello Sigmund. Thanks for pointing this out. I updated the post to support store labels.

    Cheers,
    Marius.

    ReplyDelete
  3. Dear Tzyganu. We use magento 1.5.1.0 and get the fatal error:

    Fatal error: Uncaught exception 'Mage_Core_Model_Store_Exception' in /home/users/qdealftp/qdealer.nl/app/code/core/Mage/Core/Model/App.php:1291 Stack trace: #0 /home/users/qdealftp/qdealer.nl/app/code/core/Mage/Core/Model/App.php(803): Mage_Core_Model_App->throwStoreException() #1 /home/users/qdealftp/qdealer.nl/app/code/core/Mage/Core/Model/App.php(454): Mage_Core_Model_App->getStore() #2 /home/users/qdealftp/qdealer.nl/app/code/core/Mage/Core/Model/App.php(262): Mage_Core_Model_App->_initCurrentStore('default', 'store') #3 /home/users/qdealftp/qdealer.nl/app/Mage.php(570): Mage_Core_Model_App->init('default', 'store', Array) #4 /home/users/qdealftp/qdealer.nl/create-coupon_review.php(13): Mage::app('default') #5 {main} thrown in /home/users/qdealftp/qdealer.nl/app/code/core/Mage/Core/Model/App.php on line 1291

    What is wrong?

    ReplyDelete
  4. Hello
    This could happen if you changed the code for the default store view of your Magento instance.
    Go to system->manage stores and select one store view (it doesn't mater which one) and replace the line Mage::app('default'); with Mage::app('YOUR STORE VIEW CODE HERE');

    Let me know how this turns out.
    Cheers,
    Marius.

    ReplyDelete
  5. Thanks for the great solution. If you're using a stock Centos system and get the following error -

    "Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core"

    You might need to follow these instructions -
    http://chrisjean.com/2009/01/31/unicode-support-on-centos-52-with-php-and-pcre/

    Now it all works for me.

    Thanks again,
    Greg

    ReplyDelete
  6. Thanks for the very useful script. But what if I want to create a code formated like WRMV-325 (4 uppercase letters before hyphen and 3 numbers after hyphen?

    ReplyDelete
  7. Hello
    I this is also possible. You can create a different method that generates coupon codes instead of my 'generateUniqueId' function that uses regex. Unfortunately I'm not the best at regex ,I'm not even in the top 100(00).
    Just an idea: you can use expressions like '[A*4]-[N*3]' and parse them with regex and generate your code.

    Marius.

    ReplyDelete
  8. I am not sure how to get this to import our existing Groupon codes though...any suggestions? THANKS!

    ReplyDelete
  9. Hello Jonathan.
    If you don't know how can I know? :)
    If you already have the coupon codes and you need to import them in your magento instance just change the script above and make it loop through all your coupons and change
    $rule->setCouponCode($uniqueId); with
    $rule->setCouponCode(YOUR CODE HERE);

    I hope this helps. If not please give me more details about your issue.

    ReplyDelete
  10. Hello tzyganu,

    Thanks for the great code, very useful indeed!

    I managed to add conditions for the actions tab using the following code:

    /* Creat new rules on the actions tab */
    $actions = array();
    $actions[1] = array(
    'type' => 'salesrule/rule_condition_product',
    'aggregator' => 'all',
    'value' => 1,
    'new_child' => ''
    );

    /* Example: Restrict coupon codes only to products in category with id 66 */

    $actions['1--1'] = Array (
    'type' => 'salesrule/rule_condition_product',
    'attribute' => 'category_ids',
    'operator' => '{}',
    'value' => '66',
    'is_value_processed' => 0,
    );

    /* Save data */
    $rule->setData('actions',$actions);

    Hope this help someone.
    Camilo

    ReplyDelete
    Replies
    1. I was looking to do the same but your code seem to miss TRUE FALSE in the statement
      If ALL of these conditions are (BLANK)

      Also what do I do if I want to say

      Row total in cart equals or greater than 60

      In a whole I want to have a rule like following:


      If ALL of these conditions are TRUE :
      Category does not contain 3
      Row total in cart equals or greater than 60

      Delete
  11. hi,

    Thanks for the great script!
    Can you please tell me how i can import 4 unique codes from the php file?

    cheers,
    Shal

    ReplyDelete
  12. Hi Shal.
    You can put your codes in an array like this:
    $codes = array('code1', 'code2', ..., 'codeN');
    And instead of generating a random id (in this line $uniqueId = generateUniqueId(10);) you just loop through the array and get your codes.

    Modify this:
    function generateRule(){
    $uniqueId = generateUniqueId(10);

    Into this:

    function generateRule($code){
    $uniqueId = $code;

    Leave the rest as it is.

    Now change the for loop from this:
    for ($i=1;$i<=200;$i++){//replace 200 with the number of coupons you want
    generateRule();
    }
    to this
    foreach ($codes as $code){//the $codes array is the one defined above.
    generateRule($code);
    }

    I hope It helps.
    Cheers,
    Marius.

    PS I didn't publish your other comment because I thought that if those were your real coupon codes you will not want them available on the web :).

    ReplyDelete
  13. Thanks so much Tzyganu! So lovely to get help from someone I don't know. Im not so good with php and i knew it was a simple fix.

    Really really appreciate your help. In return i will give you a link from my magento blog!

    Have a fantastic day x

    ReplyDelete
  14. Thank you so much to everyone for this code. It is exactly what I have been looking for.

    I do have one additional question. In Magento Enterprise when you create a rule you have the ability to link it to Related Banners so that when the rule is met, a banner can be displayed. We use this with our one time use coupons to remind a customer in checkout to use the full value of the coupon before they check out otherwise they lose the remaining balance.

    How in this script would I set those Related Banners when the rule is created? My initial thought was to use $rule->setRelatedBanners(79); where the 79 is the ID number of the banner I want to link. However, if I use that code the coupon gets created but the banner never gets linked.

    Any help on this would be greatly appreciated.

    ReplyDelete
    Replies
    1. Hello Greg.
      Here is how you can link banners to your rule for Mangeo EE. (I've tested on 1.9.x, but I'm sure it works on later versions also)

      After the line
      $rule->save();
      add this:
      Mage::getResourceModel('enterprise_banner/banner')->bindBannersToSalesRule($rule->getId(), array(1,2,3));

      The second parameter for the method bindBannersToSalesRule is an array with all the banner ids you need linked to your rule. In your case array(79);

      Let me know how it turns out.

      I've also updated the script to support this.
      Cheers,
      Marius.

      Delete
    2. Thank you Marius! That works like a charm and again was exactly what I needed.

      Greg

      Delete
  15. Incredibly helpful and works like a charm - thanks so much for this!!

    ReplyDelete
  16. How do I uninstall if this crashes my theme?

    ReplyDelete
    Replies
    1. Hello Tim.
      Uninstall what?
      This is just a script that generates some coupons. how does it affect your theme?
      If you don't want the coupons anymore just delete them from the admin panel.

      Marius.

      Delete
  17. This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  18. This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. This comment has been removed by a blog administrator.

      Delete
    3. This comment has been removed by a blog administrator.

      Delete
    4. This comment has been removed by the author.

      Delete
  19. Just want to say thanks, Tzyganu. I'm just not good at PHP and this saved me a loooot of sweat.

    ReplyDelete
  20. Excellent post and helped me a lot but i find one point which need to correct for latest versions of Magento.

    in you conditions you using ['value' => 1,] to marking it true, but Magento will not make it enabled until you send value as string ['value' => "1",].

    I find this issue so like to share with peoples.

    ReplyDelete