Merge pull request woocommerce/woocommerce-admin#987 from woocommerce/add/stock-info-products-variations

Add stock info to Products Report
This commit is contained in:
Paul Sealock 2018-12-11 07:37:00 +13:00 committed by GitHub
commit 500f161798
10 changed files with 187 additions and 47 deletions

View File

@ -2,7 +2,7 @@
/**
* External dependencies
*/
import { __, _n } from '@wordpress/i18n';
import { __, _n, _x } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { map, get } from 'lodash';
@ -18,6 +18,7 @@ import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
*/
import ReportTable from 'analytics/components/report-table';
import { numberFormat } from 'lib/number';
import { isLowStock } from './utils';
export default class VariationsReportTable extends Component {
constructor() {
@ -27,13 +28,6 @@ export default class VariationsReportTable extends Component {
this.getRowsContent = this.getRowsContent.bind( this );
}
getVariationName( row ) {
const extendedInfo = get( row, 'extended_info', {} );
const attributes = get( extendedInfo, 'attributes', {} );
return extendedInfo.name + ' / ' + attributes.map( a => a.option ).join( ', ' );
}
getHeadersContent() {
return [
{
@ -82,15 +76,9 @@ export default class VariationsReportTable extends Component {
const persistedQuery = getPersistedQuery( query );
return map( data, row => {
const {
items_sold,
gross_revenue,
orders_count,
stock_status = 'outofstock',
stock_quantity = '0',
product_id,
} = row;
const name = this.getVariationName( row );
const { items_sold, gross_revenue, orders_count, extended_info, product_id } = row;
const { stock_status, stock_quantity, low_stock_amount } = extended_info;
const name = get( row, [ 'extended_info', 'name' ], '' ).replace( ' - ', ' / ' );
const ordersLink = getNewPath( persistedQuery, 'orders', {
filter: 'advanced',
product_includes: query.products,
@ -123,10 +111,12 @@ export default class VariationsReportTable extends Component {
value: orders_count,
},
{
display: (
display: isLowStock( stock_status, stock_quantity, low_stock_amount ) ? (
<Link href={ editPostLink } type="wp-admin">
{ stockStatuses[ stock_status ] }
{ _x( 'Low', 'Indication of a low quantity', 'wc-admin' ) }
</Link>
) : (
stockStatuses[ stock_status ]
),
value: stockStatuses[ stock_status ],
},

View File

@ -2,7 +2,7 @@
/**
* External dependencies
*/
import { __, _n } from '@wordpress/i18n';
import { __, _n, _x } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { map } from 'lodash';
@ -18,6 +18,7 @@ import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
*/
import ReportTable from 'analytics/components/report-table';
import { numberFormat } from 'lib/number';
import { isLowStock } from './utils';
export default class ProductsReportTable extends Component {
constructor() {
@ -97,10 +98,8 @@ export default class ProductsReportTable extends Component {
orders_count,
categories = [], // @TODO
variations = [], // @TODO
stock_status = 'outofstock', // @TODO
stock_quantity = '0', // @TODO
} = row;
const { name } = extended_info;
const { name, stock_status, stock_quantity, low_stock_amount } = extended_info;
const ordersLink = getNewPath( persistedQuery, 'orders', {
filter: 'advanced',
product_includes: product_id,
@ -150,10 +149,12 @@ export default class ProductsReportTable extends Component {
value: variations.length,
},
{
display: (
display: isLowStock( stock_status, stock_quantity, low_stock_amount ) ? (
<Link href={ 'post.php?action=edit&post=' + product_id } type="wp-admin">
{ stockStatuses[ stock_status ] }
{ _x( 'Low', 'Indication of a low quantity', 'wc-admin' ) }
</Link>
) : (
stockStatuses[ stock_status ]
),
value: stockStatuses[ stock_status ],
},

View File

@ -0,0 +1,15 @@
/**
* Determine if a product or variation is in low stock.
*
* @format
* @param {number} threshold - The number at which stock is determined to be low.
* @returns {boolean} - Whether or not the stock is low.
*/
export function isLowStock( status, quantity, threshold ) {
if ( ! quantity ) {
// Sites that don't do inventory tracking will always return false.
return false;
}
return 'instock' === status && quantity <= threshold;
}

View File

@ -175,6 +175,56 @@ class WC_Admin_REST_Reports_Products_Controller extends WC_REST_Reports_Controll
'context' => array( 'view', 'edit' ),
'description' => __( 'Number of orders product appeared in.', 'wc-admin' ),
),
'extended_info' => array(
'name' => array(
'type' => 'string',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product name.', 'wc-admin' ),
),
'price' => array(
'type' => 'number',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product price.', 'wc-admin' ),
),
'image' => array(
'type' => 'string',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product image.', 'wc-admin' ),
),
'permalink' => array(
'type' => 'string',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product link.', 'wc-admin' ),
),
'attributes' => array(
'type' => 'array',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product attributes.', 'wc-admin' ),
),
'stock_status' => array(
'type' => 'string',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product inventory status.', 'wc-admin' ),
),
'stock_quantity' => array(
'type' => 'integer',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product inventory quantity.', 'wc-admin' ),
),
'low_stock_amount' => array(
'type' => 'integer',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product inventory threshold for low stock.', 'wc-admin' ),
),
),
),
);

View File

@ -31,6 +31,15 @@ class WC_Admin_REST_Reports_Variations_Controller extends WC_REST_Reports_Contro
*/
protected $rest_base = 'reports/variations';
/**
* Mapping between external parameter name and name used in query class.
*
* @var array
*/
protected $param_mapping = array(
'products' => 'product_includes',
);
/**
* Get items.
*
@ -43,7 +52,11 @@ class WC_Admin_REST_Reports_Variations_Controller extends WC_REST_Reports_Contro
$registered = array_keys( $this->get_collection_params() );
foreach ( $registered as $param_name ) {
if ( isset( $request[ $param_name ] ) ) {
$args[ $param_name ] = $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 ];
}
}
}
@ -202,6 +215,24 @@ class WC_Admin_REST_Reports_Variations_Controller extends WC_REST_Reports_Contro
'context' => array( 'view', 'edit' ),
'description' => __( 'Product attributes.', 'wc-admin' ),
),
'stock_status' => array(
'type' => 'string',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product inventory status.', 'wc-admin' ),
),
'stock_quantity' => array(
'type' => 'integer',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product inventory quantity.', 'wc-admin' ),
),
'low_stock_amount' => array(
'type' => 'integer',
'readonly' => true,
'context' => array( 'view', 'edit' ),
'description' => __( 'Product inventory threshold for low stock.', 'wc-admin' ),
),
),
),
);

View File

@ -60,6 +60,9 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
'price',
'image',
'permalink',
'stock_status',
'stock_quantity',
'low_stock_amount',
);
/**
@ -154,6 +157,10 @@ class WC_Admin_Reports_Products_Data_Store extends WC_Admin_Reports_Data_Store i
$extended_info[ $extended_attribute ] = $value;
}
}
// If there is no set low_stock_amount, use the one in user settings.
if ( '' === $extended_info['low_stock_amount'] ) {
$extended_info['low_stock_amount'] = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
}
$extended_info = $this->cast_numbers( $extended_info );
}
$products_data[ $key ]['extended_info'] = $extended_info;

View File

@ -61,6 +61,9 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
'price',
'image',
'permalink',
'stock_status',
'stock_quantity',
'low_stock_amount',
);
@ -123,20 +126,23 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
foreach ( $products_data as $key => $product_data ) {
$extended_info = new ArrayObject();
if ( $query_args['extended_info'] ) {
$extended_attributes = apply_filters( 'woocommerce_rest_reports_variations_extended_attributes', $this->extended_attributes, $product_data );
$product = wc_get_product( $product_data['product_id'] );
$extended_attributes = apply_filters( 'woocommerce_rest_reports_products_extended_attributes', $this->extended_attributes, $product_data );
foreach ( $extended_attributes as $extended_attribute ) {
$function = 'get_' . $extended_attribute;
if ( is_callable( array( $product, $function ) ) && 'get_price' !== $function ) {
$value = $product->{$function}();
$extended_info[ $extended_attribute ] = $value;
}
$variations = array();
if ( method_exists( $product, 'get_available_variations' ) ) {
$variations = $product->get_available_variations();
}
$variations = $product->get_available_variations();
foreach ( $variations as $variation ) {
if ( (int) $variation['variation_id'] === (int) $product_data['variation_id'] ) {
$attributes = array();
$variation_product = wc_get_product( $variation['variation_id'] );
foreach ( $extended_attributes as $extended_attribute ) {
$function = 'get_' . $extended_attribute;
if ( is_callable( array( $variation_product, $function ) ) ) {
$value = $variation_product->{$function}();
$extended_info[ $extended_attribute ] = $value;
}
}
foreach ( $variation['attributes'] as $attribute_name => $attribute ) {
$name = str_replace( 'attribute_', '', $attribute_name );
$option_term = get_term_by( 'slug', $attribute, $name );
@ -147,9 +153,12 @@ class WC_Admin_Reports_Variations_Data_Store extends WC_Admin_Reports_Data_Store
);
}
$extended_info['attributes'] = $attributes;
$extended_info['price'] = $variation_product->get_price();
}
}
// If there is no set low_stock_amount, use the one in user settings.
if ( '' === $extended_info['low_stock_amount'] ) {
$extended_info['low_stock_amount'] = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
}
$extended_info = $this->cast_numbers( $extended_info );
}
$products_data[ $key ]['extended_info'] = $extended_info;

View File

@ -5,6 +5,13 @@
* @package WooCommerce\Tests\API
* @since 3.5.0
*/
/**
* Reports Products REST API Test Class
*
* @package WooCommerce\Tests\API
* @since 3.5.0
*/
class WC_Tests_API_Reports_Products extends WC_REST_Unit_Test_Case {
/**
@ -99,10 +106,11 @@ class WC_Tests_API_Reports_Products extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 4, count( $properties ) );
$this->assertEquals( 5, count( $properties ) );
$this->assertArrayHasKey( 'product_id', $properties );
$this->assertArrayHasKey( 'items_sold', $properties );
$this->assertArrayHasKey( 'gross_revenue', $properties );
$this->assertArrayHasKey( 'orders_count', $properties );
$this->assertArrayHasKey( 'extended_info', $properties );
}
}

View File

@ -1,11 +1,17 @@
<?php
/**
* Reports product stats tests.
*
* @package WooCommerce\Tests\Orders
* @todo Finish up unit testing to verify bug-free product reports.
*/
/**
* Reports product stats tests class
*
* @package WooCommerce\Tests\Orders
* @todo Finish up unit testing to verify bug-free product reports.
*/
class WC_Tests_Reports_Products extends WC_Unit_Test_Case {
/**
@ -63,6 +69,11 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case {
$this->assertEquals( $expected_data, $query->get_data() );
}
/**
* Test the ordering of results by product name
*
* @since 3.5.0
*/
public function test_order_by_product_name() {
WC_Helper_Reports::reset_stats_dbs();
@ -183,7 +194,11 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case {
$product = new WC_Product_Simple();
$product->set_name( 'Test Product' );
$product->set_regular_price( 25 );
$product->set_manage_stock( true );
$product->set_stock_quantity( 25 );
$product->set_low_stock_amount( 5 );
$product->save();
$order = WC_Helper_Order::create_order( 1, $product );
$order->set_status( 'completed' );
$order->set_shipping_total( 10 );
@ -214,10 +229,13 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case {
'gross_revenue' => 100.0, // $25 * 4.
'orders_count' => 1,
'extended_info' => array(
'name' => $product->get_name(),
'image' => $product->get_image(),
'permalink' => $product->get_permalink(),
'price' => (float) $product->get_price(),
'name' => $product->get_name(),
'image' => $product->get_image(),
'permalink' => $product->get_permalink(),
'price' => (float) $product->get_price(),
'stock_status' => $product->get_stock_status(),
'stock_quantity' => $product->get_stock_quantity() - 4, // subtract the ones purchased.
'low_stock_amount' => $product->get_low_stock_amount(),
),
),
),

View File

@ -1,11 +1,17 @@
<?php
/**
* Reports order stats tests.
*
* @package WooCommerce\Tests\Orders
* @todo Finish up unit testing to verify bug-free order reports.
*/
/**
* Reports order stats tests class.
*
* @package WooCommerce\Tests\Orders
* @todo Finish up unit testing to verify bug-free order reports.
*/
class WC_Tests_Reports_Variations extends WC_Unit_Test_Case {
/**
@ -91,6 +97,8 @@ class WC_Tests_Reports_Variations extends WC_Unit_Test_Case {
$variation->set_parent_id( $product->get_id() );
$variation->set_regular_price( 10 );
$variation->set_attributes( array( 'pa_color' => 'green' ) );
$variation->set_manage_stock( true );
$variation->set_stock_quantity( 25 );
$variation->save();
$order = WC_Helper_Order::create_order( 1, $variation );
@ -120,11 +128,14 @@ class WC_Tests_Reports_Variations extends WC_Unit_Test_Case {
'gross_revenue' => 40.0, // $10 * 4.
'orders_count' => 1,
'extended_info' => array(
'name' => $product->get_name(),
'image' => $variation->get_image(),
'permalink' => $product->get_permalink(),
'price' => (float) $variation->get_price(),
'attributes' => array(
'name' => $variation->get_name(),
'image' => $variation->get_image(),
'permalink' => $variation->get_permalink(),
'price' => (float) $variation->get_price(),
'stock_status' => $variation->get_stock_status(),
'stock_quantity' => $variation->get_stock_quantity() - 4, // subtract the ones purchased.
'low_stock_amount' => get_option( 'woocommerce_notify_low_stock_amount' ),
'attributes' => array(
0 => array(
'id' => 0,
'name' => 'color',