Correcting and clarifying analytics terms and calculations (https://github.com/woocommerce/woocommerce-admin/pull/3104)

* Relabel Net Revenue to Net Sales, revert previous refund work on Gross revenue and rename to total sales. Update the orer of all the things

* Add gross sales calculation to revenue stats endpoint.

* Restore coupon_total when updating order stats.

* Wire up gross sales to revenue report.

* Fix revenue report refunds calculation when there are no refunds.

* update net sales labels and cases in order, product and category tables

* Subtract refunded shipping and taxes from gross sales.

* pluses to minuses to fix the gross revenue and refund totals when refunding

* Add gross_sales to revenue stats orderby enum.

* Change refund labels to Returns

* Remove usage of defunct coupon_total column.

* Store refunded amount in stats table.

* Rename "gross_total" column to "total_sales".

* Net total for refund orders can be used instead of a new column.

* Rename gross_revenue to total_sales.

* Coalesce coupons total in order stats query.

SUM()ing all nulls gives null, not zero.

* Use segmentation selections to backfill missing data.

Fo when report columns and segmentation columns don't match.

* Remove errant gross_sales from expected interval test data.

* Fix gross sales tests for revenue/stats.

* Move missing segment fills back to their original locations.

* Fix remaining tests failing because of gross sales.

* Fix db upgrade function rename of gross_total column.

* Fix linter errors.
This commit is contained in:
David Levin 2019-11-22 07:06:14 -08:00 committed by Jeff Stieler
parent 0db5cbb6a0
commit 52cb35f4de
44 changed files with 697 additions and 406 deletions

View File

@ -50,7 +50,7 @@ const headers = [
label: 'Orders',
},
{
label: 'Net Revenue',
label: 'Net Sales',
},
];

View File

@ -20,8 +20,8 @@ const data = {
isRequesting: false,
};
const selectedChart = {
key: 'gross_revenue',
label: 'Gross Revenue',
key: 'total_sales',
label: 'Total Sales',
type: 'currency',
};

View File

@ -19,8 +19,8 @@ describe( 'ReportSummary', () => {
props
) {
const selectedChart = {
key: 'gross_revenue',
label: 'Gross Revenue',
key: 'total_sales',
label: 'Total Sales',
type,
};
const charts = [ selectedChart ];
@ -29,10 +29,10 @@ describe( 'ReportSummary', () => {
const summaryData = {
totals: {
primary: {
gross_revenue: primaryValue,
total_sales: primaryValue,
},
secondary: {
gross_revenue: secondaryValue,
total_sales: secondaryValue,
},
},
isError,

View File

@ -47,7 +47,7 @@ class CategoriesReportTable extends Component {
isNumeric: true,
},
{
label: __( 'Net Revenue', 'woocommerce-admin' ),
label: __( 'Net Sales', 'woocommerce-admin' ),
key: 'net_revenue',
isSortable: true,
isNumeric: true,

View File

@ -23,7 +23,7 @@ export const charts = applyFilters( ORDERS_REPORT_CHARTS_FILTER, [
},
{
key: 'net_revenue',
label: __( 'Net Revenue', 'woocommerce-admin' ),
label: __( 'Net Sales', 'woocommerce-admin' ),
order: 'desc',
orderby: 'net_total',
type: 'currency',

View File

@ -81,8 +81,8 @@ export default class OrdersReportTable extends Component {
isSortable: false,
},
{
label: __( 'N. Revenue', 'woocommerce-admin' ),
screenReaderLabel: __( 'Net Revenue', 'woocommerce-admin' ),
label: __( 'Net Sales', 'woocommerce-admin' ),
screenReaderLabel: __( 'Net Sales', 'woocommerce-admin' ),
key: 'net_total',
required: true,
isSortable: true,
@ -252,7 +252,7 @@ export default class OrdersReportTable extends Component {
value: formatValue( 'number', coupons_count ),
},
{
label: __( 'net revenue', 'woocommerce-admin' ),
label: __( 'net sales', 'woocommerce-admin' ),
value: formatCurrency( net_revenue ),
},
];

View File

@ -55,8 +55,8 @@ export default class VariationsReportTable extends Component {
isNumeric: true,
},
{
label: __( 'N. Revenue', 'woocommerce-admin' ),
screenReaderLabel: __( 'Net Revenue', 'woocommerce-admin' ),
label: __( 'Net Sales', 'woocommerce-admin' ),
screenReaderLabel: __( 'Net Sales', 'woocommerce-admin' ),
key: 'net_revenue',
required: true,
isSortable: true,
@ -162,7 +162,7 @@ export default class VariationsReportTable extends Component {
value: formatValue( 'number', items_sold ),
},
{
label: __( 'net revenue', 'woocommerce-admin' ),
label: __( 'net sales', 'woocommerce-admin' ),
value: formatCurrency( net_revenue ),
},
{

View File

@ -60,8 +60,8 @@ class ProductsReportTable extends Component {
isNumeric: true,
},
{
label: __( 'N. Revenue', 'woocommerce-admin' ),
screenReaderLabel: __( 'Net Revenue', 'woocommerce-admin' ),
label: __( 'Net Sales', 'woocommerce-admin' ),
screenReaderLabel: __( 'Net Sales', 'woocommerce-admin' ),
key: 'net_revenue',
required: true,
isSortable: true,

View File

@ -11,15 +11,15 @@ const REVENUE_REPORT_ADVANCED_FILTERS_FILTER = 'woocommerce_admin_revenue_report
export const charts = applyFilters( REVENUE_REPORT_CHARTS_FILTER, [
{
key: 'gross_revenue',
label: __( 'Gross Revenue', 'woocommerce-admin' ),
key: 'gross_sales',
label: __( 'Gross Sales', 'woocommerce-admin' ),
order: 'desc',
orderby: 'gross_revenue',
orderby: 'gross_sales',
type: 'currency',
},
{
key: 'refunds',
label: __( 'Refunds', 'woocommerce-admin' ),
label: __( 'Returns', 'woocommerce-admin' ),
order: 'desc',
orderby: 'refunds',
type: 'currency',
@ -31,6 +31,12 @@ export const charts = applyFilters( REVENUE_REPORT_CHARTS_FILTER, [
orderby: 'coupons',
type: 'currency',
},
{
key: 'net_revenue',
label: __( 'Net Sales', 'woocommerce-admin' ),
orderby: 'net_revenue',
type: 'currency',
},
{
key: 'taxes',
label: __( 'Taxes', 'woocommerce-admin' ),
@ -45,9 +51,10 @@ export const charts = applyFilters( REVENUE_REPORT_CHARTS_FILTER, [
type: 'currency',
},
{
key: 'net_revenue',
label: __( 'Net Revenue', 'woocommerce-admin' ),
orderby: 'net_revenue',
key: 'total_sales',
label: __( 'Total Sales', 'woocommerce-admin' ),
order: 'desc',
orderby: 'total_sales',
type: 'currency',
},
] );

View File

@ -50,14 +50,14 @@ class RevenueReportTable extends Component {
isNumeric: true,
},
{
label: __( 'Gross Revenue', 'woocommerce-admin' ),
key: 'gross_revenue',
required: true,
label: __( 'Gross Sales', 'woocommerce-admin' ),
key: 'gross_sales',
required: false,
isSortable: true,
isNumeric: true,
},
{
label: __( 'Refunds', 'woocommerce-admin' ),
label: __( 'Returns', 'woocommerce-admin' ),
key: 'refunds',
required: false,
isSortable: true,
@ -70,6 +70,13 @@ class RevenueReportTable extends Component {
isSortable: true,
isNumeric: true,
},
{
label: __( 'Net Sales', 'woocommerce-admin' ),
key: 'net_revenue',
required: false,
isSortable: true,
isNumeric: true,
},
{
label: __( 'Taxes', 'woocommerce-admin' ),
key: 'taxes',
@ -85,9 +92,9 @@ class RevenueReportTable extends Component {
isNumeric: true,
},
{
label: __( 'Net Revenue', 'woocommerce-admin' ),
key: 'net_revenue',
required: false,
label: __( 'Total Sales', 'woocommerce-admin' ),
key: 'total_sales',
required: true,
isSortable: true,
isNumeric: true,
},
@ -98,7 +105,8 @@ class RevenueReportTable extends Component {
return data.map( row => {
const {
coupons,
gross_revenue,
gross_sales,
total_sales,
net_revenue,
orders_count,
refunds,
@ -126,8 +134,8 @@ class RevenueReportTable extends Component {
value: Number( orders_count ),
},
{
display: renderCurrency( gross_revenue ),
value: getCurrencyFormatDecimal( gross_revenue ),
display: renderCurrency( gross_sales ),
value: getCurrencyFormatDecimal( gross_sales ),
},
{
display: formatCurrency( refunds ),
@ -137,6 +145,10 @@ class RevenueReportTable extends Component {
display: formatCurrency( coupons ),
value: getCurrencyFormatDecimal( coupons ),
},
{
display: renderCurrency( net_revenue ),
value: getCurrencyFormatDecimal( net_revenue ),
},
{
display: renderCurrency( taxes ),
value: getCurrencyFormatDecimal( taxes ),
@ -146,8 +158,8 @@ class RevenueReportTable extends Component {
value: getCurrencyFormatDecimal( shipping ),
},
{
display: renderCurrency( net_revenue ),
value: getCurrencyFormatDecimal( net_revenue ),
display: renderCurrency( total_sales ),
value: getCurrencyFormatDecimal( total_sales ),
},
];
} );
@ -156,7 +168,8 @@ class RevenueReportTable extends Component {
getSummary( totals, totalResults = 0 ) {
const {
orders_count = 0,
gross_revenue = 0,
gross_sales = 0,
total_sales = 0,
refunds = 0,
coupons = 0,
taxes = 0,
@ -173,17 +186,21 @@ class RevenueReportTable extends Component {
value: formatValue( 'number', orders_count ),
},
{
label: __( 'gross revenue', 'woocommerce-admin' ),
value: formatCurrency( gross_revenue ),
label: __( 'gross sales', 'woocommerce-admin' ),
value: formatCurrency( gross_sales ),
},
{
label: __( 'refunds', 'woocommerce-admin' ),
label: __( 'returns', 'woocommerce-admin' ),
value: formatCurrency( refunds ),
},
{
label: __( 'coupons', 'woocommerce-admin' ),
value: formatCurrency( coupons ),
},
{
label: __( 'net sales', 'woocommerce-admin' ),
value: formatCurrency( net_revenue ),
},
{
label: __( 'taxes', 'woocommerce-admin' ),
value: formatCurrency( taxes ),
@ -193,8 +210,8 @@ class RevenueReportTable extends Component {
value: formatCurrency( shipping ),
},
{
label: __( 'net revenue', 'woocommerce-admin' ),
value: formatCurrency( net_revenue ),
label: __( 'total sales', 'woocommerce-admin' ),
value: formatCurrency( total_sales ),
},
];
}

View File

@ -29,12 +29,12 @@ const charts = {
const defaultCharts = [
{
label: __( 'Gross Revenue', 'woocommerce-admin' ),
label: __( 'Total Sales', 'woocommerce-admin' ),
report: 'revenue',
key: 'gross_revenue',
key: 'total_sales',
},
{
label: __( 'Net Revenue', 'woocommerce-admin' ),
label: __( 'Net Sales', 'woocommerce-admin' ),
report: 'revenue',
key: 'net_revenue',
},

View File

@ -41,7 +41,7 @@ export default applyFilters( DEFAULT_SECTIONS_FILTER, [
'avg_order_value',
'avg_items_per_order',
'items_sold',
'gross_revenue',
'total_sales',
'refunds',
'coupons',
'taxes',

View File

@ -136,7 +136,7 @@ class OrdersPanel extends Component {
const productsCount =
extended_info && extended_info.products ? extended_info.products.length : 0;
const total = order.gross_total;
const total = order.total_sales;
cards.push(
<ActivityCard

View File

@ -41,3 +41,23 @@ function wc_admin_update_0201_order_status_index() {
function wc_admin_update_0201_db_version() {
Installer::update_db_version( '0.20.1' );
}
/**
* Rename "gross_total" to "total_sales".
* See: https://github.com/woocommerce/woocommerce-admin/issues/3175
*/
function wc_admin_update_0230_rename_gross_total() {
global $wpdb;
// We first need to drop the new `total_sales` column, since dbDelta() will have created it.
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_order_stats DROP COLUMN `total_sales`" );
// Then we can rename the existing `gross_total` column.
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_order_stats CHANGE COLUMN `gross_total` `total_sales` double DEFAULT 0 NOT NULL" );
}
/**
* Update DB Version.
*/
function wc_admin_update_0230_db_version() {
Installer::update_db_version( '0.23.0' );
}

View File

@ -12,7 +12,7 @@ A container element for a list of SummaryNumbers. This component handles detecti
<SummaryNumber
key="revenue"
value={ '$829.40' }
label="Gross Revenue"
label="Total Sales"
delta={ 29 }
href="/analytics/report"
/>,

View File

@ -11,7 +11,7 @@ export default () => (
<SummaryNumber
key="revenue"
value={ '$829.40' }
label="Gross Revenue"
label="Total Sales"
delta={ 29 }
href="/analytics/report"
/>,

View File

@ -1,5 +1,5 @@
/** @format */
export default `Date,Orders,Gross Revenue,Refunds,Taxes,Shipping,Net Revenue
export default `Date,Orders,Total Sales,Refunds,Taxes,Shipping,Net Sales
2018-10-01 00:00:00,1,411,0,0,0,411
2018-10-02 00:00:00,0,0,,0,0,0`;

View File

@ -12,8 +12,8 @@ export default [
required: false,
},
{
label: 'Gross Revenue',
key: 'gross_revenue',
label: 'Total Sales',
key: 'total_sales',
required: true,
},
{
@ -33,7 +33,7 @@ export default [
key: 'shipping',
},
{
label: 'Net Revenue',
label: 'Net Sales',
key: 'net_revenue',
},
];

View File

@ -1,4 +1,4 @@
/** @format */
export default `Date,Orders,Description,"Gross Revenue",Refunds,Coupons,Taxes,Shipping,"Net Revenue","Negative Number"
export default `Date,Orders,Description,"Total Sales",Refunds,Coupons,Taxes,Shipping,"Net Sales","Negative Number"
2018-04-29T00:00:00,30,"Lorem, ""ipsum""",200,19,19,100,19,200,'-123`;

View File

@ -14,8 +14,8 @@ export default [
key: 'description',
},
{
label: 'Gross Revenue',
key: 'gross_revenue',
label: 'Total Sales',
key: 'total_sales',
},
{
label: 'Refunds',
@ -34,7 +34,7 @@ export default [
key: 'shipping',
},
{
label: 'Net Revenue',
label: 'Net Sales',
key: 'net_revenue',
},
{

View File

@ -194,7 +194,7 @@ class Leaderboards extends \WC_REST_Data_Controller {
'label' => __( 'Items Sold', 'woocommerce-admin' ),
),
array(
'label' => __( 'Net Revenue', 'woocommerce-admin' ),
'label' => __( 'Net Sales', 'woocommerce-admin' ),
),
),
'rows' => $rows,
@ -324,7 +324,7 @@ class Leaderboards extends \WC_REST_Data_Controller {
'label' => __( 'Items Sold', 'woocommerce-admin' ),
),
array(
'label' => __( 'Net Revenue', 'woocommerce-admin' ),
'label' => __( 'Net Sales', 'woocommerce-admin' ),
),
),
'rows' => $rows,

View File

@ -179,7 +179,7 @@ class Controller extends ReportsController implements ExportableInterface {
'readonly' => true,
),
'net_revenue' => array(
'description' => __( 'Gross revenue.', 'woocommerce-admin' ),
'description' => __( 'Total Sales.', 'woocommerce-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,

View File

@ -18,26 +18,28 @@ use \Automattic\WooCommerce\Admin\API\Reports\ParameterException;
class Segmenter extends ReportsSegmenter {
/**
* Returns SELECT clause statements to be used for product-related product-level segmenting query (e.g. coupon discount amount for product X when segmenting by product id or category).
* Returns column => query mapping to be used for product-related product-level segmenting query
* (e.g. coupon discount amount for product X when segmenting by product id or category).
*
* @param string $products_table Name of SQL table containing the product-level segmenting info.
*
* @return string SELECT clause statements.
* @return array Column => SELECT query mapping.
*/
protected function get_segment_selections_product_level( $products_table ) {
$columns_mapping = array(
'amount' => "SUM($products_table.coupon_amount) as amount",
);
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
* Returns SELECT clause statements to be used for order-related product-level segmenting query (e.g. orders_count when segmented by category).
* Returns column => query mapping to be used for order-related product-level segmenting query
* (e.g. orders_count when segmented by category).
*
* @param string $coupons_lookup_table Name of SQL table containing the order-level segmenting info.
*
* @return string SELECT clause statements.
* @return array Column => SELECT query mapping.
*/
protected function get_segment_selections_order_level( $coupons_lookup_table ) {
$columns_mapping = array(
@ -45,16 +47,17 @@ class Segmenter extends ReportsSegmenter {
'orders_count' => "COUNT(DISTINCT $coupons_lookup_table.order_id) as orders_count",
);
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
* Returns SELECT clause statements to be used for order-level segmenting query (e.g. discount amount when segmented by coupons).
* Returns column => query mapping to be used for order-level segmenting query
* (e.g. discount amount when segmented by coupons).
*
* @param string $coupons_lookup_table Name of SQL table containing the order-level info.
* @param array $overrides Array of overrides for default column calculations.
*
* @return string
* @return array Column => SELECT query mapping.
*/
protected function segment_selections_orders( $coupons_lookup_table, $overrides = array() ) {
$columns_mapping = array(
@ -67,7 +70,7 @@ class Segmenter extends ReportsSegmenter {
$columns_mapping = array_merge( $columns_mapping, $overrides );
}
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
@ -265,10 +268,13 @@ class Segmenter extends ReportsSegmenter {
// while coupon and customer are bound to order, so we don't need the extra JOIN for those.
// This also means that segment selections need to be calculated differently.
if ( 'product' === $this->query_args['segmentby'] ) {
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$order_level_columns = $this->get_segment_selections_order_level( $table_name );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'order_level' => $this->get_segment_selections_order_level( $table_name ),
'product_level' => $this->prepare_selections( $product_level_columns ),
'order_level' => $this->prepare_selections( $order_level_columns ),
);
$this->report_columns = array_merge( $product_level_columns, $order_level_columns );
$segmenting_from = "INNER JOIN $product_segmenting_table ON ($table_name.order_id = $product_segmenting_table.order_id)";
$segmenting_groupby = $product_segmenting_table . '.product_id';
$segmenting_dimension_name = 'product_id';
@ -279,10 +285,13 @@ class Segmenter extends ReportsSegmenter {
throw new ParameterException( 'wc_admin_reports_invalid_segmenting_variation', __( 'product_includes parameter need to specify exactly one product when segmenting by variation.', 'woocommerce-admin' ) );
}
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$order_level_columns = $this->get_segment_selections_order_level( $table_name );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'order_level' => $this->get_segment_selections_order_level( $table_name ),
'product_level' => $this->prepare_selections( $product_level_columns ),
'order_level' => $this->prepare_selections( $order_level_columns ),
);
$this->report_columns = array_merge( $product_level_columns, $order_level_columns );
$segmenting_from = "INNER JOIN $product_segmenting_table ON ($table_name.order_id = $product_segmenting_table.order_id)";
$segmenting_where = "AND $product_segmenting_table.product_id = {$this->query_args['product_includes'][0]}";
$segmenting_groupby = $product_segmenting_table . '.variation_id';
@ -290,10 +299,13 @@ class Segmenter extends ReportsSegmenter {
$segments = $this->get_product_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table );
} elseif ( 'category' === $this->query_args['segmentby'] ) {
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$order_level_columns = $this->get_segment_selections_order_level( $table_name );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'order_level' => $this->get_segment_selections_order_level( $table_name ),
'product_level' => $this->prepare_selections( $product_level_columns ),
'order_level' => $this->prepare_selections( $order_level_columns ),
);
$this->report_columns = array_merge( $product_level_columns, $order_level_columns );
$segmenting_from = "
INNER JOIN $product_segmenting_table ON ($table_name.order_id = $product_segmenting_table.order_id)
LEFT JOIN {$wpdb->prefix}term_relationships ON {$product_segmenting_table}.product_id = {$wpdb->prefix}term_relationships.object_id
@ -305,7 +317,9 @@ class Segmenter extends ReportsSegmenter {
$segments = $this->get_product_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table );
} elseif ( 'coupon' === $this->query_args['segmentby'] ) {
$segmenting_selections = $this->segment_selections_orders( $table_name );
$coupon_level_columns = $this->segment_selections_orders( $table_name );
$segmenting_selections = $this->prepare_selections( $coupon_level_columns );
$this->report_columns = $coupon_level_columns;
$segmenting_from = '';
$segmenting_groupby = "$table_name.coupon_id";

View File

@ -74,8 +74,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
'date_last_active' => 'IF( date_last_active <= "0000-00-00 00:00:00", NULL, date_last_active ) AS date_last_active',
'date_last_order' => "MAX( {$wpdb->prefix}wc_order_stats.date_created ) as date_last_order",
'orders_count' => 'SUM( CASE WHEN parent_id = 0 THEN 1 ELSE 0 END ) as orders_count',
'total_spend' => 'SUM( gross_total ) as total_spend',
'avg_order_value' => '( SUM( gross_total ) / COUNT( order_id ) ) as avg_order_value',
'total_spend' => 'SUM( total_sales ) as total_spend',
'avg_order_value' => '( SUM( total_sales ) / COUNT( order_id ) ) as avg_order_value',
);
}
@ -259,11 +259,11 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
'format' => '%d',
),
'total_spend' => array(
'column' => 'SUM( gross_total )',
'column' => 'SUM( total_sales )',
'format' => '%f',
),
'avg_order_value' => array(
'column' => '( SUM( gross_total ) / COUNT( order_id ) )',
'column' => '( SUM( total_sales ) / COUNT( order_id ) )',
'format' => '%f',
),
);
@ -464,7 +464,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
'state' => $order->get_billing_state( 'edit' ),
'postcode' => $order->get_billing_postcode( 'edit' ),
'country' => $order->get_billing_country( 'edit' ),
'date_last_active' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
'date_last_active' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
);
$format = array(
'%s',
@ -603,7 +603,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
'postcode' => $customer->get_billing_postcode( 'edit' ),
'country' => $customer->get_billing_country( 'edit' ),
'date_registered' => $customer->get_date_created( 'edit' )->date( TimeInterval::$sql_datetime_format ),
'date_last_active' => $last_active ? date( 'Y-m-d H:i:s', $last_active ) : null,
'date_last_active' => $last_active ? gmdate( 'Y-m-d H:i:s', $last_active ) : null,
);
$format = array(
'%d',

View File

@ -97,14 +97,14 @@ class DataStore extends CustomersDataStore implements DataStoreInterface {
$this->add_sql_query_params( $query_args );
// Clear SQL clauses set for parent class queries that are different here.
$this->subquery->clear_sql_clause( 'select' );
$this->subquery->add_sql_clause( 'select', 'SUM( gross_total ) AS total_spend,' );
$this->subquery->add_sql_clause( 'select', 'SUM( total_sales ) AS total_spend,' );
$this->subquery->add_sql_clause(
'select',
'CASE WHEN COUNT( order_id ) = 0 THEN NULL ELSE COUNT( order_id ) END AS orders_count,'
);
$this->subquery->add_sql_clause(
'select',
'CASE WHEN COUNT( order_id ) = 0 THEN NULL ELSE SUM( gross_total ) / COUNT( order_id ) END AS avg_order_value'
'CASE WHEN COUNT( order_id ) = 0 THEN NULL ELSE SUM( total_sales ) / COUNT( order_id ) END AS avg_order_value'
);
$this->clear_sql_clause( array( 'order_by', 'limit' ) );

View File

@ -45,7 +45,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
'status' => 'strval',
'customer_id' => 'intval',
'net_total' => 'floatval',
'gross_total' => 'floatval',
'total_sales' => 'floatval',
'num_items_sold' => 'intval',
'customer_type' => 'strval',
);
@ -71,7 +71,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
'status' => "REPLACE({$table_name}.status, 'wc-', '') as status",
'customer_id' => "{$table_name}.customer_id",
'net_total' => "{$table_name}.net_total",
'gross_total' => "{$table_name}.gross_total",
'total_sales' => "{$table_name}.total_sales",
'num_items_sold' => "{$table_name}.num_items_sold",
'customer_type' => "(CASE WHEN {$table_name}.returning_customer = 1 THEN 'returning' WHEN {$table_name}.returning_customer = 0 THEN 'new' ELSE '' END) as customer_type",
);

View File

@ -154,7 +154,7 @@ class Controller extends \Automattic\WooCommerce\Admin\API\Reports\Controller {
public function get_item_schema() {
$data_values = array(
'net_revenue' => array(
'description' => __( 'Net revenue.', 'woocommerce-admin' ),
'description' => __( 'Net Sales.', 'woocommerce-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,

View File

@ -47,7 +47,8 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
protected $column_types = array(
'orders_count' => 'intval',
'num_items_sold' => 'intval',
'gross_revenue' => 'floatval',
'gross_sales' => 'floatval',
'total_sales' => 'floatval',
'coupons' => 'floatval',
'coupons_count' => 'intval',
'refunds' => 'floatval',
@ -75,13 +76,23 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
// Avoid ambigious columns in SQL query.
$refunds = "ABS( SUM( CASE WHEN {$table_name}.net_total < 0 THEN {$table_name}.net_total ELSE 0 END ) )";
$gross_sales =
"( SUM({$table_name}.total_sales)" .
' + COALESCE( SUM(discount_amount), 0 )' . // SUM() all nulls gives null.
" - SUM({$table_name}.tax_total)" .
" - SUM({$table_name}.shipping_total)" .
" + {$refunds}" .
' ) as gross_sales';
$this->report_columns = array(
'orders_count' => "SUM( CASE WHEN {$table_name}.parent_id = 0 THEN 1 ELSE 0 END ) as orders_count",
'num_items_sold' => "SUM({$table_name}.num_items_sold) as num_items_sold",
'gross_revenue' => "SUM( CASE WHEN {$table_name}.gross_total > 0 THEN {$table_name}.gross_total END ) AS gross_revenue",
'coupons' => 'SUM(discount_amount) AS coupons',
'coupons_count' => 'coupons_count',
'refunds' => "ABS( SUM( CASE WHEN {$table_name}.gross_total < 0 THEN {$table_name}.gross_total END ) ) AS refunds",
'gross_sales' => $gross_sales,
'total_sales' => "SUM({$table_name}.total_sales) AS total_sales",
'coupons' => 'COALESCE( SUM(discount_amount), 0 ) AS coupons', // SUM() all nulls gives null.
'coupons_count' => 'COALESCE( coupons_count, 0 ) as coupons_count',
'refunds' => "{$refunds} AS refunds",
'taxes' => "SUM({$table_name}.tax_total) AS taxes",
'shipping' => "SUM({$table_name}.shipping_total) AS shipping",
'net_revenue' => "SUM({$table_name}.net_total) AS net_revenue",
@ -458,7 +469,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
'date_created' => $order->get_date_created()->date( 'Y-m-d H:i:s' ),
'date_created_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created()->getTimestamp() ),
'num_items_sold' => self::get_num_items_sold( $order ),
'gross_total' => $order->get_total(),
'total_sales' => $order->get_total(),
'tax_total' => $order->get_total_tax(),
'shipping_total' => $order->get_shipping_total(),
'net_total' => self::get_net_total( $order ),

View File

@ -18,16 +18,17 @@ use \Automattic\WooCommerce\Admin\API\Reports\ParameterException;
class Segmenter extends ReportsSegmenter {
/**
* Returns SELECT clause statements to be used for product-related product-level segmenting query (e.g. products sold, revenue from product X when segmenting by category).
* Returns column => query mapping to be used for product-related product-level segmenting query
* (e.g. products sold, revenue from product X when segmenting by category).
*
* @param string $products_table Name of SQL table containing the product-level segmenting info.
*
* @return string SELECT clause statements.
* @return array Column => SELECT query mapping.
*/
protected function get_segment_selections_product_level( $products_table ) {
$columns_mapping = array(
'num_items_sold' => "SUM($products_table.product_qty) as num_items_sold",
'gross_revenue' => "SUM($products_table.product_gross_revenue) AS gross_revenue",
'total_sales' => "SUM($products_table.product_gross_revenue) AS total_sales",
'coupons' => 'SUM( coupon_lookup_left_join.discount_amount ) AS coupons',
'coupons_count' => 'COUNT( DISTINCT( coupon_lookup_left_join.coupon_id ) ) AS coupons_count',
'refunds' => "SUM( CASE WHEN $products_table.product_gross_revenue < 0 THEN $products_table.product_gross_revenue ELSE 0 END ) AS refunds",
@ -36,15 +37,16 @@ class Segmenter extends ReportsSegmenter {
'net_revenue' => "SUM($products_table.product_net_revenue) AS net_revenue",
);
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
* Returns SELECT clause statements to be used for order-related product-level segmenting query (e.g. avg items per order when segmented by category).
* Returns column => query mapping to be used for order-related product-level segmenting query
* (e.g. avg items per order when segmented by category).
*
* @param string $unique_orders_table Name of SQL table containing the order-level segmenting info.
*
* @return string SELECT clause statements.
* @return array Column => SELECT query mapping.
*/
protected function get_segment_selections_order_level( $unique_orders_table ) {
$columns_mapping = array(
@ -55,24 +57,25 @@ class Segmenter extends ReportsSegmenter {
'num_new_customers' => "COUNT($unique_orders_table.returning_customer) - SUM($unique_orders_table.returning_customer) AS num_new_customers",
);
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
* Returns SELECT clause statements to be used for order-level segmenting query (e.g. avg items per order or net revenue when segmented by coupons).
* Returns column => query mapping to be used for order-level segmenting query
* (e.g. avg items per order or Net Sales when segmented by coupons).
*
* @param string $order_stats_table Name of SQL table containing the order-level info.
* @param array $overrides Array of overrides for default column calculations.
*
* @return string
* @return array Column => SELECT query mapping.
*/
protected function segment_selections_orders( $order_stats_table, $overrides = array() ) {
$columns_mapping = array(
'num_items_sold' => "SUM($order_stats_table.num_items_sold) as num_items_sold",
'gross_revenue' => "SUM($order_stats_table.gross_total) AS gross_revenue",
'total_sales' => "SUM($order_stats_table.total_sales) AS total_sales",
'coupons' => "SUM($order_stats_table.discount_amount) AS coupons",
'coupons_count' => 'COUNT( DISTINCT(coupon_lookup_left_join.coupon_id) ) AS coupons_count',
'refunds' => "SUM( CASE WHEN $order_stats_table.parent_id != 0 THEN $order_stats_table.gross_total END ) AS refunds",
'refunds' => "SUM( CASE WHEN $order_stats_table.parent_id != 0 THEN $order_stats_table.total_sales END ) AS refunds",
'taxes' => "SUM($order_stats_table.tax_total) AS taxes",
'shipping' => "SUM($order_stats_table.shipping_total) AS shipping",
'net_revenue' => "SUM($order_stats_table.net_total) AS net_revenue",
@ -87,7 +90,7 @@ class Segmenter extends ReportsSegmenter {
$columns_mapping = array_merge( $columns_mapping, $overrides );
}
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
@ -360,10 +363,13 @@ class Segmenter extends ReportsSegmenter {
// This also means that segment selections need to be calculated differently.
if ( 'product' === $this->query_args['segmentby'] ) {
// @todo How to handle shipping taxes when grouped by product?
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$order_level_columns = $this->get_segment_selections_order_level( $unique_orders_table );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'order_level' => $this->get_segment_selections_order_level( $unique_orders_table ),
'product_level' => $this->prepare_selections( $product_level_columns ),
'order_level' => $this->prepare_selections( $order_level_columns ),
);
$this->report_columns = array_merge( $product_level_columns, $order_level_columns );
$segmenting_from .= "INNER JOIN $product_segmenting_table ON ($table_name.order_id = $product_segmenting_table.order_id)";
$segmenting_groupby = $product_segmenting_table . '.product_id';
$segmenting_dimension_name = 'product_id';
@ -374,10 +380,13 @@ class Segmenter extends ReportsSegmenter {
throw new ParameterException( 'wc_admin_reports_invalid_segmenting_variation', __( 'product_includes parameter need to specify exactly one product when segmenting by variation.', 'woocommerce-admin' ) );
}
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$order_level_columns = $this->get_segment_selections_order_level( $unique_orders_table );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'order_level' => $this->get_segment_selections_order_level( $unique_orders_table ),
'product_level' => $this->prepare_selections( $product_level_columns ),
'order_level' => $this->prepare_selections( $order_level_columns ),
);
$this->report_columns = array_merge( $product_level_columns, $order_level_columns );
$segmenting_from .= "INNER JOIN $product_segmenting_table ON ($table_name.order_id = $product_segmenting_table.order_id)";
$segmenting_where = "AND $product_segmenting_table.product_id = {$this->query_args['product_includes'][0]}";
$segmenting_groupby = $product_segmenting_table . '.variation_id';
@ -385,10 +394,13 @@ class Segmenter extends ReportsSegmenter {
$segments = $this->get_product_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table );
} elseif ( 'category' === $this->query_args['segmentby'] ) {
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$order_level_columns = $this->get_segment_selections_order_level( $unique_orders_table );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'order_level' => $this->get_segment_selections_order_level( $unique_orders_table ),
'product_level' => $this->prepare_selections( $product_level_columns ),
'order_level' => $this->prepare_selections( $order_level_columns ),
);
$this->report_columns = array_merge( $product_level_columns, $order_level_columns );
$segmenting_from .= "
INNER JOIN $product_segmenting_table ON ($table_name.order_id = $product_segmenting_table.order_id)
LEFT JOIN {$wpdb->term_relationships} ON {$product_segmenting_table}.product_id = {$wpdb->term_relationships}.object_id
@ -404,7 +416,9 @@ class Segmenter extends ReportsSegmenter {
$coupon_override = array(
'coupons' => 'SUM(coupon_lookup.discount_amount) AS coupons',
);
$segmenting_selections = $this->segment_selections_orders( $table_name, $coupon_override );
$coupon_level_columns = $this->segment_selections_orders( $table_name, $coupon_override );
$segmenting_selections = $this->prepare_selections( $coupon_level_columns );
$this->report_columns = $coupon_level_columns;
$segmenting_from .= "
INNER JOIN {$wpdb->prefix}wc_order_coupon_lookup AS coupon_lookup ON ($table_name.order_id = coupon_lookup.order_id)
";
@ -412,8 +426,10 @@ class Segmenter extends ReportsSegmenter {
$segments = $this->get_order_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $query_params );
} elseif ( 'customer_type' === $this->query_args['segmentby'] ) {
$segmenting_selections = $this->segment_selections_orders( $table_name );
$segmenting_groupby = "$table_name.returning_customer";
$customer_level_columns = $this->segment_selections_orders( $table_name );
$segmenting_selections = $this->prepare_selections( $customer_level_columns );
$this->report_columns = $customer_level_columns;
$segmenting_groupby = "$table_name.returning_customer";
$segments = $this->get_order_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $query_params );
}

View File

@ -231,7 +231,7 @@ class Controller extends \WC_REST_Reports_Controller {
$stat_order = apply_filters(
'woocommerce_rest_report_sort_performance_indicators',
array(
'revenue/gross_revenue',
'revenue/total_sales',
'revenue/net_revenue',
'orders/orders_count',
'orders/avg_order_value',

View File

@ -171,7 +171,7 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
'type' => 'number',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Total net revenue of all items sold.', 'woocommerce-admin' ),
'description' => __( 'Total Net Sales of all items sold.', 'woocommerce-admin' ),
),
'orders_count' => array(
'type' => 'integer',

View File

@ -165,7 +165,7 @@ class Controller extends \WC_REST_Reports_Controller {
'indicator' => true,
),
'net_revenue' => array(
'description' => __( 'Net revenue.', 'woocommerce-admin' ),
'description' => __( 'Net Sales.', 'woocommerce-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,

View File

@ -18,11 +18,12 @@ use \Automattic\WooCommerce\Admin\API\Reports\ParameterException;
class Segmenter extends ReportsSegmenter {
/**
* Returns SELECT clause statements to be used for product-related product-level segmenting query (e.g. products sold, revenue from product X when segmenting by category).
* Returns column => query mapping to be used for product-related product-level segmenting query
* (e.g. products sold, revenue from product X when segmenting by category).
*
* @param string $products_table Name of SQL table containing the product-level segmenting info.
*
* @return string SELECT clause statements.
* @return array Column => SELECT query mapping.
*/
protected function get_segment_selections_product_level( $products_table ) {
$columns_mapping = array(
@ -33,7 +34,7 @@ class Segmenter extends ReportsSegmenter {
'variations_count' => "COUNT( DISTINCT $products_table.variation_id ) AS variations_count",
);
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
@ -155,9 +156,11 @@ class Segmenter extends ReportsSegmenter {
// while coupon and customer are bound to order, so we don't need the extra JOIN for those.
// This also means that segment selections need to be calculated differently.
if ( 'product' === $this->query_args['segmentby'] ) {
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'product_level' => $this->prepare_selections( $product_level_columns ),
);
$this->report_columns = $product_level_columns;
$segmenting_from = '';
$segmenting_groupby = $product_segmenting_table . '.product_id';
$segmenting_dimension_name = 'product_id';
@ -168,9 +171,11 @@ class Segmenter extends ReportsSegmenter {
throw new ParameterException( 'wc_admin_reports_invalid_segmenting_variation', __( 'product_includes parameter need to specify exactly one product when segmenting by variation.', 'woocommerce-admin' ) );
}
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'product_level' => $this->prepare_selections( $product_level_columns ),
);
$this->report_columns = $product_level_columns;
$segmenting_from = '';
$segmenting_where = "AND $product_segmenting_table.product_id = {$this->query_args['product_includes'][0]}";
$segmenting_groupby = $product_segmenting_table . '.variation_id';
@ -178,9 +183,11 @@ class Segmenter extends ReportsSegmenter {
$segments = $this->get_product_related_segments( $type, $segmenting_selections, $segmenting_from, $segmenting_where, $segmenting_groupby, $segmenting_dimension_name, $table_name, $query_params, $unique_orders_table );
} elseif ( 'category' === $this->query_args['segmentby'] ) {
$product_level_columns = $this->get_segment_selections_product_level( $product_segmenting_table );
$segmenting_selections = array(
'product_level' => $this->get_segment_selections_product_level( $product_segmenting_table ),
'product_level' => $this->prepare_selections( $product_level_columns ),
);
$this->report_columns = $product_level_columns;
$segmenting_from = "
LEFT JOIN {$wpdb->term_relationships} ON {$product_segmenting_table}.product_id = {$wpdb->term_relationships}.object_id
LEFT JOIN {$wpdb->wc_category_lookup} ON {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->wc_category_lookup}.category_id

View File

@ -42,13 +42,14 @@ class Query extends ReportsQuery {
'fields' => array(
'orders_count',
'num_items_sold',
'gross_revenue',
'total_sales',
'coupons',
'coupons_count',
'refunds',
'taxes',
'shipping',
'net_revenue',
'gross_sales',
),
);
}

View File

@ -165,8 +165,8 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
*/
public function get_item_schema() {
$data_values = array(
'gross_revenue' => array(
'description' => __( 'Gross revenue.', 'woocommerce-admin' ),
'total_sales' => array(
'description' => __( 'Total Sales.', 'woocommerce-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
@ -174,7 +174,7 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
'format' => 'currency',
),
'net_revenue' => array(
'description' => __( 'Net revenue.', 'woocommerce-admin' ),
'description' => __( 'Net Sales.', 'woocommerce-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
@ -237,6 +237,14 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'gross_sales' => array(
'description' => __( 'Gross Sales.', 'woocommerce-admin' ),
'type' => 'number',
'context' => array( 'view', 'edit' ),
'readonly' => true,
'indicator' => true,
'format' => 'currency',
),
);
$segments = array(
@ -390,7 +398,7 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
'default' => 'date',
'enum' => array(
'date',
'gross_revenue',
'total_sales',
'coupons',
'refunds',
'shipping',
@ -398,6 +406,7 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
'net_revenue',
'orders_count',
'items_sold',
'gross_sales',
),
'validate_callback' => 'rest_validate_request_arg',
);
@ -438,14 +447,14 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
*/
public function get_export_columns() {
return array(
'date' => __( 'Date', 'woocommerce-admin' ),
'orders_count' => __( 'Orders', 'woocommerce-admin' ),
'gross_revenue' => __( 'Gross Revenue', 'woocommerce-admin' ),
'refunds' => __( 'Refunds', 'woocommerce-admin' ),
'coupons' => __( 'Coupons', 'woocommerce-admin' ),
'taxes' => __( 'Taxes', 'woocommerce-admin' ),
'shipping' => __( 'Shipping', 'woocommerce-admin' ),
'net_revenue' => __( 'Net Revenue', 'woocommerce-admin' ),
'date' => __( 'Date', 'woocommerce-admin' ),
'orders_count' => __( 'Orders', 'woocommerce-admin' ),
'total_sales' => __( 'Total Sales', 'woocommerce-admin' ),
'refunds' => __( 'Refunds', 'woocommerce-admin' ),
'coupons' => __( 'Coupons', 'woocommerce-admin' ),
'taxes' => __( 'Taxes', 'woocommerce-admin' ),
'shipping' => __( 'Shipping', 'woocommerce-admin' ),
'net_revenue' => __( 'Net Revenue', 'woocommerce-admin' ),
);
}
@ -459,14 +468,14 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
$subtotals = (array) $item['subtotals'];
return array(
'date' => $item['date_start'],
'orders_count' => $subtotals['orders_count'],
'gross_revenue' => self::csv_number_format( $subtotals['gross_revenue'] ),
'refunds' => self::csv_number_format( $subtotals['refunds'] ),
'coupons' => self::csv_number_format( $subtotals['coupons'] ),
'taxes' => self::csv_number_format( $subtotals['taxes'] ),
'shipping' => self::csv_number_format( $subtotals['shipping'] ),
'net_revenue' => self::csv_number_format( $subtotals['net_revenue'] ),
'date' => $item['date_start'],
'orders_count' => $subtotals['orders_count'],
'total_sales' => self::csv_number_format( $subtotals['total_sales'] ),
'refunds' => self::csv_number_format( $subtotals['refunds'] ),
'coupons' => self::csv_number_format( $subtotals['coupons'] ),
'taxes' => self::csv_number_format( $subtotals['taxes'] ),
'shipping' => self::csv_number_format( $subtotals['shipping'] ),
'net_revenue' => self::csv_number_format( $subtotals['net_revenue'] ),
);
}
}

View File

@ -106,7 +106,7 @@ class Segmenter {
}
unset( $segment_data[ $segment_dimension ] );
$segment_datum = array(
$segment_datum = array(
'segment_id' => $segment_id,
'segment_label' => $segment_labels[ $segment_id ],
'subtotals' => $segment_data,
@ -391,7 +391,7 @@ class Segmenter {
// This is to catch simple products with prior sales converted into variable products.
// See: https://github.com/woocommerce/woocommerce-admin/issues/2719.
if ( empty( $this->query_args['variations'] ) ) {
$parent_object = wc_get_product( $this->query_args['product_includes'][0] );
$parent_object = wc_get_product( $this->query_args['product_includes'][0] );
$segments[] = 0;
$segment_labels[0] = $parent_object->get_name();
}
@ -496,7 +496,6 @@ class Segmenter {
* @return array
*/
protected function fill_in_missing_segments( $segments ) {
$segment_subtotals = array();
if ( isset( $this->query_args['fields'] ) && is_array( $this->query_args['fields'] ) ) {
foreach ( $this->query_args['fields'] as $field ) {
@ -620,7 +619,9 @@ class Segmenter {
*/
public function get_totals_segments( $query_params, $table_name ) {
$segments = $this->get_segments( 'totals', $query_params, $table_name );
return $this->fill_in_missing_segments( $segments );
$segments = $this->fill_in_missing_segments( $segments );
return $segments;
}
/**
@ -632,6 +633,7 @@ class Segmenter {
*/
public function add_intervals_segments( &$data, $intervals_query, $table_name ) {
$intervals_segments = $this->get_segments( 'intervals', $intervals_query, $table_name );
$this->assign_segments_to_intervals( $data->intervals, $intervals_segments );
$this->fill_in_missing_interval_segments( $data );
}

View File

@ -350,7 +350,7 @@ class Controller extends \WC_REST_Reports_Controller {
'enum' => array(
'date',
'items_sold',
'gross_revenue',
'total_sales',
'orders_count',
'products_count',
),

View File

@ -17,11 +17,11 @@ use \Automattic\WooCommerce\Admin\API\Reports\Segmenter as ReportsSegmenter;
class Segmenter extends ReportsSegmenter {
/**
* Returns SELECT clause statements to be used for order-related order-level segmenting query (e.g. tax_rate_id).
* Returns column => query mapping to be used for order-related order-level segmenting query (e.g. tax_rate_id).
*
* @param string $lookup_table Name of SQL table containing the order-level segmenting info.
*
* @return string SELECT clause statements.
* @return array Column => SELECT query mapping.
*/
protected function get_segment_selections_order_level( $lookup_table ) {
$columns_mapping = array(
@ -32,7 +32,7 @@ class Segmenter extends ReportsSegmenter {
'orders_count' => "COUNT(DISTINCT $lookup_table.order_id) as orders_count",
);
return $this->prepare_selections( $columns_mapping );
return $columns_mapping;
}
/**
@ -140,8 +140,10 @@ class Segmenter extends ReportsSegmenter {
$segments = array();
if ( 'tax_rate_id' === $this->query_args['segmentby'] ) {
$segmenting_select = $this->get_segment_selections_order_level( $table_name );
$segmenting_groupby = $table_name . '.tax_rate_id';
$tax_rate_level_columns = $this->get_segment_selections_order_level( $table_name );
$segmenting_select = $this->prepare_selections( $tax_rate_level_columns );
$this->report_columns = $tax_rate_level_columns;
$segmenting_groupby = $table_name . '.tax_rate_id';
$segments = $this->get_order_related_segments( $type, $segmenting_select, $segmenting_from, $segmenting_where, $segmenting_groupby, $table_name, $query_params );
}

View File

@ -185,7 +185,7 @@ class Controller extends \WC_REST_Reports_Controller implements ExportableInterf
'type' => 'number',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Total net revenue of all items sold.', 'woocommerce-admin' ),
'description' => __( 'Total Net Sales of all items sold.', 'woocommerce-admin' ),
),
'orders_count' => array(
'type' => 'integer',

View File

@ -31,6 +31,10 @@ class Install {
'wc_admin_update_0201_order_status_index',
'wc_admin_update_0201_db_version',
),
'0.23.0' => array(
'wc_admin_update_0230_rename_gross_total',
'wc_admin_update_0230_db_version',
),
);
/**
@ -128,7 +132,7 @@ class Install {
date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
date_created_gmt datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
num_items_sold int(11) DEFAULT 0 NOT NULL,
gross_total double DEFAULT 0 NOT NULL,
total_sales double DEFAULT 0 NOT NULL,
tax_total double DEFAULT 0 NOT NULL,
shipping_total double DEFAULT 0 NOT NULL,
net_total double DEFAULT 0 NOT NULL,

View File

@ -54,7 +54,7 @@ class WC_Admin_Notes_New_Sales_Record {
return;
}
$yesterday = date( 'Y-m-d', current_time( 'timestamp', 0 ) - DAY_IN_SECONDS );
$yesterday = gmdate( 'Y-m-d', current_time( 'timestamp', 0 ) - DAY_IN_SECONDS );
$total = self::sum_sales_for_date( $yesterday );
// No sales yesterday? Bail.
@ -77,14 +77,14 @@ class WC_Admin_Notes_New_Sales_Record {
update_option( self::RECORD_DATE_OPTION_KEY, $yesterday );
update_option( self::RECORD_AMOUNT_OPTION_KEY, $total );
$formatted_yesterday = date( 'F jS', strtotime( $yesterday ) );
$formatted_yesterday = gmdate( 'F jS', strtotime( $yesterday ) );
$formatted_total = html_entity_decode( wp_strip_all_tags( wc_price( $total ) ) );
$formatted_record_date = date( 'F jS', strtotime( $record_date ) );
$formatted_record_date = gmdate( 'F jS', strtotime( $record_date ) );
$formatted_record_amt = html_entity_decode( wp_strip_all_tags( wc_price( $record_amt ) ) );
$content = sprintf(
/* translators: 1 and 4: Date (e.g. October 16th), 2 and 3: Amount (e.g. $160.00) */
__( 'Woohoo, %1$s was your record day for sales! Net revenue was %2$s beating the previous record of %3$s set on %4$s.', 'woocommerce-admin' ),
__( 'Woohoo, %1$s was your record day for sales! Net Sales was %2$s beating the previous record of %3$s set on %4$s.', 'woocommerce-admin' ),
$formatted_yesterday,
$formatted_total,
$formatted_record_amt,

View File

@ -97,8 +97,9 @@ class WC_Tests_API_Reports_Revenue_Stats extends WC_REST_Unit_Test_Case {
$this->assertArrayHasKey( 'intervals', $properties );
$totals = $properties['totals']['properties'];
$this->assertEquals( 11, count( $totals ) );
$this->assertArrayHasKey( 'gross_revenue', $totals );
$this->assertEquals( 12, count( $totals ) );
$this->assertArrayHasKey( 'gross_sales', $totals );
$this->assertArrayHasKey( 'total_sales', $totals );
$this->assertArrayHasKey( 'net_revenue', $totals );
$this->assertArrayHasKey( 'coupons', $totals );
$this->assertArrayHasKey( 'coupons_count', $totals );
@ -120,8 +121,9 @@ class WC_Tests_API_Reports_Revenue_Stats extends WC_REST_Unit_Test_Case {
$this->assertArrayHasKey( 'subtotals', $intervals );
$subtotals = $properties['intervals']['items']['properties']['subtotals']['properties'];
$this->assertEquals( 10, count( $subtotals ) );
$this->assertArrayHasKey( 'gross_revenue', $subtotals );
$this->assertEquals( 11, count( $subtotals ) );
$this->assertArrayHasKey( 'gross_sales', $subtotals );
$this->assertArrayHasKey( 'total_sales', $subtotals );
$this->assertArrayHasKey( 'net_revenue', $subtotals );
$this->assertArrayHasKey( 'coupons', $subtotals );
$this->assertArrayHasKey( 'coupons_count', $subtotals );

View File

@ -48,8 +48,8 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case {
// /reports/revenue/stats is mapped to Orders_Data_Store.
$data_store = new OrdersStatsDataStore();
$start_time = date( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() );
$end_time = date( 'Y-m-d H:59:59', $order->get_date_created()->getOffsetTimestamp() );
$start_time = gmdate( 'Y-m-d H:00:00', $order->get_date_created()->getOffsetTimestamp() );
$end_time = gmdate( 'Y-m-d H:59:59', $order->get_date_created()->getOffsetTimestamp() );
$args = array(
'interval' => 'hour',
@ -60,7 +60,8 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case {
'totals' => array(
'orders_count' => 1,
'num_items_sold' => 4,
'gross_revenue' => 97,
'total_sales' => 97,
'gross_sales' => 100,
'coupons' => 20,
'coupons_count' => 1,
'refunds' => 0,
@ -76,7 +77,7 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case {
),
'intervals' => array(
array(
'interval' => date( 'Y-m-d H', $order->get_date_created()->getTimestamp() ),
'interval' => gmdate( 'Y-m-d H', $order->get_date_created()->getTimestamp() ),
'date_start' => $start_time,
'date_start_gmt' => $start_time,
'date_end' => $end_time,
@ -84,7 +85,8 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case {
'subtotals' => array(
'orders_count' => 1,
'num_items_sold' => 4,
'gross_revenue' => 97,
'total_sales' => 97,
'gross_sales' => 100,
'coupons' => 20,
'coupons_count' => 1,
'refunds' => 0,
@ -112,7 +114,8 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case {
'totals' => array(
'orders_count' => 1,
'num_items_sold' => 4,
'gross_revenue' => 97,
'total_sales' => 97,
'gross_sales' => 100,
'coupons' => 20,
'coupons_count' => 1,
'refunds' => 0,
@ -124,7 +127,7 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case {
),
'intervals' => array(
array(
'interval' => date( 'Y-m-d H', $order->get_date_created()->getTimestamp() ),
'interval' => gmdate( 'Y-m-d H', $order->get_date_created()->getTimestamp() ),
'date_start' => $start_time,
'date_start_gmt' => $start_time,
'date_end' => $end_time,
@ -132,7 +135,8 @@ class WC_Admin_Tests_Reports_Revenue_Stats extends WC_Unit_Test_Case {
'subtotals' => array(
'orders_count' => 1,
'num_items_sold' => 4,
'gross_revenue' => 97,
'total_sales' => 97,
'gross_sales' => 100,
'coupons' => 20,
'coupons_count' => 1,
'refunds' => 0,