Magento – Magento 2 Core Bug Found For Multiple discount calculation in All 2.2.x version

discountmagento-2.1magento-2.2.1magento2.2.2shopping-cart-price-rules

This is the core issue in all Magento 2.2.x. When we add two cart price rule with action " Percent of product price discount". It makes little calculation issue in total discount.

For example, I have created two cart price rules with the same priority:

Rule 1:

Rule Name: 10% Fix Discount
Coupon: No Coupon
Conditions: Apply for all product
Action: 
    Apply: Percent of product price discount
    Discount Amount: 10

Rule 2:

Rule Name: 5% Fix Discount
Coupon: No Coupon
Conditions: Apply for all product
Action: 
    Apply: Percent of product price discount
    Discount Amount: 5

So the calculation should be like this

For ex. Product Price = $45

First Rule(10% of product price) Discount: (45*10) / 100 = 4.5
Second Rule(5% of product price) Discount: (45*5) / 100 = 2.25

Total Discount = 6.75

But it's shows $6.53 in total discount.

I have raised the issue on GitHub and magento team have also approved the issue. But it takes some time to resolve this issue. https://github.com/magento/magento2/issues/12837

Anyone have idea or any patch to resolve this issue?

Best Answer

Obviously it applies the second rule on the discounted price of rule one. I think this is in app/code/Magento/SalesRule/Model/Quote/Discount.php:

public function collect(...) {

    // ...

    foreach ($items as $item) {

        // ...

        if ($item->getHasChildren() && $item->isChildrenCalculated()) {
            $this->calculator->process($item);
            $this->distributeDiscount($item);
            foreach ($item->getChildren() as $child) {
                $eventArgs['item'] = $child;
                $this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
                $this->aggregateItemDiscount($child, $total);
            }
        } else {
            $this->calculator->process($item);
            $this->aggregateItemDiscount($item, $total);
        }
    }

    // ...
}

/**
 * Aggregate item discount information to total data and related properties
 *
 * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
 * @param \Magento\Quote\Model\Quote\Address\Total $total
 * @return $this
 */
protected function aggregateItemDiscount(
    \Magento\Quote\Model\Quote\Item\AbstractItem $item,
    \Magento\Quote\Model\Quote\Address\Total $total
) {
    $total->addTotalAmount($this->getCode(), -$item->getDiscountAmount());
    $total->addBaseTotalAmount($this->getCode(), -$item->getBaseDiscountAmount());
    return $this;
}

Check the foreach ($item->getChildren() as $child) { loop. I guess you have to change that behaviour.

Change that to:

// ...

/** @var \Magento\Quote\Model\Quote\Item $item */
foreach ($items as $item) {
    // ...
    $this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);

    $this->aggregateItemDiscount($item, $total);
}

// ...

/**
 * Aggregate item discount information to total data and related properties
 *
 * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item
 * @param \Magento\Quote\Model\Quote\Address\Total $total
 * @return $this
 */
protected function aggregateItemDiscount(
    \Magento\Quote\Model\Quote\Item\AbstractItem $item,
    \Magento\Quote\Model\Quote\Address\Total $total
) {
    if ($item->getHasChildren() && $item->isChildrenCalculated()) {
        $this->calculator->process($item);
        $this->distributeDiscount($item);
        foreach ($item->getChildren() as $child) {
            $eventArgs['item'] = $child;
            $this->eventManager->dispatch('sales_quote_address_discount_item', $eventArgs);
            $discountAmount -= -$child->getDiscountAmount();
            $baseDiscountAmount -= -$child->getBaseDiscountAmount();
        }
    } else {
        $this->calculator->process($item);
        $discountAmount = -$item->getDiscountAmount();
        $baseDiscountAmount = -$item->getBaseDiscountAmount();
    }

    $total->addTotalAmount($this->getCode(), $discountAmount);
    $total->addBaseTotalAmount($this->getCode(), $baseDiscountAmount);
    return $this;
}

I haven't tested this so try out what happens. There might be other places where you have to do this.

Related Topic