Merge pull request woocommerce/woocommerce-admin#943 from woocommerce/fix/749
Adding support for advanced filters in orders reports.
This commit is contained in:
commit
22fa3f5b05
|
@ -116,7 +116,7 @@ export const advancedFilters = {
|
||||||
} ) ),
|
} ) ),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
code: {
|
coupon: {
|
||||||
labels: {
|
labels: {
|
||||||
add: __( 'Coupon Codes', 'wc-admin' ),
|
add: __( 'Coupon Codes', 'wc-admin' ),
|
||||||
placeholder: __( 'Search coupons', 'wc-admin' ),
|
placeholder: __( 'Search coupons', 'wc-admin' ),
|
||||||
|
|
|
@ -47,6 +47,8 @@ class WC_Admin_REST_Reports_Categories_Controller extends WC_REST_Reports_Contro
|
||||||
$args['orderby'] = $request['orderby'];
|
$args['orderby'] = $request['orderby'];
|
||||||
$args['order'] = $request['order'];
|
$args['order'] = $request['order'];
|
||||||
$args['categories'] = (array) $request['categories'];
|
$args['categories'] = (array) $request['categories'];
|
||||||
|
$args['status_is'] = (array) $request['status_is'];
|
||||||
|
$args['status_is_not'] = (array) $request['status_is_not'];
|
||||||
|
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
@ -265,6 +267,26 @@ class WC_Admin_REST_Reports_Categories_Controller extends WC_REST_Reports_Contro
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
|
$params['status_is'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that have the specified order status.', 'wc-admin' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'enum' => $this->get_order_statuses(),
|
||||||
|
'type' => 'string',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$params['status_is_not'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that don\'t have the specified order status.', 'wc-admin' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'enum' => $this->get_order_statuses(),
|
||||||
|
'type' => 'string',
|
||||||
|
),
|
||||||
|
);
|
||||||
$params['categories'] = array(
|
$params['categories'] = array(
|
||||||
'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'wc-admin' ),
|
'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'wc-admin' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
|
@ -277,4 +299,19 @@ class WC_Admin_REST_Reports_Categories_Controller extends WC_REST_Reports_Contro
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get order statuses without prefixes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_order_statuses() {
|
||||||
|
$order_statuses = array();
|
||||||
|
|
||||||
|
foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
|
||||||
|
$order_statuses[] = str_replace( 'wc-', '', $status );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order_statuses;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,16 @@ class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_REST_Reports_Cont
|
||||||
$args['per_page'] = $request['per_page'];
|
$args['per_page'] = $request['per_page'];
|
||||||
$args['orderby'] = $request['orderby'];
|
$args['orderby'] = $request['orderby'];
|
||||||
$args['order'] = $request['order'];
|
$args['order'] = $request['order'];
|
||||||
|
|
||||||
|
$args['match'] = $request['match'];
|
||||||
|
$args['status_is'] = (array) $request['status_is'];
|
||||||
|
$args['status_is_not'] = (array) $request['status_is_not'];
|
||||||
|
$args['product_includes'] = (array) $request['product_includes'];
|
||||||
|
$args['product_excludes'] = (array) $request['product_excludes'];
|
||||||
|
$args['coupon_includes'] = (array) $request['coupon_includes'];
|
||||||
|
$args['coupon_excludes'] = (array) $request['coupon_excludes'];
|
||||||
|
$args['customer'] = $request['customer'];
|
||||||
$args['categories'] = (array) $request['categories'];
|
$args['categories'] = (array) $request['categories'];
|
||||||
$args['coupons'] = (array) $request['coupons'];
|
|
||||||
$args['products'] = (array) $request['products'];
|
|
||||||
$args['order_status'] = (array) $request['order_status'];
|
|
||||||
|
|
||||||
return $args;
|
return $args;
|
||||||
}
|
}
|
||||||
|
@ -298,27 +304,18 @@ class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_REST_Reports_Cont
|
||||||
),
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['categories'] = array(
|
$params['match'] = array(
|
||||||
'description' => __( 'Limit result set to all items that have the specified term assigned in the categories taxonomy.', 'wc-admin' ),
|
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'wc-admin' ),
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
'default' => 'all',
|
||||||
|
'enum' => array(
|
||||||
|
'all',
|
||||||
|
'any',
|
||||||
|
),
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
);
|
);
|
||||||
$params['coupons'] = array(
|
$params['status_is'] = array(
|
||||||
'description' => __( 'Limit result set to all items that have the specified coupon assigned.', 'wc-admin' ),
|
'description' => __( 'Limit result set to items that have the specified order status.', 'wc-admin' ),
|
||||||
'type' => 'string',
|
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
$params['products'] = array(
|
|
||||||
'description' => __( 'Limit result set to all items that have the specified product assigned.', 'wc-admin' ),
|
|
||||||
'type' => 'string',
|
|
||||||
'sanitize_callback' => 'wp_parse_id_list',
|
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
|
||||||
);
|
|
||||||
$params['order_status'] = array(
|
|
||||||
'default' => array( 'completed', 'processing', 'on-hold' ),
|
|
||||||
'description' => __( 'Limit result set to orders assigned one or more statuses', 'wc-admin' ),
|
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'sanitize_callback' => 'wp_parse_slug_list',
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
'validate_callback' => 'rest_validate_request_arg',
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
@ -327,6 +324,62 @@ class WC_Admin_REST_Reports_Orders_Stats_Controller extends WC_REST_Reports_Cont
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
$params['status_is_not'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that don\'t have the specified order status.', 'wc-admin' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'sanitize_callback' => 'wp_parse_slug_list',
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
'items' => array(
|
||||||
|
'enum' => $this->get_order_statuses(),
|
||||||
|
'type' => 'string',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$params['product_includes'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that have the specified product(s) assigned.', 'wc-admin' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
'default' => array(),
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
|
||||||
|
);
|
||||||
|
$params['product_excludes'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that don\'t have the specified product(s) assigned.', 'wc-admin' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
'default' => array(),
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
);
|
||||||
|
$params['coupon_includes'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that have the specified coupon(s) assigned.', 'wc-admin' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
'default' => array(),
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
);
|
||||||
|
$params['coupon_excludes'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that don\'t have the specified coupon(s) assigned.', 'wc-admin' ),
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => array(
|
||||||
|
'type' => 'integer',
|
||||||
|
),
|
||||||
|
'default' => array(),
|
||||||
|
'sanitize_callback' => 'wp_parse_id_list',
|
||||||
|
);
|
||||||
|
$params['customer'] = array(
|
||||||
|
'description' => __( 'Limit result set to items that don\'t have the specified coupon(s) assigned.', 'wc-admin' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'enum' => array(
|
||||||
|
'new',
|
||||||
|
'returning',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,15 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/products';
|
protected $rest_base = 'reports/products';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping between external parameter name and name used in query class.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $param_mapping = array(
|
||||||
|
'products' => 'product_includes',
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get items.
|
* Get items.
|
||||||
*
|
*
|
||||||
|
@ -43,9 +52,13 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
|
||||||
$registered = array_keys( $this->get_collection_params() );
|
$registered = array_keys( $this->get_collection_params() );
|
||||||
foreach ( $registered as $param_name ) {
|
foreach ( $registered as $param_name ) {
|
||||||
if ( isset( $request[ $param_name ] ) ) {
|
if ( isset( $request[ $param_name ] ) ) {
|
||||||
|
if ( isset( $this->param_mapping[ $param_name ] ) ) {
|
||||||
|
$args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
|
||||||
|
} else {
|
||||||
$args[ $param_name ] = $request[ $param_name ];
|
$args[ $param_name ] = $request[ $param_name ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$reports = new WC_Admin_Reports_Products_Query( $args );
|
$reports = new WC_Admin_Reports_Products_Query( $args );
|
||||||
$products_data = $reports->get_data();
|
$products_data = $reports->get_data();
|
||||||
|
@ -234,6 +247,16 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
$params['match'] = array(
|
||||||
|
'description' => __( 'Indicates whether all the conditions should be true for the resulting set, or if any one of them is sufficient. Match affects the following parameters: status_is, status_is_not, product_includes, product_excludes, coupon_includes, coupon_excludes, customer, categories', 'wc-admin' ),
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'all',
|
||||||
|
'enum' => array(
|
||||||
|
'all',
|
||||||
|
'any',
|
||||||
|
),
|
||||||
|
'validate_callback' => 'rest_validate_request_arg',
|
||||||
|
);
|
||||||
$params['products'] = array(
|
$params['products'] = array(
|
||||||
'description' => __( 'Limit result to items with specified product ids.', 'wc-admin' ),
|
'description' => __( 'Limit result to items with specified product ids.', 'wc-admin' ),
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
|
@ -242,6 +265,7 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
|
||||||
'items' => array(
|
'items' => array(
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
),
|
),
|
||||||
|
|
||||||
);
|
);
|
||||||
$params['extended_info'] = array(
|
$params['extended_info'] = array(
|
||||||
'description' => __( 'Add additional piece of info about each product to the report.', 'wc-admin' ),
|
'description' => __( 'Add additional piece of info about each product to the report.', 'wc-admin' ),
|
||||||
|
|
|
@ -31,6 +31,15 @@ class WC_Admin_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Co
|
||||||
*/
|
*/
|
||||||
protected $rest_base = 'reports/products/stats';
|
protected $rest_base = 'reports/products/stats';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping between external parameter name and name used in query class.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $param_mapping = array(
|
||||||
|
'products' => 'product_includes',
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
|
@ -54,9 +63,14 @@ class WC_Admin_REST_Reports_Products_Stats_Controller extends WC_REST_Reports_Co
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ( array_keys( $this->get_collection_params() ) as $arg ) {
|
$registered = array_keys( $this->get_collection_params() );
|
||||||
if ( isset( $request[ $arg ] ) ) {
|
foreach ( $registered as $param_name ) {
|
||||||
$query_args[ $arg ] = $request[ $arg ];
|
if ( isset( $request[ $param_name ] ) ) {
|
||||||
|
if ( isset( $this->param_mapping[ $param_name ] ) ) {
|
||||||
|
$query_args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
|
||||||
|
} else {
|
||||||
|
$query_args[ $param_name ] = $request[ $param_name ];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,8 +173,23 @@ class WC_Admin_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Con
|
||||||
'context' => array( 'view', 'edit' ),
|
'context' => array( 'view', 'edit' ),
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
),
|
),
|
||||||
|
'num_items_sold' => array(
|
||||||
|
'description' => __( 'Amount of orders', 'wc-admin' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
|
'products' => array(
|
||||||
|
'description' => __( 'Amount of orders', 'wc-admin' ),
|
||||||
|
'type' => 'integer',
|
||||||
|
'context' => array( 'view', 'edit' ),
|
||||||
|
'readonly' => true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$intervals = $totals;
|
||||||
|
unset( $intervals['products'] );
|
||||||
|
|
||||||
$schema = array(
|
$schema = array(
|
||||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||||
'title' => 'report_revenue_stats',
|
'title' => 'report_revenue_stats',
|
||||||
|
@ -231,7 +246,7 @@ class WC_Admin_REST_Reports_Revenue_Stats_Controller extends WC_REST_Reports_Con
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'context' => array( 'view', 'edit' ),
|
'context' => array( 'view', 'edit' ),
|
||||||
'readonly' => true,
|
'readonly' => true,
|
||||||
'properties' => $totals,
|
'properties' => $intervals,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -350,7 +350,7 @@ class WC_Admin_Api_Init {
|
||||||
coupon_id BIGINT UNSIGNED NOT NULL,
|
coupon_id BIGINT UNSIGNED NOT NULL,
|
||||||
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
date_created timestamp DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||||
coupon_gross_discount double DEFAULT 0 NOT NULL,
|
coupon_gross_discount double DEFAULT 0 NOT NULL,
|
||||||
KEY order_id (order_id),
|
PRIMARY KEY (order_id, coupon_id),
|
||||||
KEY coupon_id (coupon_id),
|
KEY coupon_id (coupon_id),
|
||||||
KEY date_created (date_created)
|
KEY date_created (date_created)
|
||||||
) $collate;
|
) $collate;
|
||||||
|
|
|
@ -67,25 +67,22 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store
|
||||||
*/
|
*/
|
||||||
protected function get_sql_query_params( $query_args ) {
|
protected function get_sql_query_params( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
|
||||||
$sql_query_params = $this->get_time_period_sql_params( $query_args );
|
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||||
// Limit is left out here so that the grouping in code by PHP can be applied correctly.
|
// Limit is left out here so that the grouping in code by PHP can be applied correctly.
|
||||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||||
|
|
||||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
// TODO: only products in the category C or orders with products from category C (and, possibly others?).
|
||||||
|
$included_products = $this->get_included_products( $query_args );
|
||||||
$allowed_products = $this->get_allowed_products( $query_args );
|
if ( $included_products ) {
|
||||||
|
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||||
if ( count( $allowed_products ) > 0 ) {
|
|
||||||
$allowed_products_str = implode( ',', $allowed_products );
|
|
||||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||||
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
if ( $order_status_filter ) {
|
||||||
|
|
||||||
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
$sql_query_params['where_clause'] .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sql_query_params;
|
return $sql_query_params;
|
||||||
|
@ -204,6 +201,7 @@ class WC_Admin_Reports_Categories_Data_Store extends WC_Admin_Reports_Data_Store
|
||||||
{$sql_query_params['from_clause']}
|
{$sql_query_params['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$sql_query_params['where_time_clause']}
|
||||||
{$sql_query_params['where_clause']}
|
{$sql_query_params['where_clause']}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
product_id
|
product_id
|
||||||
|
|
|
@ -121,6 +121,8 @@ class WC_Admin_Reports_Data_Store {
|
||||||
foreach ( $totals_arr as $key => $val ) {
|
foreach ( $totals_arr as $key => $val ) {
|
||||||
$totals_arr[ $key ] = 0;
|
$totals_arr[ $key ] = 0;
|
||||||
}
|
}
|
||||||
|
// TODO: should 'products' be in intervals?
|
||||||
|
unset( $totals_arr['products'] );
|
||||||
while ( $datetime <= $end_datetime ) {
|
while ( $datetime <= $end_datetime ) {
|
||||||
$next_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval );
|
$next_start = WC_Admin_Reports_Interval::iterate( $datetime, $time_interval );
|
||||||
$time_id = WC_Admin_Reports_Interval::time_interval_id( $time_interval, $datetime );
|
$time_id = WC_Admin_Reports_Interval::time_interval_id( $time_interval, $datetime );
|
||||||
|
@ -260,9 +262,9 @@ class WC_Admin_Reports_Data_Store {
|
||||||
}
|
}
|
||||||
$query_args['adj_after'] = $new_start_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
$query_args['adj_after'] = $new_start_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
||||||
$query_args['adj_before'] = $new_end_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
$query_args['adj_before'] = $new_end_date->format( WC_Admin_Reports_Interval::$iso_datetime_format );
|
||||||
$intervals_query['where_clause'] = '';
|
$intervals_query['where_time_clause'] = '';
|
||||||
$intervals_query['where_clause'] .= " AND date_created <= '{$query_args['adj_before']}'";
|
$intervals_query['where_time_clause'] .= " AND date_created <= '{$query_args['adj_before']}'";
|
||||||
$intervals_query['where_clause'] .= " AND date_created >= '{$query_args['adj_after']}'";
|
$intervals_query['where_time_clause'] .= " AND date_created >= '{$query_args['adj_after']}'";
|
||||||
$intervals_query['limit'] = 'LIMIT 0,' . $intervals_query['per_page'];
|
$intervals_query['limit'] = 'LIMIT 0,' . $intervals_query['per_page'];
|
||||||
} else {
|
} else {
|
||||||
if ( 'asc' === $query_args['order'] ) {
|
if ( 'asc' === $query_args['order'] ) {
|
||||||
|
@ -425,28 +427,30 @@ class WC_Admin_Reports_Data_Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills WHERE clause of SQL request for 'Totals' section of data response based on user supplied parameters.
|
* Fills WHERE clause of SQL request with date-related constraints.
|
||||||
*
|
*
|
||||||
* @param array $query_args Parameters supplied by the user.
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_time_period_sql_params( $query_args ) {
|
protected function get_time_period_sql_params( $query_args, $table_name ) {
|
||||||
$sql_query = array(
|
$sql_query = array(
|
||||||
'from_clause' => '',
|
'from_clause' => '',
|
||||||
|
'where_time_clause' => '',
|
||||||
'where_clause' => '',
|
'where_clause' => '',
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) {
|
if ( isset( $query_args['before'] ) && '' !== $query_args['before'] ) {
|
||||||
$datetime = new DateTime( $query_args['before'] );
|
$datetime = new DateTime( $query_args['before'] );
|
||||||
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||||
$sql_query['where_clause'] .= " AND date_created <= '$datetime_str'";
|
$sql_query['where_time_clause'] .= " AND {$table_name}.date_created <= '$datetime_str'";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) {
|
if ( isset( $query_args['after'] ) && '' !== $query_args['after'] ) {
|
||||||
$datetime = new DateTime( $query_args['after'] );
|
$datetime = new DateTime( $query_args['after'] );
|
||||||
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
$datetime_str = $datetime->format( WC_Admin_Reports_Interval::$sql_datetime_format );
|
||||||
$sql_query['where_clause'] .= " AND date_created >= '$datetime_str'";
|
$sql_query['where_time_clause'] .= " AND {$table_name}.date_created >= '$datetime_str'";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sql_query;
|
return $sql_query;
|
||||||
|
@ -498,15 +502,17 @@ class WC_Admin_Reports_Data_Store {
|
||||||
* Fills FROM and WHERE clauses of SQL request for 'Intervals' section of data response based on user supplied parameters.
|
* Fills FROM and WHERE clauses of SQL request for 'Intervals' section of data response based on user supplied parameters.
|
||||||
*
|
*
|
||||||
* @param array $query_args Parameters supplied by the user.
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @param string $table_name Name of the db table relevant for the date constraint.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function get_intervals_sql_params( $query_args ) {
|
protected function get_intervals_sql_params( $query_args, $table_name ) {
|
||||||
$intervals_query = array(
|
$intervals_query = array(
|
||||||
'from_clause' => '',
|
'from_clause' => '',
|
||||||
|
'where_time_clause' => '',
|
||||||
'where_clause' => '',
|
'where_clause' => '',
|
||||||
);
|
);
|
||||||
|
|
||||||
$intervals_query = array_merge( $intervals_query, $this->get_time_period_sql_params( $query_args ) );
|
$intervals_query = array_merge( $intervals_query, $this->get_time_period_sql_params( $query_args, $table_name ) );
|
||||||
|
|
||||||
if ( isset( $query_args['interval'] ) && '' !== $query_args['interval'] ) {
|
if ( isset( $query_args['interval'] ) && '' !== $query_args['interval'] ) {
|
||||||
$interval = $query_args['interval'];
|
$interval = $query_args['interval'];
|
||||||
|
@ -548,26 +554,151 @@ class WC_Admin_Reports_Data_Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns ids of allowed products, based on query arguments from the user.
|
* Returns comma separated ids of allowed products, based on query arguments from the user.
|
||||||
*
|
*
|
||||||
* @param array $query_args Parameters supplied by the user.
|
* @param array $query_args Parameters supplied by the user.
|
||||||
* @return array
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function get_allowed_products( $query_args ) {
|
protected function get_included_products( $query_args ) {
|
||||||
$allowed_products = array();
|
$included_products = array();
|
||||||
|
$operator = $this->get_match_operator( $query_args );
|
||||||
|
|
||||||
if ( isset( $query_args['categories'] ) && is_array( $query_args['categories'] ) && count( $query_args['categories'] ) > 0 ) {
|
if ( isset( $query_args['categories'] ) && is_array( $query_args['categories'] ) && count( $query_args['categories'] ) > 0 ) {
|
||||||
$allowed_products = $this->get_products_by_cat_ids( $query_args['categories'] );
|
$included_products = $this->get_products_by_cat_ids( $query_args['categories'] );
|
||||||
$allowed_products = wc_list_pluck( $allowed_products, 'get_id' );
|
$included_products = wc_list_pluck( $included_products, 'get_id' );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset( $query_args['products'] ) && is_array( $query_args['products'] ) && count( $query_args['products'] ) > 0 ) {
|
if ( isset( $query_args['product_includes'] ) && is_array( $query_args['product_includes'] ) && count( $query_args['product_includes'] ) > 0 ) {
|
||||||
if ( count( $allowed_products ) > 0 ) {
|
if ( count( $included_products ) > 0 ) {
|
||||||
$allowed_products = array_intersect( $allowed_products, $query_args['products'] );
|
if ( 'AND' === $operator ) {
|
||||||
|
$included_products = array_intersect( $included_products, $query_args['product_includes'] );
|
||||||
|
} elseif ( 'OR' === $operator ) {
|
||||||
|
// Union of products from selected categories and manually included products.
|
||||||
|
$included_products = array_unique( array_merge( $included_products, $query_args['product_includes'] ) );
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$allowed_products = $query_args['products'];
|
$included_products = $query_args['product_includes'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $allowed_products;
|
|
||||||
|
$included_products_str = implode( ',', $included_products );
|
||||||
|
return $included_products_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns comma separated ids of excluded products, based on query arguments from the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_excluded_products( $query_args ) {
|
||||||
|
$excluded_products_str = '';
|
||||||
|
|
||||||
|
if ( isset( $query_args['product_excludes'] ) && is_array( $query_args['product_excludes'] ) && count( $query_args['product_excludes'] ) > 0 ) {
|
||||||
|
$excluded_products_str = implode( ',', $query_args['product_excludes'] );
|
||||||
|
}
|
||||||
|
return $excluded_products_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns comma separated ids of included coupons, based on query arguments from the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_included_coupons( $query_args ) {
|
||||||
|
$included_coupons_str = '';
|
||||||
|
|
||||||
|
if ( isset( $query_args['coupon_includes'] ) && is_array( $query_args['coupon_includes'] ) && count( $query_args['coupon_includes'] ) > 0 ) {
|
||||||
|
$included_coupons_str = implode( ',', $query_args['coupon_includes'] );
|
||||||
|
}
|
||||||
|
return $included_coupons_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns comma separated ids of excluded coupons, based on query arguments from the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_excluded_coupons( $query_args ) {
|
||||||
|
$excluded_coupons_str = '';
|
||||||
|
|
||||||
|
if ( isset( $query_args['coupon_excludes'] ) && is_array( $query_args['coupon_excludes'] ) && count( $query_args['coupon_excludes'] ) > 0 ) {
|
||||||
|
$excluded_coupons_str = implode( ',', $query_args['coupon_excludes'] );
|
||||||
|
}
|
||||||
|
return $excluded_coupons_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns order status subquery to be used in WHERE SQL query, based on query arguments from the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @param string $operator AND or OR, based on match query argument.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_status_subquery( $query_args, $operator = 'AND' ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$subqueries = array();
|
||||||
|
if ( isset( $query_args['status_is'] ) && is_array( $query_args['status_is'] ) && count( $query_args['status_is'] ) > 0 ) {
|
||||||
|
$allowed_statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['status_is'] );
|
||||||
|
if ( $allowed_statuses ) {
|
||||||
|
$subqueries[] = "{$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $allowed_statuses ) . "' )";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $query_args['status_is_not'] ) && is_array( $query_args['status_is_not'] ) && count( $query_args['status_is_not'] ) > 0 ) {
|
||||||
|
$forbidden_statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['status_is_not'] );
|
||||||
|
if ( $forbidden_statuses ) {
|
||||||
|
$subqueries[] = "{$wpdb->prefix}posts.post_status NOT IN ( '" . implode( "','", $forbidden_statuses ) . "' )";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode( " $operator ", $subqueries );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns customer subquery to be used in WHERE SQL query, based on query arguments from the user.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_customer_subquery( $query_args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$customer_filter = '';
|
||||||
|
if ( isset( $query_args['customer'] ) ) {
|
||||||
|
if ( 'new' === strtolower( $query_args['customer'] ) ) {
|
||||||
|
$customer_filter = " {$wpdb->prefix}wc_order_stats.returning_customer = 0";
|
||||||
|
} elseif ( 'returning' === strtolower( $query_args['customer'] ) ) {
|
||||||
|
$customer_filter = " {$wpdb->prefix}wc_order_stats.returning_customer = 1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $customer_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns logic operator for WHERE subclause based on 'match' query argument.
|
||||||
|
*
|
||||||
|
* @param array $query_args Parameters supplied by the user.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_match_operator( $query_args ) {
|
||||||
|
$operator = 'AND';
|
||||||
|
|
||||||
|
if ( ! isset( $query_args['match'] ) ) {
|
||||||
|
return $operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'all' === strtolower( $query_args['match'] ) ) {
|
||||||
|
$operator = 'AND';
|
||||||
|
} elseif ( 'any' === strtolower( $query_args['match'] ) ) {
|
||||||
|
$operator = 'OR';
|
||||||
|
}
|
||||||
|
return $operator;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
'avg_order_value' => 'floatval',
|
'avg_order_value' => 'floatval',
|
||||||
'num_returning_customers' => 'intval',
|
'num_returning_customers' => 'intval',
|
||||||
'num_new_customers' => 'intval',
|
'num_new_customers' => 'intval',
|
||||||
|
'products' => 'intval',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,6 +92,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
public static function init() {
|
||||||
add_action( 'save_post', array( __CLASS__, 'sync_order' ) );
|
add_action( 'save_post', array( __CLASS__, 'sync_order' ) );
|
||||||
|
// TODO: this is required as order update skips save_post.
|
||||||
|
add_action( 'clean_post_cache', array( __CLASS__, 'sync_order' ) );
|
||||||
|
|
||||||
if ( ! self::$background_process ) {
|
if ( ! self::$background_process ) {
|
||||||
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
|
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
|
||||||
|
@ -173,51 +176,85 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
// TODO: performance of all of this?
|
// TODO: performance of all of this?
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$where_clause = '';
|
|
||||||
$from_clause = '';
|
$from_clause = '';
|
||||||
|
|
||||||
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
$orders_stats_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
$allowed_products = $this->get_allowed_products( $query_args );
|
$operator = $this->get_match_operator( $query_args );
|
||||||
|
|
||||||
if ( count( $allowed_products ) > 0 ) {
|
$where_filters = array();
|
||||||
$allowed_products_str = implode( ',', $allowed_products );
|
|
||||||
|
|
||||||
$where_clause .= " AND {$orders_stats_table}.order_id IN (
|
// TODO: maybe move the sql inside the get_included/excluded functions?
|
||||||
|
// Products filters.
|
||||||
|
$included_products = $this->get_included_products( $query_args );
|
||||||
|
$excluded_products = $this->get_excluded_products( $query_args );
|
||||||
|
if ( $included_products ) {
|
||||||
|
$where_filters[] = " {$orders_stats_table}.order_id IN (
|
||||||
SELECT
|
SELECT
|
||||||
DISTINCT {$wpdb->prefix}wc_order_product_lookup.order_id
|
DISTINCT {$wpdb->prefix}wc_order_product_lookup.order_id
|
||||||
FROM
|
FROM
|
||||||
{$wpdb->prefix}wc_order_product_lookup
|
{$wpdb->prefix}wc_order_product_lookup
|
||||||
WHERE
|
WHERE
|
||||||
{$wpdb->prefix}wc_order_product_lookup.product_id IN ({$allowed_products_str})
|
{$wpdb->prefix}wc_order_product_lookup.product_id IN ({$included_products})
|
||||||
)";
|
)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( is_array( $query_args['coupons'] ) && count( $query_args['coupons'] ) > 0 ) {
|
if ( $excluded_products ) {
|
||||||
$allowed_coupons_str = implode( ', ', $query_args['coupons'] );
|
$where_filters[] = " {$orders_stats_table}.order_id NOT IN (
|
||||||
|
SELECT
|
||||||
|
DISTINCT {$wpdb->prefix}wc_order_product_lookup.order_id
|
||||||
|
FROM
|
||||||
|
{$wpdb->prefix}wc_order_product_lookup
|
||||||
|
WHERE
|
||||||
|
{$wpdb->prefix}wc_order_product_lookup.product_id IN ({$excluded_products})
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
$where_clause .= " AND {$orders_stats_table}.order_id IN (
|
// Coupons filters.
|
||||||
|
$included_coupons = $this->get_included_coupons( $query_args );
|
||||||
|
$excluded_coupons = $this->get_excluded_coupons( $query_args );
|
||||||
|
if ( $included_coupons ) {
|
||||||
|
$where_filters[] = " {$orders_stats_table}.order_id IN (
|
||||||
SELECT
|
SELECT
|
||||||
DISTINCT {$wpdb->prefix}wc_order_coupon_lookup.order_id
|
DISTINCT {$wpdb->prefix}wc_order_coupon_lookup.order_id
|
||||||
FROM
|
FROM
|
||||||
{$wpdb->prefix}wc_order_coupon_lookup
|
{$wpdb->prefix}wc_order_coupon_lookup
|
||||||
WHERE
|
WHERE
|
||||||
{$wpdb->prefix}wc_order_coupon_lookup.coupon_id IN ({$allowed_coupons_str})
|
{$wpdb->prefix}wc_order_coupon_lookup.coupon_id IN ({$included_coupons})
|
||||||
)";
|
)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
if ( $excluded_coupons ) {
|
||||||
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
$where_filters[] = " {$orders_stats_table}.order_id NOT IN (
|
||||||
|
SELECT
|
||||||
$from_clause .= " JOIN {$wpdb->prefix}posts ON {$orders_stats_table}.order_id = {$wpdb->prefix}posts.ID";
|
DISTINCT {$wpdb->prefix}wc_order_coupon_lookup.order_id
|
||||||
$where_clause .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
FROM
|
||||||
|
{$wpdb->prefix}wc_order_coupon_lookup
|
||||||
|
WHERE
|
||||||
|
{$wpdb->prefix}wc_order_coupon_lookup.coupon_id IN ({$excluded_coupons})
|
||||||
|
)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move order status to wc_order_stats so that JOIN is not necessary.
|
||||||
|
$order_status_filter = $this->get_status_subquery( $query_args, $operator );
|
||||||
|
if ( $order_status_filter ) {
|
||||||
|
$from_clause .= " JOIN {$wpdb->prefix}posts ON {$orders_stats_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
|
$where_filters[] = $order_status_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer_filter = $this->get_customer_subquery( $query_args );
|
||||||
|
if ( $customer_filter ) {
|
||||||
|
$where_filters[] = $customer_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_subclause = implode( " $operator ", $where_filters );
|
||||||
|
|
||||||
// To avoid requesting the subqueries twice, the result is applied to all queries passed to the method.
|
// To avoid requesting the subqueries twice, the result is applied to all queries passed to the method.
|
||||||
$totals_query['where_clause'] .= $where_clause;
|
if ( $where_subclause ) {
|
||||||
|
$totals_query['where_clause'] .= " AND ( $where_subclause )";
|
||||||
$totals_query['from_clause'] .= $from_clause;
|
$totals_query['from_clause'] .= $from_clause;
|
||||||
$intervals_query['where_clause'] .= $where_clause;
|
$intervals_query['where_clause'] .= " AND ( $where_subclause )";
|
||||||
$intervals_query['from_clause'] .= $from_clause;
|
$intervals_query['from_clause'] .= $from_clause;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the report data based on parameters supplied by the user.
|
* Returns the report data based on parameters supplied by the user.
|
||||||
|
@ -243,10 +280,16 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||||
'interval' => 'week',
|
'interval' => 'week',
|
||||||
'fields' => '*',
|
'fields' => '*',
|
||||||
|
|
||||||
|
'match' => 'all',
|
||||||
|
'status_is' => array(),
|
||||||
|
'status_is_not' => array(),
|
||||||
|
'product_includes' => array(),
|
||||||
|
'product_excludes' => array(),
|
||||||
|
'coupon_includes' => array(),
|
||||||
|
'coupon_excludes' => array(),
|
||||||
|
'customer' => '',
|
||||||
'categories' => array(),
|
'categories' => array(),
|
||||||
'coupons' => array(),
|
|
||||||
'order_status' => parent::get_report_order_statuses(),
|
|
||||||
'products' => array(),
|
|
||||||
);
|
);
|
||||||
$query_args = wp_parse_args( $query_args, $defaults );
|
$query_args = wp_parse_args( $query_args, $defaults );
|
||||||
|
|
||||||
|
@ -263,8 +306,8 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
);
|
);
|
||||||
|
|
||||||
$selections = $this->selected_columns( $query_args );
|
$selections = $this->selected_columns( $query_args );
|
||||||
$totals_query = $this->get_time_period_sql_params( $query_args );
|
$totals_query = $this->get_time_period_sql_params( $query_args, $table_name );
|
||||||
$intervals_query = $this->get_intervals_sql_params( $query_args );
|
$intervals_query = $this->get_intervals_sql_params( $query_args, $table_name );
|
||||||
|
|
||||||
// Additional filtering for Orders report.
|
// Additional filtering for Orders report.
|
||||||
$this->orders_stats_sql_filter( $query_args, $totals_query, $intervals_query );
|
$this->orders_stats_sql_filter( $query_args, $totals_query, $intervals_query );
|
||||||
|
@ -277,6 +320,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
{$totals_query['from_clause']}
|
{$totals_query['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$totals_query['where_time_clause']}
|
||||||
{$totals_query['where_clause']}",
|
{$totals_query['where_clause']}",
|
||||||
ARRAY_A
|
ARRAY_A
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
@ -284,13 +328,9 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
return new WP_Error( 'woocommerce_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'wc-admin' ) );
|
return new WP_Error( 'woocommerce_reports_revenue_result_failed', __( 'Sorry, fetching revenue data failed.', 'wc-admin' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$unique_products = $this->get_unique_products( $totals_query['where_clause'] );
|
$unique_products = $this->get_unique_product_count( $totals_query['from_clause'], $totals_query['where_time_clause'], $totals_query['where_clause'] );
|
||||||
$totals[0]['products'] = $unique_products;
|
$totals[0]['products'] = $unique_products;
|
||||||
|
|
||||||
// Specification says these are not included in totals.
|
|
||||||
unset( $totals[0]['date_start'] );
|
|
||||||
unset( $totals[0]['date_end'] );
|
|
||||||
|
|
||||||
$totals = (object) $this->cast_numbers( $totals[0] );
|
$totals = (object) $this->cast_numbers( $totals[0] );
|
||||||
|
|
||||||
$db_intervals = $wpdb->get_col(
|
$db_intervals = $wpdb->get_col(
|
||||||
|
@ -301,6 +341,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
{$intervals_query['from_clause']}
|
{$intervals_query['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$intervals_query['where_time_clause']}
|
||||||
{$intervals_query['where_clause']}
|
{$intervals_query['where_clause']}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
time_interval"
|
time_interval"
|
||||||
|
@ -330,6 +371,7 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
{$intervals_query['from_clause']}
|
{$intervals_query['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$intervals_query['where_time_clause']}
|
||||||
{$intervals_query['where_clause']}
|
{$intervals_query['where_clause']}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
time_interval
|
time_interval
|
||||||
|
@ -369,19 +411,25 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp
|
||||||
/**
|
/**
|
||||||
* Get unique products based on user time query
|
* Get unique products based on user time query
|
||||||
*
|
*
|
||||||
|
* @param string $from_clause From clause with date query.
|
||||||
|
* @param string $where_time_clause Where clause with date query.
|
||||||
* @param string $where_clause Where clause with date query.
|
* @param string $where_clause Where clause with date query.
|
||||||
* @return integer Unique product count.
|
* @return integer Unique product count.
|
||||||
*/
|
*/
|
||||||
public function get_unique_products( $where_clause ) {
|
public function get_unique_product_count( $from_clause, $where_time_clause, $where_clause ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
|
$table_name = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
|
||||||
return $wpdb->get_var(
|
return $wpdb->get_var(
|
||||||
"SELECT
|
"SELECT
|
||||||
COUNT( DISTINCT {$wpdb->prefix}wc_order_product_lookup.product_id )
|
COUNT( DISTINCT {$wpdb->prefix}wc_order_product_lookup.product_id )
|
||||||
FROM
|
FROM
|
||||||
{$wpdb->prefix}wc_order_product_lookup JOIN {$wpdb->prefix}posts ON {$wpdb->prefix}wc_order_product_lookup.order_id = {$wpdb->prefix}posts.ID
|
{$wpdb->prefix}wc_order_product_lookup JOIN {$table_name} ON {$wpdb->prefix}wc_order_product_lookup.order_id = {$table_name}.order_id
|
||||||
|
{$from_clause}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$where_time_clause}
|
||||||
{$where_clause}"
|
{$where_clause}"
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,24 +98,21 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
|
||||||
*/
|
*/
|
||||||
protected function get_sql_query_params( $query_args ) {
|
protected function get_sql_query_params( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
|
||||||
$sql_query_params = $this->get_time_period_sql_params( $query_args );
|
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||||
|
|
||||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
$included_products = $this->get_included_products( $query_args );
|
||||||
$allowed_products = $this->get_allowed_products( $query_args );
|
if ( $included_products ) {
|
||||||
|
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||||
if ( count( $allowed_products ) > 0 ) {
|
|
||||||
$allowed_products_str = implode( ',', $allowed_products );
|
|
||||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||||
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
if ( $order_status_filter ) {
|
||||||
|
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
$sql_query_params['from_clause'] .= " JOIN {$wpdb->prefix}posts AS _orders ON {$order_product_lookup_table}.order_id = _orders.ID";
|
$sql_query_params['where_clause'] .= " AND ( {$order_status_filter} )";
|
||||||
$sql_query_params['where_clause'] .= " AND _orders.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $sql_query_params;
|
return $sql_query_params;
|
||||||
|
@ -186,7 +183,7 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
|
||||||
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
'after' => date( WC_Admin_Reports_Interval::$iso_datetime_format, $week_back ),
|
||||||
'fields' => '*',
|
'fields' => '*',
|
||||||
'categories' => array(),
|
'categories' => array(),
|
||||||
'products' => array(),
|
'product_includes' => array(),
|
||||||
'extended_info' => false,
|
'extended_info' => false,
|
||||||
// This is not a parameter for products reports per se, but we want to only take into account selected order types.
|
// This is not a parameter for products reports per se, but we want to only take into account selected order types.
|
||||||
'order_status' => parent::get_report_order_statuses(),
|
'order_status' => parent::get_report_order_statuses(),
|
||||||
|
@ -217,6 +214,7 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
|
||||||
{$sql_query_params['from_clause']}
|
{$sql_query_params['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$sql_query_params['where_time_clause']}
|
||||||
{$sql_query_params['where_clause']}
|
{$sql_query_params['where_clause']}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
product_id
|
product_id
|
||||||
|
@ -236,6 +234,7 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
|
||||||
{$sql_query_params['from_clause']}
|
{$sql_query_params['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$sql_query_params['where_time_clause']}
|
||||||
{$sql_query_params['where_clause']}
|
{$sql_query_params['where_clause']}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
product_id
|
product_id
|
||||||
|
|
|
@ -50,28 +50,27 @@ class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Produc
|
||||||
protected function update_sql_query_params( $query_args, &$totals_params, &$intervals_params ) {
|
protected function update_sql_query_params( $query_args, &$totals_params, &$intervals_params ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$allowed_products = $this->get_allowed_products( $query_args );
|
|
||||||
$products_where_clause = '';
|
$products_where_clause = '';
|
||||||
$products_from_clause = '';
|
$products_from_clause = '';
|
||||||
|
|
||||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
if ( count( $allowed_products ) > 0 ) {
|
|
||||||
$allowed_products_str = implode( ',', $allowed_products );
|
$included_products = $this->get_included_products( $query_args );
|
||||||
$products_where_clause .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})";
|
if ( $included_products ) {
|
||||||
|
$products_where_clause .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) {
|
$order_status_filter = $this->get_status_subquery( $query_args );
|
||||||
$statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] );
|
if ( $order_status_filter ) {
|
||||||
|
|
||||||
$products_from_clause .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
$products_from_clause .= " JOIN {$wpdb->prefix}posts ON {$order_product_lookup_table}.order_id = {$wpdb->prefix}posts.ID";
|
||||||
$products_where_clause .= " AND {$wpdb->prefix}posts.post_status IN ( '" . implode( "','", $statuses ) . "' ) ";
|
$products_where_clause .= " AND ( {$order_status_filter} )";
|
||||||
}
|
}
|
||||||
|
|
||||||
$totals_params = array_merge( $totals_params, $this->get_time_period_sql_params( $query_args ) );
|
$totals_params = array_merge( $totals_params, $this->get_time_period_sql_params( $query_args, $order_product_lookup_table ) );
|
||||||
$totals_params['where_clause'] .= $products_where_clause;
|
$totals_params['where_clause'] .= $products_where_clause;
|
||||||
$totals_params['from_clause'] .= $products_from_clause;
|
$totals_params['from_clause'] .= $products_from_clause;
|
||||||
|
|
||||||
$intervals_params = array_merge( $intervals_params, $this->get_intervals_sql_params( $query_args ) );
|
$intervals_params = array_merge( $intervals_params, $this->get_intervals_sql_params( $query_args, $order_product_lookup_table ) );
|
||||||
$intervals_params['where_clause'] .= $products_where_clause;
|
$intervals_params['where_clause'] .= $products_where_clause;
|
||||||
$intervals_params['from_clause'] .= $products_from_clause;
|
$intervals_params['from_clause'] .= $products_from_clause;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +100,7 @@ class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Produc
|
||||||
'fields' => '*',
|
'fields' => '*',
|
||||||
'categories' => array(),
|
'categories' => array(),
|
||||||
'interval' => 'week',
|
'interval' => 'week',
|
||||||
'products' => array(),
|
'product_includes' => array(),
|
||||||
// This is not a parameter for products reports per se, but we should probably restricts order statuses here, too.
|
// This is not a parameter for products reports per se, but we should probably restricts order statuses here, too.
|
||||||
'order_status' => parent::get_report_order_statuses(),
|
'order_status' => parent::get_report_order_statuses(),
|
||||||
);
|
);
|
||||||
|
@ -125,6 +124,7 @@ class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Produc
|
||||||
{$intervals_query['from_clause']}
|
{$intervals_query['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$intervals_query['where_time_clause']}
|
||||||
{$intervals_query['where_clause']}
|
{$intervals_query['where_clause']}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
time_interval
|
time_interval
|
||||||
|
@ -144,6 +144,7 @@ class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Produc
|
||||||
{$totals_query['from_clause']}
|
{$totals_query['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$totals_query['where_time_clause']}
|
||||||
{$totals_query['where_clause']}",
|
{$totals_query['where_clause']}",
|
||||||
ARRAY_A
|
ARRAY_A
|
||||||
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
|
||||||
|
@ -166,6 +167,7 @@ class WC_Admin_Reports_Products_Stats_Data_Store extends WC_Admin_Reports_Produc
|
||||||
{$intervals_query['from_clause']}
|
{$intervals_query['from_clause']}
|
||||||
WHERE
|
WHERE
|
||||||
1=1
|
1=1
|
||||||
|
{$intervals_query['where_time_clause']}
|
||||||
{$intervals_query['where_clause']}
|
{$intervals_query['where_clause']}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
time_interval
|
time_interval
|
||||||
|
|
|
@ -72,17 +72,15 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
|
||||||
*/
|
*/
|
||||||
protected function get_sql_query_params( $query_args ) {
|
protected function get_sql_query_params( $query_args ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
||||||
|
|
||||||
$sql_query_params = $this->get_time_period_sql_params( $query_args );
|
$sql_query_params = $this->get_time_period_sql_params( $query_args, $order_product_lookup_table );
|
||||||
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
$sql_query_params = array_merge( $sql_query_params, $this->get_limit_sql_params( $query_args ) );
|
||||||
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
$sql_query_params = array_merge( $sql_query_params, $this->get_order_by_sql_params( $query_args ) );
|
||||||
|
|
||||||
$order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME;
|
$included_products = $this->get_included_products( $query_args );
|
||||||
$allowed_products = $this->get_allowed_products( $query_args );
|
if ( $included_products ) {
|
||||||
|
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$included_products})";
|
||||||
if ( count( $allowed_products ) > 0 ) {
|
|
||||||
$allowed_products_str = implode( ',', $allowed_products );
|
|
||||||
$sql_query_params['where_clause'] .= " AND {$order_product_lookup_table}.product_id IN ({$allowed_products_str})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( count( $query_args['variations'] ) > 0 ) {
|
if ( count( $query_args['variations'] ) > 0 ) {
|
||||||
|
|
|
@ -46,7 +46,9 @@ function wc_admin_order_product_lookup_entry( $order_id ) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: maybe replace these with woocommerce_create_order, woocommerce_update_order, woocommerce_trash_order, woocommerce_delete_order, as clean_post_cache might be called in other circumstances and trigger too many updates?
|
||||||
add_action( 'save_post', 'wc_admin_order_product_lookup_entry', 10, 1 );
|
add_action( 'save_post', 'wc_admin_order_product_lookup_entry', 10, 1 );
|
||||||
|
add_action( 'clean_post_cache', 'wc_admin_order_product_lookup_entry', 10, 1 );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an entry in the wc_order_tax_lookup table for an order.
|
* Make an entry in the wc_order_tax_lookup table for an order.
|
||||||
|
@ -84,6 +86,7 @@ function wc_order_tax_lookup_entry( $order_id ) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_action( 'save_post', 'wc_order_tax_lookup_entry', 10, 1 );
|
add_action( 'save_post', 'wc_order_tax_lookup_entry', 10, 1 );
|
||||||
|
add_action( 'clean_post_cache', 'wc_order_tax_lookup_entry', 10, 1 );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make an entry in the wc_order_coupon_lookup table for an order.
|
* Make an entry in the wc_order_coupon_lookup table for an order.
|
||||||
|
@ -106,7 +109,7 @@ function wc_order_coupon_lookup_entry( $order_id ) {
|
||||||
$wpdb->prefix . 'wc_order_coupon_lookup',
|
$wpdb->prefix . 'wc_order_coupon_lookup',
|
||||||
array(
|
array(
|
||||||
'order_id' => $order_id,
|
'order_id' => $order_id,
|
||||||
'coupon_id' => $coupon_item->get_id(),
|
'coupon_id' => wc_get_coupon_id_by_code( $coupon_item->get_code() ),
|
||||||
'coupon_gross_discount' => $coupon_item->get_discount(),
|
'coupon_gross_discount' => $coupon_item->get_discount(),
|
||||||
'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
'date_created' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||||
),
|
),
|
||||||
|
@ -120,3 +123,4 @@ function wc_order_coupon_lookup_entry( $order_id ) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_action( 'save_post', 'wc_order_coupon_lookup_entry', 10, 1 );
|
add_action( 'save_post', 'wc_order_coupon_lookup_entry', 10, 1 );
|
||||||
|
add_action( 'clean_post_cache', 'wc_order_coupon_lookup_entry', 10, 1 );
|
||||||
|
|
|
@ -88,7 +88,7 @@ class WC_Tests_API_Reports_Revenue_Stats extends WC_REST_Unit_Test_Case {
|
||||||
$this->assertArrayHasKey( 'intervals', $properties );
|
$this->assertArrayHasKey( 'intervals', $properties );
|
||||||
|
|
||||||
$totals = $properties['totals']['properties'];
|
$totals = $properties['totals']['properties'];
|
||||||
$this->assertEquals( 7, count( $totals ) );
|
$this->assertEquals( 9, count( $totals ) );
|
||||||
$this->assertArrayHasKey( 'gross_revenue', $totals );
|
$this->assertArrayHasKey( 'gross_revenue', $totals );
|
||||||
$this->assertArrayHasKey( 'net_revenue', $totals );
|
$this->assertArrayHasKey( 'net_revenue', $totals );
|
||||||
$this->assertArrayHasKey( 'coupons', $totals );
|
$this->assertArrayHasKey( 'coupons', $totals );
|
||||||
|
@ -96,6 +96,8 @@ class WC_Tests_API_Reports_Revenue_Stats extends WC_REST_Unit_Test_Case {
|
||||||
$this->assertArrayHasKey( 'taxes', $totals );
|
$this->assertArrayHasKey( 'taxes', $totals );
|
||||||
$this->assertArrayHasKey( 'refunds', $totals );
|
$this->assertArrayHasKey( 'refunds', $totals );
|
||||||
$this->assertArrayHasKey( 'orders_count', $totals );
|
$this->assertArrayHasKey( 'orders_count', $totals );
|
||||||
|
$this->assertArrayHasKey( 'num_items_sold', $totals );
|
||||||
|
$this->assertArrayHasKey( 'products', $totals );
|
||||||
|
|
||||||
$intervals = $properties['intervals']['items']['properties'];
|
$intervals = $properties['intervals']['items']['properties'];
|
||||||
$this->assertEquals( 6, count( $intervals ) );
|
$this->assertEquals( 6, count( $intervals ) );
|
||||||
|
@ -107,7 +109,7 @@ class WC_Tests_API_Reports_Revenue_Stats extends WC_REST_Unit_Test_Case {
|
||||||
$this->assertArrayHasKey( 'subtotals', $intervals );
|
$this->assertArrayHasKey( 'subtotals', $intervals );
|
||||||
|
|
||||||
$subtotals = $properties['intervals']['items']['properties']['subtotals']['properties'];
|
$subtotals = $properties['intervals']['items']['properties']['subtotals']['properties'];
|
||||||
$this->assertEquals( 7, count( $subtotals ) );
|
$this->assertEquals( 8, count( $subtotals ) );
|
||||||
$this->assertArrayHasKey( 'gross_revenue', $subtotals );
|
$this->assertArrayHasKey( 'gross_revenue', $subtotals );
|
||||||
$this->assertArrayHasKey( 'net_revenue', $subtotals );
|
$this->assertArrayHasKey( 'net_revenue', $subtotals );
|
||||||
$this->assertArrayHasKey( 'coupons', $subtotals );
|
$this->assertArrayHasKey( 'coupons', $subtotals );
|
||||||
|
@ -115,5 +117,6 @@ class WC_Tests_API_Reports_Revenue_Stats extends WC_REST_Unit_Test_Case {
|
||||||
$this->assertArrayHasKey( 'taxes', $subtotals );
|
$this->assertArrayHasKey( 'taxes', $subtotals );
|
||||||
$this->assertArrayHasKey( 'refunds', $subtotals );
|
$this->assertArrayHasKey( 'refunds', $subtotals );
|
||||||
$this->assertArrayHasKey( 'orders_count', $subtotals );
|
$this->assertArrayHasKey( 'orders_count', $subtotals );
|
||||||
|
$this->assertArrayHasKey( 'num_items_sold', $subtotals );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue