Merge pull request #30692 from woocommerce/fix/30231

Use proper location for taxes when adding products via admin
This commit is contained in:
Vedanshu Jain 2021-09-22 15:14:13 +05:30 committed by GitHub
commit 5cdf979961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 172 additions and 11 deletions

View File

@ -10,6 +10,8 @@
* @package WooCommerce\Classes
*/
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\ArrayUtil;
use Automattic\WooCommerce\Utilities\NumberUtil;
defined( 'ABSPATH' ) || exit;
@ -1361,14 +1363,23 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
*/
public function add_product( $product, $qty = 1, $args = array() ) {
if ( $product ) {
$order = ArrayUtil::get_value_or_default( $args, 'order' );
$total = wc_get_price_excluding_tax(
$product,
array(
'qty' => $qty,
'order' => $order,
)
);
$default_args = array(
'name' => $product->get_name(),
'tax_class' => $product->get_tax_class(),
'product_id' => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
'variation' => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
'total' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
'subtotal' => $total,
'total' => $total,
'quantity' => $qty,
);
} else {
@ -1392,7 +1403,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
}
}
$item = new WC_Order_Item_Product();
$item = wc_get_container()->get( LegacyProxy::class )->get_instance_of( WC_Order_Item_Product::class );
$item->set_props( $args );
$item->set_backorder_meta();
$item->set_order_id( $this->get_id() );

View File

@ -955,7 +955,7 @@ class WC_AJAX {
/* translators: %s: error message */
throw new Exception( sprintf( __( 'Error: %s', 'woocommerce' ), $validation_error->get_error_message() ) );
}
$item_id = $order->add_product( $product, $qty );
$item_id = $order->add_product( $product, $qty, array( 'order' => $order ) );
$item = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id, $order, $product );
$added_items[ $item_id ] = $item;
$order_notes[ $item_id ] = $product->get_formatted_name();

View File

@ -9,6 +9,8 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\ArrayUtil;
use Automattic\WooCommerce\Utilities\NumberUtil;
defined( 'ABSPATH' ) || exit;
@ -1081,7 +1083,9 @@ function wc_get_price_excluding_tax( $product, $args = array() ) {
$line_price = $price * $qty;
if ( $product->is_taxable() && wc_prices_include_tax() ) {
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
$order = ArrayUtil::get_value_or_default( $args, 'order' );
$customer = $order ? wc_get_container()->get( LegacyProxy::class )->get_instance_of( WC_Customer::class, $order->get_customer_id() ) : null;
$tax_rates = WC_Tax::get_rates( $product->get_tax_class(), $customer );
$base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) );
$remove_taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $line_price, $base_tax_rates, true ) : WC_Tax::calc_tax( $line_price, $tax_rates, true );
$return_price = $line_price - array_sum( $remove_taxes ); // Unrounded since we're dealing with tax inclusive prices. Matches logic in cart-totals class. @see adjust_non_base_location_price.

View File

@ -12,6 +12,8 @@ return array(
'get_bloginfo',
'get_woocommerce_currencies',
'get_woocommerce_currency_symbol',
'wc_get_price_excluding_tax',
'wc_get_shipping_method_count',
'wc_prices_include_tax',
'wc_site_is_https',
);

View File

@ -5,6 +5,8 @@
* @package WooCommerce\Tests\Abstracts
*/
use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack;
/**
* Class WC_Abstract_Order.
*/
@ -100,12 +102,12 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
update_option( 'woocommerce_default_country', 'IN:AP' );
$tax_rate = array(
'tax_rate_country' => 'IN',
'tax_rate_state' => '',
'tax_rate' => '25.0000',
'tax_rate_name' => 'tax',
'tax_rate_order' => '1',
'tax_rate_class' => '',
'tax_rate_country' => 'IN',
'tax_rate_state' => '',
'tax_rate' => '25.0000',
'tax_rate_name' => 'tax',
'tax_rate_order' => '1',
'tax_rate_class' => '',
);
WC_Tax::_insert_tax_rate( $tax_rate );
@ -137,4 +139,49 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
$this->assertEquals( 2, $order->get_discount_tax() );
}
/**
* @testdox 'add_product' passes the order supplied in '$args' to 'wc_get_price_excluding_tax', and uses the obtained price as total and subtotal for the line item.
*/
public function test_add_product_passes_order_to_wc_get_price_excluding_tax() {
$product_passed_to_get_price = false;
$args_passed_to_get_price = false;
FunctionsMockerHack::add_function_mocks(
array(
'wc_get_price_excluding_tax' => function( $product, $args = array() ) use ( &$product_passed_to_get_price, &$args_passed_to_get_price ) {
$product_passed_to_get_price = $product;
$args_passed_to_get_price = $args;
return 1234;
},
)
);
//phpcs:disable Squiz.Commenting
$order_item = new class() extends WC_Order_Item_Product {
public $passed_props;
public function set_props( $args, $context = 'set' ) {
$this->passed_props = $args;
}
};
//phpcs:enable Squiz.Commenting
$this->register_legacy_proxy_class_mocks(
array( 'WC_Order_Item_Product' => $order_item )
);
$product = WC_Helper_Product::create_simple_product();
$product->set_regular_price( 100 );
$product->save();
$order = wc_create_order();
$order->add_product( $product, 1, array( 'order' => $order ) );
$this->assertSame( $product, $product_passed_to_get_price );
$this->assertSame( $order, $args_passed_to_get_price['order'] );
$this->assertEquals( 1234, $order_item->passed_props['total'] );
$this->assertEquals( 1234, $order_item->passed_props['subtotal'] );
}
}

View File

@ -0,0 +1,97 @@
<?php
/**
* Unit tests for wc-product-functions.php.
*
* @package WooCommerce\Tests\Functions\Stock
*/
use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack;
use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\StaticMockerHack;
/**
* Class WC_Stock_Functions_Tests.
*/
class WC_Product_Functions_Tests extends \WC_Unit_Test_Case {
/**
* @testdox If 'wc_get_price_excluding_tax' gets an order as argument, it passes the order customer to 'WC_Tax::get_rates'.
*
* @testWith [true]
* [false]
*
* @param bool $pass_order Whether an order is passed to 'wc_get_price_excluding_tax' or not.
*/
public function test_wc_get_price_excluding_tax_passes_order_customer_to_get_rates_if_order_is_available( $pass_order ) {
$customer_passed_to_get_rates = false;
$customer_id_passed_to_wc_customer_constructor = false;
FunctionsMockerHack::add_function_mocks(
array(
'wc_prices_include_tax' => '__return_true',
)
);
StaticMockerHack::add_method_mocks(
array(
'WC_Tax' =>
array(
'get_rates' => function( $tax_class, $customer ) use ( &$customer_passed_to_get_rates ) {
$customer_passed_to_get_rates = $customer;
},
'get_base_tax_rates' => function( $tax_class ) {
return 0;
},
'calc_tax' => function( $price, $rates, $price_includes_tax = false, $deprecated = false ) {
return array( 0 );
},
),
)
);
// phpcs:disable Squiz.Commenting.FunctionComment.Missing
$product = new class() extends WC_Product {
public function get_price( $context = 'view' ) {
return 0;
}
public function is_taxable() {
return true;
}
public function get_tax_class( $context = 'view' ) {
return '';
}
};
$customer = new stdClass();
$this->register_legacy_proxy_class_mocks(
array(
'WC_Customer' => function( $customer_id ) use ( &$customer_id_passed_to_wc_customer_constructor, $customer ) {
$customer_id_passed_to_wc_customer_constructor = $customer_id;
return $customer;
},
)
);
if ( $pass_order ) {
$order = new class() {
public function get_customer_id() {
return 1;
}
};
wc_get_price_excluding_tax( $product, array( 'order' => $order ) );
$this->assertEquals( $order->get_customer_id(), $customer_id_passed_to_wc_customer_constructor );
$this->assertSame( $customer, $customer_passed_to_get_rates );
} else {
wc_get_price_excluding_tax( $product );
$this->assertFalse( $customer_id_passed_to_wc_customer_constructor );
$this->assertNull( $customer_passed_to_get_rates );
}
// phpcs:enable Squiz.Commenting.FunctionComment.Missing
}
}