Fixed order totals calculation if it contains taxable and non-taxable products and percentage coupons
Added PHPUnit tests
This commit is contained in:
parent
81187dc359
commit
dd3427958b
|
@ -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,21 +1145,17 @@ 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 );
|
||||
}
|
||||
|
||||
$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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After applying coupons via the WC_Discounts class, update or create coupon items.
|
||||
|
@ -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 ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
|
||||
$taxes = array_map( 'wc_round_tax_total', $taxes );
|
||||
if ( 'taxable' !== $item->get_tax_status() || !wc_tax_enabled() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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() ) );
|
||||
|
||||
$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 = array_map( 'wc_round_tax_total', $taxes );
|
||||
$taxes = wc_round_tax_total( $taxes );
|
||||
}
|
||||
|
||||
$discount_tax += array_sum( $taxes );
|
||||
$discount_tax += $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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue