diff --git a/.changes/unreleased/Added-20240202-162014.yaml b/.changes/unreleased/Added-20240202-162014.yaml
new file mode 100644
index 00000000..9d68191b
--- /dev/null
+++ b/.changes/unreleased/Added-20240202-162014.yaml
@@ -0,0 +1,3 @@
+kind: Added
+body: Added tax_category_v2 resource to improve tax rate stability
+time: 2024-02-02T16:20:14.042422395+01:00
diff --git a/.github/actions/has-change-file/action.yaml b/.github/actions/has-change-file/action.yaml
new file mode 100644
index 00000000..458b9c7f
--- /dev/null
+++ b/.github/actions/has-change-file/action.yaml
@@ -0,0 +1,19 @@
+name: Has Change File
+description: Check if a new change file is set
+
+runs:
+ using: "composite"
+ steps:
+ - uses: dorny/paths-filter@v3
+ id: changes
+ with:
+ filters: |
+ changes:
+ - '.changes/unreleased/**'
+
+ - name: Check changes
+ if: steps.changes.outputs.changes == 'false'
+ shell: bash
+ run: |
+ echo "No new change file set in .changes/unreleased"
+ exit 1
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index e5e9d12b..cdc007f2 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -16,6 +16,9 @@ jobs:
steps:
- uses: actions/checkout@v3
+ - name: Has changes file
+ uses: ./.github/actions/has-change-file
+
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.3.7
diff --git a/commercetools/resource_tax_category.go b/commercetools/resource_tax_category.go
index 4e766dc4..c55e0f98 100644
--- a/commercetools/resource_tax_category.go
+++ b/commercetools/resource_tax_category.go
@@ -15,6 +15,9 @@ func resourceTaxCategory() *schema.Resource {
return &schema.Resource{
Description: "Tax Categories define how products are to be taxed in different countries.\n\n" +
"See also the [Tax Category API Documentation](https://docs.commercetools.com/api/projects/taxCategories)",
+
+ DeprecationMessage: "This resource is deprecated and will be removed in the next major release. " +
+ "Please use the commercetools_tax_category_v2 resource instead.",
CreateContext: resourceTaxCategoryCreate,
ReadContext: resourceTaxCategoryRead,
UpdateContext: resourceTaxCategoryUpdate,
diff --git a/commercetools/resource_tax_category_rate.go b/commercetools/resource_tax_category_rate.go
index 91b94233..0242da33 100644
--- a/commercetools/resource_tax_category_rate.go
+++ b/commercetools/resource_tax_category_rate.go
@@ -24,6 +24,8 @@ func resourceTaxCategoryRate() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: resourceTaxCategoryRateImportState,
},
+ DeprecationMessage: "This resource is deprecated and will be removed in the next major release. " +
+ "Please use the tax_rate attributes on commercetools_tax_category resource instead.",
Schema: map[string]*schema.Schema{
"tax_category_id": {
Type: schema.TypeString,
diff --git a/docs/resources/tax_category.md b/docs/resources/tax_category.md
index ccc14fe2..451dd817 100644
--- a/docs/resources/tax_category.md
+++ b/docs/resources/tax_category.md
@@ -9,6 +9,8 @@ description: |-
# commercetools_tax_category (Resource)
+> **DEPRECATED**: The resource has been deprecated. Please use [commercetools_tax_category_v2](./tax_category_v2.md) instead.
+
Tax Categories define how products are to be taxed in different countries.
See also the [Tax Category API Documentation](https://docs.commercetools.com/api/projects/taxCategories)
diff --git a/docs/resources/tax_category_rate.md b/docs/resources/tax_category_rate.md
index d8ff6705..412810e6 100644
--- a/docs/resources/tax_category_rate.md
+++ b/docs/resources/tax_category_rate.md
@@ -9,6 +9,8 @@ description: |-
# commercetools_tax_category_rate (Resource)
+> **DEPRECATED**: The resource has been deprecated. Please use [commercetools_tax_category_v2](./tax_category_v2.md) instead.
+
Tax rate for Tax Category.
See also [Tax Rate API Documentation](https://docs.commercetools.com/api/projects/taxCategories#taxrate)
diff --git a/docs/resources/tax_category_v2.md b/docs/resources/tax_category_v2.md
new file mode 100644
index 00000000..635975be
--- /dev/null
+++ b/docs/resources/tax_category_v2.md
@@ -0,0 +1,62 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "commercetools_tax_category_v2 Resource - terraform-provider-commercetools"
+subcategory: ""
+description: |-
+ Tax Categories define how products are to be taxed in different countries.
+ See also the Tax Category API Documentation https://docs.commercetools.com/api/projects/taxCategories.
+---
+
+# commercetools_tax_category_v2 (Resource)
+
+Tax Categories define how products are to be taxed in different countries.
+
+See also the [Tax Category API Documentation](https://docs.commercetools.com/api/projects/taxCategories).
+
+
+
+
+## Schema
+
+### Required
+
+- `name` (String) Name of the TaxCategory.
+
+### Optional
+
+- `description` (String) Description of the TaxCategory.
+- `key` (String) User-defined unique identifier of the TaxCategory
+- `tax_rate` (Block List) Attributes with unique values. (see [below for nested schema](#nestedblock--tax_rate))
+
+### Read-Only
+
+- `id` (String) Unique identifier of the TaxCategory.
+- `version` (Number) Current version of the TaxCategory.
+
+
+### Nested Schema for `tax_rate`
+
+Required:
+
+- `amount` (Number) Number Percentage in the range of [0..1]. The sum of the amounts of all subRates, if there are any
+- `country` (String) A two-digit country code as per [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
+- `included_in_price` (Boolean) If true, tax is included in Embedded Prices or Standalone Prices, and the taxedPrice is present on LineItems. In this case, the totalNet price on TaxedPrice includes the TaxRate.
+- `name` (String) Name of the TaxRate.
+
+Optional:
+
+- `key` (String) User-defined unique identifier of the TaxCategory
+- `state` (String) The state in the country
+- `sub_rate` (Block List) For countries (for example the US) where the total tax is a combination of multiple taxes (for example state and local taxes) (see [below for nested schema](#nestedblock--tax_rate--sub_rate))
+
+Read-Only:
+
+- `id` (String) Present if the TaxRate is part of a TaxCategory. Absent for external TaxRates in LineItem, CustomLineItem, and ShippingInfo.
+
+
+### Nested Schema for `tax_rate.sub_rate`
+
+Required:
+
+- `amount` (Number) Number Percentage in the range of [0..1]. The sum of the amounts of all subRates, if there are any
+- `name` (String) Name of the SubRate.
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 16d0218d..6e0a0a06 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -3,6 +3,7 @@ package provider
import (
"context"
"fmt"
+ "github.com/labd/terraform-provider-commercetools/internal/resources/tax_category_v2"
"net/http"
"os"
"strings"
@@ -196,5 +197,6 @@ func (p *ctProvider) Resources(_ context.Context) []func() resource.Resource {
attribute_group.NewResource,
associate_role.NewResource,
product_selection.NewResource,
+ tax_category_v2.NewResource,
}
}
diff --git a/internal/resources/tax_category_v2/model.go b/internal/resources/tax_category_v2/model.go
new file mode 100644
index 00000000..39725336
--- /dev/null
+++ b/internal/resources/tax_category_v2/model.go
@@ -0,0 +1,174 @@
+package tax_category_v2
+
+import (
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/labd/commercetools-go-sdk/platform"
+ "reflect"
+)
+
+type TaxCategory struct {
+ ID types.String `tfsdk:"id"`
+ Version types.Int64 `tfsdk:"version"`
+ Key types.String `tfsdk:"key"`
+ Name types.String `tfsdk:"name"`
+ Description types.String `tfsdk:"description"`
+ TaxRates []TaxRate `tfsdk:"tax_rates"`
+}
+
+type TaxRate struct {
+ ID types.String `tfsdk:"id"`
+ Key types.String `tfsdk:"key"`
+ Name types.String `tfsdk:"name"`
+ Amount types.Float64 `tfsdk:"amount"`
+ IncludedInPrice types.Bool `tfsdk:"included_in_price"`
+ Country types.String `tfsdk:"country"`
+ State types.String `tfsdk:"state"`
+ SubRates []SubRate `tfsdk:"sub_rates"`
+}
+
+type SubRate struct {
+ Name types.String `tfsdk:"name"`
+ Amount types.Float64 `tfsdk:"amount"`
+}
+
+func (tr TaxRate) draft() platform.TaxRateDraft {
+ var subRateDrafts = make([]platform.SubRate, 0, len(tr.SubRates))
+
+ for _, subRate := range tr.SubRates {
+ subRateDrafts = append(subRateDrafts, platform.SubRate{
+ Name: subRate.Name.ValueString(),
+ Amount: subRate.Amount.ValueFloat64(),
+ })
+ }
+
+ return platform.TaxRateDraft{
+ Name: tr.Name.ValueString(),
+ Amount: tr.Amount.ValueFloat64Pointer(),
+ IncludedInPrice: tr.IncludedInPrice.ValueBool(),
+ Country: tr.Country.ValueString(),
+ State: tr.State.ValueStringPointer(),
+ SubRates: subRateDrafts,
+ }
+}
+
+func (tc TaxCategory) draft() platform.TaxCategoryDraft {
+ var taxRateDrafts = make([]platform.TaxRateDraft, 0, len(tc.TaxRates))
+
+ for _, taxRate := range tc.TaxRates {
+ taxRateDrafts = append(taxRateDrafts, taxRate.draft())
+ }
+
+ return platform.TaxCategoryDraft{
+ Name: tc.Name.ValueString(),
+ Key: tc.Key.ValueStringPointer(),
+ Description: tc.Description.ValueStringPointer(),
+ Rates: taxRateDrafts,
+ }
+}
+
+func TaxCategoryFromNative(tc *platform.TaxCategory) (TaxCategory, error) {
+ var rates = make([]TaxRate, 0, len(tc.Rates))
+ for _, taxRate := range tc.Rates {
+ var idPtr = taxRate.ID
+ if idPtr == nil {
+ return TaxCategory{}, fmt.Errorf("tax rate ID is nil")
+ }
+
+ var subRates = make([]SubRate, 0, len(taxRate.SubRates))
+ for _, subRate := range taxRate.SubRates {
+ var subRate = SubRate{
+ Name: types.StringValue(subRate.Name),
+ Amount: types.Float64Value(subRate.Amount),
+ }
+ subRates = append(subRates, subRate)
+ }
+
+ var rate = TaxRate{
+ ID: types.StringValue(*idPtr),
+ Key: types.StringPointerValue(taxRate.Key),
+ Name: types.StringValue(taxRate.Name),
+ Amount: types.Float64Value(taxRate.Amount),
+ IncludedInPrice: types.BoolValue(taxRate.IncludedInPrice),
+ Country: types.StringValue(taxRate.Country),
+ State: types.StringPointerValue(taxRate.State),
+ SubRates: subRates,
+ }
+ rates = append(rates, rate)
+ }
+
+ return TaxCategory{
+ ID: types.StringValue(tc.ID),
+ Version: types.Int64Value(int64(tc.Version)),
+ Key: types.StringPointerValue(tc.Key),
+ Name: types.StringValue(tc.Name),
+ Description: types.StringPointerValue(tc.Description),
+ TaxRates: rates,
+ }, nil
+}
+
+func (tc TaxCategory) updateActions(plan TaxCategory) platform.TaxCategoryUpdate {
+ result := platform.TaxCategoryUpdate{
+ Version: int(tc.Version.ValueInt64()),
+ Actions: []platform.TaxCategoryUpdateAction{},
+ }
+
+ // setKey
+ if !tc.Key.Equal(plan.Key) {
+ var newKey *string
+ if !plan.Key.IsNull() && !plan.Key.IsUnknown() {
+ newKey = plan.Key.ValueStringPointer()
+ }
+
+ result.Actions = append(
+ result.Actions,
+ platform.TaxCategorySetKeyAction{Key: newKey},
+ )
+ }
+
+ // changeName
+ if !tc.Name.Equal(plan.Name) {
+ var newName string
+ if !plan.Name.IsNull() && !plan.Name.IsUnknown() {
+ newName = plan.Name.ValueString()
+ }
+
+ result.Actions = append(
+ result.Actions,
+ platform.TaxCategoryChangeNameAction{Name: newName},
+ )
+ }
+
+ // setDescription
+ if !tc.Description.Equal(plan.Description) {
+ var newDescription *string
+ if !plan.Description.IsNull() && !plan.Description.IsUnknown() {
+ newDescription = plan.Description.ValueStringPointer()
+ }
+
+ result.Actions = append(
+ result.Actions,
+ platform.TaxCategorySetDescriptionAction{Description: newDescription},
+ )
+ }
+
+ // If a change occurred in tax rates we remove all the tax rates and re-add them.
+ // This is because a change manual change in tax rate through the API or merchant
+ // center re-creates the tax rate so the ID cannot be trusted
+ if !reflect.DeepEqual(tc.TaxRates, plan.TaxRates) {
+ for _, tr := range tc.TaxRates {
+ result.Actions = append(
+ result.Actions,
+ platform.TaxCategoryRemoveTaxRateAction{TaxRateId: tr.ID.ValueStringPointer()},
+ )
+ }
+
+ for _, dtr := range plan.TaxRates {
+ result.Actions = append(
+ result.Actions,
+ platform.TaxCategoryAddTaxRateAction{TaxRate: dtr.draft()})
+ }
+ }
+
+ return result
+}
diff --git a/internal/resources/tax_category_v2/resource.go b/internal/resources/tax_category_v2/resource.go
new file mode 100644
index 00000000..3c4f6cb2
--- /dev/null
+++ b/internal/resources/tax_category_v2/resource.go
@@ -0,0 +1,304 @@
+package tax_category_v2
+
+import (
+ "context"
+ "errors"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
+ "github.com/labd/commercetools-go-sdk/platform"
+ "github.com/labd/terraform-provider-commercetools/internal/utils"
+ "time"
+)
+
+var (
+ _ resource.Resource = &taxCategoryV2Resource{}
+ _ resource.ResourceWithConfigure = &taxCategoryV2Resource{}
+ _ resource.ResourceWithImportState = &taxCategoryV2Resource{}
+)
+
+type taxCategoryV2Resource struct {
+ client *platform.ByProjectKeyRequestBuilder
+}
+
+// NewResource is a helper function to simplify the provider implementation.
+func NewResource() resource.Resource {
+ return &taxCategoryV2Resource{}
+}
+
+// Schema implements resource.Resource.
+func (*taxCategoryV2Resource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: "Tax Categories define how products are to be taxed in different countries.\n\n" +
+ "See also the [Tax Category API Documentation](https://docs.commercetools.com/api/projects/taxCategories).",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Unique identifier of the TaxCategory.",
+ Computed: true,
+ },
+ "version": schema.Int64Attribute{
+ Description: "Current version of the TaxCategory.",
+ Computed: true,
+ },
+ "key": schema.StringAttribute{
+ Description: "User-defined unique identifier of the TaxCategory",
+ Optional: true,
+ //TODO: add key validation
+ },
+ "name": schema.StringAttribute{
+ Description: "Name of the TaxCategory.",
+ Required: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "Description of the TaxCategory.",
+ Optional: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "tax_rate": schema.ListNestedBlock{
+ MarkdownDescription: "Attributes with unique values.",
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Present if the TaxRate is part of a TaxCategory. Absent for external TaxRates in LineItem, CustomLineItem, and ShippingInfo.",
+ Computed: true,
+ },
+ "key": schema.StringAttribute{
+ Description: "User-defined unique identifier of the TaxCategory",
+ Optional: true,
+ //TODO: add key validation
+ },
+ "name": schema.StringAttribute{
+ Description: "Name of the TaxRate.",
+ Required: true,
+ },
+ "amount": schema.Float64Attribute{
+ Description: "Number Percentage in the range of [0..1]. The sum of the amounts of all subRates, " +
+ "if there are any",
+ Required: true,
+ //ValidateFunc: validateTaxRateAmount, //TODO add ValidateTaxRateAmount
+ },
+ "included_in_price": schema.BoolAttribute{
+ Description: "If true, tax is included in Embedded Prices or Standalone Prices, and the taxedPrice is present on LineItems. " +
+ "In this case, the totalNet price on TaxedPrice includes the TaxRate.",
+ Required: true,
+ },
+ "country": schema.StringAttribute{
+ Description: "A two-digit country code as per [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)",
+ Required: true,
+ },
+ "state": schema.StringAttribute{
+ Description: "The state in the country",
+ Optional: true,
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "sub_rate": schema.ListNestedBlock{
+ Description: "For countries (for example the US) where the total tax is a combination of multiple " +
+ "taxes (for example state and local taxes)",
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Description: "Name of the SubRate.",
+ Required: true,
+ },
+ "amount": schema.Float64Attribute{
+ Description: "Number Percentage in the range of [0..1]. The sum of the amounts of all subRates, " +
+ "if there are any",
+ Required: true,
+ //ValidateFunc: validateTaxRateAmount, //TODO add ValidateTaxRateAmount
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func (*taxCategoryV2Resource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_tax_category_v2"
+}
+
+func (r *taxCategoryV2Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan TaxCategory
+ diags := req.Plan.Get(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var draft platform.TaxCategoryDraft
+ draft = plan.draft()
+ if diags.HasError() {
+ resp.Diagnostics.Append(diags...)
+ }
+
+ var taxCategory *platform.TaxCategory
+ err := retry.RetryContext(ctx, 20*time.Second, func() *retry.RetryError {
+ var err error
+ taxCategory, err = r.client.TaxCategories().Post(draft).Execute(ctx)
+ return utils.ProcessRemoteError(err)
+ })
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating tax category",
+ err.Error(),
+ )
+ return
+ }
+
+ current, err := TaxCategoryFromNative(taxCategory)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating tax category from native",
+ err.Error(),
+ )
+ return
+ }
+
+ diags = resp.State.Set(ctx, current)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+}
+
+func (r *taxCategoryV2Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ // Get the current state.
+ var state TaxCategory
+ diags := req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err := retry.RetryContext(
+ ctx,
+ 5*time.Second,
+ func() *retry.RetryError {
+ _, err := r.client.TaxCategories().
+ WithId(state.ID.ValueString()).
+ Delete().
+ Version(int(state.Version.ValueInt64())).
+ Execute(ctx)
+
+ return utils.ProcessRemoteError(err)
+ })
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error deleting tax category",
+ "Could not delete tax category, unexpected error: "+err.Error(),
+ )
+ return
+ }
+}
+
+func (r *taxCategoryV2Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ // Get the current state.
+ var state TaxCategory
+ diags := req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Read remote tax category and check for errors.
+ taxCategory, err := r.client.TaxCategories().WithId(state.ID.ValueString()).Get().Execute(ctx)
+ if err != nil {
+ if errors.Is(err, platform.ErrNotFound) {
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ resp.Diagnostics.AddError(
+ "Error reading tax category",
+ "Could not retrieve the tax category, unexpected error: "+err.Error(),
+ )
+ return
+ }
+
+ // Transform the remote platform tax category to the
+ // tf schema matching representation.
+ current, err := TaxCategoryFromNative(taxCategory)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating tax category from native",
+ err.Error(),
+ )
+ return
+ }
+
+ // Set current data as state.
+ diags = resp.State.Set(ctx, ¤t)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+}
+
+func (r *taxCategoryV2Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan TaxCategory
+ diags := req.Plan.Get(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var state TaxCategory
+ diags = req.State.Get(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ input := state.updateActions(plan)
+ var taxCategory *platform.TaxCategory
+ err := retry.RetryContext(ctx, 5*time.Second, func() *retry.RetryError {
+ var err error
+ taxCategory, err = r.client.TaxCategories().
+ WithId(state.ID.ValueString()).
+ Post(input).
+ Execute(ctx)
+
+ return utils.ProcessRemoteError(err)
+ })
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error updating tax category",
+ "Could not update tax category, unexpected error: "+err.Error(),
+ )
+ return
+ }
+
+ current, err := TaxCategoryFromNative(taxCategory)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating tax category from native",
+ err.Error(),
+ )
+ return
+ }
+
+ diags = resp.State.Set(ctx, current)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+}
+
+func (r *taxCategoryV2Resource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ data := req.ProviderData.(*utils.ProviderData)
+ r.client = data.Client
+}
+
+func (*taxCategoryV2Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/main.go b/main.go
index 6463fb86..cbb4571f 100644
--- a/main.go
+++ b/main.go
@@ -17,7 +17,7 @@ import (
// Run "go generate" to format example terraform files and generate the docs for the registry/website
-// If you do not have terraform installed, you can remove the formatting command, but its suggested to
+// If you do not have Terraform installed, you can remove the formatting command, but it's suggested to
// ensure the documentation is formatted properly.
//go:generate terraform fmt -recursive ./examples/
diff --git a/templates/resources/tax_category.md.tmpl b/templates/resources/tax_category.md.tmpl
new file mode 100644
index 00000000..dfd16015
--- /dev/null
+++ b/templates/resources/tax_category.md.tmpl
@@ -0,0 +1,29 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
+subcategory: ""
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+> **DEPRECATED**: The resource has been deprecated. Please use [commercetools_tax_category_v2](./tax_category_v2.md) instead.
+
+{{ .Description | trimspace }}
+
+{{ if .HasExample -}}
+ ## Example Usage
+
+{{tffile .ExampleFile }}
+{{- end }}
+
+{{ .SchemaMarkdown | trimspace }}
+{{- if .HasImport }}
+
+ ## Import
+
+ Import is supported using the following syntax:
+
+ {{codefile "shell" .ImportFile }}
+{{- end }}
diff --git a/templates/resources/tax_category_rate.md.tmpl b/templates/resources/tax_category_rate.md.tmpl
new file mode 100644
index 00000000..dfd16015
--- /dev/null
+++ b/templates/resources/tax_category_rate.md.tmpl
@@ -0,0 +1,29 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
+subcategory: ""
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+> **DEPRECATED**: The resource has been deprecated. Please use [commercetools_tax_category_v2](./tax_category_v2.md) instead.
+
+{{ .Description | trimspace }}
+
+{{ if .HasExample -}}
+ ## Example Usage
+
+{{tffile .ExampleFile }}
+{{- end }}
+
+{{ .SchemaMarkdown | trimspace }}
+{{- if .HasImport }}
+
+ ## Import
+
+ Import is supported using the following syntax:
+
+ {{codefile "shell" .ImportFile }}
+{{- end }}