From 98876f54d9a86ae7fa11c64c371b75d55fcaca8e Mon Sep 17 00:00:00 2001 From: louwie17 Date: Tue, 10 Oct 2023 08:55:05 -0300 Subject: [PATCH] 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 --- .../add-40593_pricing_tab_for_variations | 4 ++ .../date-time-picker-control.tsx | 6 +- .../add-40593_pricing_tab_for_variations | 4 ++ .../product-fields/regular-price/block.json | 4 ++ .../product-fields/regular-price/edit.tsx | 19 +++++- .../product-fields/regular-price/types.ts | 1 + .../product-fields/sale-price/block.json | 1 + .../blocks/product-fields/sale-price/edit.tsx | 5 +- .../product-fields/schedule-sale/block.json | 3 +- .../product-fields/schedule-sale/edit.tsx | 7 ++- .../add-40593_pricing_tab_for_variations | 4 ++ ...-wc-rest-product-variations-controller.php | 43 ++++++++----- .../ProductVariationTemplate.php | 61 +++---------------- 13 files changed, 84 insertions(+), 78 deletions(-) create mode 100644 packages/js/components/changelog/add-40593_pricing_tab_for_variations create mode 100644 packages/js/product-editor/changelog/add-40593_pricing_tab_for_variations create mode 100644 plugins/woocommerce/changelog/add-40593_pricing_tab_for_variations diff --git a/packages/js/components/changelog/add-40593_pricing_tab_for_variations b/packages/js/components/changelog/add-40593_pricing_tab_for_variations new file mode 100644 index 00000000000..58dca500033 --- /dev/null +++ b/packages/js/components/changelog/add-40593_pricing_tab_for_variations @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Small condition change in the date time picker to avoid edge case where inputControl is null. diff --git a/packages/js/components/src/date-time-picker-control/date-time-picker-control.tsx b/packages/js/components/src/date-time-picker-control/date-time-picker-control.tsx index c414f3f09c7..82ed87d32b5 100644 --- a/packages/js/components/src/date-time-picker-control/date-time-picker-control.tsx +++ b/packages/js/components/src/date-time-picker-control/date-time-picker-control.tsx @@ -265,7 +265,11 @@ export const DateTimePickerControl = forwardRef( }, [ onBlur ] ); 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 // change has been processed, immediately set the input string // to the current value of the input field, so that diff --git a/packages/js/product-editor/changelog/add-40593_pricing_tab_for_variations b/packages/js/product-editor/changelog/add-40593_pricing_tab_for_variations new file mode 100644 index 00000000000..ed24753f879 --- /dev/null +++ b/packages/js/product-editor/changelog/add-40593_pricing_tab_for_variations @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update pricing, and schedule field blocks to use postType context value. diff --git a/packages/js/product-editor/src/blocks/product-fields/regular-price/block.json b/packages/js/product-editor/src/blocks/product-fields/regular-price/block.json index 502d7fb5cfd..b8e759226e7 100644 --- a/packages/js/product-editor/src/blocks/product-fields/regular-price/block.json +++ b/packages/js/product-editor/src/blocks/product-fields/regular-price/block.json @@ -14,6 +14,10 @@ }, "help": { "type": "string" + }, + "isRequired": { + "type": "boolean", + "default": false } }, "supports": { diff --git a/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx index 8df18197f31..0762b954fd9 100644 --- a/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx @@ -9,8 +9,12 @@ import { getNewPath } from '@woocommerce/navigation'; import { recordEvent } from '@woocommerce/tracks'; import { useInstanceId } from '@wordpress/compose'; import { useEntityProp } from '@wordpress/core-data'; -import { createElement, createInterpolateElement } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { + createElement, + createInterpolateElement, + useEffect, +} from '@wordpress/element'; +import { sprintf, __ } from '@wordpress/i18n'; import { BaseControl, // @ts-expect-error `__experimentalInputControl` does exist. @@ -31,7 +35,7 @@ export function Edit( { context, }: ProductEditorBlockEditProps< SalePriceBlockAttributes > ) { const blockProps = useWooBlockProps( attributes ); - const { label, help } = attributes; + const { label, help, isRequired } = attributes; const [ regularPrice, setRegularPrice ] = useEntityProp< string >( 'postType', context.postType || 'product', @@ -89,11 +93,20 @@ export function Edit( { 'woocommerce' ); } + } else if ( isRequired ) { + /* translators: label of required field. */ + return sprintf( __( '%s is required.', 'woocommerce' ), label ); } }, [ regularPrice, salePrice ] ); + useEffect( () => { + if ( isRequired ) { + validateRegularPrice(); + } + }, [] ); + return (
) { const blockProps = useWooBlockProps( attributes ); const { label, help } = attributes; const [ regularPrice ] = useEntityProp< string >( 'postType', - 'product', + context.postType || 'product', 'regular_price' ); const [ salePrice, setSalePrice ] = useEntityProp< string >( 'postType', - 'product', + context.postType || 'product', 'sale_price' ); const inputProps = useCurrencyInputProps( { diff --git a/packages/js/product-editor/src/blocks/product-fields/schedule-sale/block.json b/packages/js/product-editor/src/blocks/product-fields/schedule-sale/block.json index d54f9354f0f..fe4a0d8dc6f 100644 --- a/packages/js/product-editor/src/blocks/product-fields/schedule-sale/block.json +++ b/packages/js/product-editor/src/blocks/product-fields/schedule-sale/block.json @@ -22,5 +22,6 @@ "lock": false, "__experimentalToolbar": false }, - "editorStyle": "file:./editor.css" + "editorStyle": "file:./editor.css", + "usesContext": [ "postType" ] } diff --git a/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx index 9f792134a22..ef0a6b438d9 100644 --- a/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx @@ -26,6 +26,7 @@ import { ProductEditorBlockEditProps } from '../../../types'; export function Edit( { attributes, clientId, + context, }: ProductEditorBlockEditProps< ScheduleSalePricingBlockAttributes > ) { const blockProps = useWooBlockProps( attributes ); const { hasEdit } = useProductEdits(); @@ -36,7 +37,7 @@ export function Edit( { const [ salePrice ] = useEntityProp< string | null >( 'postType', - 'product', + context.postType || 'product', 'sale_price' ); @@ -45,11 +46,11 @@ export function Edit( { const [ dateOnSaleFromGmt, setDateOnSaleFromGmt ] = useEntityProp< string | null - >( 'postType', 'product', 'date_on_sale_from_gmt' ); + >( 'postType', context.postType || 'product', 'date_on_sale_from_gmt' ); const [ dateOnSaleToGmt, setDateOnSaleToGmt ] = useEntityProp< string | null - >( 'postType', 'product', 'date_on_sale_to_gmt' ); + >( 'postType', context.postType || 'product', 'date_on_sale_to_gmt' ); const today = moment().startOf( 'minute' ).toISOString(); diff --git a/plugins/woocommerce/changelog/add-40593_pricing_tab_for_variations b/plugins/woocommerce/changelog/add-40593_pricing_tab_for_variations new file mode 100644 index 00000000000..849a299e2b8 --- /dev/null +++ b/plugins/woocommerce/changelog/add-40593_pricing_tab_for_variations @@ -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. diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php index 4b4c21feece..349403e5ca5 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php @@ -40,19 +40,19 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V '/' . $this->rest_base . '/generate', array( 'args' => array( - 'product_id' => array( + 'product_id' => array( 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ), 'type' => 'integer', ), - 'delete' => array( + 'delete' => array( 'description' => __( 'Deletes unused variations.', 'woocommerce' ), 'type' => 'boolean', ), 'default_values' => array( 'description' => __( 'Default values for generated variations.', 'woocommerce' ), - 'type' => 'object', - 'properties' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ) + 'type' => 'object', + 'properties' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), ), array( '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_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1, 'tax_status' => $object->get_tax_status(), - 'tax_class' => $object->get_tax_class(), + 'tax_class' => $object->get_tax_class( $context ), 'manage_stock' => $object->managing_stock(), 'stock_quantity' => $object->get_stock_quantity(), '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, * refers to object type being prepared for the response. * + * @since 4.5.0 * @param WP_REST_Response $response The response object. * @param WC_Data $object Object data. * @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`, * refers to the object type slug. * + * @since 4.5.0 * @param WC_Data $variation Object object. * @param WP_REST_Request $request Request 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'] ) ); 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 ) ) ) { throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 ); } @@ -648,7 +659,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'low_stock_amount' => array( + 'low_stock_amount' => array( 'description' => __( 'Low Stock amount for the variation.', 'woocommerce' ), 'type' => array( 'integer', 'null' ), 'context' => array( 'view', 'edit' ), @@ -971,16 +982,16 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V ); $params['attributes'] = array( - 'description' => __( 'Limit result set to products with specified attributes.', 'woocommerce' ), - 'type' => 'array', - 'items' => array( - 'type' => 'object', + 'description' => __( 'Limit result set to products with specified attributes.', 'woocommerce' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', 'properties' => array( - 'attribute' => array( + 'attribute' => array( 'type' => 'string', 'description' => __( 'Attribute slug.', 'woocommerce' ), ), - 'term' => array( + 'term' => array( 'type' => 'string', 'description' => __( 'Attribute term.', 'woocommerce' ), ), @@ -1013,7 +1024,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V foreach ( $existing_variations as $existing_variation ) { $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. unset( $possible_attribute_combinations[ $matching_attribute_key ] ); continue; @@ -1043,12 +1054,12 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V $response = array(); $product = wc_get_product( $product_id ); - $default_values = isset( $request['default_values'] ) ? $request['default_values'] : array(); + $default_values = isset( $request['default_values'] ) ? $request['default_values'] : array(); $data_store = $product->get_data_store(); $response['count'] = $data_store->create_all_product_variations( $product, Constants::get_constant( 'WC_MAX_LINKED_VARIATIONS' ), $default_values ); if ( isset( $request['delete'] ) && $request['delete'] ) { - $deleted_count = $this->delete_unmatched_product_variations( $product ); + $deleted_count = $this->delete_unmatched_product_variations( $product ); $response['deleted_count'] = $deleted_count; } diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php index f455bc6158c..c7342166061 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php @@ -156,18 +156,6 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr */ private function add_pricing_group_blocks() { $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 = $pricing_group->add_section( [ @@ -208,8 +196,9 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr 'blockName' => 'woocommerce/product-regular-price-field', 'order' => 10, 'attributes' => [ - 'name' => 'regular_price', - 'label' => __( 'List price', 'woocommerce' ), + 'name' => 'regular_price', + 'label' => __( 'Regular price', 'woocommerce' ), + 'isRequired' => true, ], ] ); @@ -240,48 +229,12 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr 'order' => 20, ] ); + $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', 'blockName' => 'woocommerce/product-radio-field', - 'order' => 10, + 'order' => 40, 'attributes' => [ 'title' => __( 'Tax class', 'woocommerce' ), 'description' => sprintf( @@ -292,6 +245,10 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr ), 'property' => 'tax_class', 'options' => [ + [ + 'label' => __( 'Same as main product', 'woocommerce' ), + 'value' => 'parent', + ], [ 'label' => __( 'Standard', 'woocommerce' ), 'value' => '',