Dlaczego Magento przechowuje deltę zaokrąglania przy obliczaniu podatków

14

W modelu tax/Sales_Total_Quote_Taxistnieje metoda _deltaRound()zaokrąglająca cenę. Dodaje małą deltę, aby zatrzymać niedeterministyczne zachowanie podczas zaokrąglania 0,5.

/**
 * Round price based on previous rounding operation delta
 *
 * @param float $price
 * @param string $rate
 * @param bool $direction price including or excluding tax
 * @param string $type
 * @return float
 */
protected function _deltaRound($price, $rate, $direction, $type = 'regular')
{
    if ($price) {
        $rate = (string)$rate;
        $type = $type . $direction;
        // initialize the delta to a small number to avoid non-deterministic behavior with rounding of 0.5
        $delta = isset($this->_roundingDeltas[$type][$rate]) ? $this->_roundingDeltas[$type][$rate] : 0.000001;
        $price += $delta;
        $this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);
        $price = $this->_calculator->round($price);
    }
    return $price;
}

Ale przechowuje deltę. Jeśli nie może znaleźć takiej przechowywanej delty, tworzy ją. Dlaczego? O ile mogę powiedzieć, prowadzi to do różnych wyników przy identycznych operacjach.

Powiedzmy, że mamy $price3.595 i nie mamy pamięci podręcznej $delta. Gdy przejdziemy przez tę metodę, otrzymamy deltę = 0,000001. Otrzymujemy wtedy $price= 3.595001, co zaokrągla do 3,60, więc mamy nową wartość $delta-0.004999. I zwracamy 3,60.

Z wyjątkiem tego, że mamy deltę, więc zróbmy to jeszcze raz, z $price= 3.595. $price= 3,595 - 0,004999 = 3,590001

Co jeśli zaokrąglimy, otrzymamy 3,59. Różne odpowiedzi.

Wydaje mi się, że każdy zastosowany algorytm zaokrąglania powinien przynajmniej dać tę samą odpowiedź za każdym razem, gdy jest uruchamiany z tymi samymi argumentami, ale nie tym razem.

Max Bucknell
źródło
BTW, napotkał ten sam błąd w Magento 2.2.2
TheKitMurkit

Odpowiedzi:

9

Mam Magento 1.8 na moim serwerze i sprawdziłem _deltaRound()metodę. Teraz tak to wygląda.

/**
 * Round price based on previous rounding operation delta
 *
 * @param float $price
 * @param string $rate
 * @param bool $direction price including or excluding tax
 * @param string $type
 * @return float
 */
protected function _deltaRound($price, $rate, $direction, $type = 'regular')
{
    if ($price) {
        $rate  = (string) $rate;
        $type  = $type . $direction;
        $delta = isset($this->_roundingDeltas[$type][$rate]) ? $this->_roundingDeltas[$type][$rate] : 0;
        $price += $delta;
        $this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);
        $price = $this->_calculator->round($price);
    }
    return $price;
}

Jak widać, jeśli _roundingDeltas()nie jest ustawiony, przyjmuje zerojako wartość domyślną. To tylko dla ciebie. Zespół Magento może podsłuchać twoje wątpliwości. Po cichu rozwiązali twój problem. :)

EDYTOWAĆ

Przeanalizujmy wykorzystanie tej funkcji przez zastosowanie jej w przykładzie w czasie rzeczywistym. Załóżmy, że mam produkt w koszyku, który podlega opodatkowaniu. Ilość, którą zamierzam kupić, to 5. Po nałożeniu podatku cena prdouct wynosi 10,5356 USD. Więc to moja sytuacja

CART
-------
   Product A
       - Price (including tax) - 10.5356
       - Quantity              - 5
       - Tax Rule  - Apply tax for each product. Then calculate the total price according to the quantity purchased.

A zatem obliczmy teraz rzeczywistą cenę, która przyniesie w tej sytuacji. To będzie

  Total =  10.5356 x 5 = 52.678

Załóżmy teraz, że magento nie używa _deltaRound()metody. Po prostu zaokrągla cenę produktu do dwóch miejsc po przecinku, a następnie oblicza cenę całkowitą. W takim przypadku cena produktu zostanie zaokrąglona, 10.54a zatem będzie to cena całkowita

  Total = 10.54 x 5 = 52.7

Załóżmy teraz, że magento używa _deltaRound()metody i ta funkcja faktycznie zaokrągla cenę produktu do dwóch miejsc po przecinku. Oprócz tego zachowa wartość delta, która w rzeczywistości jest różnicą między ceną rzeczywistą a ceną zaokrągloną, zostanie wykorzystana do późniejszego obliczenia ceny zaokrąglonej. Tutajwprowadź opis zdjęcia tutaj

  Total =  10.54+10.53+10.54+10.53+10.54 = 52.68

Oznacza to, że _deltaRound()metoda faktycznie dokładniej zaokrągla cenę podatkową do rzeczywistej ceny podatkowej. Jak powiedziałeś, ta metoda zwraca inną wartość zaokrąglenia zależy od wartości delta. Ta wartość delta faktycznie dokładniej zaokrągla podatki.

Zgodnie z tym możemy stwierdzić, że wraz ze wzrostem ilości, jeśli nie zastosujemy tej metody, spowoduje to dużą różnicę między wartością zaokrągloną a wartością rzeczywistą. Ale jeśli użyjemy tej metody, spowoduje to, że nasza zaokrąglona wartość będzie możliwie najbliższa rzeczywistej wartości.

Magento domyślnie zaokrągla do dwóch miejsc po przecinku. Jest to metoda odpowiedzialna za zaokrąglanie dwóch miejsc po przecinku

Location :app/code/core/Mage/Core/Model/Store.php
public function roundPrice($price)
{
    return round($price, 2);
}

Jeśli ustawimy wartość 4 lub coś, możemy dodatkowo zwiększyć dokładność zaokrąglania.

Uwaga: To jest moje otwarcie i przegląd. To może być prawda lub nie. Jednak wydaje mi się to trafne i logiczne.

Dzięki.

Rajeev K Tomy
źródło
Naprawdę nienawidzę zakodowanego 2 inroundPrice
David Manners
@DavidManners: tak, jest poprawny. Ale magento używa _deltaRound()do pewnego stopnia pokonania trudności. Jakikolwiek jest na stałe zakodowany. W niektórych przypadkach z pewnością spowoduje pewne trudności
Rajeev K Tomy
1
Patrząc na github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/… domyślna wartość w Magento 1.9 wynosi nadal 0,0001, co dla nas spowodowało błąd wyłudzeń i kalkulacje podatkowe przy wysyłce
ProxiBlue
2

Informacje

Cena zaokrąglania w Magento na podstawie poprzedniej operacji zaokrąglania delty.

app / code / core / Mage / Tax / Model / Sales / Total / Quote / Tax.php: 1392 app / code / core / Mage / Tax / Model / Sales / Total / Quote / Subtotal.php: 719

protected function _deltaRound($price, $rate, $direction, $type = 'regular')
{
    if ($price) {
        $rate = (string)$rate;
        $type = $type . $direction;
        // initialize the delta to a small number to avoid non-deterministic behavior with rounding of 0.5
        $delta = isset($this->_roundingDeltas[$type][$rate]) ? $this->_roundingDeltas[$type][$rate] : 0.000001;
        $price += $delta;
        $this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);
        $price = $this->_calculator->round($price);
    }
    return $price;
}

Czasami może to powodować błąd z powodu błędu obliczania dużej delty ( $this->_calculator->round($price)). Na przykład z tego powodu niektóre ceny mogą się różnić w przedziale ± 1 cent .

Rozwiązanie

Aby tego uniknąć, musisz poprawić dokładność obliczania delty.

Zmiana

$this->_roundingDeltas[$type][$rate] = $price - $this->_calculator->round($price);

do

$this->_roundingDeltas[$type][$rate] = $price - round($price, 4);

Zmiany należy wprowadzić w obu plikach:

app / code / core / Mage / Tax / Model / Sales / Total / Quote / Tax.php: 1392 app / code / core / Mage / Tax / Model / Sales / Total / Quote / Subtotal.php: 719

Nie modyfikuj ani nie rąbaj podstawowych plików! Przeredaguj!

Rozwiązanie przetestowano na różnych wersjach Magento 1.9.x, ale być może zadziała to we wcześniejszych wersjach.

PS

roundPriceFunkcja zmiany , jak pokazano poniżej, może rozwiązać problem błędu zaokrąglania, ale może powodować inne (na przykład niektóre platformy wymagają zaokrąglania w górę do 2 miejsc po przecinku).

app / code / core / Mage / Core / Model / Store.php: 995

public function roundPrice($price)
{
    return round($price, 4);
}
Victor S.
źródło