diff --git a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php index 41368eb1596..b73dba30d1c 100644 --- a/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php +++ b/plugins/woocommerce-admin/includes/api/class-wc-admin-rest-reports-products-controller.php @@ -221,6 +221,7 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll 'gross_revenue', 'orders_count', 'items_sold', + 'product_name', ), 'validate_callback' => 'rest_validate_request_arg', ); diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php index 63168c4af21..81e255abc2d 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-products-data-store.php @@ -62,6 +62,33 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i 'permalink', ); + /** + * Fills ORDER BY clause of SQL request based on user supplied parameters. + * + * @param array $query_args Parameters supplied by the user. + * @return array + */ + protected function get_order_by_sql_params( $query_args ) { + global $wpdb; + $order_product_lookup_table = $wpdb->prefix . self::TABLE_NAME; + + $sql_query['order_by_clause'] = ''; + if ( isset( $query_args['orderby'] ) ) { + $sql_query['order_by_clause'] = $this->normalize_order_by( $query_args['orderby'] ); + } + // Order by product name requires extra JOIN. + if ( false !== strpos( $sql_query['order_by_clause'], '_products' ) ) { + $sql_query['from_clause'] .= " JOIN {$wpdb->prefix}posts AS _products ON {$order_product_lookup_table}.product_id = _products.ID"; + } + + if ( isset( $query_args['order'] ) ) { + $sql_query['order_by_clause'] .= ' ' . $query_args['order']; + } else { + $sql_query['order_by_clause'] .= ' DESC'; + } + + return $sql_query; + } /** * Updates the database query with parameters used for Products report: categories and order status. @@ -87,8 +114,8 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i if ( is_array( $query_args['order_status'] ) && count( $query_args['order_status'] ) > 0 ) { $statuses = array_map( array( $this, 'normalize_order_status' ), $query_args['order_status'] ); - $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['from_clause'] .= " JOIN {$wpdb->prefix}posts AS _orders ON {$order_product_lookup_table}.order_id = _orders.ID"; + $sql_query_params['where_clause'] .= " AND _orders.post_status IN ( '" . implode( "','", $statuses ) . "' ) "; } return $sql_query_params; @@ -104,6 +131,9 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i if ( 'date' === $order_by ) { return 'date_created'; } + if ( 'product_name' === $order_by ) { + return '_products.post_title'; + } return $order_by; } diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php index c5423a79637..da3eabd978a 100644 --- a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-products.php @@ -62,4 +62,109 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case { $this->assertEquals( $expected_data, $query->get_data() ); } + public function test_order_by_product_name() { + WC_Helper_Reports::reset_stats_dbs(); + + // Populate all of the data. + $product = new WC_Product_Simple(); + $product->set_name( 'A Test Product' ); + $product->set_regular_price( 25 ); + $product->save(); + + $product_2 = new WC_Product_Simple(); + $product_2->set_name( 'B Test Product' ); + $product_2->set_regular_price( 20 ); + $product_2->save(); + + $date_created = time(); + $date_created_2 = $date_created + 5; + + $order = WC_Helper_Order::create_order( 1, $product ); + $order->set_status( 'completed' ); + $order->set_shipping_total( 10 ); + $order->set_discount_total( 20 ); + $order->set_discount_tax( 0 ); + $order->set_cart_tax( 5 ); + $order->set_shipping_tax( 2 ); + $order->set_total( 97 ); // $25x4 products + $10 shipping - $20 discount + $7 tax. + $order->set_date_created( $date_created ); + $order->save(); + + $order_2 = WC_Helper_Order::create_order( 1, $product_2 ); + $order_2->set_status( 'completed' ); + $order_2->set_shipping_total( 10 ); + $order_2->set_discount_total( 20 ); + $order_2->set_discount_tax( 0 ); + $order_2->set_cart_tax( 5 ); + $order_2->set_shipping_tax( 2 ); + $order_2->set_total( 77 ); // $20x4 products + $10 shipping - $20 discount + $7 tax. + $order_2->set_date_created( $date_created_2 ); + $order_2->save(); + + $data_store = new WC_Admin_Reports_Products_Data_Store(); + $start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() ); + $end_time = date( 'Y-m-d H:00:00', $order_2->get_date_created()->getOffsetTimestamp() + HOUR_IN_SECONDS ); + // Test retrieving the stats through the data store, default order by date/time desc. + $args = array( + 'after' => $start_time, + 'before' => $end_time, + ); + + $data = $data_store->get_data( $args ); + $expected_data = (object) array( + 'total' => 2, + 'pages' => 1, + 'page_no' => 1, + 'data' => array( + 0 => array( + 'product_id' => $product_2->get_id(), + 'items_sold' => 4, + 'gross_revenue' => 80.0, // $20 * 4. + 'orders_count' => 1, + ), + 1 => array( + 'product_id' => $product->get_id(), + 'items_sold' => 4, + 'gross_revenue' => 100.0, // $25 * 4. + 'orders_count' => 1, + ), + ), + ); + $this->assertEquals( $expected_data, $data ); + + // Test retrieving the stats through the data store, order by product name asc. + $args = array( + 'after' => $start_time, + 'before' => $end_time, + 'order_by' => 'product_name', + 'order' => 'asc', + ); + + $data = $data_store->get_data( $args ); + $expected_data = (object) array( + 'total' => 2, + 'pages' => 1, + 'page_no' => 1, + 'data' => array( + 0 => array( + 'product_id' => $product->get_id(), + 'items_sold' => 4, + 'gross_revenue' => 100.0, // $25 * 4. + 'orders_count' => 1, + ), + 1 => array( + 'product_id' => $product_2->get_id(), + 'items_sold' => 4, + 'gross_revenue' => 80.0, // $20 * 4. + 'orders_count' => 1, + ), + ), + ); + $this->assertEquals( $expected_data, $data ); + + // Test retrieving the stats through the query class. + $query = new WC_Admin_Reports_Products_Query( $args ); + $this->assertEquals( $expected_data, $query->get_data() ); + } + }