From 32e0194772cd44efa1930f2ee99f147fa2976f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frane=20Poli=C4=87?= <16856471+fPolic@users.noreply.github.com> Date: Mon, 12 May 2025 18:04:48 +0200 Subject: [PATCH] fix(dashboard, medusa): validate provider for top level tax regions (#12450) * fix(dashboard, medusa): validate provider for top level tax regions * chore: changesets, message * chore: add test case for province * fix: remove comment --- .changeset/old-carrots-develop.md | 6 +++ .../tax-region/admin/tax-region.spec.ts | 53 +++++++++++++++++++ .../src/i18n/translations/$schema.json | 6 +-- .../dashboard/src/i18n/translations/en.json | 4 +- .../tax-region-create-form.tsx | 39 ++++++++++---- .../src/api/admin/tax-regions/validators.ts | 15 +++++- 6 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 .changeset/old-carrots-develop.md diff --git a/.changeset/old-carrots-develop.md b/.changeset/old-carrots-develop.md new file mode 100644 index 0000000000..55e2847b25 --- /dev/null +++ b/.changeset/old-carrots-develop.md @@ -0,0 +1,6 @@ +--- +"@medusajs/dashboard": patch +"@medusajs/medusa": patch +--- + +fix(dashboard, medusa): validate provider exists when creating a top level tax region diff --git a/integration-tests/http/__tests__/tax-region/admin/tax-region.spec.ts b/integration-tests/http/__tests__/tax-region/admin/tax-region.spec.ts index d7c9922114..492227f942 100644 --- a/integration-tests/http/__tests__/tax-region/admin/tax-region.spec.ts +++ b/integration-tests/http/__tests__/tax-region/admin/tax-region.spec.ts @@ -20,12 +20,22 @@ medusaIntegrationTestRunner({ let taxRegion beforeEach(async () => { + const parentRegion = await api.post( + "/admin/tax-regions", + { + country_code: "us", + provider_id: "tp_system", + }, + adminHeaders + ) + taxRegion = ( await api.post( "/admin/tax-regions", { country_code: "us", province_code: "tx", + parent_id: parentRegion.data.tax_region.id, metadata: { test: "created" }, }, adminHeaders @@ -83,6 +93,49 @@ medusaIntegrationTestRunner({ ) }) + it("should create a province tax region without a provider", async () => { + const response = await api.post( + `/admin/tax-regions`, + { + country_code: "us", + parent_id: taxRegion.id, + province_code: "ny", + }, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.tax_region).toEqual( + expect.objectContaining({ + id: expect.any(String), + country_code: "us", + province_code: "ny", + provider_id: null, + }) + ) + }) + + it("should fail to create a country tax region without a provider", async () => { + const { + response: { status, data }, + } = await api + .post( + `/admin/tax-regions`, + { + country_code: "uk", + }, + adminHeaders + ) + .catch((err) => err) + + expect(status).toEqual(400) + expect(data).toEqual({ + message: + "Invalid request: Provider is required when creating a non-province tax region.", + type: "invalid_data", + }) + }) + it("should throw if tax region does not exist", async () => { const { response: { status, data }, diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json index 2fc9da45a3..a65eb0ee97 100644 --- a/packages/admin/dashboard/src/i18n/translations/$schema.json +++ b/packages/admin/dashboard/src/i18n/translations/$schema.json @@ -6462,14 +6462,14 @@ "errors": { "type": "object", "properties": { - "rateIsRequired": { + "missingProvider": { "type": "string" }, - "nameIsRequired": { + "missingCountry": { "type": "string" } }, - "required": ["rateIsRequired", "nameIsRequired"], + "required": ["missingProvider", "missingCountry"], "additionalProperties": false }, "successToast": { diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json index fcad3dde1e..ff86747a88 100644 --- a/packages/admin/dashboard/src/i18n/translations/en.json +++ b/packages/admin/dashboard/src/i18n/translations/en.json @@ -1718,8 +1718,8 @@ "header": "Create Tax Region", "hint": "Create a new tax region to define tax rates for a specific country.", "errors": { - "rateIsRequired": "Tax rate is required when creating a default tax rate.", - "nameIsRequired": "Name is required when creating a default tax rate." + "missingProvider": "Provider is required when creating a tax region.", + "missingCountry": "Country is required when creating a tax region." }, "successToast": "The tax region was successfully created." }, diff --git a/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx b/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx index d8a4c2281a..b7f91fcb61 100644 --- a/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx +++ b/packages/admin/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx @@ -18,21 +18,40 @@ import { useComboboxData } from "../../../../../hooks/use-combobox-data" import { Combobox } from "../../../../../components/inputs/combobox" import { formatProvider } from "../../../../../lib/format-provider" import { sdk } from "../../../../../lib/client" +import { i18n } from "../../../../../components/utilities/i18n" type TaxRegionCreateFormProps = { parentId?: string } -const TaxRegionCreateSchema = z.object({ - name: z.string().optional(), - code: z.string().optional(), - rate: z.object({ - float: z.number().optional(), - value: z.string().optional(), - }), - country_code: z.string().min(1), - provider_id: z.string(), -}) +const TaxRegionCreateSchema = z + .object({ + name: z.string().optional(), + code: z.string().optional(), + rate: z.object({ + float: z.number().optional(), + value: z.string().optional(), + }), + country_code: z.string(), + provider_id: z.string(), + }) + .superRefine(({ provider_id, country_code }, ctx) => { + if (!provider_id) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: i18n.t("taxRegions.create.errors.missingProvider"), + path: ["provider_id"], + }) + } + + if (!country_code) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: i18n.t("taxRegions.create.errors.missingCountry"), + path: ["country_code"], + }) + } + }) export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => { const { t } = useTranslation() diff --git a/packages/medusa/src/api/admin/tax-regions/validators.ts b/packages/medusa/src/api/admin/tax-regions/validators.ts index e704a37ae5..08a980d7e9 100644 --- a/packages/medusa/src/api/admin/tax-regions/validators.ts +++ b/packages/medusa/src/api/admin/tax-regions/validators.ts @@ -3,6 +3,7 @@ import { createFindParams, createOperatorMap, createSelectParams, + WithAdditionalData, } from "../../utils/validators" import { applyAndAndOrOperators } from "../../utils/common-validators" @@ -39,8 +40,8 @@ export const AdminGetTaxRegionsParams = createFindParams({ .merge(AdminGetTaxRegionsParamsFields) .merge(applyAndAndOrOperators(AdminGetTaxRegionsParamsFields)) -export type AdminCreateTaxRegionType = z.infer -export const AdminCreateTaxRegion = z.object({ +export type AdminCreateTaxRegionType = z.infer +export const CreateTaxRegion = z.object({ country_code: z.string(), provider_id: z.string().nullish(), province_code: z.string().nullish(), @@ -57,6 +58,16 @@ export const AdminCreateTaxRegion = z.object({ metadata: z.record(z.unknown()).nullish(), }) +export const AdminCreateTaxRegion = WithAdditionalData( + CreateTaxRegion, + (schema) => { + return schema.refine((data) => data.parent_id || data.provider_id, { + path: ["provider_id"], + message: "Provider is required when creating a non-province tax region.", + }) + } +) + export type AdminUpdateTaxRegionType = z.infer export const AdminUpdateTaxRegion = z.object({ province_code: z.string().nullish(),