Handle taxes not being enabled and customized tax classes (#45531)

* Create and register the woocommerce/product-select-field block

* Replace the tax class radio group block by woocommerce/product-select-field block

* Hide tax fields when taxes are disabled

* Hide tax fields when taxes are disabled in product variations

* Add changelog files

* Fix linter errors
This commit is contained in:
Maikel Perez 2024-03-18 15:09:03 -03:00 committed by GitHub
parent ee044a7b50
commit dea68ee3e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 404 additions and 120 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Create woocommerce/product-select-field block

View File

@ -0,0 +1,98 @@
# woocommerce/product-select-field
A reusable select field for the product editor.
## Attributes
### label
- **Type:** `String`
- **Required:** `Yes`
Label that appears on top of the field.
### property
- **Type:** `String`
- **Required:** `Yes`
Property in which the value is stored.
### help
- **Type:** `String`
- **Required:** `No`
Help text that appears below the field.
### multiple
- **Type:** `Boolean`
- **Required:** `No`
Indicates where the select is of multiple choices or not.
### placeholder
- **Type:** `String`
- **Required:** `No`
Placeholder text that appears in the field when it's empty.
### disabled
- **Type:** `Boolean`
- **Required:** `No`
Indicates and enforces that the field is disabled.
### options
- **Type:** `Array`
- **Items:** `Object`
- `value`
- **Type:** `String`
- **Required:** `Yes`
- `label`
- **Type:** `String`
- **Required:** `Yes`
- `disabled`
- **Type:** `Boolean`
- **Required:** `No`
- **Required:** `No`
Refers to the options of the select field.
## Usage
Here's a snippet that adds tax classes as options to the
single selection field:
```php
$section->add_block(
array(
'id' => 'unique-block-id',
'blockName' => 'woocommerce/product-select-field',
'order' => 13,
'attributes' => array(
'label' => 'Tax class',
'property' => 'tax_class',
'help' => 'Apply a tax rate if this product qualifies for tax reduction or exemption.',
'options' => array(
array(
'value' => 'Standard rate',
'label' => '',
),
array(
'value' => 'Reduced rate',
'label' => 'reduced-rate',
),
array(
'value' => 'Zero rate',
'label' => 'zero-rate',
),
),
),
)
);
```

View File

@ -0,0 +1,61 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-select-field",
"title": "Product select field",
"category": "woocommerce",
"description": "A select field for use in the product editor.",
"keywords": [ "products", "select" ],
"textdomain": "default",
"attributes": {
"label": {
"type": "string",
"__experimentalRole": "content"
},
"property": {
"type": "string"
},
"placeholder": {
"type": "string"
},
"help": {
"type": "string"
},
"disabled": {
"type": "boolean"
},
"multiple": {
"type": "boolean",
"default": false
},
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"value": {
"type": "string"
},
"disabled": {
"type": "boolean",
"default": false
}
}
},
"default": []
}
},
"supports": {
"align": false,
"html": false,
"multiple": true,
"reusable": false,
"inserter": false,
"lock": false,
"__experimentalToolbar": false
},
"usesContext": [ "postType" ]
}

View File

@ -0,0 +1,53 @@
/**
* External dependencies
*/
import { useWooBlockProps } from '@woocommerce/block-templates';
import { SelectControl } from '@wordpress/components';
import { createElement } from '@wordpress/element';
/**
* Internal dependencies
*/
import useProductEntityProp from '../../../hooks/use-product-entity-prop';
import { sanitizeHTML } from '../../../utils/sanitize-html';
import type { ProductEditorBlockEditProps } from '../../../types';
import type { SelectBlockAttributes } from './types';
export function Edit( {
attributes,
context: { postType },
}: ProductEditorBlockEditProps< SelectBlockAttributes > ) {
const blockProps = useWooBlockProps( attributes );
const { property, label, placeholder, help, disabled, options, multiple } =
attributes;
const [ value, setValue ] = useProductEntityProp< string | string[] >(
property,
{
postType,
fallbackValue: '',
}
);
function renderHelp() {
if ( help ) {
return <span dangerouslySetInnerHTML={ sanitizeHTML( help ) } />;
}
}
return (
<div { ...blockProps }>
<SelectControl
value={ value }
disabled={ disabled }
label={ label }
onChange={ setValue }
help={ renderHelp() }
placeholder={ placeholder }
options={ options }
multiple={ multiple as never }
/>
</div>
);
}

View File

@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { registerProductEditorBlockType } from '../../../utils';
/**
* Internal dependencies
*/
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { SelectBlockAttributes } from './types';
const { name, ...metadata } =
blockConfiguration as BlockConfiguration< SelectBlockAttributes >;
export { metadata, name };
export const settings = {
example: {},
edit: Edit,
};
export const init = () =>
registerProductEditorBlockType( {
name,
metadata: metadata as never,
settings: settings as never,
} );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import type { BlockAttributes } from '@wordpress/blocks';
import { SelectControl } from '@wordpress/components';
export interface SelectBlockAttributes extends BlockAttributes {
property: string;
label: string;
help?: string;
placeholder?: string;
disabled?: boolean;
multiple?: boolean;
options?: SelectControl.Option[];
}

View File

@ -39,3 +39,4 @@ export { init as initText } from './generic/text';
export { init as initNumber } from './generic/number';
export { init as initLinkedProductList } from './generic/linked-product-list';
export { init as initTextArea } from './generic/text-area';
export { init as initSelect } from './generic/select';

View File

@ -3,17 +3,10 @@
*/
import classNames from 'classnames';
import { useWooBlockProps } from '@woocommerce/block-templates';
import { Link } from '@woocommerce/components';
import { Product } from '@woocommerce/data';
import { getNewPath } from '@woocommerce/navigation';
import { recordEvent } from '@woocommerce/tracks';
import { useInstanceId } from '@wordpress/compose';
import { useEntityProp } from '@wordpress/core-data';
import {
createElement,
createInterpolateElement,
useEffect,
} from '@wordpress/element';
import { createElement, useEffect } from '@wordpress/element';
import { sprintf, __ } from '@wordpress/i18n';
import {
BaseControl,
@ -24,11 +17,12 @@ import {
/**
* Internal dependencies
*/
import { Label } from '../../../components/label/label';
import { useValidation } from '../../../contexts/validation-context';
import { useCurrencyInputProps } from '../../../hooks/use-currency-input-props';
import { SalePriceBlockAttributes } from './types';
import { ProductEditorBlockEditProps } from '../../../types';
import { Label } from '../../../components/label/label';
import { sanitizeHTML } from '../../../utils/sanitize-html';
import type { ProductEditorBlockEditProps } from '../../../types';
import type { SalePriceBlockAttributes } from './types';
export function Edit( {
attributes,
@ -52,18 +46,11 @@ export function Edit( {
onChange: setRegularPrice,
} );
const interpolatedHelp = help
? createInterpolateElement( help, {
PricingTab: (
<Link
href={ getNewPath( { tab: 'pricing' } ) }
onClick={ () => {
recordEvent( 'product_pricing_help_click' );
} }
/>
),
} )
: null;
function renderHelp() {
if ( help ) {
return <span dangerouslySetInnerHTML={ sanitizeHTML( help ) } />;
}
}
const regularPriceId = useInstanceId(
BaseControl,
@ -115,7 +102,7 @@ export function Edit( {
help={
regularPriceValidationError
? regularPriceValidationError
: interpolatedHelp
: renderHelp()
}
className={ classNames( {
'has-error': regularPriceValidationError,

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Hide tax fields when taxes are disabled in product and variations

View File

@ -41,6 +41,7 @@ class BlockRegistry {
'woocommerce/product-text-area-field',
'woocommerce/product-number-field',
'woocommerce/product-linked-list-field',
'woocommerce/product-select-field',
);
/**

View File

@ -194,6 +194,8 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
* Adds the pricing group blocks to the template.
*/
private function add_pricing_group_blocks() {
$is_calc_taxes_enabled = wc_tax_enabled();
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
$pricing_group->add_block(
array(
@ -251,6 +253,12 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
'name' => 'regular_price',
'label' => __( 'Regular price', 'woocommerce' ),
'isRequired' => true,
'help' => $is_calc_taxes_enabled ? null : sprintf(
/* translators: %1$s: store settings link opening tag. %2$s: store settings link closing tag.*/
__( 'Per your %1$sstore settings%2$s, taxes are not enabled.', 'woocommerce' ),
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=general' ) . '" target="_blank" rel="noreferrer">',
'</a>'
),
),
)
);
@ -282,41 +290,26 @@ class ProductVariationTemplate extends AbstractProductFormTemplate implements Pr
)
);
$product_pricing_section->add_block(
array(
'id' => 'product-tax-class',
'blockName' => 'woocommerce/product-radio-field',
'order' => 40,
'attributes' => array(
'title' => __( 'Tax class', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s.', 'woocommerce' ),
'<a href="https://woo.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
'</a>'
if ( $is_calc_taxes_enabled ) {
$product_pricing_section->add_block(
array(
'id' => 'product-tax-class',
'blockName' => 'woocommerce/product-select-field',
'order' => 40,
'attributes' => array(
'label' => __( 'Tax class', 'woocommerce' ),
'help' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s', 'woocommerce' ),
'<a href="https://woo.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
'</a>'
),
'property' => 'tax_class',
'options' => SimpleProductTemplate::get_tax_classes( 'product_variation' ),
),
'property' => 'tax_class',
'options' => array(
array(
'label' => __( 'Same as main product', 'woocommerce' ),
'value' => 'parent',
),
array(
'label' => __( 'Standard', 'woocommerce' ),
'value' => '',
),
array(
'label' => __( 'Reduced rate', 'woocommerce' ),
'value' => 'reduced-rate',
),
array(
'label' => __( 'Zero rate', 'woocommerce' ),
'value' => 'zero-rate',
),
),
),
)
);
)
);
}
}
/**

View File

@ -7,6 +7,7 @@ namespace Automattic\WooCommerce\Internal\Features\ProductBlockEditor\ProductTem
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductFormTemplateInterface;
use WC_Tax;
/**
* Simple Product Template.
@ -548,6 +549,8 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
* Adds the pricing group blocks to the template.
*/
private function add_pricing_group_blocks() {
$is_calc_taxes_enabled = wc_tax_enabled();
$pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] );
$pricing_group->add_block(
array(
@ -603,6 +606,12 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
'attributes' => array(
'name' => 'regular_price',
'label' => __( 'List price', 'woocommerce' ),
'help' => $is_calc_taxes_enabled ? null : sprintf(
/* translators: %1$s: store settings link opening tag. %2$s: store settings link closing tag.*/
__( 'Per your %1$sstore settings%2$s, taxes are not enabled.', 'woocommerce' ),
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=general' ) . '" target="_blank" rel="noreferrer">',
'</a>'
),
),
)
);
@ -633,74 +642,98 @@ class SimpleProductTemplate extends AbstractProductFormTemplate implements Produ
'order' => 20,
)
);
$product_pricing_section->add_block(
array(
'id' => 'product-sale-tax',
'blockName' => 'woocommerce/product-radio-field',
'order' => 30,
'attributes' => array(
'title' => __( 'Charge sales tax on', 'woocommerce' ),
'property' => 'tax_status',
'options' => array(
array(
'label' => __( 'Product and shipping', 'woocommerce' ),
'value' => 'taxable',
),
array(
'label' => __( 'Only shipping', 'woocommerce' ),
'value' => 'shipping',
),
array(
'label' => __( "Don't charge tax", 'woocommerce' ),
'value' => 'none',
if ( $is_calc_taxes_enabled ) {
$product_pricing_section->add_block(
array(
'id' => 'product-sale-tax',
'blockName' => 'woocommerce/product-radio-field',
'order' => 30,
'attributes' => array(
'title' => __( 'Charge sales tax on', 'woocommerce' ),
'property' => 'tax_status',
'options' => array(
array(
'label' => __( 'Product and shipping', 'woocommerce' ),
'value' => 'taxable',
),
array(
'label' => __( 'Only shipping', 'woocommerce' ),
'value' => 'shipping',
),
array(
'label' => __( "Don't charge tax", 'woocommerce' ),
'value' => 'none',
),
),
),
),
)
);
$pricing_advanced_block = $product_pricing_section->add_block(
array(
'id' => 'product-pricing-advanced',
'blockName' => 'woocommerce/product-collapsible',
'order' => 40,
'attributes' => array(
'toggleText' => __( 'Advanced', 'woocommerce' ),
'initialCollapsed' => true,
'persistRender' => true,
),
)
);
$pricing_advanced_block->add_block(
array(
'id' => 'product-tax-class',
'blockName' => 'woocommerce/product-radio-field',
'order' => 10,
'attributes' => array(
'title' => __( 'Tax class', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s.', 'woocommerce' ),
'<a href="https://woo.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
'</a>'
)
);
$pricing_advanced_block = $product_pricing_section->add_block(
array(
'id' => 'product-pricing-advanced',
'blockName' => 'woocommerce/product-collapsible',
'order' => 40,
'attributes' => array(
'toggleText' => __( 'Advanced', 'woocommerce' ),
'initialCollapsed' => true,
'persistRender' => true,
),
'property' => 'tax_class',
'options' => array(
array(
'label' => __( 'Standard', 'woocommerce' ),
'value' => '',
),
array(
'label' => __( 'Reduced rate', 'woocommerce' ),
'value' => 'reduced-rate',
),
array(
'label' => __( 'Zero rate', 'woocommerce' ),
'value' => 'zero-rate',
)
);
$pricing_advanced_block->add_block(
array(
'id' => 'product-tax-class',
'blockName' => 'woocommerce/product-select-field',
'order' => 10,
'attributes' => array(
'label' => __( 'Tax class', 'woocommerce' ),
'help' => sprintf(
/* translators: %1$s: Learn more link opening tag. %2$s: Learn more link closing tag.*/
__( 'Apply a tax rate if this product qualifies for tax reduction or exemption. %1$sLearn more%2$s', 'woocommerce' ),
'<a href="https://woo.com/document/setting-up-taxes-in-woocommerce/#shipping-tax-class" target="_blank" rel="noreferrer">',
'</a>'
),
'property' => 'tax_class',
'options' => self::get_tax_classes(),
),
),
)
)
);
}
}
/**
* Get the tax classes as select options.
*
* @param string $post_type The post type.
* @return array Array of options.
*/
public static function get_tax_classes( $post_type = 'product' ) {
$tax_classes = array();
if ( 'product_variation' === $post_type ) {
$tax_classes[] = array(
'label' => __( 'Same as main product', 'woocommerce' ),
'value' => 'parent',
);
}
// Add standard class.
$tax_classes[] = array(
'label' => __( 'Standard rate', 'woocommerce' ),
'value' => '',
);
$classes = WC_Tax::get_tax_rate_classes();
foreach ( $classes as $tax_class ) {
$tax_classes[] = array(
'label' => $tax_class->name,
'value' => $tax_class->slug,
);
}
return $tax_classes;
}
/**

View File

@ -34,6 +34,7 @@ class BlockRegistryTest extends WC_Unit_Test_Case {
$this->assertTrue( $block_registry->is_registered( 'woocommerce/product-taxonomy-field' ), 'Taxonomy field not registered.' );
$this->assertTrue( $block_registry->is_registered( 'woocommerce/product-text-field' ), 'Text field not registered.' );
$this->assertTrue( $block_registry->is_registered( 'woocommerce/product-number-field' ), 'Number field not registered.' );
$this->assertTrue( $block_registry->is_registered( 'woocommerce/product-select-field' ), 'Select field not registered.' );
}
/**