1

I am migrating code from Magento1 to Magento2 and need to create a group of sales rules programatically. I need to do this because it's based on the price of the item, plus $1 if they purchase two (buy one at full price, get a second for $1).

I was previously able to accomplish this using this code. I am wondering how one would go about it under Magento2?

        $discount = ($price - (($price + 1) / 2));
        $shoppingCartPriceRule = Mage::getModel('salesrule/rule');

        $shoppingCartPriceRule->setName('Add a second for $1 - ' . $sku)
        ->setDescription('Buy one item at regular price, and receive a second item for just $1.00 more!')
        ->setFromDate('2000-01-01')
        ->setToDate(NULL)
        ->setUsesPerCustomer('0')
        ->setCustomerGroupIds(array('0','1','2','3',))
        ->setIsActive('1')
        ->setStopRulesProcessing('0')
        ->setIsAdvanced('1')
        ->setProductIds(NULL)
        ->setSortOrder('1')
        ->setSimpleAction('by_fixed')
        ->setDiscountAmount($discount)
        ->setDiscountQty(NULL)
        ->setDiscountStep('0')
        ->setSimpleFreeShipping('0')
        ->setApplyToShipping('0')
        ->setTimesUsed('0')
        ->setIsRss('0')
        ->setWebsiteIds(array('1',))
        ->setCouponType('1')
        ->setCouponCode(NULL)
        ->setUsesPerCoupon(NULL);   

        // Add Sku Condition
        $skuCond = Mage::getModel('salesrule/rule_condition_product')
               ->setType('salesrule/rule_condition_product')
               ->setAttribute('sku')
               ->setOperator('==')
               ->setValue($sku);
        $shoppingCartPriceRule->getActions()->addCondition($skuCond);               

        // Add Qty Condition
        $qtyCond = Mage::getModel('salesrule/rule_condition_product')
               ->setType('salesrule/rule_condition_product')
               ->setAttribute('quote_item_qty')
               ->setOperator('()')
               ->setValue('2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96');
        $shoppingCartPriceRule->getActions()->addCondition($qtyCond);                 

Thank you!

5 Answers 5

9

If you have object manager then you can go with following example code

$price = 100;
$sku = '24-WG085';
$discount = ($price - (($price + 1) / 2));
$shoppingCartPriceRule = $this->_objectManager->create('Magento\SalesRule\Model\Rule');

$shoppingCartPriceRule->setName('Add a second for $1 - ' . $sku)
    ->setDescription('Buy one item at regular price, and receive a second item for just $1.00 more!')
    ->setFromDate('2000-01-01')
    ->setToDate(NULL)
    ->setUsesPerCustomer('0')
    ->setCustomerGroupIds(array('0','1','2','3',))
    ->setIsActive('1')
    ->setStopRulesProcessing('0')
    ->setIsAdvanced('1')
    ->setProductIds(NULL)
    ->setSortOrder('1')
    ->setSimpleAction('by_fixed')
    ->setDiscountAmount($discount)
    ->setDiscountQty(NULL)
    ->setDiscountStep('0')
    ->setSimpleFreeShipping('0')
    ->setApplyToShipping('0')
    ->setTimesUsed('0')
    ->setIsRss('0')
    ->setWebsiteIds(array('1',))
    ->setCouponType('1')
    ->setCouponCode(NULL)
    ->setUsesPerCoupon(NULL);

$item_found = $this->_objectManager->create('Magento\SalesRule\Model\Rule\Condition\Product\Found')
    ->setType('Magento\SalesRule\Model\Rule\Condition\Product\Found')
    ->setValue(1) // 1 == FOUND
    ->setAggregator('all'); // match ALL conditions
$shoppingCartPriceRule->getConditions()->addCondition($item_found);
$conditions = $this->_objectManager->create('Magento\SalesRule\Model\Rule\Condition\Product')
    ->setType('Magento\SalesRule\Model\Rule\Condition\Product')
    ->setAttribute('sku')
    ->setOperator('==')
    ->setValue($sku);
$item_found->addCondition($conditions);

$actions = $this->_objectManager->create('Magento\SalesRule\Model\Rule\Condition\Product')
    ->setType('Magento\SalesRule\Model\Rule\Condition\Product')
    ->setAttribute('sku')
    ->setOperator('==')
    ->setValue($sku);
$shoppingCartPriceRule->getActions()->addCondition($actions);

// Add Qty Condition
$qtyCond = $this->_objectManager->create('Magento\SalesRule\Model\Rule\Condition\Product')
    ->setType('Magento\SalesRule\Model\Rule\Condition\Product')
    ->setAttribute('quote_item_qty')
    ->setOperator('()')
    ->setValue('2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96');
$shoppingCartPriceRule->getActions()->addCondition($qtyCond);

$shoppingCartPriceRule->save();

Otherwise create a object manager using DI

/**
 * @var \Magento\Framework\ObjectManagerInterface
 */
protected $_objectManager;

public function __construct(
    \Magento\Framework\ObjectManagerInterface $_objectManager
) {
    $this->_objectManager = $_objectManager;
}
5
  • 2
    You shouldn't use the object manager directly, it's preferrable to use a factory class.
    – Mir
    Commented Oct 10, 2016 at 12:25
  • @Sohel Rana this is very useful answer. Commented Mar 23, 2018 at 14:38
  • @sohel, i got the following error while set the voucher to the particular sku Call to a member function setOperator() on null. then how can i assign this coupon to more than one sku and particular attribute
    – senthil
    Commented Mar 11, 2019 at 13:18
  • @senthil Sir how can we apply this coupon on the specific products?? Like I want to apply this on product ID 1,2,3 only how can I apply this?
    – Asad Khan
    Commented Mar 29, 2019 at 14:28
  • Sir I followed your code same it is but it gives me the error Uncaught Error: Call to a member function setOperator() on null how to solve this and what is the problem in the code?
    – Asad Khan
    Commented Apr 3, 2019 at 11:09
6

Sohel Rana's answer is excellent. To answer Mir's point, here is an example of how DI would be used to bring in the relevant classes in a class context, this is a small tweak to the accepted answer:

protected $ruleFactory;
protected $productRuleFactory;
protected $foundProductRuleFactory;
protected $ruleResource;

public function __construct(
    \Magento\SalesRule\Model\RuleFactory $ruleFactory,
    \Magento\SalesRule\Model\Rule\Condition\ProductFactory $productRuleFactory,
    \Magento\SalesRule\Model\Rule\Condition\Product\FoundFactory $foundProductRuleFactory,
    \Magento\SalesRule\Model\ResourceModel\Rule $ruleResource
) {
    $this->ruleFactory = $ruleFactory;
    $this->productRuleFactory = $productRuleFactory;
    $this->foundProductRuleFactory = $foundProductRuleFactory;
    $this->ruleResource = $ruleResource;
}

public function createRule()
{
    $price = 100;
    $sku = '24-WG085';
    $discount = ($price - (($price + 1) / 2));
    $shoppingCartPriceRule = $this->ruleFactory->create();

    $shoppingCartPriceRule->setName('Add a second for $1 - ' . $sku)
        ->setDescription('Buy one item at regular price, and receive a second item for just $1.00 more!')
        ->setFromDate('2000-01-01')
        ->setToDate(NULL)
        ->setUsesPerCustomer('0')
        ->setCustomerGroupIds(array('0','1','2','3',))
        ->setIsActive('1')
        ->setStopRulesProcessing('0')
        ->setIsAdvanced('1')
        ->setProductIds(NULL)
        ->setSortOrder('1')
        ->setSimpleAction('by_fixed')
        ->setDiscountAmount($discount)
        ->setDiscountQty(NULL)
        ->setDiscountStep('0')
        ->setSimpleFreeShipping('0')
        ->setApplyToShipping('0')
        ->setTimesUsed('0')
        ->setIsRss('0')
        ->setWebsiteIds(array('1',))
        ->setCouponType('1')
        ->setCouponCode(NULL)
        ->setUsesPerCoupon(NULL);

    $item_found = $this->foundProductRuleFactory->create()
        ->setType('Magento\SalesRule\Model\Rule\Condition\Product\Found')
        ->setValue(1) // 1 == FOUND
        ->setAggregator('all'); // match ALL conditions
    $shoppingCartPriceRule->getConditions()->addCondition($item_found);

    $conditions = $this->productRuleFactory->create()
        ->setType('Magento\SalesRule\Model\Rule\Condition\Product')
        ->setAttribute('sku')
        ->setOperator('==')
        ->setValue($sku);
    $item_found->addCondition($conditions);

    $actions = $this->productRuleFactory->create()
        ->setType('Magento\SalesRule\Model\Rule\Condition\Product')
        ->setAttribute('sku')
        ->setOperator('==')
        ->setValue($sku);
    $shoppingCartPriceRule->getActions()->addCondition($actions);

    // Add Qty Condition
    $qtyCond = $this->productRuleFactory->create()
        ->setType('Magento\SalesRule\Model\Rule\Condition\Product')
        ->setAttribute('quote_item_qty')
        ->setOperator('()')
        ->setValue('2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96');
    $shoppingCartPriceRule->getActions()->addCondition($qtyCond);

    $this->ruleResource->save($shoppingCartPriceRule);
}

The original save statement will work fine, but Magento have depreciated the direct save, so it's more correct to save via the resource model.

4
  • i am getting "Uncaught Error: Call to a member function setOperator() on null", then how can i set the multiple sku in this code
    – senthil
    Commented Mar 11, 2019 at 15:20
  • I fixed the issue and the coupon is created, but while apply the coupon to the cart page, it's showing 'The coupon code couldn't be applied. Verify the coupon code and try again'
    – senthil
    Commented Mar 14, 2019 at 6:41
  • Sir I followed your code same it is but it gives me the error Uncaught Error: Call to a member function setOperator() on null how to solve this and what is the problem in the code?
    – Asad Khan
    Commented Apr 3, 2019 at 11:08
  • Keep in mind that this answer was correct in 2016 when Magento was at version 2.0, so this is likely a little out of date. Commented Apr 12, 2019 at 10:00
1

If anyone has problems with:

Uncaught Error: Call to a member function setOperator() on null", all you have to do is to replace with "setData('operator', '==').

0

I hope this code help someone work in magento 2.3, because it work for me

this class Magento\Framework\App\Action\Context $context just if you work in a controller otherwise you have to delete it

public function __construct(
    \Magento\Framework\App\Action\Context $context,
    \Magento\Framework\App\StateFactory $stateFactory,
    \Magento\SalesRule\Model\Rule\Condition\Product\FoundFactory $foundFactory,
    \Magento\SalesRule\Model\Rule\Condition\Product\CombineFactory  $combineFactory,
    \Magento\SalesRule\Model\RuleFactory $ruleFactory,
    \Magento\SalesRule\Model\ResourceModel\Rule $resourceRule,
    \Psr\Log\LoggerInterface $logger
) {
    parent::__construct($context);
    $this->stateFactory = $stateFactory;
    $this->foundFactory = $foundFactory;
    $this->combineFactory = $combineFactory;
    $this->ruleFactory = $ruleFactory;
    $this->resourceRule = $resourceRule;
    $this->logger = $logger;
}

public function execute()
{
    try {
        $this->stateFactory->create()->setAreaCode('adminhtml');
        $coupon['name'] = 'coupon name';
        $coupon['desc'] = 'coupon description';
        $coupon['start'] = date('Y-m-d');
        $coupon['end'] = date('Y-m-d', strtotime("+3 months"));
        //this code will normally be auto generated but we generated
        $coupon['code'] ='GIFT-WSH-' . rand(1000, 99999);
        /** @var Rule $rule */
        $rule = $this->ruleFactory->create();
        $rule->setName($coupon['name'])
            ->setDescription($coupon['desc'])
            ->setFromDate($coupon['start'])
            ->setToDate($coupon['end'])
            ->setUsesPerCustomer(1)
            ->setIsActive(1)
            ->setIsAdvanced("1")
            ->setSimpleAction('by_fixed')
            ->setDiscountAmount(60)
            ->setApplyToShipping("no")
            ->setIsRss(1)
            ->setCouponType(2)
            ->setCouponCode($coupon['code'])
            ->setUsesPerCoupon(1)
            ->setCustomerGroupIds(array('0','1','2','3'))
            ->setWebsiteIds(array('1'));
        $itemFound = $this->foundFactory->create()
            ->setType('Magento\SalesRule\Model\Rule\Condition\Product\Found')
            ->setValue(1)
            ->setAggregator('all');
        $rule->getConditions()->addCondition($itemFound);
        $conditions = $this->combineFactory->create()
            ->setType('Magento\SalesRule\Model\Rule\Condition\Product\Combine')
            ->setAttribute('attribute_set_id')
            ->setOperator('==')
            ->setValue(13);
        $itemFound->addCondition($conditions);
        // Validating rule data before Saving
        $validateResult = $rule->validateData(new \Magento\Framework\DataObject($rule->getData()));
        if ($validateResult !== true) {
            foreach ($validateResult as $errorMessage) {
                dump($errorMessage);
            }
        }
        $this->resourceRule->save($rule);
        return $rule;
    } catch (\Exception $e) {
        $this->logger->error($e);
        return null;
    }
}
0

This solution was the best for me. Just try to create rule in admin pannel and after that find it in salesrule table. There you can see "conditions_serialized" column and there will be different classes. In my case it was something like this

{"type":"Magento\\SalesRule\\Model\\Rule\\Condition\\Combine","attribute":null,"operator":null,"value":"1","is_value_processed":null,"aggregator":"all","conditions":
    [
    {"type":"Magento\\SalesRule\\Model\\Rule\\Condition\\Address","attribute":"base_subtotal_with_discount","operator":">=","value":0,"is_value_processed":false},
    {"type":"Magento\\SalesRule\\Model\\Rule\\Condition\\Address","attribute":"base_subtotal_with_discount","operator":"<=","value":600,"is_value_processed":false}

]}

Take these classes and include them into your code. Watch for the structure nesting.

use Magento\Customer\Model\GroupManagement;
use Magento\SalesRule\Model\Rule\Condition\Address;
use Magento\SalesRule\Model\Rule\Condition\Combine;
use Magento\SalesRule\Model\RuleFactory;
use Magento\Customer\Model\ResourceModel\Group\Collection;

public function createRules()
{    
     /*$this->customerGroupColl is instance of 
       Magento\Customer\Model\ResourceModel\Group\Collection*/
    $customerGroups = $this->customerGroupColl->toOptionArray();
    
    /*"$this->ruleFactory" is 
      Magento\SalesRule\Model\RuleFactory instance;*/
    $salesRule = $this->ruleFactory->create();

    // General rule data
    $salesRule->setData(
        [
            'name' => 'some rule name',
            'description' => 'some rule description',
            'is_active' => 1,
            'customer_group_ids' => array_keys($customerGroups), //[0,1,2...]
            'coupon_type' => Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON,
            'simple_action' => Magento\SalesRule\Model\Rule::BY_PERCENT_ACTION,
            'discount_amount' => 50,
            'discount_step' => 0,
            'stop_rules_processing' => 0,
            'website_ids' => [
                1, 2
            ],
            'store_labels' => [
                0 => 'Label for store with Id=0',
                1 => 'Label for store with Id=1'
            ]
    );

    /*including conditions. The required classes for type keys can be found in 
       "conditions_serialized" column*/
    $salesRule->getConditions()->loadArray(
        [
            'type' => Combine::class,
            'attribute' => null,
            'operator' => null,
            'value' => '1',
            'is_value_processed' => null,
            'aggregator' => 'all',
            'conditions' => [
                    [
                        'type' => Address::class,
                        'attribute' => 'base_subtotal_with_discount',
                        'operator' => '>=',
                        'value' => 0,
                        'is_value_processed' => false,
                    ],
                    [
                        'type' => Address::class,
                        'attribute' => 'base_subtotal_with_discount',
                        'operator' => '<=',
                        'value' => 600,
                        'is_value_processed' => false,
                    ]
            ],
        ]
    );

    $salesRule->save();
}
3
  • You could even improve this by passing the saleRule to a SaleRuleRepository (more Symfony Style) instead of letting the entity being responsible for saving itself.
    – Max
    Commented Aug 8, 2021 at 20:38
  • @MaxS Probably you're right but the focus was on a creation of salesRule an on the structure nesting that could be different but not on the options of saving it. Commented Aug 11, 2021 at 11:21
  • Well, reason for my comment is that your solution will not work in future updates of Magento 2.4.x
    – Max
    Commented Aug 12, 2021 at 10:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.