Question

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!

Was it helpful?

Solution

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;
}

OTHER TIPS

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.

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', '==').

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;
    }
}

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();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top