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:
commit
500f161798
|
@ -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 ],
|
||||
},
|
||||
|
|
|
@ -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 ],
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -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,9 +52,13 @@ 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 ] ) ) {
|
||||
if ( isset( $this->param_mapping[ $param_name ] ) ) {
|
||||
$args[ $this->param_mapping[ $param_name ] ] = $request[ $param_name ];
|
||||
} else {
|
||||
$args[ $param_name ] = $request[ $param_name ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$reports = new WC_Admin_Reports_Variations_Query( $args );
|
||||
$products_data = $reports->get_data();
|
||||
|
@ -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' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
@ -218,6 +233,9 @@ class WC_Tests_Reports_Products extends WC_Unit_Test_Case {
|
|||
'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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,10 +128,13 @@ 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(),
|
||||
'name' => $variation->get_name(),
|
||||
'image' => $variation->get_image(),
|
||||
'permalink' => $product->get_permalink(),
|
||||
'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,
|
||||
|
|
Loading…
Reference in New Issue