3

We have Magento CE 1.9.1.0 installed and we guess that we have a strange problem with the tax calculation. Here is our case study:

In our b2b-shop we want to display all prices without tax. Here are our tax setting in the backend. Magento Backend - Taxconfiguration

The customer tax should applied after discount. The shipping prices are displayed exclusive tax. At the end we want to have a total excl. tax, then the final taxamount and the grandtotal at the end, like you see in the next image of our cart. The shoppingcart

We have a taxrate of 19%. Now the problem: When we calculate the grandtotal like this: 126,60 * 1,19 = 150,654 we see that our customer pays 1cent too much. Our dilemma: Statistically, every 10 order occur like this. Why?

What we have done before? The calculation in Magento at the beginning of the development was even worse. So we made a little adjustment in the tax.php at /app/code/local/Mage/Tax/Model/Sales/Total/Quote from line 548 in the method "_calcUnitTaxAmount" - near "case Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL:".

protected function _calcUnitTaxAmount(
    $item, $rate, &$taxGroups = null, $taxId = null, $recalculateRowTotalInclTax = false
)
{
    $qty = $item->getTotalQty();
    $inclTax = $item->getIsPriceInclTax();
    $price = $item->getTaxableAmount();
    $basePrice = $item->getBaseTaxableAmount();
    $rateKey = ($taxId == null) ? (string)$rate : $taxId;

    $isWeeeEnabled = $this->_weeeHelper->isEnabled();
    $isWeeeTaxable = $this->_weeeHelper->isTaxable();

    $hiddenTax = null;
    $baseHiddenTax = null;
    $weeeTax = null;
    $baseWeeeTax = null;
    $unitTaxBeforeDiscount = null;
    $weeeTaxBeforeDiscount = null;
    $baseUnitTaxBeforeDiscount = null;
    $baseWeeeTaxBeforeDiscount = null;

    switch ($this->_config->getCalculationSequence($this->_store)) {
        case Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_EXCL:
        case Mage_Tax_Model_Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_INCL:
            $unitTaxBeforeDiscount = $this->_calculator->calcTaxAmount($price, $rate, $inclTax, false);
            $baseUnitTaxBeforeDiscount = $this->_calculator->calcTaxAmount($basePrice, $rate, $inclTax, false);

            if ($isWeeeEnabled && $isWeeeTaxable) {
                $weeeTaxBeforeDiscount = $this->_calculateWeeeTax(0, $item, $rate, false);
                $unitTaxBeforeDiscount += $weeeTaxBeforeDiscount;
                $baseWeeeTaxBeforeDiscount = $this->_calculateWeeeTax(0, $item, $rate);
                $baseUnitTaxBeforeDiscount += $baseWeeeTaxBeforeDiscount;
            }
            $unitTaxBeforeDiscount = $unitTax = $this->_calculator->round($unitTaxBeforeDiscount);
            $baseUnitTaxBeforeDiscount = $baseUnitTax = $this->_calculator->round($baseUnitTaxBeforeDiscount);
            break;
        case Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL:
        case Mage_Tax_Model_Calculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL:
            // our changes here - formerly: $discountAmount = $item->getDiscountAmount() / $qty;
            $discountAmount = $item->getDiscountAmount();
            $baseDiscountAmount = $item->getBaseDiscountAmount() / $qty;

            //We want to remove weee
            if ($isWeeeEnabled) {
                $discountAmount = $discountAmount - $item->getWeeeDiscount() / $qty;
                $baseDiscountAmount = $baseDiscountAmount - $item->getBaseWeeeDiscount() / $qty;
            }

            $unitTaxBeforeDiscount = $this->_calculator->calcTaxAmount($price, $rate, $inclTax, false);
            $unitTaxDiscount = $this->_calculator->calcTaxAmount($discountAmount, $rate, $inclTax, false);
            $unitTax = $this->_calculator->round(max($unitTaxBeforeDiscount - $unitTaxDiscount, 0));

            $baseUnitTaxBeforeDiscount = $this->_calculator->calcTaxAmount($basePrice, $rate, $inclTax, false);
            $baseUnitTaxDiscount = $this->_calculator->calcTaxAmount($baseDiscountAmount, $rate, $inclTax, false);
            $baseUnitTax = $this->_calculator->round(max($baseUnitTaxBeforeDiscount - $baseUnitTaxDiscount, 0));

            if ($isWeeeEnabled && $this->_weeeHelper->isTaxable()) {
                $weeeTax = $this->_calculateRowWeeeTax($item->getWeeeDiscount(), $item, $rate, false);
                $weeeTax = $weeeTax / $qty;
                $unitTax += $weeeTax;
                $baseWeeeTax = $this->_calculateRowWeeeTax($item->getBaseWeeeDiscount(), $item, $rate);
                $baseWeeeTax = $baseWeeeTax / $qty;
                $baseUnitTax += $baseWeeeTax;
            }

            $unitTax = $this->_calculator->round($unitTax);
            $baseUnitTax = $this->_calculator->round($baseUnitTax);

            //Calculate the weee taxes before discount
            $weeeTaxBeforeDiscount = 0;
            $baseWeeeTaxBeforeDiscount = 0;

            if ($isWeeeTaxable) {
                $weeeTaxBeforeDiscount = $this->_calculateWeeeTax(0, $item, $rate, false);
                $unitTaxBeforeDiscount += $weeeTaxBeforeDiscount;
                $baseWeeeTaxBeforeDiscount = $this->_calculateWeeeTax(0, $item, $rate);
                $baseUnitTaxBeforeDiscount += $baseWeeeTaxBeforeDiscount;
            }

            $unitTaxBeforeDiscount = max(0, $this->_calculator->round($unitTaxBeforeDiscount));
            $baseUnitTaxBeforeDiscount = max(0, $this->_calculator->round($baseUnitTaxBeforeDiscount));


            if ($inclTax && $discountAmount > 0) {
                $hiddenTax = $unitTaxBeforeDiscount - $unitTax;
                $baseHiddenTax = $baseUnitTaxBeforeDiscount - $baseUnitTax;
                $this->_hiddenTaxes[] = array(
                    'rate_key' => $rateKey,
                    'qty' => $qty,
                    'item' => $item,
                    'value' => 0, // our changes here - formerly: $hiddenTax
                    'base_value' => 0, // our changes here - formerly: $baseHiddenTax
                    'incl_tax' => $inclTax,
                );
            } elseif ($discountAmount > $price) { // case with 100% discount on price incl. tax
                $hiddenTax = $discountAmount - $price;
                $baseHiddenTax = $baseDiscountAmount - $basePrice;
                $this->_hiddenTaxes[] = array(
                    'rate_key' => $rateKey,
                    'qty' => $qty,
                    'item' => $item,
                    'value' => $hiddenTax,
                    'base_value' => $baseHiddenTax,
                    'incl_tax' => $inclTax,
                );
            }
            // calculate discount compensation
            // We need the discount compensation when dont calculate the hidden taxes
            // (when product does not include taxes)
            if (!$item->getNoDiscount() && $item->getWeeeTaxApplied()) {
                $item->setDiscountTaxCompensation($item->getDiscountTaxCompensation() +
                $unitTaxBeforeDiscount * $qty - max(0, $unitTax) * $qty);
            }
            break;
    }

    /* begin of our addings */
    $totalWithoutTax = $item->getRowTotal();
    $totalReady = $totalWithoutTax - $discountAmount;
    $unitTax = $totalReady * ($rateKey / 100);
    $rowTax = $unitTax;
    /* end of our addings */

    // our changes here - formerly: $rowTax = $this->_store->roundPrice(max(0, $qty * $unitTax));
    $baseRowTax = $this->_store->roundPrice(max(0, $qty * $baseUnitTax));
    $item->setTaxAmount($item->getTaxAmount() + $rowTax);
    $item->setBaseTaxAmount($item->getBaseTaxAmount() + $baseRowTax);
    if (is_array($taxGroups)) {
        $taxGroups[$rateKey]['tax'] = max(0, $rowTax);
        $taxGroups[$rateKey]['base_tax'] = max(0, $baseRowTax);
    }

    $rowTotalInclTax = $item->getRowTotalInclTax();
    if (!isset($rowTotalInclTax) || $recalculateRowTotalInclTax) {
        if ($this->_config->priceIncludesTax($this->_store)) {
            $item->setRowTotalInclTax($price * $qty);
            $item->setBaseRowTotalInclTax($basePrice * $qty);
        } else {
            $item->setRowTotalInclTax(
                $item->getRowTotalInclTax() + ($unitTaxBeforeDiscount - $weeeTaxBeforeDiscount) * $qty);
            $item->setBaseRowTotalInclTax(
                $item->getBaseRowTotalInclTax() +
                ($baseUnitTaxBeforeDiscount - $baseWeeeTaxBeforeDiscount) * $qty);
        }
    }

    return $this;
}

The deleted the hiddentax amount and recalculated the rowtax. But this is not working for the 10th order. What we have done wrong? Have we basically did not understand Magento? We are struggeling with this since two weeks :(

3

There is a long history of rounding errors with Magento, see this question for where that was back in the 1.7 days:

What is the status of rounding issues in 1.7?

For a quick test to see if something can be done, try changing the rounding precision from 2 to 4 in app/code/core/Mage/Core/Model/Store.php:

public function roundPrice($price)
{
    return round($price, 4);
}

This will probably break the checkout for PayPal users, however, that problem can be solved. If the above forces the sums to add up correctly then you will know it is Magento and not your configuration that is at fault here.

  • I added a new answer below with more informations and testings. I hope this helps. – scrat Mar 9 '15 at 12:37
1

After a new brainstorm we got the solution.

We added one new change in the tax.php at /app/code/local/Mage/Tax/Model/Sales/Total/Quote near line 330. We changed this part

if ($this->_config->getAlgorithm($this->_store) == Mage_Tax_Model_Calculation::CALC_TOTAL_BASE) {
    $tax = $this->_deltaRound($tax, $rateKey, $inclTax);
    $baseTax = $this->_deltaRound($baseTax, $rateKey, $inclTax, 'base');
    $this->_addAmount(max(0, $tax));
    $this->_addBaseAmount(max(0, $baseTax));
} else {
    $tax = $this->_calculator->round($tax);
    $baseTax = $this->_calculator->round($baseTax);
    $this->_addAmount(max(0, $tax));
    $this->_addBaseAmount(max(0, $baseTax));
}

to

if ($this->_config->getAlgorithm($this->_store) == Mage_Tax_Model_Calculation::CALC_TOTAL_BASE) {
    $tax = $this->_deltaRound($tax, $rateKey, $inclTax);
    $baseTax = $this->_deltaRound($baseTax, $rateKey, $inclTax, 'base');
    $this->_addAmount(max(0, $tax));
    $this->_addBaseAmount(max(0, $baseTax));
} else {
    // our additional changes here - formerly: $tax = $this->_calculator->round($tax);
    $tax = $tax;
    $baseTax = $this->_calculator->round($baseTax);
    $this->_addAmount(max(0, $tax));
    $this->_addBaseAmount(max(0, $baseTax));
}

Because almost every tenth order has been rounded incorrectly. 0.4 Cents (0.004 €) to much or in some cases 0.2 Cents less (0.002 €). The tax for the shipping costs was rounded too early, that was sticking point. Now with this additional adjustment, we have prevented Magento rounds the taxes too early.

We hope that this solution prevents some headache to anyone.

  • why you removed the round from $tax but not from $baseTax ? – Fra Oct 11 '17 at 8:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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