Fixed order totals calculation if it contains taxable and non-taxable products and percentage coupons

Added PHPUnit tests
This commit is contained in:
Francesco Leanza 2019-11-21 11:28:22 +01:00
parent 81187dc359
commit dd3427958b
2 changed files with 120 additions and 28 deletions

View File

@ -439,7 +439,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$subtotal = 0;
foreach ( $this->get_items() as $item ) {
$subtotal += $item->get_subtotal();
$subtotal += $this->round_item_subtotal( $item->get_subtotal() );
}
return apply_filters( 'woocommerce_order_get_subtotal', (float) $subtotal, $this );
@ -1145,18 +1145,14 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$item = $this->get_item( $item_id, false );
// If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
if ( $this->get_prices_include_tax() && wc_tax_enabled() && 'taxable' === $item->get_tax_status() ) {
$taxes = WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
$taxes = array_map( 'wc_round_tax_total', $taxes );
}
// Use unrounded taxes so totals will be re-calculated accurately, like in cart.
$amount = $amount - array_sum( $taxes );
$item->set_total( max( 0, round( $item->get_total() - $amount, wc_get_price_decimals() ) ) );
} else {
$item->set_total( max( 0, $item->get_total() - $amount ) );
}
$item->set_total( max( 0, $item->get_total() - $amount ) );
}
}
}
@ -1190,23 +1186,19 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
$item = $this->get_item( $item_id, false );
if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
if ( 'taxable' !== $item->get_tax_status() || !wc_tax_enabled() ) {
continue;
}
if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
$taxes = array_map( 'wc_round_tax_total', $taxes );
}
$taxes = array_sum( WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), $this->get_prices_include_tax() ) );
if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
$taxes = wc_round_tax_total( $taxes );
}
$discount_tax += array_sum( $taxes );
$amount = $amount - array_sum( $taxes );
} else {
$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) );
$discount_tax += $taxes;
if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
$taxes = array_map( 'wc_round_tax_total', $taxes );
}
$discount_tax += array_sum( $taxes );
if ( $this->get_prices_include_tax() ) {
$amount = $amount - $taxes;
}
}
@ -1517,8 +1509,8 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
// Sum line item costs.
foreach ( $this->get_items() as $item ) {
$cart_subtotal += round( $item->get_subtotal(), wc_get_price_decimals() );
$cart_total += round( $item->get_total(), wc_get_price_decimals() );
$cart_subtotal += $item->get_subtotal();
$cart_total += $item->get_total();
}
// Sum shipping costs.
@ -1561,7 +1553,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
}
}
$this->set_discount_total( $cart_subtotal - $cart_total );
$this->set_discount_total( round( $cart_subtotal - $cart_total, wc_get_price_decimals() ) );
$this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
$this->set_total( round( $cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) );
@ -1744,7 +1736,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
if ( ! $compound ) {
foreach ( $this->get_items() as $item ) {
$subtotal += $item->get_subtotal();
$subtotal += $this->round_item_subtotal( $item->get_subtotal() );
if ( 'incl' === $tax_display ) {
$subtotal += $item->get_subtotal_tax();
@ -1762,7 +1754,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
}
foreach ( $this->get_items() as $item ) {
$subtotal += $item->get_subtotal();
$subtotal += $this->round_item_subtotal( $item->get_subtotal() );
}
// Add Shipping Costs.
@ -2012,4 +2004,17 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
}
return false;
}
/**
* Apply rounding to item subtotal before summing.
*
* @param float $value Item subtotal value.
* @return float
*/
protected function round_item_subtotal( $value ) {
if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
$value = round( $value, wc_get_price_decimals() );
}
return $value;
}
}

View File

@ -363,4 +363,91 @@ class WC_Tests_Order_Coupons extends WC_Unit_Test_Case {
$order->apply_coupon( 'test-coupon-2' );
$this->assertEquals( '599.00', $order->get_total(), $order->get_total() );
}
/**
* Test a rounding issue on order totals when the order includes a percentage coupon and taxable and non-taxable items
* See: #25091.
*/
public function test_inclusive_tax_rounding_on_totals() {
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
WC_Tax::_insert_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' => '1',
'tax_rate_order' => '1',
'tax_rate_class' => '',
)
);
WC_Tax::_insert_tax_rate(
array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '5.0000',
'tax_rate_name' => 'VAT',
'tax_rate_priority' => '2',
'tax_rate_compound' => '1',
'tax_rate_shipping' => '1',
'tax_rate_order' => '1',
'tax_rate_class' => '',
)
);
$product_1 = WC_Helper_Product::create_simple_product();
$product_1->set_regular_price( 3.17 );
$product_1->save();
$product_1 = wc_get_product( $product_1->get_id() );
$product_2 = WC_Helper_Product::create_simple_product();
$product_2->set_regular_price( 6.13 );
$product_2->save();
$product_2 = wc_get_product( $product_2->get_id() );
$product_3 = WC_Helper_Product::create_simple_product();
$product_3->set_regular_price( 9.53 );
$product_3->set_tax_status( 'none' );
$product_3->save();
$product_3 = wc_get_product( $product_3->get_id() );
$coupon = new WC_Coupon();
$coupon->set_code( 'test-coupon-1' );
$coupon->set_amount( 10 );
$coupon->set_discount_type( 'percent' );
$coupon->save();
$order = wc_create_order(
array(
'status' => 'pending',
'customer_id' => 1,
'customer_note' => '',
'total' => '',
)
);
$order->add_product( $product_1 );
$order->add_product( $product_2 );
$order->add_product( $product_3 );
$order->calculate_totals( true );
$order->apply_coupon( $coupon->get_code() );
$applied_coupons = $order->get_items( 'coupon' );
$applied_coupon = current( $applied_coupons );
$this->assertEquals( '16.95', $order->get_total() );
$this->assertEquals( '1.73', $order->get_total_tax() );
$this->assertEquals( '1.69', $order->get_discount_total() );
$this->assertEquals( '1.69', $applied_coupon->get_discount() );
}
}