Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rounding different in order to original quote calculation #446

Closed
fooman opened this issue Dec 21, 2013 · 7 comments
Closed

rounding different in order to original quote calculation #446

fooman opened this issue Dec 21, 2013 · 7 comments
Labels
Issue: Format is not valid Gate 1 Failed. Automatic verification of issue format is failed

Comments

@fooman
Copy link
Contributor

fooman commented Dec 21, 2013

When retrieving calculatedTaxes here

https://github.com/magento/magento2/blob/master/app/code/Magento/Tax/Helper/Data.php#L897

$taxClassAmount[$taxClassId]['tax_amount']      += $price * $percent / 100;

the amounts are not rounded leading to different results to the original quote calculation.

Magento should be able to reconstruct the complete taxation on the order and produce the same results as the original quote (irrespective of any setting changes).

@verklov
Copy link
Contributor

verklov commented Dec 23, 2013

Hello fooman,
Thank you for reporting this issue! We are considering to apply a bunch of fixes related to tax calculation. The issue reported by you will most likely be fixed by one of them. So far we are leaving the issue open and will close it once it is fixed by one of the future code updates.

@verklov
Copy link
Contributor

verklov commented Dec 26, 2013

Hello fooman,
Could you please provide us with the steps how we can reproduce this issue and see the difference in the results because of the rounding issue you reported?

@fooman
Copy link
Contributor Author

fooman commented Dec 27, 2013

The code is clearly different:

$taxClassAmount[$taxClassId]['tax_amount']      += $price * $percent / 100;

compared to

/**
* Calculate address tax amount based on one unit price and tax amount
*
* @param \Magento\Sales\Model\Quote\Address $address
* @return \Magento\Tax\Model\Sales\Total\Quote\Tax
*/
protected function _unitBaseCalculation(\Magento\Sales\Model\Quote\Address $address, $taxRateRequest)
{
    $items = $this->_getAddressItems($address);
    $itemTaxGroups = array();
    foreach ($items as $item) {
        if ($item->getParentItem()) {
            continue;
        }

        if ($item->getHasChildren() && $item->isChildrenCalculated()) {
            foreach ($item->getChildren() as $child) {
                $taxRateRequest->setProductClassId($child->getProduct()->getTaxClassId());
                $rate = $this->_calculator->getRate($taxRateRequest);
                $this->_calcUnitTaxAmount($child, $rate);
                $this->_addAmount($child->getTaxAmount());
                $this->_addBaseAmount($child->getBaseTaxAmount());
                $applied = $this->_calculator->getAppliedRates($taxRateRequest);
                if ($rate > 0) {
                    $itemTaxGroups[$child->getId()] = $applied;
                }
                $this->_saveAppliedTaxes(
                    $address,
                    $applied,
                    $child->getTaxAmount(),
                    $child->getBaseTaxAmount(),
                    $rate
                );
                $child->setTaxRates($applied);
            }
            $this->_recalculateParent($item);
        }
        else {
            $taxRateRequest->setProductClassId($item->getProduct()->getTaxClassId());
            $rate = $this->_calculator->getRate($taxRateRequest);
            $this->_calcUnitTaxAmount($item, $rate);
            $this->_addAmount($item->getTaxAmount());
            $this->_addBaseAmount($item->getBaseTaxAmount());
            $applied = $this->_calculator->getAppliedRates($taxRateRequest);
            if ($rate > 0) {
                $itemTaxGroups[$item->getId()] = $applied;
            }
            $this->_saveAppliedTaxes(
                $address,
                $applied,
                $item->getTaxAmount(),
                $item->getBaseTaxAmount(),
                $rate
            );
            $item->setTaxRates($applied);
        }
    }
    if ($address->getQuote()->getTaxesForItems()) {
        $itemTaxGroups += $address->getQuote()->getTaxesForItems();
    }
    $address->getQuote()->setTaxesForItems($itemTaxGroups);
    return $this;
}

/**
* Calculate unit tax anount based on unit price
*
* @param \Magento\Sales\Model\Quote\Item\AbstractItem $item
* @param float $rate
* @return \Magento\Tax\Model\Sales\Total\Quote\Tax
*/
protected function _calcUnitTaxAmount(\Magento\Sales\Model\Quote\Item\AbstractItem $item, $rate)
{
    $qty = $item->getTotalQty();
    $inclTax = $item->getIsPriceInclTax();
    $price = $item->getTaxableAmount() + $item->getExtraTaxableAmount();
    $basePrice = $item->getBaseTaxableAmount() + $item->getBaseExtraTaxableAmount();
    $rateKey = (string)$rate;
    $item->setTaxPercent($rate);

    $hiddenTax = null;
    $baseHiddenTax = null;
    switch ($this->_config->getCalculationSequence($this->_store)) {
        case \Magento\Tax\Model\Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_EXCL:
        case \Magento\Tax\Model\Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_INCL:
            $unitTax = $this->_calculator->calcTaxAmount($price, $rate, $inclTax);
            $baseUnitTax = $this->_calculator->calcTaxAmount($basePrice, $rate, $inclTax);
            break;
        case \Magento\Tax\Model\Calculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL:
        case \Magento\Tax\Model\Calculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL:
            $discountAmount = $item->getDiscountAmount() / $qty;
            $baseDiscountAmount = $item->getBaseDiscountAmount() / $qty;

            $unitTax = $this->_calculator->calcTaxAmount($price, $rate, $inclTax);
            $discountRate = ($unitTax/$price) * 100;
            $unitTaxDiscount = $this->_calculator->calcTaxAmount($discountAmount, $discountRate, $inclTax, false);
            $unitTax = max($unitTax - $unitTaxDiscount, 0);
            $baseUnitTax = $this->_calculator->calcTaxAmount($basePrice, $rate, $inclTax);
            $baseDiscountRate = ($baseUnitTax/$basePrice) * 100;
            $baseUnitTaxDiscount = $this->_calculator
                ->calcTaxAmount($baseDiscountAmount, $baseDiscountRate, $inclTax, false);
            $baseUnitTax = max($baseUnitTax - $baseUnitTaxDiscount, 0);

            if ($inclTax && $discountAmount > 0) {
                $hiddenTax = $this->_calculator->calcTaxAmount($discountAmount, $rate, $inclTax, false);
                $baseHiddenTax = $this->_calculator->calcTaxAmount($baseDiscountAmount, $rate, $inclTax, false);
                $this->_hiddenTaxes[] = array(
                    'rate_key' => $rateKey,
                    'qty' => $qty,
                    'item' => $item,
                    'value' => $hiddenTax,
                    'base_value' => $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,
                );
            }
            break;
    }
    $item->setTaxAmount($this->_store->roundPrice(max(0, $qty*$unitTax)));
    $item->setBaseTaxAmount($this->_store->roundPrice(max(0, $qty*$baseUnitTax)));

    return $this;
}

/**
* Calculate address total tax based on row total
*
* @param \Magento\Sales\Model\Quote\Address $address
* @param \Magento\Object $taxRateRequest
* @return \Magento\Tax\Model\Sales\Total\Quote\Tax
*/
protected function _rowBaseCalculation(\Magento\Sales\Model\Quote\Address $address, $taxRateRequest)
{
    $items = $this->_getAddressItems($address);
    $itemTaxGroups = array();
    foreach ($items as $item) {
        if ($item->getParentItem()) {
            continue;
        }
        if ($item->getHasChildren() && $item->isChildrenCalculated()) {
            foreach ($item->getChildren() as $child) {
                $taxRateRequest->setProductClassId($child->getProduct()->getTaxClassId());
                $rate = $this->_calculator->getRate($taxRateRequest);
                $this->_calcRowTaxAmount($child, $rate);
                $this->_addAmount($child->getTaxAmount());
                $this->_addBaseAmount($child->getBaseTaxAmount());
                $applied = $this->_calculator->getAppliedRates($taxRateRequest);
                if ($rate > 0) {
                    $itemTaxGroups[$child->getId()] = $applied;
                }
                $this->_saveAppliedTaxes(
                    $address,
                    $applied,
                    $child->getTaxAmount(),
                    $child->getBaseTaxAmount(),
                    $rate
                );
                $child->setTaxRates($applied);
            }
            $this->_recalculateParent($item);
        }
        else {
            $taxRateRequest->setProductClassId($item->getProduct()->getTaxClassId());
            $rate = $this->_calculator->getRate($taxRateRequest);
            $this->_calcRowTaxAmount($item, $rate);
            $this->_addAmount($item->getTaxAmount());
            $this->_addBaseAmount($item->getBaseTaxAmount());
            $applied = $this->_calculator->getAppliedRates($taxRateRequest);
            if ($rate > 0) {
                $itemTaxGroups[$item->getId()] = $applied;
            }
            $this->_saveAppliedTaxes(
                $address,
                $applied,
                $item->getTaxAmount(),
                $item->getBaseTaxAmount(),
                $rate
            );
            $item->setTaxRates($applied);
        }
    }

    if ($address->getQuote()->getTaxesForItems()) {
        $itemTaxGroups += $address->getQuote()->getTaxesForItems();
    }
    $address->getQuote()->setTaxesForItems($itemTaxGroups);
    return $this;
}

/**
* Calculate item tax amount based on row total
*
* @param \Magento\Sales\Model\Quote\Item\AbstractItem $item
* @param float $rate
* @return \Magento\Tax\Model\Sales\Total\Quote\Tax
*/
protected function _calcRowTaxAmount($item, $rate)
{
    $inclTax = $item->getIsPriceInclTax();
    $subtotal = $item->getTaxableAmount() + $item->getExtraRowTaxableAmount();
    $baseSubtotal = $item->getBaseTaxableAmount() + $item->getBaseExtraRowTaxableAmount();
    $rateKey = (string)$rate;
    $item->setTaxPercent($rate);

    $hiddenTax = null;
    $baseHiddenTax = null;
    switch ($this->_taxData->getCalculationSequence($this->_store)) {
        case \Magento\Tax\Model\Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_EXCL:
        case \Magento\Tax\Model\Calculation::CALC_TAX_BEFORE_DISCOUNT_ON_INCL:
            $rowTax = $this->_calculator->calcTaxAmount($subtotal, $rate, $inclTax);
            $baseRowTax = $this->_calculator->calcTaxAmount($baseSubtotal, $rate, $inclTax);
            break;
        case \Magento\Tax\Model\Calculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL:
        case \Magento\Tax\Model\Calculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL:
            $discountAmount = $item->getDiscountAmount();
            $baseDiscountAmount = $item->getBaseDiscountAmount();
            $rowTax = $this->_calculator->calcTaxAmount(
                max($subtotal - $discountAmount, 0),
                $rate,
                $inclTax
            );
            $baseRowTax = $this->_calculator->calcTaxAmount(
                max($baseSubtotal - $baseDiscountAmount, 0),
                $rate,
                $inclTax
            );
            if ($inclTax && $discountAmount > 0) {
                $hiddenTax = $this->_calculator->calcTaxAmount($discountAmount, $rate, $inclTax, false);
                $baseHiddenTax = $this->_calculator->calcTaxAmount($baseDiscountAmount, $rate, $inclTax, false);
                $this->_hiddenTaxes[] = array(
                    'rate_key' => $rateKey,
                    'qty' => 1,
                    'item' => $item,
                    'value' => $hiddenTax,
                    'base_value' => $baseHiddenTax,
                    'incl_tax' => $inclTax,
                );
            } elseif ($discountAmount > $subtotal) { // case with 100% discount on price incl. tax
                $hiddenTax = $discountAmount - $subtotal;
                $baseHiddenTax = $baseDiscountAmount - $baseSubtotal;
                $this->_hiddenTaxes[] = array(
                    'rate_key' => $rateKey,
                    'qty' => 1,
                    'item' => $item,
                    'value' => $hiddenTax,
                    'base_value' => $baseHiddenTax,
                    'incl_tax' => $inclTax,
                );
            }
            break;
    }
    $item->setTaxAmount(max(0, $rowTax));
    $item->setBaseTaxAmount(max(0, $baseRowTax));
    return $this;
}

@choukalos
Copy link

The tax calculation was changed extensively in 1.x branch. Those changes will be ported to 2x as part of our current roadmap. I'm closing this issue for now as I believe this will be resolved when the tax calculation changes from 1.x are ported.

@fooman
Copy link
Contributor Author

fooman commented Feb 17, 2014

The above comment is true for Magento 1.8.1 and Magento 2 - I don't consider this fixed yet.

magento-team added a commit that referenced this issue Sep 5, 2014
* Implemented API services:
   * Sales transactions
 * Added the following functional tests:
   * Create Store Group
   * Customer Review Report
   * Delete Store Group
   * Update Store Group
 * Improved error reporting when ini_set fails
 * Increased unit test coverage for the following modules:
   * SalesRule
   * Payment
 * Checkout API:
   * Create Shopping Cart Gift Message service
   * Create Shopping Cart Totals service
 * Fixed bugs:
   * Fixed an issue where  selecting a shipping method in PayPal Express Checkout resulted in a fatal error
   * Fixed an issue where the information displayed on the Payment Information step of Zero Subtotal Checkout was confusing
   * Fixed a JavaScript error in shipping label
   * Fixed an issue with wrong layout of the storefront pages
   * Fixed an issue where the price including tax value was incorrect on catalog pages when customer tax rate is different from store tax rate
   * Fixed an issue where fixed product tax (FPT) was not included in the Grand total when 'Include FPT in Subtotal' was set to Yes
   * Fixed an issue where Shipping Incl. Tax amount was not updated when changing shipping method
   * Fixed an issue where the store tax configuration was ignored during backend order creation
   * Fixed an issue where taxes were not applied in the shopping cart after registering customer on the storefront
   * Fixed an issue where the wrong html markup was generated on My order pages for the WEEE tax
   * Fixed an issue where the built-in caching did not work on product pages
   * Removed the stream resource usage to avoid errors when  the allow_url_fopen PHP option is set to Off
   * Fixed the New Return page layout on the backend
   * Fixed an issue where  it was impossible to apply a specific coupon code when the Apply to Shipping Amount option of the Shopping Cart Rule was set to Yes
   * Removed file paths/content from test case names in data-driven tests
   * Fixed an issue where pagination was absent in the  Order Status grid
   * Fixed an issue where after applying a discount coupon and changing the currency the discount value was incorrect
   * Fixed an issue where trying to a new rating resulted in a fatal error
   * Fixed an issue where the minimum order amount was compared with subtotal without taxes
   * Fixed an issue where it was impossible to open the previous step during Onepage Checkout
   * Fixed an issue with Persistent Shopping Cart where an unexpected message was displayed during checkout if a user started the checkout  after the short-term cookie had expired
   * Fixed an issue where a customer was redirected to the shopping cart after selecting shipping method during checkout with a payment method using 3D Secure
   * Fixed an issue where the Cart Item service used itemSku instead itemId
   * Fixed an issue where gift messages for individual items were not saved during backend order creation
   * Fixed an issue where the Purchase Order Number input field was not displayed in Onepage Checkout  if only one payment method was enabled
 * GitHub requests:
   * [#446] (#446) -- Rounding different in order to original quote calculation
@verklov
Copy link
Contributor

verklov commented Sep 5, 2014

@fooman, the team processed this issue. The fix has just been released with version 0.1.0-alpha94. Please verify the latest code available.

magento-team pushed a commit that referenced this issue Jul 15, 2015
magento-team pushed a commit that referenced this issue Mar 20, 2016
mmansoor-magento pushed a commit that referenced this issue Sep 30, 2016
MAGETWO-55838: [Add address] Same address can be added several times at once
@seansan
Copy link

seansan commented Nov 27, 2017

Was this bug also fixed in m1? or is there a solution somewhere?

@magento-engcom-team magento-engcom-team added the Issue: Format is not valid Gate 1 Failed. Automatic verification of issue format is failed label Nov 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue: Format is not valid Gate 1 Failed. Automatic verification of issue format is failed
Projects
None yet
Development

No branches or pull requests

5 participants