Merge pull request #18008 from woocommerce/update/rounding-17970-17863

Rounding fixes/line item rounding
This commit is contained in:
Claudiu Lodromanean 2017-12-07 08:34:15 -08:00 committed by GitHub
commit 7c06e7d63d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 330 additions and 124 deletions

View File

@ -1366,9 +1366,14 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$this->add_item( $item );
}
// Save tax totals.
$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
$this->set_shipping_tax( wc_round_tax_total( array_sum( array_map( 'wc_round_tax_total', $shipping_taxes ) ) ) );
$this->set_cart_tax( wc_round_tax_total( array_sum( array_map( 'wc_round_tax_total', $cart_taxes ) ) ) );
} else {
$this->set_shipping_tax( wc_round_tax_total( array_sum( $shipping_taxes ) ) );
$this->set_cart_tax( wc_round_tax_total( array_sum( $cart_taxes ) ) );
}
$this->save();
}
@ -1599,7 +1604,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
$subtotal = 0;
if ( ! $compound ) {
foreach ( $this->get_items() as $item ) {
$subtotal += $item->get_subtotal();

View File

@ -5,6 +5,10 @@
* Methods are protected and class is final to keep this as an internal API.
* May be opened in the future once structure is stable.
*
* Rounding guide:
* - if something is being stored e.g. item total, store unrounded. This is so taxes can be recalculated later accurately.
* - if calculating a total, round (if settings allow).
*
* @author Automattic
* @package WooCommerce/Classes
*/
@ -315,12 +319,7 @@ final class WC_Cart_Totals {
}
$fee->taxes = apply_filters( 'woocommerce_cart_totals_get_fees_from_cart_taxes', $fee->taxes, $fee, $this );
$fee->total_tax = array_sum( $fee->taxes );
if ( ! $this->round_at_subtotal() ) {
$fee->total_tax = wc_round_tax_total( $fee->total_tax, wc_get_rounding_precision() );
}
$fee->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $fee->taxes ) );
// Set totals within object.
$fee->object->total = wc_remove_number_precision_deep( $fee->total );
@ -350,11 +349,7 @@ final class WC_Cart_Totals {
$shipping_line->taxable = true;
$shipping_line->total = wc_add_number_precision_deep( $shipping_object->cost );
$shipping_line->taxes = wc_add_number_precision_deep( $shipping_object->taxes, false );
$shipping_line->total_tax = wc_add_number_precision_deep( array_sum( $shipping_object->taxes ), false );
if ( ! $this->round_at_subtotal() ) {
$shipping_line->total_tax = wc_round_tax_total( $shipping_line->total_tax, wc_get_rounding_precision() );
}
$shipping_line->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $shipping_line->taxes ) );
$this->shipping[ $key ] = $shipping_line;
}
@ -427,7 +422,7 @@ final class WC_Cart_Totals {
$base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'unfiltered' ) );
// Work out a new base price without the shop's base tax.
$taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true, true );
$taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true );
// Now we have a new item price (excluding TAX).
$item->price = absint( $item->price - array_sum( $taxes ) );
@ -454,8 +449,8 @@ final class WC_Cart_Totals {
if ( $item->tax_rates !== $base_tax_rates ) {
// Work out a new base price without the shop's base tax.
$taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true, true );
$new_taxes = WC_Tax::calc_tax( $item->price - array_sum( $taxes ), $item->tax_rates, false, true );
$taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true );
$new_taxes = WC_Tax::calc_tax( $item->price - array_sum( $taxes ), $item->tax_rates, false );
// Now we have a new item price.
$item->price = $item->price - array_sum( $taxes ) + array_sum( $new_taxes );
@ -575,7 +570,7 @@ final class WC_Cart_Totals {
if ( ! isset( $taxes[ $rate_id ] ) ) {
$taxes[ $rate_id ] = 0;
}
$taxes[ $rate_id ] += $rate;
$taxes[ $rate_id ] += $this->round_line_tax( $rate );
}
}
@ -636,15 +631,13 @@ final class WC_Cart_Totals {
}
if ( $this->calculate_tax && $item->product->is_taxable() ) {
$item->taxes = WC_Tax::calc_tax( $item->total, $item->tax_rates, $item->price_includes_tax );
$item->total_tax = array_sum( $item->taxes );
if ( ! $this->round_at_subtotal() ) {
$item->total_tax = wc_round_tax_total( $item->total_tax, wc_get_rounding_precision() );
}
$total_taxes = WC_Tax::calc_tax( $item->total, $item->tax_rates, $item->price_includes_tax );
$item->taxes = $total_taxes;
$item->total_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $item->taxes ) );
if ( $item->price_includes_tax ) {
$item->total = $item->total - $item->total_tax;
// Use unrounded taxes so we can re-calculate from the orders screen accurately later.
$item->total = $item->total - array_sum( $item->taxes );
}
}
@ -653,7 +646,7 @@ final class WC_Cart_Totals {
$this->cart->cart_contents[ $item_key ]['line_tax'] = wc_remove_number_precision( $item->total_tax );
}
$this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) );
$this->set_total( 'items_total', array_sum( array_map( 'round', array_values( wp_list_pluck( $this->items, 'total' ) ) ) ) );
$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
$this->cart->set_cart_contents_total( $this->get_total( 'items_total' ) );
@ -690,14 +683,11 @@ final class WC_Cart_Totals {
if ( $this->calculate_tax && $item->product->is_taxable() ) {
$subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $item->tax_rates, $item->price_includes_tax );
$item->subtotal_tax = array_sum( $subtotal_taxes );
if ( ! $this->round_at_subtotal() ) {
$item->subtotal_tax = wc_round_tax_total( $item->subtotal_tax, wc_get_rounding_precision() );
}
$item->subtotal_tax = array_sum( array_map( array( $this, 'round_line_tax' ), $subtotal_taxes ) );
if ( $item->price_includes_tax ) {
$item->subtotal = $item->subtotal - $item->subtotal_tax;
// Use unrounded taxes so we can re-calculate from the orders screen accurately later.
$item->subtotal = $item->subtotal - array_sum( $subtotal_taxes );
}
}
@ -705,7 +695,7 @@ final class WC_Cart_Totals {
$this->cart->cart_contents[ $item_key ]['line_subtotal'] = wc_remove_number_precision( $item->subtotal );
$this->cart->cart_contents[ $item_key ]['line_subtotal_tax'] = wc_remove_number_precision( $item->subtotal_tax );
}
$this->set_total( 'items_subtotal', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) );
$this->set_total( 'items_subtotal', array_sum( array_map( 'round', array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) ) );
$this->set_total( 'items_subtotal_tax', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) );
$this->cart->set_subtotal( $this->get_total( 'items_subtotal' ) );
@ -816,7 +806,7 @@ final class WC_Cart_Totals {
* @since 3.2.0
*/
protected function calculate_totals() {
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ) ) );
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ), 0 ) );
$this->cart->set_total_tax( array_sum( $this->get_merged_taxes( false ) ) );
// Allow plugins to hook and alter totals before final total is calculated.
@ -827,4 +817,18 @@ final class WC_Cart_Totals {
// Allow plugins to filter the grand total, and sum the cart totals in case of modifications.
$this->cart->set_total( max( 0, apply_filters( 'woocommerce_calculated_total', $this->get_total( 'total' ), $this->cart ) ) );
}
/**
* Apply rounding to an array of taxes before summing. Rounds to store DP setting, ignoring precision.
*
* @since 3.2.6
* @param float $value Tax value.
* @return float
*/
protected function round_line_tax( $value ) {
if ( ! $this->round_at_subtotal() ) {
$value = wc_round_tax_total( $value, 0 );
}
return $value;
}
}

View File

@ -430,7 +430,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @param string $value Value to set.
*/
public function set_subtotal( $value ) {
$this->totals['subtotal'] = wc_format_decimal( $value );
$this->totals['subtotal'] = wc_format_decimal( $value, wc_get_price_decimals() );
}
/**
@ -460,7 +460,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @param string $value Value to set.
*/
public function set_discount_tax( $value ) {
$this->totals['discount_tax'] = wc_format_decimal( $value );
$this->totals['discount_tax'] = wc_round_tax_total( $value );
}
/**
@ -470,7 +470,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @param string $value Value to set.
*/
public function set_shipping_total( $value ) {
$this->totals['shipping_total'] = wc_format_decimal( $value );
$this->totals['shipping_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
}
/**
@ -490,7 +490,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @param string $value Value to set.
*/
public function set_cart_contents_total( $value ) {
$this->totals['cart_contents_total'] = wc_format_decimal( $value );
$this->totals['cart_contents_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
}
/**
@ -510,7 +510,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @param string $value Value to set.
*/
public function set_total( $value ) {
$this->totals['total'] = wc_format_decimal( $value );
$this->totals['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
}
/**
@ -530,7 +530,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @param string $value Value to set.
*/
public function set_fee_total( $value ) {
$this->totals['fee_total'] = wc_format_decimal( $value );
$this->totals['fee_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
}
/**

View File

@ -85,7 +85,7 @@ class WC_Discounts {
$item->object = $cart_item;
$item->product = $cart_item['data'];
$item->quantity = $cart_item['quantity'];
$item->price = wc_add_number_precision_deep( $item->product->get_price() ) * $item->quantity;
$item->price = wc_add_number_precision_deep( $item->product->get_price() * $item->quantity );
$this->items[ $key ] = $item;
}
@ -370,7 +370,7 @@ class WC_Discounts {
}
}
$discount = min( $discounted_price, $discount );
$discount = wc_cart_round_discount( min( $discounted_price, $discount ), 0 );
$cart_total = $cart_total + $price_to_discount;
$total_discount = $total_discount + $discount;
$applied_count = $applied_count + $apply_quantity;

View File

@ -27,7 +27,7 @@ class WC_Tax {
*
* @var bool
*/
public static $round_at_subtotal;
public static $round_at_subtotal = false;
/**
* Load options.
@ -64,32 +64,20 @@ class WC_Tax {
/**
* Calculate tax for a line.
* @param float $price Price to calc tax on
* @param array $rates Rates to apply
* @param boolean $price_includes_tax Whether the passed price has taxes included
* @param boolean $suppress_rounding Whether to suppress any rounding from taking place
* @return array Array of rates + prices after tax
*
* @param float $price Price to calc tax on.
* @param array $rates Rates to apply.
* @param boolean $price_includes_tax Whether the passed price has taxes included.
* @param boolean $deprecated Whether to suppress any rounding from taking place. No longer used here.
* @return array Array of rates + prices after tax.
*/
public static function calc_tax( $price, $rates, $price_includes_tax = false, $suppress_rounding = false ) {
// Work in pence to X precision
$price = self::precision( $price );
public static function calc_tax( $price, $rates, $price_includes_tax = false, $deprecated = false ) {
if ( $price_includes_tax ) {
$taxes = self::calc_inclusive_tax( $price, $rates );
} else {
$taxes = self::calc_exclusive_tax( $price, $rates );
}
// Round to precision
if ( ! self::$round_at_subtotal && ! $suppress_rounding ) {
$taxes = array_map( 'round', $taxes ); // Round to precision
}
// Remove precision
$price = self::remove_precision( $price );
$taxes = array_map( array( __CLASS__, 'remove_precision' ), $taxes );
return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $suppress_rounding );
return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $deprecated );
}
/**
@ -104,24 +92,6 @@ class WC_Tax {
return apply_filters( 'woocommerce_calc_shipping_tax', $taxes, $price, $rates );
}
/**
* Multiply cost by pow precision.
* @param float $price
* @return float
*/
private static function precision( $price ) {
return $price * ( pow( 10, self::$precision ) );
}
/**
* Divide cost by pow precision.
* @param float $price
* @return float
*/
private static function remove_precision( $price ) {
return $price / ( pow( 10, self::$precision ) );
}
/**
* Round to precision.
*
@ -137,7 +107,7 @@ class WC_Tax {
* @return float
*/
public static function round( $in ) {
return apply_filters( 'woocommerce_tax_round', round( $in, self::$precision ), $in );
return apply_filters( 'woocommerce_tax_round', round( $in, wc_get_rounding_precision() ), $in );
}
/**

View File

@ -362,32 +362,23 @@ function wc_cart_totals_shipping_method_label( $method ) {
/**
* Round discount.
*
* @param float $value
* @param int $precision
* @param double $value Amount to round.
* @param int $precision DP to round.
* @return float
*/
function wc_cart_round_discount( $value, $precision ) {
if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
return round( $value, $precision, WC_DISCOUNT_ROUNDING_MODE );
} elseif ( 2 === WC_DISCOUNT_ROUNDING_MODE ) {
return wc_legacy_round_half_down( $value, $precision );
} else {
// Fake it in PHP 5.2.
if ( 2 === WC_DISCOUNT_ROUNDING_MODE && strstr( $value, '.' ) ) {
$value = (string) $value;
$value = explode( '.', $value );
$value[1] = substr( $value[1], 0, $precision + 1 );
$value = implode( '.', $value );
if ( substr( $value, -1 ) === '5' ) {
$value = substr( $value, 0, -1 ) . '4';
}
$value = floatval( $value );
}
return round( $value, $precision );
return round( $value, $precision );
}
}
/**
* Gets chosen shipping method IDs from chosen_shipping_methods session, without instance IDs.
*
* @since 2.6.2
* @return string[]
*/

View File

@ -1496,17 +1496,17 @@ function wc_get_rounding_precision() {
}
/**
* Add precision to a number and return an int.
* Add precision to a number and return a number.
*
* @since 3.2.0
* @param float $value Number to add precision to.
* @param bool $round Should we round after adding precision?
* @return int|float
* @return float
*/
function wc_add_number_precision( $value, $round = true ) {
$precision = pow( 10, wc_get_price_decimals() );
$value = $value * $precision;
return $round ? intval( round( $value ) ) : $value;
$cent_precision = pow( 10, wc_get_price_decimals() );
$value = $value * $cent_precision;
return $round ? round( $value, wc_get_rounding_precision() - wc_get_price_decimals() ) : $value;
}
/**
@ -1517,8 +1517,8 @@ function wc_add_number_precision( $value, $round = true ) {
* @return float
*/
function wc_remove_number_precision( $value ) {
$precision = pow( 10, wc_get_price_decimals() );
return $value / $precision;
$cent_precision = pow( 10, wc_get_price_decimals() );
return $value / $cent_precision;
}
/**

View File

@ -226,32 +226,46 @@ function wc_trim_zeros( $price ) {
*
* @param double $value Amount to round.
* @param int $precision DP to round. Defaults to wc_get_price_decimals.
* @return double
* @return float
*/
function wc_round_tax_total( $value, $precision = null ) {
$precision = is_null( $precision ) ? wc_get_price_decimals() : absint( $precision );
$precision = is_null( $precision ) ? wc_get_price_decimals() : intval( $precision );
if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
$rounded_tax = round( $value, $precision, wc_get_tax_rounding_mode() );
} elseif ( 2 === wc_get_tax_rounding_mode() ) {
$rounded_tax = wc_legacy_round_half_down( $value, $precision );
} else {
// Fake it in PHP 5.2.
if ( 2 === wc_get_tax_rounding_mode() && strstr( $value, '.' ) ) {
$value = (string) $value;
$value = explode( '.', $value );
$value[1] = substr( $value[1], 0, $precision + 1 );
$value = implode( '.', $value );
if ( substr( $value, -1 ) === '5' ) {
$value = substr( $value, 0, -1 ) . '4';
}
$value = floatval( $value );
}
$rounded_tax = round( $value, $precision );
}
return apply_filters( 'wc_round_tax_total', $rounded_tax, $value, $precision, WC_TAX_ROUNDING_MODE );
}
/**
* Round half down in PHP 5.2.
*
* @since 3.2.6
* @param float $value Value to round.
* @param int $precision Precision to round down to.
* @return float
*/
function wc_legacy_round_half_down( $value, $precision ) {
$value = wc_float_to_string( $value );
if ( false !== strstr( $value, '.' ) ) {
$value = explode( '.', $value );
if ( strlen( $value[1] ) > $precision && substr( $value[1], -1 ) === '5' ) {
$value[1] = substr( $value[1], 0, -1 ) . '4';
}
$value = implode( '.', $value );
}
return round( floatval( $value ), $precision );
}
/**
* Make a refund total negative.
*

View File

@ -301,7 +301,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->calculate_totals();
$this->assertEquals( '8.33', wc_format_decimal( WC()->cart->get_subtotal(), 2 ) );
$this->assertEquals( '0.83', wc_format_decimal( WC()->cart->get_discount_total(), 2 ) );
$this->assertEquals( '1.50', wc_format_decimal( WC()->cart->get_total_tax(), 2 ) );
$this->assertEquals( '1.50', wc_format_decimal( WC()->cart->get_total_tax(), 2 ), WC()->cart->get_total_tax() );
$this->assertEquals( '8.99', wc_format_decimal( WC()->cart->get_total( 'edit' ), 2 ) );
WC()->cart->remove_coupons();
@ -396,6 +396,213 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
return 'US';
}
/**
* Test a rounding issue on prices that are entered inclusive tax and shipping is used.
* See: #17970.
*
* @since 3.2.6
*/
public function test_inclusive_tax_rounding() {
global $wpdb;
// Store is set to enter product prices inclusive tax.
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
// 19% tax.
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '19.0000',
'tax_rate_name' => 'VAT',
'tax_rate_priority' => '1',
'tax_rate_compound' => '0',
'tax_rate_shipping' => '1',
'tax_rate_order' => '1',
'tax_rate_class' => '',
);
WC_Tax::_insert_tax_rate( $tax_rate );
// Create a flat rate method.
$flat_rate_settings = array(
'enabled' => 'yes',
'title' => 'Flat rate',
'availability' => 'all',
'countries' => '',
'tax_status' => 'taxable',
'cost' => '4.12',
);
update_option( 'woocommerce_flat_rate_settings', $flat_rate_settings );
update_option( 'woocommerce_flat_rate', array() );
WC_Cache_Helper::get_transient_version( 'shipping', true );
WC()->shipping->load_shipping_methods();
// We need this to have the calculate_totals() method calculate totals.
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
define( 'WOOCOMMERCE_CHECKOUT', true );
}
// Create the product and add it to the cart.
$product = new WC_Product_Simple;
$product->set_regular_price( '149.14' );
$product->save();
WC()->cart->add_to_cart( $product->get_id(), 1 );
// Set the flat_rate shipping method
WC()->session->set( 'chosen_shipping_methods', array( 'flat_rate' ) );
WC()->cart->calculate_totals();
$this->assertEquals( '154.04', wc_format_decimal( WC()->cart->get_total( 'edit' ), 2 ) );
$this->assertEquals( '24.59', wc_format_decimal( WC()->cart->get_total_tax(), 2 ) );
// Clean up.
WC()->cart->empty_cart();
WC_Helper_Product::delete_product( $product->get_id() );
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" );
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" );
update_option( 'woocommerce_prices_include_tax', 'no' );
update_option( 'woocommerce_calc_taxes', 'no' );
// Delete the flat rate method
WC()->session->set( 'chosen_shipping_methods', array() );
delete_option( 'woocommerce_flat_rate_settings' );
delete_option( 'woocommerce_flat_rate' );
WC_Cache_Helper::get_transient_version( 'shipping', true );
WC()->shipping->unregister_shipping_methods();
}
/**
* Test a rounding issue on prices that are entered exclusive tax.
* See: #17970.
*
* @since 3.2.6
*/
public function test_exclusive_tax_rounding() {
global $wpdb;
// todo remove this line when previous test stops failing.
WC()->cart->empty_cart();
// Store is set to enter product prices excluding tax.
update_option( 'woocommerce_prices_include_tax', 'no' );
update_option( 'woocommerce_calc_taxes', 'yes' );
// 20% tax.
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '20.0000',
'tax_rate_name' => 'VAT',
'tax_rate_priority' => '1',
'tax_rate_compound' => '0',
'tax_rate_shipping' => '0',
'tax_rate_order' => '1',
'tax_rate_class' => '',
);
WC_Tax::_insert_tax_rate( $tax_rate );
// Add 2 products whose retail prices (inc tax) are: £65, £50.
// Their net prices are therefore: £54.1666667 and £41.6666667.
$product1 = new WC_Product_Simple;
$product1->set_regular_price( '54.1666667' );
$product1->save();
$product2 = new WC_Product_Simple;
$product2->set_regular_price( '41.6666667' );
$product2->save();
WC()->cart->add_to_cart( $product1->get_id(), 1 );
WC()->cart->add_to_cart( $product2->get_id(), 1 );
WC()->cart->calculate_totals();
$this->assertEquals( '115.00', wc_format_decimal( WC()->cart->get_total( 'edit' ), 2 ) );
// Clean up.
WC()->cart->empty_cart();
WC_Helper_Product::delete_product( $product1->get_id() );
WC_Helper_Product::delete_product( $product2->get_id() );
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" );
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" );
update_option( 'woocommerce_prices_include_tax', 'no' );
update_option( 'woocommerce_calc_taxes', 'no' );
}
/**
* Test a rounding issue on prices and totals that are entered exclusive tax.
* See: #17647.
*
* @since 3.2.6
*/
public function test_exclusive_tax_rounding_and_totals() {
global $wpdb;
WC()->cart->empty_cart();
WC()->cart->remove_coupons();
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" );
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" );
$product_data = array(
// price, quantity.
array( 2.13, 1 ),
array( 2.55, 0.5 ),
array( 39, 1 ),
array( 1.76, 1 ),
);
foreach ( $product_data as $data ) {
$product = new WC_Product_Simple();
$product->set_regular_price( $data[0] );
$product->save();
$products[] = array( $product, $data[1] );
}
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '5.5',
'tax_rate_name' => 'TAX',
'tax_rate_priority' => '1',
'tax_rate_compound' => '0',
'tax_rate_shipping' => '1',
'tax_rate_order' => '1',
'tax_rate_class' => '',
);
WC_Tax::_insert_tax_rate( $tax_rate );
update_option( 'woocommerce_prices_include_tax', 'no' );
update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_tax_round_at_subtotal', 'no' );
foreach ( $products as $data ) {
WC()->cart->add_to_cart( $data[0]->get_id(), $data[1] );
}
// We need this to have the calculate_totals() method calculate totals.
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
define( 'WOOCOMMERCE_CHECKOUT', true );
}
WC()->cart->calculate_totals();
$cart_totals = WC()->cart->get_totals();
$this->assertEquals( '2.44', wc_format_decimal( $cart_totals['total_tax'], 2 ) );
$this->assertEquals( '2.44', wc_format_decimal( $cart_totals['cart_contents_tax'], 2 ) );
$this->assertEquals( '44.17', wc_format_decimal( $cart_totals['cart_contents_total'], 2 ) );
$this->assertEquals( '46.61', wc_format_decimal( $cart_totals['total'], 2 ) );
// Clean up.
WC()->cart->empty_cart();
foreach ( $products as $data ) {
WC_Helper_Product::delete_product( $data[0]->get_id() );
}
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" );
$wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" );
update_option( 'woocommerce_prices_include_tax', 'no' );
update_option( 'woocommerce_calc_taxes', 'no' );
}
/**
* Test get_remove_url.
*

View File

@ -221,15 +221,29 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
/**
* Test wc_round_tax_total().
*
* Note the PHP 5.2 section of wc_round_tax_total() is excluded from test.
* coverage.
*
* @since 2.2
*/
public function test_wc_round_tax_total() {
update_option( 'woocommerce_prices_include_tax', 'no' );
$this->assertEquals( 1.25, wc_round_tax_total( 1.246 ) );
$this->assertEquals( 20, wc_round_tax_total( 19.9997 ) );
$this->assertEquals( 19.99, wc_round_tax_total( 19.99 ) );
$this->assertEquals( 19.99, wc_round_tax_total( 19.99 ) );
$this->assertEquals( 83, wc_round_tax_total( 82.5, 0 ) );
$this->assertEquals( 83, wc_round_tax_total( 82.54, 0 ) );
$this->assertEquals( 83, wc_round_tax_total( 82.546, 0 ) );
update_option( 'woocommerce_prices_include_tax', 'yes' );
$this->assertEquals( 1.25, wc_round_tax_total( 1.246 ) );
$this->assertEquals( 20, wc_round_tax_total( 19.9997 ) );
$this->assertEquals( 19.99, wc_round_tax_total( 19.99 ) );
$this->assertEquals( 19.99, wc_round_tax_total( 19.99 ) );
$this->assertEquals( 82, wc_round_tax_total( 82.5, 0 ) );
$this->assertEquals( 83, wc_round_tax_total( 82.54, 0 ) );
$this->assertEquals( 83, wc_round_tax_total( 82.546, 0 ) );
// Default.
update_option( 'woocommerce_prices_include_tax', 'no' );
}
/**

View File

@ -272,7 +272,8 @@ class WC_Tests_Tax extends WC_Unit_Test_Case {
* Next tax would be calced on 100 - 7.8341 = 92.1659.
* 92.1659 - ( 92.1659 / 1.05 ) = 4.38885.
*/
$this->assertEquals( $calced_tax, array( $tax_rate_1_id => 4.3889, $tax_rate_2_id => 7.8341 ) );
$this->assertEquals( round( $calced_tax[ $tax_rate_1_id ], 4 ), 4.3889 );
$this->assertEquals( round( $calced_tax[ $tax_rate_2_id ], 4 ), 7.8341 );
WC_Tax::_delete_tax_rate( $tax_rate_1_id );
WC_Tax::_delete_tax_rate( $tax_rate_2_id );