Commit 3247c83ddc for woocommerce

commit 3247c83ddc0a7fb20741c5eb26332db6e1a1394b
Author: woocommercebot <30233865+woocommercebot@users.noreply.github.com>
Date:   Tue Sep 16 17:03:19 2025 +0200

    [Backport to trunk] Disable the Analytics refund fix, and only enable for stores without refunds (#60874)

    * Disable the Analytics refund fix, and only enable for stores without refunds (#60814)

    * Limit the refund fix until explicitly triggered

    * Add tool to fix refund data already

    * Fix tooltip

    * Revert refunds logic if not using full refund data yet

    * Add changelog

    * Add missing import

    * Fix lint issues

    * Add doc block

    * Update refund tool message

    * Add two tests

    * Fix lint errors

    * Fix lint issue

    * Update new full refund check

    * Cherry-pick 92f39296583b008351a5ea8f5515a835ff181c63 with unresolved conflicts from #60944

    ---------

    Co-authored-by: louwie17 <lourensschep@gmail.com>
    Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com>

diff --git a/plugins/woocommerce/changelog/fix-reports-orders-stats-test-10.2 b/plugins/woocommerce/changelog/fix-reports-orders-stats-test-10.2
new file mode 100644
index 0000000000..ab2916434e
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-reports-orders-stats-test-10.2
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Fix DB version update logic in order stats tests
+
+
diff --git a/plugins/woocommerce/changelog/wooplug-5486-selectively-apply-the-fix-for-the-tax-report-accounting-for b/plugins/woocommerce/changelog/wooplug-5486-selectively-apply-the-fix-for-the-tax-report-accounting-for
new file mode 100644
index 0000000000..1dcc831f52
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-5486-selectively-apply-the-fix-for-the-tax-report-accounting-for
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Add tool to Status > Tools page for fixing the refund logic.
diff --git a/plugins/woocommerce/client/admin/client/analytics/report/revenue/config.js b/plugins/woocommerce/client/admin/client/analytics/report/revenue/config.js
index 519518a9ec..03f55df804 100644
--- a/plugins/woocommerce/client/admin/client/analytics/report/revenue/config.js
+++ b/plugins/woocommerce/client/admin/client/analytics/report/revenue/config.js
@@ -4,6 +4,11 @@
 import { __, _x } from '@wordpress/i18n';
 import { applyFilters } from '@wordpress/hooks';

+/**
+ * Internal dependencies
+ */
+import { getAdminSetting } from '~/utils/admin-settings';
+
 const REVENUE_REPORT_CHARTS_FILTER = 'woocommerce_admin_revenue_report_charts';
 const REVENUE_REPORT_FILTERS_FILTER =
 	'woocommerce_admin_revenue_report_filters';
@@ -14,6 +19,7 @@ const REVENUE_REPORT_ADVANCED_FILTERS_FILTER =
  * @typedef {import('../index.js').chart} chart
  */

+const usesNewFullRefundData = getAdminSetting( 'usesNewFullRefundData', true );
 /**
  * Revenue Report charts filter.
  *
@@ -36,10 +42,12 @@ export const charts = applyFilters( REVENUE_REPORT_CHARTS_FILTER, [
 		orderby: 'refunds',
 		type: 'currency',
 		isReverseTrend: true,
-		labelTooltipText: __(
-			'Returns include returned shipping and tax amounts.',
-			'woocommerce'
-		),
+		labelTooltipText: usesNewFullRefundData
+			? __(
+					'Returns include returned shipping and tax amounts.',
+					'woocommerce'
+			  )
+			: null,
 	},
 	{
 		key: 'coupons',
@@ -55,6 +63,12 @@ export const charts = applyFilters( REVENUE_REPORT_CHARTS_FILTER, [
 		orderby: 'net_revenue',
 		type: 'currency',
 		isReverseTrend: false,
+		labelTooltipText: usesNewFullRefundData
+			? null
+			: __(
+					'Full refunds are not deducted from tax or net sales totals',
+					'woocommerce'
+			  ),
 	},
 	{
 		key: 'taxes',
@@ -63,6 +77,12 @@ export const charts = applyFilters( REVENUE_REPORT_CHARTS_FILTER, [
 		orderby: 'taxes',
 		type: 'currency',
 		isReverseTrend: false,
+		labelTooltipText: usesNewFullRefundData
+			? null
+			: __(
+					'Full refunds are not deducted from tax or net sales totals',
+					'woocommerce'
+			  ),
 	},
 	{
 		key: 'shipping',
diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php
index 1871b4660d..9a94add1cd 100644
--- a/plugins/woocommerce/includes/wc-update-functions.php
+++ b/plugins/woocommerce/includes/wc-update-functions.php
@@ -3039,6 +3039,7 @@ function wc_update_1020_add_old_refunded_order_items_to_product_lookup_table() {
 	);

 	if ( $refunded_orders ) {
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
 		foreach ( $refunded_orders as $refunded_order ) {
 			if ( intval( $refunded_order->num_items_sold ) === 0 ) {
 				$order = wc_get_order( $refunded_order->order_id );
@@ -3051,14 +3052,6 @@ function wc_update_1020_add_old_refunded_order_items_to_product_lookup_table() {
 					$order->save_meta_data();
 				}
 			}
-
-			/**
-			 * Trigger an action to schedule the data import for old refunded order items.
-			 *
-			 * @param int $order_id The ID of the order to be synced.
-			 * @since 10.2.0
-			 */
-			do_action( 'woocommerce_schedule_import', intval( $refunded_order->order_id ) );
 		}
 	}
 }
diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php
index 30ef2e055a..86febba4c7 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php
@@ -97,7 +97,10 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
 	protected function assign_report_columns() {
 		$table_name = self::get_db_table_name();
 		// Avoid ambiguous columns in SQL query.
-		$refunds        = "ABS( SUM( CASE WHEN {$table_name}.net_total < 0 THEN {$table_name}.net_total + {$table_name}.tax_total + {$table_name}.shipping_total ELSE 0 END ) )";
+		$refunds = "ABS( SUM( CASE WHEN {$table_name}.net_total < 0 THEN {$table_name}.net_total + {$table_name}.tax_total + {$table_name}.shipping_total ELSE 0 END ) )";
+		if ( ! OrderUtil::uses_new_full_refund_data() ) {
+			$refunds = "ABS( SUM( CASE WHEN {$table_name}.net_total < 0 THEN {$table_name}.net_total ELSE 0 END ) )";
+		}
 		$gross_sale_sum = "{$table_name}.total_sales - {$table_name}.tax_total - {$table_name}.shipping_total";
 		$gross_sales    = "SUM( CASE WHEN {$table_name}.parent_id = 0 THEN {$gross_sale_sum} ELSE 0 END ) + COALESCE( SUM(discount_amount), 0 ) as gross_sales";

@@ -562,8 +565,9 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
 				$data['parent_id'] = $parent_order->get_id();
 				$data['status']    = self::normalize_order_status( $parent_order->get_status() );

-				$refund_type = $order->get_meta( '_refund_type' );
-				if ( 'full' === $refund_type ) {
+				$refund_type               = $order->get_meta( '_refund_type' );
+				$uses_new_full_refund_data = OrderUtil::uses_new_full_refund_data();
+				if ( 'full' === $refund_type && $uses_new_full_refund_data ) {
 					$data['num_items_sold'] = -1 * self::get_num_items_sold( $parent_order );
 					$data['tax_total']      = -1 * $parent_order->get_total_tax();
 					$data['net_total']      = -1 * self::get_net_total( $parent_order );
diff --git a/plugins/woocommerce/src/Admin/API/Reports/Products/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Products/DataStore.php
index 7cebe782ca..08af457ebd 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/Products/DataStore.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/Products/DataStore.php
@@ -11,6 +11,7 @@ use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore;
 use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
 use Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
 use Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
+use Automattic\WooCommerce\Utilities\OrderUtil;
 use Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache;
 use Automattic\WooCommerce\Enums\ProductType;

@@ -474,6 +475,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
 		$is_full_refund_without_line_items = false;
 		$partial_refund_product_revenue    = array();
 		$refund_type                       = $order->get_meta( '_refund_type' );
+		$uses_new_full_refund_data         = OrderUtil::uses_new_full_refund_data();

 		$parent_order = null;

@@ -482,7 +484,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
 		if (
 			'shop_order_refund' === $order->get_type() &&
 			'full' === $refund_type &&
-			empty( $order_items )
+			empty( $order_items ) &&
+			$uses_new_full_refund_data
 		) {
 			$is_full_refund_without_line_items = true;

diff --git a/plugins/woocommerce/src/Internal/Admin/Analytics.php b/plugins/woocommerce/src/Internal/Admin/Analytics.php
index eca437caf7..62b38dd5b4 100644
--- a/plugins/woocommerce/src/Internal/Admin/Analytics.php
+++ b/plugins/woocommerce/src/Internal/Admin/Analytics.php
@@ -7,6 +7,7 @@ namespace Automattic\WooCommerce\Internal\Admin;

 use Automattic\WooCommerce\Admin\API\Reports\Cache;
 use Automattic\WooCommerce\Admin\Features\Features;
+use Automattic\WooCommerce\Utilities\OrderUtil;

 /**
  * Contains backend logic for the Analytics feature.
@@ -20,6 +21,10 @@ class Analytics {
 	 * Clear cache tool identifier.
 	 */
 	const CACHE_TOOL_ID = 'clear_woocommerce_analytics_cache';
+	/**
+	 * Full refund fix data tool identifier.
+	 */
+	const FULL_REFUND_FIX_DATA_TOOL_ID = 'fix_woocommerce_analytics_full_refund_data';

 	/**
 	 * Class instance.
@@ -60,6 +65,7 @@ class Analytics {
 		add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
 		add_action( 'admin_menu', array( $this, 'register_pages' ) );
 		add_filter( 'woocommerce_debug_tools', array( $this, 'register_cache_clear_tool' ) );
+		add_filter( 'woocommerce_debug_tools', array( $this, 'register_full_refund_fix_data_tool' ) );
 	}

 	/**
@@ -169,6 +175,27 @@ class Analytics {
 		return $debug_tools;
 	}

+	/**
+	 * Register the full refund fix data tool on the WooCommerce > Status > Tools page.
+	 *
+	 * @param array $debug_tools Available debug tool registrations.
+	 * @return array Filtered debug tool registrations.
+	 */
+	public function register_full_refund_fix_data_tool( $debug_tools ) {
+		if ( OrderUtil::uses_new_full_refund_data() ) {
+			return $debug_tools;
+		}
+
+		$debug_tools[ self::FULL_REFUND_FIX_DATA_TOOL_ID ] = array(
+			'name'     => __( 'Fix analytics full refund data', 'woocommerce' ),
+			'button'   => __( 'Fix', 'woocommerce' ),
+			'desc'     => __( 'This tool will fix the full refund data used in WooCommerce Analytics and re-import all the refunded historical data.', 'woocommerce' ),
+			'callback' => array( $this, 'run_full_refund_fix_data_tool' ),
+		);
+
+		return $debug_tools;
+	}
+
 	/**
 	 * Registers report pages.
 	 */
@@ -285,4 +312,40 @@ class Analytics {

 		return __( 'Analytics cache cleared.', 'woocommerce' );
 	}
+
+	/**
+	 * "Fix" full refund data by re-importing all the refunded historical data.
+	 */
+	public function run_full_refund_fix_data_tool() {
+		global $wpdb;
+
+		Cache::invalidate();
+
+		// Get every order ID where:
+		// 1. the total sales is less than 0, and
+		// 2. is not refunded shipping fee only, and
+		// 3. is not refunded tax fee only.
+		$refunded_orders = $wpdb->get_results(
+			"SELECT order_stats.order_id
+			FROM {$wpdb->prefix}wc_order_stats AS order_stats
+			WHERE order_stats.total_sales < 0 # Refunded orders
+				AND order_stats.total_sales != order_stats.shipping_total # Exclude refunded orders that only include a shipping refund
+				AND order_stats.total_sales != order_stats.tax_total # Exclude refunded orders that only include a tax refund"
+		);
+
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+		if ( $refunded_orders ) {
+			foreach ( $refunded_orders as $refunded_order ) {
+				/**
+				 * Trigger an action to schedule the data import for old refunded order items.
+				 *
+				 * @param int $order_id The ID of the order to be synced.
+				 * @since 10.2.0
+				 */
+				do_action( 'woocommerce_schedule_import', intval( $refunded_order->order_id ) );
+			}
+		}
+
+		return __( 'Re-importing refunded orders, full refund data will be updated shortly.', 'woocommerce' );
+	}
 }
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings.php b/plugins/woocommerce/src/Internal/Admin/Settings.php
index cb9ccd6ee0..bac84f7d60 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings.php
@@ -11,6 +11,7 @@ use Automattic\WooCommerce\Admin\Features\Features;
 use Automattic\WooCommerce\Admin\PageController;
 use Automattic\WooCommerce\Admin\PluginsHelper;
 use Automattic\WooCommerce\Utilities\FeaturesUtil;
+use Automattic\WooCommerce\Utilities\OrderUtil;
 use WC_Marketplace_Suggestions;

 /**
@@ -211,6 +212,7 @@ class Settings {
 			// We may have synced orders with a now-unregistered status.
 			// E.g. an extension that added statuses is now inactive or removed.
 			$settings['unregisteredOrderStatuses'] = $this->get_unregistered_order_statuses();
+			$settings['usesNewFullRefundData']     = OrderUtil::uses_new_full_refund_data();
 		}

 		// The separator used for attributes found in Variation titles.
diff --git a/plugins/woocommerce/src/Utilities/OrderUtil.php b/plugins/woocommerce/src/Utilities/OrderUtil.php
index 08ea29737b..7dcc2fbba9 100644
--- a/plugins/woocommerce/src/Utilities/OrderUtil.php
+++ b/plugins/woocommerce/src/Utilities/OrderUtil.php
@@ -24,7 +24,7 @@ final class OrderUtil {
 	 *
 	 * @return string
 	 */
-	public static function get_order_admin_screen() : string {
+	public static function get_order_admin_screen(): string {
 		return wc_get_container()->get( COTMigrationUtil::class )->get_order_admin_screen();
 	}

@@ -34,7 +34,7 @@ final class OrderUtil {
 	 *
 	 * @return bool
 	 */
-	public static function custom_orders_table_usage_is_enabled() : bool {
+	public static function custom_orders_table_usage_is_enabled(): bool {
 		return wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled();
 	}

@@ -52,7 +52,7 @@ final class OrderUtil {
 	 *
 	 * @return bool True if the orders cache should be used, false otherwise.
 	 */
-	public static function orders_cache_usage_is_enabled() : bool {
+	public static function orders_cache_usage_is_enabled(): bool {
 		return wc_get_container()->get( OrderCacheController::class )->orders_cache_usage_is_enabled();
 	}

@@ -61,7 +61,7 @@ final class OrderUtil {
 	 *
 	 * @return bool
 	 */
-	public static function is_custom_order_tables_in_sync() : bool {
+	public static function is_custom_order_tables_in_sync(): bool {
 		return wc_get_container()->get( COTMigrationUtil::class )->is_custom_order_tables_in_sync();
 	}

@@ -98,7 +98,7 @@ final class OrderUtil {
 	 *
 	 * @return int Order or post ID.
 	 */
-	public static function get_post_or_order_id( $post_or_order_object ) : int {
+	public static function get_post_or_order_id( $post_or_order_object ): int {
 		return wc_get_container()->get( COTMigrationUtil::class )->get_post_or_order_id( $post_or_order_object );
 	}

@@ -132,7 +132,7 @@ final class OrderUtil {
 	 *
 	 * @return string Admin url for an order.
 	 */
-	public static function get_order_admin_edit_url( int $order_id ) : string {
+	public static function get_order_admin_edit_url( int $order_id ): string {
 		return wc_get_container()->get( PageController::class )->get_edit_url( $order_id );
 	}

@@ -141,7 +141,7 @@ final class OrderUtil {
 	 *
 	 * @return string Link for new order.
 	 */
-	public static function get_order_admin_new_url() : string {
+	public static function get_order_admin_new_url(): string {
 		return wc_get_container()->get( PageController::class )->get_new_page_url();
 	}

@@ -152,7 +152,7 @@ final class OrderUtil {
 	 *
 	 * @return bool
 	 */
-	public static function is_order_list_table_screen( $order_type = 'shop_order' ) : bool {
+	public static function is_order_list_table_screen( $order_type = 'shop_order' ): bool {
 		return wc_get_container()->get( PageController::class )->is_order_screen( $order_type, 'list' );
 	}

@@ -163,7 +163,7 @@ final class OrderUtil {
 	 *
 	 * @return bool
 	 */
-	public static function is_order_edit_screen( $order_type = 'shop_order' ) : bool {
+	public static function is_order_edit_screen( $order_type = 'shop_order' ): bool {
 		return wc_get_container()->get( PageController::class )->is_order_screen( $order_type, 'edit' );
 	}

@@ -174,7 +174,7 @@ final class OrderUtil {
 	 *
 	 * @return bool
 	 */
-	public static function is_new_order_screen( $order_type = 'shop_order' ) : bool {
+	public static function is_new_order_screen( $order_type = 'shop_order' ): bool {
 		return wc_get_container()->get( PageController::class )->is_order_screen( $order_type, 'new' );
 	}

@@ -257,4 +257,18 @@ final class OrderUtil {

 		return $status;
 	}
+
+	/**
+	 * Checks if the new full refund data is used.
+	 *
+	 * @return bool
+	 */
+	public static function uses_new_full_refund_data() {
+		$db_version                = get_option( 'woocommerce_db_version', null );
+		$uses_old_full_refund_data = get_option( 'woocommerce_analytics_uses_old_full_refund_data', 'no' );
+		if ( null === $db_version ) {
+			return 'no' === $uses_old_full_refund_data;
+		}
+		return version_compare( $db_version, '10.2.0', '>=' ) && 'no' === $uses_old_full_refund_data;
+	}
 }
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php
index 7fba41e269..32c55c975b 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders-stats.php
@@ -22,6 +22,10 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 	 */
 	public static function setUpBeforeClass(): void {
 		add_filter( 'woocommerce_analytics_report_should_use_cache', '__return_false' );
+
+		$db_version = strstr( WC()->version, '-', true );
+		$db_version = $db_version ? $db_version : WC()->version;
+		update_option( 'woocommerce_db_version', $db_version );
 	}

 	/**
@@ -625,6 +629,215 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		$this->assertEquals( $expected_stats, json_decode( wp_json_encode( $data_store->get_data( $args ) ), true ) );
 	}

+	/**
+	 * Test refund type filtering.
+	 */
+	public function test_populate_and_query_refunds_with_old_full_refund_data() {
+		WC_Helper_Reports::reset_stats_dbs();
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
+
+		// Populate all of the data.
+		$product = new WC_Product_Simple();
+		$product->set_name( 'Test Product' );
+		$product->set_regular_price( 25 );
+		$product->save();
+
+		$order_types = array(
+			array(
+				'status' => OrderStatus::COMPLETED,
+				'total'  => 50,
+			),
+			array(
+				'status' => OrderStatus::COMPLETED,
+				'total'  => 100,
+			),
+		);
+
+		$time = time();
+
+		foreach ( $order_types as $order_type ) {
+			$order = WC_Helper_Order::create_order( 1, $product );
+			$order->set_status( $order_type['status'] );
+			$order->set_total( $order_type['total'] );
+			$order->set_date_created( $time );
+			$order->set_date_paid( $time );
+			$order->set_shipping_total( 10 );
+			$order->set_cart_tax( 10 );
+			$order->save();
+		}
+
+		WC_Helper_Queue::run_all_pending( 'wc-admin-data' );
+
+		// Refund the order completely by changing the order status to refunded.
+		$order->set_status( OrderStatus::REFUNDED );
+		$order->save();
+
+		WC_Helper_Queue::run_all_pending( 'wc-admin-data' );
+
+		$data_store = new OrdersStatsDataStore();
+
+		$start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() );
+		$end_time   = gmdate( 'Y-m-d H:59:59', strtotime( '+1 day', $order->get_date_created()->getOffsetTimestamp() ) );
+
+		$args            = array(
+			'interval' => 'hour',
+			'after'    => $start_time,
+			'before'   => $end_time,
+		);
+		$expected_totals = array(
+			'orders_count'        => 2,
+			'num_items_sold'      => 8, // 4 per order.
+			'avg_items_per_order' => 4, // 8 / 2 orders.
+			'avg_order_value'     => 55, // 110 / 2 orders.
+			'total_sales'         => 50, // 50 + 100 - 100.
+			'gross_sales'         => 110, // 150 - 40 ( 10 + 10 + 10 + 10 ).
+			'coupons'             => 0,
+			'coupons_count'       => 0,
+			'refunds'             => 100,
+			'taxes'               => 20,
+			'shipping'            => 20,
+			'net_revenue'         => 10, // 50 + 100 - 100 - 20 - 20.
+			'total_customers'     => 1,
+			'products'            => 1,
+			'segments'            => array(),
+		);
+
+		$this->assertEquals( $expected_totals, json_decode( wp_json_encode( $data_store->get_data( $args ) ), true )['totals'] );
+
+		// Query full refunds.
+		$args            = array(
+			'interval' => 'hour',
+			'after'    => $start_time,
+			'before'   => $end_time,
+			'refunds'  => 'full',
+		);
+		$expected_totals = array(
+			'orders_count'        => 0,
+			'num_items_sold'      => 0, // bug fixed by PR #58744.
+			'avg_items_per_order' => 0,
+			'avg_order_value'     => 0,
+			'total_sales'         => -100,
+			'gross_sales'         => 0,
+			'coupons'             => 0,
+			'coupons_count'       => 0,
+			'refunds'             => 100, // bug fixed by PR #58744.
+			'taxes'               => 0,
+			'shipping'            => 0,
+			'net_revenue'         => -100,       // @todo - does this value make sense?
+			'total_customers'     => 1,
+			'products'            => 0, // bug fixed by PR #58744.
+			'segments'            => array(),
+		);
+
+		$this->assertEquals( $expected_totals, json_decode( wp_json_encode( $data_store->get_data( $args ) ), true )['totals'] );
+
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+	}
+
+	/**
+	 * Test refund type filtering.
+	 */
+	public function test_populate_and_query_refunds_with_new_full_refund_data() {
+		WC_Helper_Reports::reset_stats_dbs();
+
+		// Populate all of the data.
+		$product = new WC_Product_Simple();
+		$product->set_name( 'Test Product' );
+		$product->set_regular_price( 25 );
+		$product->save();
+
+		$order_types = array(
+			array(
+				'status' => OrderStatus::COMPLETED,
+				'total'  => 50,
+			),
+			array(
+				'status' => OrderStatus::COMPLETED,
+				'total'  => 100,
+			),
+		);
+
+		$time = time();
+
+		foreach ( $order_types as $order_type ) {
+			$order = WC_Helper_Order::create_order( 1, $product );
+			$order->set_status( $order_type['status'] );
+			$order->set_total( $order_type['total'] );
+			$order->set_date_created( $time );
+			$order->set_date_paid( $time );
+			$order->set_shipping_total( 10 );
+			$order->set_cart_tax( 10 );
+			$order->save();
+		}
+
+		WC_Helper_Queue::run_all_pending( 'wc-admin-data' );
+
+		// Refund the order completely by changing the order status to refunded.
+		$order->set_status( OrderStatus::REFUNDED );
+		$order->save();
+
+		WC_Helper_Queue::run_all_pending( 'wc-admin-data' );
+
+		$data_store = new OrdersStatsDataStore();
+
+		$start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() );
+		$end_time   = gmdate( 'Y-m-d H:59:59', strtotime( '+1 day', $order->get_date_created()->getOffsetTimestamp() ) );
+
+		$args            = array(
+			'interval' => 'hour',
+			'after'    => $start_time,
+			'before'   => $end_time,
+		);
+		$expected_totals = array(
+			'orders_count'        => 2,
+			'num_items_sold'      => 4, // 4 per order.
+			'avg_items_per_order' => 4, // 8 / 2 orders.
+			'avg_order_value'     => 55, // 110 / 2 orders.
+			'total_sales'         => 50, // 50 + 100 - 100.
+			'gross_sales'         => 110, // 150 - 40 ( 10 + 10 + 10 + 10 ).
+			'coupons'             => 0,
+			'coupons_count'       => 0,
+			'refunds'             => 100,
+			'taxes'               => 10,
+			'shipping'            => 10,
+			'net_revenue'         => 30,
+			'total_customers'     => 1,
+			'products'            => 1,
+			'segments'            => array(),
+		);
+
+		$this->assertEquals( $expected_totals, json_decode( wp_json_encode( $data_store->get_data( $args ) ), true )['totals'] );
+
+		// Query full refunds.
+		$args            = array(
+			'interval' => 'hour',
+			'after'    => $start_time,
+			'before'   => $end_time,
+			'refunds'  => 'full',
+		);
+		$expected_totals = array(
+			'orders_count'        => 0,
+			'num_items_sold'      => -4,
+			'avg_items_per_order' => 0,
+			'avg_order_value'     => 0,
+			'total_sales'         => -100,
+			'gross_sales'         => 0,
+			'coupons'             => 0,
+			'coupons_count'       => 0,
+			'refunds'             => 100,
+			'taxes'               => -10,
+			'shipping'            => -10,
+			'net_revenue'         => -80,
+			'total_customers'     => 1,
+			'products'            => 1,
+			'segments'            => array(),
+		);
+
+		$this->assertEquals( $expected_totals, json_decode( wp_json_encode( $data_store->get_data( $args ) ), true )['totals'] );
+
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+	}
+
 	/**
 	 * Test the calculation of multiple coupons on orders.
 	 */
@@ -666,7 +879,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 			foreach ( $coupons as $amount => $coupon ) {
 				if ( $amount >= $order_number ) {
 					$order->apply_coupon( $coupon );
-					$applied_coupons++;
+					++$applied_coupons;
 					$applied_amount += $amount;
 				}
 			}
@@ -3809,7 +4022,6 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 			'page_no'   => 1,
 		);
 		$this->assertEquals( $expected_stats, json_decode( wp_json_encode( $data_store->get_data( $query_args ) ), true ), 'Query args: ' . $this->return_print_r( $query_args ) . "; query: {$wpdb->last_query}" );
-
 	}

 	/**
@@ -4678,7 +4890,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		// e.g. 20:30:51 -(minus 5 hours)- 15:30:51 means intervals 15:30:51--15:59:59, 16:00-16:59, 17, 18, 19, 20:00-20:30, i.e. 6 intervals
 		// also if this run exactly at 20:00 -(minus 5 hours)- 15:00, then intervals should be 15:00-15:59, 16, 17, 18, 19, 20:00-20:00.
 		$interval_count = $hour_offset + 1;
-		for ( $i = 0; $i < $interval_count; $i ++ ) {
+		for ( $i = 0; $i < $interval_count; $i++ ) {
 			if ( 0 === $i ) {
 				$date_start = new DateTime( $current_hour_end->format( 'Y-m-d H:00:00' ) );
 				$date_end   = $current_hour_end;
@@ -4822,7 +5034,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		// Expected Intervals section construction.
 		$expected_intervals = array();
 		$interval_count     = $hour_offset + 1;
-		for ( $i = 0; $i < $interval_count; $i ++ ) {
+		for ( $i = 0; $i < $interval_count; $i++ ) {
 			if ( 0 === $i ) {
 				$date_start = new DateTime( $current_hour_end->format( 'Y-m-d H:00:00' ) );
 				$date_end   = $current_hour_end;
@@ -4977,7 +5189,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		// Expected Intervals section construction.
 		$expected_intervals = array();
 		$interval_count     = 11;
-		for ( $i = 0; $i < $interval_count; $i ++ ) {
+		for ( $i = 0; $i < $interval_count; $i++ ) {
 			if ( 0 === $i ) {
 				$date_start = new DateTime( $current_hour_end->format( 'Y-m-d H:00:00' ) );
 				$date_end   = $current_hour_end;
@@ -5467,7 +5679,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		// e.g. 20:30:51 -(minus 5 hours)- 15:30:51 means intervals 15:30:51--15:59:59, 16:00-16:59, 17, 18, 19, 20:00-20:30, i.e. 6 intervals
 		// also if this run exactly at 20:00 -(minus 5 hours)- 15:00, then intervals should be 15:00-15:59, 16, 17, 18, 19, 20:00-20:00.
 		$interval_count = $hour_offset + 1;
-		for ( $i = $interval_count - 1; $i >= 0; $i -- ) {
+		for ( $i = $interval_count - 1; $i >= 0; $i-- ) {
 			if ( 0 === $i ) {
 				$date_start = new DateTime( $current_hour_end->format( 'Y-m-d H:00:00' ) );
 				$date_end   = $current_hour_end;
@@ -5648,7 +5860,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		 */
 		$expected_intervals = array();
 		$interval_count     = $hour_offset + 1;
-		for ( $i = $interval_count - 1; $i >= 0; $i -- ) {
+		for ( $i = $interval_count - 1; $i >= 0; $i-- ) {
 			if ( 0 === $i ) {
 				$date_start = new DateTime( $current_hour_end->format( 'Y-m-d H:00:00' ) );
 				$date_end   = $current_hour_end;
@@ -5821,7 +6033,7 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		 */
 		$expected_intervals = array();
 		$interval_count     = 11;
-		for ( $i = $interval_count - 1; $i >= 0; $i -- ) {
+		for ( $i = $interval_count - 1; $i >= 0; $i-- ) {
 			if ( 0 === $i ) {
 				$date_start = new DateTime( $current_hour_end->format( 'Y-m-d H:00:00' ) );
 				$date_end   = $current_hour_end;
@@ -6178,7 +6390,6 @@ class WC_Admin_Tests_Reports_Orders_Stats extends WC_Unit_Test_Case {
 		$actual_data = json_decode( wp_json_encode( $data_store->get_data( $query_args ) ) );
 		// It's still the same customer who ordered for the first time in this hour, they just placed 2 orders.
 		$this->assertEquals( 1, $actual_data->totals->total_customers );
-
 	}

 	/**