fix: Tax region + rates clean up (#9279)

What
- Require `code` on Tax Rates
- Update dashboard to account for non-nullable code on Tax Rates

- Include `automatic_taxes` in API Route response

Closes CC-524 CC-525
This commit is contained in:
Oli Juhl authored and GitHub committed 2024-09-29 10:25:33 +00:00
1 parent 0efbcd2344
commit 1b4372ae71
23 files changed
+121 -45

No files matched your search

@@ -2,7 +2,7 @@ import { IEventBusModuleService } from "@medusajs/types"
import { CommonEvents, Modules } from "@medusajs/utils" import { CommonEvents, Modules } from "@medusajs/utils"
import FormData from "form-data" import FormData from "form-data"
import fs from "fs/promises" import fs from "fs/promises"
import { TestEventUtils, medusaIntegrationTestRunner } from "medusa-test-utils" import { medusaIntegrationTestRunner, TestEventUtils } from "medusa-test-utils"
import path from "path" import path from "path"
import { import {
adminHeaders, adminHeaders,
@@ -55,9 +55,17 @@ medusaIntegrationTestRunner({
expect(response.data.regions).toEqual([ expect(response.data.regions).toEqual([
expect.objectContaining({ expect.objectContaining({
name: "United Kingdom", name: "United Kingdom",
id: region1.id,
currency_code: "gbp",
automatic_taxes: region1.automatic_taxes,
countries: [],
}), }),
expect.objectContaining({ expect.objectContaining({
name: "United States", name: "United States",
id: region2.id,
currency_code: "usd",
automatic_taxes: region2.automatic_taxes,
countries: [],
}), }),
]) ])
}) })
@@ -46,7 +46,11 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
}, },
{ {
country_code: "CA", country_code: "CA",
default_tax_rate: { name: "Canada Default Rate", rate: 5 }, default_tax_rate: {
name: "Canada Default Rate",
rate: 5,
code: "CA_DEF",
},
}, },
]) ])
@@ -234,6 +234,29 @@ medusaIntegrationTestRunner({
) )
}) })
it("should fail to create a tax rate without a code", async () => {
const errResponse = await api
.post(
`/admin/tax-regions`,
{
country_code: "us",
default_tax_rate: {
rate: 2,
name: "default rate",
},
},
adminHeaders
)
.catch((e) => e)
expect(errResponse.response.status).toEqual(400)
expect(errResponse.response.data).toEqual({
message:
"Invalid request: Field 'default_tax_rate, code' is required",
type: "invalid_data",
})
})
it("can create a tax rate and update it", async () => { it("can create a tax rate and update it", async () => {
const regionRes = await api.post( const regionRes = await api.post(
`/admin/tax-regions`, `/admin/tax-regions`,
@@ -59,6 +59,7 @@ export const EditRegionForm = ({
await updateRegion( await updateRegion(
{ {
name: values.name, name: values.name,
automatic_taxes: values.automatic_taxes,
currency_code: values.currency_code.toLowerCase(), currency_code: values.currency_code.toLowerCase(),
payment_providers: values.payment_providers, payment_providers: values.payment_providers,
is_tax_inclusive: values.is_tax_inclusive, is_tax_inclusive: values.is_tax_inclusive,
@@ -21,12 +21,10 @@ type TaxRegionCreateFormProps = {
const TaxRegionCreateSchema = z.object({ const TaxRegionCreateSchema = z.object({
name: z.string().optional(), name: z.string().optional(),
code: z.string().optional(), code: z.string().optional(),
rate: z rate: z.object({
.object({ float: z.number().optional(),
float: z.number().optional(), value: z.string().optional(),
value: z.string().optional(), }),
})
.optional(),
country_code: z.string().min(1), country_code: z.string().min(1),
}) })
@@ -49,14 +47,16 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
const { mutateAsync, isPending } = useCreateTaxRegion() const { mutateAsync, isPending } = useCreateTaxRegion()
const handleSubmit = form.handleSubmit(async (values) => { const handleSubmit = form.handleSubmit(async (values) => {
const defaultRate = const defaultRate = values.name
values.name && values.rate?.float ? {
? { name: values.name,
name: values.name, rate:
rate: values.rate.float, values.rate?.value === ""
code: values.code, ? undefined
} : parseFloat(values.rate.value!),
: undefined code: values.code,
}
: undefined
await mutateAsync( await mutateAsync(
{ {
@@ -24,7 +24,7 @@ type TaxRegionProvinceCreateFormProps = {
const CreateTaxRegionProvinceSchema = z.object({ const CreateTaxRegionProvinceSchema = z.object({
province_code: z.string().min(1), province_code: z.string().min(1),
name: z.string().optional(), name: z.string().optional(),
code: z.string().optional(), code: z.string().min(1),
rate: z rate: z
.object({ .object({
float: z.number().optional(), float: z.number().optional(),
@@ -38,7 +38,7 @@ import { createTaxRulePayload } from "../../../common/utils"
const TaxRegionCreateTaxOverrideSchema = z.object({ const TaxRegionCreateTaxOverrideSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
code: z.string().optional(), code: z.string().min(1),
rate: z rate: z
.object({ .object({
float: z.number().optional(), float: z.number().optional(),
@@ -408,7 +408,7 @@ export const TaxRegionCreateTaxOverrideForm = ({
render={({ field }) => { render={({ field }) => {
return ( return (
<Form.Item> <Form.Item>
<Form.Label optional> <Form.Label>
{t("taxRegions.fields.taxCode")} {t("taxRegions.fields.taxCode")}
</Form.Label> </Form.Label>
<Form.Control> <Form.Control>
@@ -48,7 +48,7 @@ const getStackedModalId = (type: TaxRateRuleReferenceType) =>
const TaxRegionTaxRateEditSchema = z.object({ const TaxRegionTaxRateEditSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
code: z.string().optional(), code: z.string().min(1),
rate: z.object({ rate: z.object({
float: z.number().optional(), float: z.number().optional(),
value: z.string().optional(), value: z.string().optional(),
@@ -80,7 +80,7 @@ export const TaxRegionTaxOverrideEditForm = ({
const form = useForm<z.infer<typeof TaxRegionTaxRateEditSchema>>({ const form = useForm<z.infer<typeof TaxRegionTaxRateEditSchema>>({
defaultValues: { defaultValues: {
name: taxRate.name, name: taxRate.name,
code: taxRate.code || "", code: taxRate.code,
rate: { rate: {
value: taxRate.rate?.toString() || "", value: taxRate.rate?.toString() || "",
}, },
@@ -20,7 +20,7 @@ type TaxRegionTaxRateCreateFormProps = {
const TaxRegionTaxRateCreateSchema = z.object({ const TaxRegionTaxRateCreateSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
code: z.string().optional(), code: z.string().min(1),
rate: z rate: z
.object({ .object({
float: z.number().optional(), float: z.number().optional(),
@@ -57,7 +57,7 @@ export const TaxRegionTaxRateCreateForm = ({
tax_region_id: taxRegion.id, tax_region_id: taxRegion.id,
is_default: true, is_default: true,
name: values.name, name: values.name,
code: values.code || undefined, code: values.code,
rate: values.rate?.float, rate: values.rate?.float,
is_combinable: values.is_combinable, is_combinable: values.is_combinable,
}, },
@@ -36,7 +36,7 @@ export const TaxRegionTaxRateEditForm = ({
const form = useForm<z.infer<typeof TaxRegionTaxRateEditSchema>>({ const form = useForm<z.infer<typeof TaxRegionTaxRateEditSchema>>({
defaultValues: { defaultValues: {
name: taxRate.name, name: taxRate.name,
code: taxRate.code || "", code: taxRate.code,
rate: { rate: {
value: taxRate.rate?.toString() || "", value: taxRate.rate?.toString() || "",
}, },
@@ -51,7 +51,7 @@ export const TaxRegionTaxRateEditForm = ({
await mutateAsync( await mutateAsync(
{ {
name: values.name, name: values.name,
code: values.code || null, code: values.code,
rate: values.rate?.float, rate: values.rate?.float,
is_combinable: values.is_combinable, is_combinable: values.is_combinable,
}, },
@@ -8,7 +8,7 @@ export interface AdminTaxRateRule {
export interface AdminTaxRate { export interface AdminTaxRate {
id: string id: string
rate: number | null rate: number | null
code: string | null code: string
name: string name: string
metadata: Record<string, unknown> | null metadata: Record<string, unknown> | null
tax_region_id: string tax_region_id: string
@@ -7,7 +7,7 @@ export interface AdminCreateTaxRate {
name: string name: string
tax_region_id: string tax_region_id: string
rate?: number rate?: number
code?: string code: string
rules?: AdminCreateTaxRateRule[] rules?: AdminCreateTaxRateRule[]
is_default?: boolean is_default?: boolean
is_combinable?: boolean is_combinable?: boolean
@@ -17,7 +17,7 @@ export interface AdminCreateTaxRate {
export interface AdminUpdateTaxRate { export interface AdminUpdateTaxRate {
name?: string name?: string
rate?: number rate?: number
code?: string | null code: string
rules?: AdminCreateTaxRateRule[] rules?: AdminCreateTaxRateRule[]
is_default?: boolean is_default?: boolean
is_combinable?: boolean is_combinable?: boolean
@@ -4,7 +4,7 @@ export interface AdminCreateTaxRegion {
parent_id?: string parent_id?: string
default_tax_rate?: { default_tax_rate?: {
rate?: number rate?: number
code?: string code: string
name: string name: string
is_combinable?: boolean is_combinable?: boolean
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
@@ -5,6 +5,7 @@ export const defaultAdminRegionFields = [
"created_at", "created_at",
"updated_at", "updated_at",
"deleted_at", "deleted_at",
"automatic_taxes",
"metadata", "metadata",
"*countries", "*countries",
] ]
@@ -1,14 +1,14 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { createRegionsWorkflow } from "@medusajs/core-flows" import { createRegionsWorkflow } from "@medusajs/core-flows"
import { HttpTypes } from "@medusajs/framework/types"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
remoteQueryObjectFromString, remoteQueryObjectFromString,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { refetchRegion } from "./helpers" import { refetchRegion } from "./helpers"
import { HttpTypes } from "@medusajs/framework/types"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminRegionFilters>, req: AuthenticatedMedusaRequest<HttpTypes.AdminRegionFilters>,
@@ -36,7 +36,7 @@ export const AdminCreateTaxRateRule = z.object({
export type AdminCreateTaxRateType = z.infer<typeof AdminCreateTaxRate> export type AdminCreateTaxRateType = z.infer<typeof AdminCreateTaxRate>
export const AdminCreateTaxRate = z.object({ export const AdminCreateTaxRate = z.object({
rate: z.number().optional(), rate: z.number().optional(),
code: z.string().optional(), code: z.string(),
rules: z.array(AdminCreateTaxRateRule).optional(), rules: z.array(AdminCreateTaxRateRule).optional(),
name: z.string(), name: z.string(),
is_default: z.boolean().optional(), is_default: z.boolean().optional(),
@@ -48,7 +48,7 @@ export const AdminCreateTaxRate = z.object({
export type AdminUpdateTaxRateType = z.infer<typeof AdminUpdateTaxRate> export type AdminUpdateTaxRateType = z.infer<typeof AdminUpdateTaxRate>
export const AdminUpdateTaxRate = z.object({ export const AdminUpdateTaxRate = z.object({
rate: z.number().optional(), rate: z.number().optional(),
code: z.string().nullish(), code: z.string().optional(),
rules: z.array(AdminCreateTaxRateRule).optional(), rules: z.array(AdminCreateTaxRateRule).optional(),
name: z.string().optional(), name: z.string().optional(),
is_default: z.boolean().optional(), is_default: z.boolean().optional(),
@@ -46,7 +46,7 @@ export const AdminCreateTaxRegion = z.object({
default_tax_rate: z default_tax_rate: z
.object({ .object({
rate: z.number().optional(), rate: z.number().optional(),
code: z.string().optional(), code: z.string(),
name: z.string(), name: z.string(),
is_combinable: z.boolean().optional(), is_combinable: z.boolean().optional(),
metadata: z.record(z.unknown()).nullish(), metadata: z.record(z.unknown()).nullish(),
@@ -72,6 +72,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -90,6 +91,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -99,6 +101,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -125,6 +128,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -135,6 +139,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -161,6 +166,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -171,6 +177,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -181,6 +188,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -215,6 +223,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -224,6 +233,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
.catch((e) => e) .catch((e) => e)
@@ -240,6 +250,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
@@ -250,6 +261,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST",
}, },
}) })
.catch((e) => e) .catch((e) => e)
@@ -265,6 +277,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST-CODE",
}, },
}) })
@@ -301,7 +314,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
tax_region_id: region.id, tax_region_id: region.id,
rate: 2, rate: 2,
name: "Test Rate", name: "Test Rate",
code: null, code: "TEST-CODE",
is_default: true, is_default: true,
}), }),
]) ])
@@ -313,6 +326,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
tax_region_id: region.id, tax_region_id: region.id,
rate: 2, rate: 2,
name: "Test Rate", name: "Test Rate",
code: "TEST-CODE",
is_default: true, is_default: true,
}), }),
expect.objectContaining({ expect.objectContaining({
@@ -332,6 +346,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST-CODE",
}, },
}) })
@@ -413,6 +428,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
const region = await service.createTaxRegions({ const region = await service.createTaxRegions({
country_code: "US", country_code: "US",
default_tax_rate: { default_tax_rate: {
code: "TEST",
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
}, },
@@ -424,6 +440,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
province_code: "CA", province_code: "CA",
parent_id: region.id, parent_id: region.id,
default_tax_rate: { default_tax_rate: {
code: "TEST",
name: "CA Rate", name: "CA Rate",
rate: 8.25, rate: 8.25,
}, },
@@ -472,6 +489,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
{ {
country_code: "US", country_code: "US",
default_tax_rate: { default_tax_rate: {
code: "TEST",
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
}, },
@@ -482,6 +500,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
tax_region_id: region.id, tax_region_id: region.id,
name: "Shipping Rate", name: "Shipping Rate",
rate: 8.23, rate: 8.23,
code: "TEST-CODE",
}) })
await service.createTaxRateRules([ await service.createTaxRateRules([
@@ -1036,6 +1055,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
default_tax_rate: { default_tax_rate: {
name: "Test Rate", name: "Test Rate",
rate: 0.2, rate: 0.2,
code: "TEST-CODE",
}, },
}) })
@@ -1045,6 +1065,7 @@ moduleIntegrationTestRunner<ITaxModuleService>({
name: "Shipping Rate", name: "Shipping Rate",
rate: 8.23, rate: 8.23,
is_default: true, is_default: true,
code: "TEST-CODE-2",
}) })
).rejects.toThrowError( ).rejects.toThrowError(
/Tax rate with tax_region_id: .*?, already exists./ /Tax rate with tax_region_id: .*?, already exists./
@@ -27,11 +27,11 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
const [us, dk, de, ca] = await service.createTaxRegions([ const [us, dk, de, ca] = await service.createTaxRegions([
{ {
country_code: "US", country_code: "US",
default_tax_rate: { name: "US Default Rate", rate: 2 }, default_tax_rate: { name: "US Default Rate", rate: 2, code: "US" },
}, },
{ {
country_code: "DK", country_code: "DK",
default_tax_rate: { name: "Denmark Default Rate", rate: 25 }, default_tax_rate: { name: "Denmark Default Rate", rate: 25, code: "DK" },
}, },
{ {
country_code: "DE", country_code: "DE",
@@ -43,7 +43,7 @@ export const setupTaxStructure = async (service: ITaxModuleService) => {
}, },
{ {
country_code: "CA", country_code: "CA",
default_tax_rate: { name: "Canada Default Rate", rate: 5 }, default_tax_rate: { name: "Canada Default Rate", rate: 5, code: "CA" },
}, },
]) ])
@@ -246,7 +246,7 @@
"unsigned": false, "unsigned": false,
"autoincrement": false, "autoincrement": false,
"primary": false, "primary": false,
"nullable": true, "nullable": false,
"mappedType": "float" "mappedType": "float"
}, },
"code": { "code": {
@@ -255,7 +255,7 @@
"unsigned": false, "unsigned": false,
"autoincrement": false, "autoincrement": false,
"primary": false, "primary": false,
"nullable": true, "nullable": false,
"mappedType": "text" "mappedType": "text"
}, },
"name": { "name": {
@@ -0,0 +1,18 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20240924114005 extends Migration {
async up(): Promise<void> {
this.addSql(
`UPDATE "tax_rate" SET code = 'default' WHERE code IS NULL;`
)
this.addSql(
`ALTER TABLE IF EXISTS "tax_rate" ALTER COLUMN "code" SET NOT NULL;`
)
}
async down(): Promise<void> {
this.addSql(
`ALTER TABLE IF EXISTS "tax_rate" ALTER COLUMN "code" DROP NOT NULL;`
)
}
}
+2 -2
View File
@@ -61,8 +61,8 @@ export default class TaxRate {
rate: number | null = null rate: number | null = null
@Searchable() @Searchable()
@Property({ columnType: "text", nullable: true }) @Property({ columnType: "text" })
code: string | null = null code: string
@Searchable() @Searchable()
@Property({ columnType: "text" }) @Property({ columnType: "text" })