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 );
-
}
/**