Add pricing tab for variations (#40642)
* Update blocks with postType context * Add tax class * Pass context into get_tax_class * Add parent option * Add changelog * Update changelog * Add isRequired attribute to regular price block for use in variations * Add additional condition to avoid error in date time picker * Add changelog * Fix lint errors
This commit is contained in:
parent
914a1dfd09
commit
98876f54d9
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: tweak
|
||||||
|
|
||||||
|
Small condition change in the date time picker to avoid edge case where inputControl is null.
|
|
@ -265,7 +265,11 @@ export const DateTimePickerControl = forwardRef(
|
||||||
}, [ onBlur ] );
|
}, [ onBlur ] );
|
||||||
|
|
||||||
const callOnBlurIfDropdownIsNotOpening = useCallback( ( willOpen ) => {
|
const callOnBlurIfDropdownIsNotOpening = useCallback( ( willOpen ) => {
|
||||||
if ( ! willOpen && typeof onBlurRef.current === 'function' ) {
|
if (
|
||||||
|
! willOpen &&
|
||||||
|
typeof onBlurRef.current === 'function' &&
|
||||||
|
inputControl.current
|
||||||
|
) {
|
||||||
// in case the component is blurred before a debounced
|
// in case the component is blurred before a debounced
|
||||||
// change has been processed, immediately set the input string
|
// change has been processed, immediately set the input string
|
||||||
// to the current value of the input field, so that
|
// to the current value of the input field, so that
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: update
|
||||||
|
|
||||||
|
Update pricing, and schedule field blocks to use postType context value.
|
|
@ -14,6 +14,10 @@
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isRequired": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"supports": {
|
"supports": {
|
||||||
|
|
|
@ -9,8 +9,12 @@ import { getNewPath } from '@woocommerce/navigation';
|
||||||
import { recordEvent } from '@woocommerce/tracks';
|
import { recordEvent } from '@woocommerce/tracks';
|
||||||
import { useInstanceId } from '@wordpress/compose';
|
import { useInstanceId } from '@wordpress/compose';
|
||||||
import { useEntityProp } from '@wordpress/core-data';
|
import { useEntityProp } from '@wordpress/core-data';
|
||||||
import { createElement, createInterpolateElement } from '@wordpress/element';
|
import {
|
||||||
import { __ } from '@wordpress/i18n';
|
createElement,
|
||||||
|
createInterpolateElement,
|
||||||
|
useEffect,
|
||||||
|
} from '@wordpress/element';
|
||||||
|
import { sprintf, __ } from '@wordpress/i18n';
|
||||||
import {
|
import {
|
||||||
BaseControl,
|
BaseControl,
|
||||||
// @ts-expect-error `__experimentalInputControl` does exist.
|
// @ts-expect-error `__experimentalInputControl` does exist.
|
||||||
|
@ -31,7 +35,7 @@ export function Edit( {
|
||||||
context,
|
context,
|
||||||
}: ProductEditorBlockEditProps< SalePriceBlockAttributes > ) {
|
}: ProductEditorBlockEditProps< SalePriceBlockAttributes > ) {
|
||||||
const blockProps = useWooBlockProps( attributes );
|
const blockProps = useWooBlockProps( attributes );
|
||||||
const { label, help } = attributes;
|
const { label, help, isRequired } = attributes;
|
||||||
const [ regularPrice, setRegularPrice ] = useEntityProp< string >(
|
const [ regularPrice, setRegularPrice ] = useEntityProp< string >(
|
||||||
'postType',
|
'postType',
|
||||||
context.postType || 'product',
|
context.postType || 'product',
|
||||||
|
@ -89,11 +93,20 @@ export function Edit( {
|
||||||
'woocommerce'
|
'woocommerce'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if ( isRequired ) {
|
||||||
|
/* translators: label of required field. */
|
||||||
|
return sprintf( __( '%s is required.', 'woocommerce' ), label );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ regularPrice, salePrice ]
|
[ regularPrice, salePrice ]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if ( isRequired ) {
|
||||||
|
validateRegularPrice();
|
||||||
|
}
|
||||||
|
}, [] );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...blockProps }>
|
<div { ...blockProps }>
|
||||||
<BaseControl
|
<BaseControl
|
||||||
|
|
|
@ -6,4 +6,5 @@ import { BlockAttributes } from '@wordpress/blocks';
|
||||||
export interface SalePriceBlockAttributes extends BlockAttributes {
|
export interface SalePriceBlockAttributes extends BlockAttributes {
|
||||||
label: string;
|
label: string;
|
||||||
help?: string;
|
help?: string;
|
||||||
|
isRequired?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,5 +25,6 @@
|
||||||
"lock": false,
|
"lock": false,
|
||||||
"__experimentalToolbar": false
|
"__experimentalToolbar": false
|
||||||
},
|
},
|
||||||
|
"usesContext": [ "postType" ],
|
||||||
"editorStyle": "file:./editor.css"
|
"editorStyle": "file:./editor.css"
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,17 +25,18 @@ import { ProductEditorBlockEditProps } from '../../../types';
|
||||||
export function Edit( {
|
export function Edit( {
|
||||||
attributes,
|
attributes,
|
||||||
clientId,
|
clientId,
|
||||||
|
context,
|
||||||
}: ProductEditorBlockEditProps< SalePriceBlockAttributes > ) {
|
}: ProductEditorBlockEditProps< SalePriceBlockAttributes > ) {
|
||||||
const blockProps = useWooBlockProps( attributes );
|
const blockProps = useWooBlockProps( attributes );
|
||||||
const { label, help } = attributes;
|
const { label, help } = attributes;
|
||||||
const [ regularPrice ] = useEntityProp< string >(
|
const [ regularPrice ] = useEntityProp< string >(
|
||||||
'postType',
|
'postType',
|
||||||
'product',
|
context.postType || 'product',
|
||||||
'regular_price'
|
'regular_price'
|
||||||
);
|
);
|
||||||
const [ salePrice, setSalePrice ] = useEntityProp< string >(
|
const [ salePrice, setSalePrice ] = useEntityProp< string >(
|
||||||
'postType',
|
'postType',
|
||||||
'product',
|
context.postType || 'product',
|
||||||
'sale_price'
|
'sale_price'
|
||||||
);
|
);
|
||||||
const inputProps = useCurrencyInputProps( {
|
const inputProps = useCurrencyInputProps( {
|
||||||
|
|
|
@ -22,5 +22,6 @@
|
||||||
"lock": false,
|
"lock": false,
|
||||||
"__experimentalToolbar": false
|
"__experimentalToolbar": false
|
||||||
},
|
},
|
||||||
"editorStyle": "file:./editor.css"
|
"editorStyle": "file:./editor.css",
|
||||||
|
"usesContext": [ "postType" ]
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { ProductEditorBlockEditProps } from '../../../types';
|
||||||
export function Edit( {
|
export function Edit( {
|
||||||
attributes,
|
attributes,
|
||||||
clientId,
|
clientId,
|
||||||
|
context,
|
||||||
}: ProductEditorBlockEditProps< ScheduleSalePricingBlockAttributes > ) {
|
}: ProductEditorBlockEditProps< ScheduleSalePricingBlockAttributes > ) {
|
||||||
const blockProps = useWooBlockProps( attributes );
|
const blockProps = useWooBlockProps( attributes );
|
||||||
const { hasEdit } = useProductEdits();
|
const { hasEdit } = useProductEdits();
|
||||||
|
@ -36,7 +37,7 @@ export function Edit( {
|
||||||
|
|
||||||
const [ salePrice ] = useEntityProp< string | null >(
|
const [ salePrice ] = useEntityProp< string | null >(
|
||||||
'postType',
|
'postType',
|
||||||
'product',
|
context.postType || 'product',
|
||||||
'sale_price'
|
'sale_price'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -45,11 +46,11 @@ export function Edit( {
|
||||||
|
|
||||||
const [ dateOnSaleFromGmt, setDateOnSaleFromGmt ] = useEntityProp<
|
const [ dateOnSaleFromGmt, setDateOnSaleFromGmt ] = useEntityProp<
|
||||||
string | null
|
string | null
|
||||||
>( 'postType', 'product', 'date_on_sale_from_gmt' );
|
>( 'postType', context.postType || 'product', 'date_on_sale_from_gmt' );
|
||||||
|
|
||||||
const [ dateOnSaleToGmt, setDateOnSaleToGmt ] = useEntityProp<
|
const [ dateOnSaleToGmt, setDateOnSaleToGmt ] = useEntityProp<
|
||||||
string | null
|
string | null
|
||||||
>( 'postType', 'product', 'date_on_sale_to_gmt' );
|
>( 'postType', context.postType || 'product', 'date_on_sale_to_gmt' );
|
||||||
|
|
||||||
const today = moment().startOf( 'minute' ).toISOString();
|
const today = moment().startOf( 'minute' ).toISOString();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: update
|
||||||
|
|
||||||
|
Update variation API to adhere tax class to context, and updated variation template to use tax class field.
|
|
@ -52,7 +52,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
||||||
'description' => __( 'Default values for generated variations.', 'woocommerce' ),
|
'description' => __( 'Default values for generated variations.', 'woocommerce' ),
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
'properties' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'methods' => WP_REST_Server::CREATABLE,
|
'methods' => WP_REST_Server::CREATABLE,
|
||||||
|
@ -99,7 +99,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
||||||
'download_limit' => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,
|
'download_limit' => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1,
|
||||||
'download_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,
|
'download_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1,
|
||||||
'tax_status' => $object->get_tax_status(),
|
'tax_status' => $object->get_tax_status(),
|
||||||
'tax_class' => $object->get_tax_class(),
|
'tax_class' => $object->get_tax_class( $context ),
|
||||||
'manage_stock' => $object->managing_stock(),
|
'manage_stock' => $object->managing_stock(),
|
||||||
'stock_quantity' => $object->get_stock_quantity(),
|
'stock_quantity' => $object->get_stock_quantity(),
|
||||||
'stock_status' => $object->get_stock_status(),
|
'stock_status' => $object->get_stock_status(),
|
||||||
|
@ -132,6 +132,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
||||||
* The dynamic portion of the hook name, $this->post_type,
|
* The dynamic portion of the hook name, $this->post_type,
|
||||||
* refers to object type being prepared for the response.
|
* refers to object type being prepared for the response.
|
||||||
*
|
*
|
||||||
|
* @since 4.5.0
|
||||||
* @param WP_REST_Response $response The response object.
|
* @param WP_REST_Response $response The response object.
|
||||||
* @param WC_Data $object Object data.
|
* @param WC_Data $object Object data.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
|
@ -350,6 +351,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
||||||
* The dynamic portion of the hook name, `$this->post_type`,
|
* The dynamic portion of the hook name, `$this->post_type`,
|
||||||
* refers to the object type slug.
|
* refers to the object type slug.
|
||||||
*
|
*
|
||||||
|
* @since 4.5.0
|
||||||
* @param WC_Data $variation Object object.
|
* @param WC_Data $variation Object object.
|
||||||
* @param WP_REST_Request $request Request object.
|
* @param WP_REST_Request $request Request object.
|
||||||
* @param bool $creating If is creating a new object.
|
* @param bool $creating If is creating a new object.
|
||||||
|
@ -410,6 +412,15 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
||||||
$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
|
$upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) );
|
||||||
|
|
||||||
if ( is_wp_error( $upload ) ) {
|
if ( is_wp_error( $upload ) ) {
|
||||||
|
/**
|
||||||
|
* Filter to check if it should supress the image upload error, false by default.
|
||||||
|
*
|
||||||
|
* @since 4.5.0
|
||||||
|
* @param bool false If it should suppress.
|
||||||
|
* @param array $upload Uploaded image array.
|
||||||
|
* @param int id Variation id.
|
||||||
|
* @param array Array of image to set.
|
||||||
|
*/
|
||||||
if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) {
|
if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) {
|
||||||
throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 );
|
throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 );
|
||||||
}
|
}
|
||||||
|
@ -1013,7 +1024,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
|
||||||
|
|
||||||
foreach ( $existing_variations as $existing_variation ) {
|
foreach ( $existing_variations as $existing_variation ) {
|
||||||
$matching_attribute_key = array_search( $existing_variation->get_attributes(), $possible_attribute_combinations ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
|
$matching_attribute_key = array_search( $existing_variation->get_attributes(), $possible_attribute_combinations ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||||
if ( $matching_attribute_key !== false ) {
|
if ( false !== $matching_attribute_key ) {
|
||||||
// We only want one possible variation for each possible attribute combination.
|
// We only want one possible variation for each possible attribute combination.
|
||||||
unset( $possible_attribute_combinations[ $matching_attribute_key ] );
|
unset( $possible_attribute_combinations[ $matching_attribute_key ] );
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -156,18 +156,6 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
|
||||||
*/
|
*/
|
||||||
private function add_pricing_group_blocks() {
|
private function add_pricing_group_blocks() {
|
||||||
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
|
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
|
||||||
$pricing_group->add_block(
|
|
||||||
[
|
|
||||||
'id' => 'pricing-has-variations-notice',
|
|
||||||
'blockName' => 'woocommerce/product-has-variations-notice',
|
|
||||||
'order' => 10,
|
|
||||||
'attributes' => [
|
|
||||||
'content' => __( 'This product has options, such as size or color. You can now manage each variation\'s price and other details individually.', 'woocommerce' ),
|
|
||||||
'buttonText' => __( 'Go to Variations', 'woocommerce' ),
|
|
||||||
'type' => 'info',
|
|
||||||
],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
// Product Pricing Section.
|
// Product Pricing Section.
|
||||||
$product_pricing_section = $pricing_group->add_section(
|
$product_pricing_section = $pricing_group->add_section(
|
||||||
[
|
[
|
||||||
|
@ -209,7 +197,8 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
|
||||||
'order' => 10,
|
'order' => 10,
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'name' => 'regular_price',
|
'name' => 'regular_price',
|
||||||
'label' => __( 'List price', 'woocommerce' ),
|
'label' => __( 'Regular price', 'woocommerce' ),
|
||||||
|
'isRequired' => true,
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -240,48 +229,12 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
|
||||||
'order' => 20,
|
'order' => 20,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$product_pricing_section->add_block(
|
$product_pricing_section->add_block(
|
||||||
[
|
|
||||||
'id' => 'product-sale-tax',
|
|
||||||
'blockName' => 'woocommerce/product-radio-field',
|
|
||||||
'order' => 30,
|
|
||||||
'attributes' => [
|
|
||||||
'title' => __( 'Charge sales tax on', 'woocommerce' ),
|
|
||||||
'property' => 'tax_status',
|
|
||||||
'options' => [
|
|
||||||
[
|
|
||||||
'label' => __( 'Product and shipping', 'woocommerce' ),
|
|
||||||
'value' => 'taxable',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => __( 'Only shipping', 'woocommerce' ),
|
|
||||||
'value' => 'shipping',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'label' => __( "Don't charge tax", 'woocommerce' ),
|
|
||||||
'value' => 'none',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
$pricing_advanced_block = $product_pricing_section->add_block(
|
|
||||||
[
|
|
||||||
'id' => 'product-pricing-advanced',
|
|
||||||
'blockName' => 'woocommerce/product-collapsible',
|
|
||||||
'order' => 40,
|
|
||||||
'attributes' => [
|
|
||||||
'toggleText' => __( 'Advanced', 'woocommerce' ),
|
|
||||||
'initialCollapsed' => true,
|
|
||||||
'persistRender' => true,
|
|
||||||
],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
$pricing_advanced_block->add_block(
|
|
||||||
[
|
[
|
||||||
'id' => 'product-tax-class',
|
'id' => 'product-tax-class',
|
||||||
'blockName' => 'woocommerce/product-radio-field',
|
'blockName' => 'woocommerce/product-radio-field',
|
||||||
'order' => 10,
|
'order' => 40,
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'title' => __( 'Tax class', 'woocommerce' ),
|
'title' => __( 'Tax class', 'woocommerce' ),
|
||||||
'description' => sprintf(
|
'description' => sprintf(
|
||||||
|
@ -292,6 +245,10 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
|
||||||
),
|
),
|
||||||
'property' => 'tax_class',
|
'property' => 'tax_class',
|
||||||
'options' => [
|
'options' => [
|
||||||
|
[
|
||||||
|
'label' => __( 'Same as main product', 'woocommerce' ),
|
||||||
|
'value' => 'parent',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'label' => __( 'Standard', 'woocommerce' ),
|
'label' => __( 'Standard', 'woocommerce' ),
|
||||||
'value' => '',
|
'value' => '',
|
||||||
|
|
Loading…
Reference in New Issue