feat(tax): add support for updating tax rates (#6537)
This commit is contained in:
1 parent
2407b443f1
commit
6279fb3c67
11 files changed
+262
-1
No files matched your search
@@ -205,4 +205,94 @@ describe("Taxes - Admin", () => {
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("can create a tax rate and update it", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
const regionRes = await api.post(
|
||||||
|
`/admin/tax-regions`,
|
||||||
|
{
|
||||||
|
country_code: "us",
|
||||||
|
default_tax_rate: { code: "default", rate: 2, name: "default rate" },
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const usRegionId = regionRes.data.tax_region.id
|
||||||
|
|
||||||
|
expect(regionRes.status).toEqual(200)
|
||||||
|
expect(regionRes.data).toEqual({
|
||||||
|
tax_region: {
|
||||||
|
id: expect.any(String),
|
||||||
|
country_code: "us",
|
||||||
|
parent_id: null,
|
||||||
|
province_code: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
created_by: "admin_user",
|
||||||
|
provider_id: null,
|
||||||
|
metadata: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const rateRes = await api.post(
|
||||||
|
`/admin/tax-rates`,
|
||||||
|
{
|
||||||
|
tax_region_id: usRegionId,
|
||||||
|
code: "RATE2",
|
||||||
|
name: "another rate",
|
||||||
|
rate: 10,
|
||||||
|
rules: [{ reference: "product", reference_id: "prod_1234" }],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(rateRes.status).toEqual(200)
|
||||||
|
expect(rateRes.data).toEqual({
|
||||||
|
tax_rate: {
|
||||||
|
id: expect.any(String),
|
||||||
|
code: "RATE2",
|
||||||
|
rate: 10,
|
||||||
|
name: "another rate",
|
||||||
|
is_default: false,
|
||||||
|
metadata: null,
|
||||||
|
tax_region_id: usRegionId,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
deleted_at: null,
|
||||||
|
created_by: "admin_user",
|
||||||
|
is_combinable: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateRes = await api.post(
|
||||||
|
`/admin/tax-rates/${rateRes.data.tax_rate.id}`,
|
||||||
|
{
|
||||||
|
code: "updatedcode",
|
||||||
|
rate: 12,
|
||||||
|
is_combinable: true,
|
||||||
|
name: "Another Name",
|
||||||
|
metadata: { you: "know it" },
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updateRes.status).toEqual(200)
|
||||||
|
expect(updateRes.data).toEqual({
|
||||||
|
tax_rate: {
|
||||||
|
id: expect.any(String),
|
||||||
|
code: "updatedcode",
|
||||||
|
rate: 12,
|
||||||
|
name: "Another Name",
|
||||||
|
is_default: false,
|
||||||
|
metadata: { you: "know it" },
|
||||||
|
tax_region_id: usRegionId,
|
||||||
|
deleted_at: null,
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
created_by: "admin_user",
|
||||||
|
is_combinable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./create-tax-regions"
|
export * from "./create-tax-regions"
|
||||||
export * from "./create-tax-rates"
|
export * from "./create-tax-rates"
|
||||||
|
export * from "./update-tax-rates"
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
FilterableTaxRateProps,
|
||||||
|
ITaxModuleService,
|
||||||
|
UpdateTaxRateDTO,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
type UpdateTaxRatesStepInput = {
|
||||||
|
selector: FilterableTaxRateProps
|
||||||
|
update: UpdateTaxRateDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateTaxRatesStepId = "update-tax-rates"
|
||||||
|
export const updateTaxRatesStep = createStep(
|
||||||
|
updateTaxRatesStepId,
|
||||||
|
async (data: UpdateTaxRatesStepInput, { container }) => {
|
||||||
|
const service = container.resolve<ITaxModuleService>(
|
||||||
|
ModuleRegistrationName.TAX
|
||||||
|
)
|
||||||
|
|
||||||
|
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||||
|
data.update,
|
||||||
|
])
|
||||||
|
|
||||||
|
const prevData = await service.list(data.selector, {
|
||||||
|
select: selects,
|
||||||
|
relations,
|
||||||
|
})
|
||||||
|
|
||||||
|
const taxRates = await service.update(data.selector, data.update)
|
||||||
|
|
||||||
|
return new StepResponse(taxRates, prevData)
|
||||||
|
},
|
||||||
|
async (prevData, { container }) => {
|
||||||
|
if (!prevData?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<ITaxModuleService>(
|
||||||
|
ModuleRegistrationName.TAX
|
||||||
|
)
|
||||||
|
|
||||||
|
await service.upsert(prevData)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./create-tax-regions"
|
export * from "./create-tax-regions"
|
||||||
export * from "./create-tax-rates"
|
export * from "./create-tax-rates"
|
||||||
|
export * from "./update-tax-rates"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import {
|
||||||
|
FilterableTaxRateProps,
|
||||||
|
TaxRateDTO,
|
||||||
|
UpdateTaxRateDTO,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { updateTaxRatesStep } from "../steps"
|
||||||
|
|
||||||
|
type UpdateTaxRatesStepInput = {
|
||||||
|
selector: FilterableTaxRateProps
|
||||||
|
update: UpdateTaxRateDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkflowInput = UpdateTaxRatesStepInput
|
||||||
|
|
||||||
|
export const updateTaxRatesWorkflowId = "update-tax-rates"
|
||||||
|
export const updateTaxRatesWorkflow = createWorkflow(
|
||||||
|
updateTaxRatesWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInput>): WorkflowData<TaxRateDTO[]> => {
|
||||||
|
return updateTaxRatesStep(input)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -5,6 +5,37 @@ import {
|
|||||||
|
|
||||||
import { defaultAdminTaxRateFields } from "../query-config"
|
import { defaultAdminTaxRateFields } from "../query-config"
|
||||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||||
|
import { AdminPostTaxRatesTaxRateReq } from "../../../../api/routes/admin/tax-rates"
|
||||||
|
import { updateTaxRatesWorkflow } from "@medusajs/core-flows"
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminPostTaxRatesTaxRateReq>,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const { errors } = await updateTaxRatesWorkflow(req.scope).run({
|
||||||
|
input: {
|
||||||
|
selector: { id: req.params.id },
|
||||||
|
update: req.validatedBody,
|
||||||
|
},
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||||
|
|
||||||
|
const queryObject = remoteQueryObjectFromString({
|
||||||
|
entryPoint: "tax_rate",
|
||||||
|
variables: { id: req.params.id },
|
||||||
|
fields: defaultAdminTaxRateFields,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [taxRate] = await remoteQuery(queryObject)
|
||||||
|
|
||||||
|
res.status(200).json({ tax_rate: taxRate })
|
||||||
|
}
|
||||||
|
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
req: AuthenticatedMedusaRequest,
|
req: AuthenticatedMedusaRequest,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as QueryConfig from "./query-config"
|
|||||||
import {
|
import {
|
||||||
AdminGetTaxRatesTaxRateParams,
|
AdminGetTaxRatesTaxRateParams,
|
||||||
AdminPostTaxRatesReq,
|
AdminPostTaxRatesReq,
|
||||||
|
AdminPostTaxRatesTaxRateReq,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ export const adminTaxRateRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
matcher: "/admin/tax-rates",
|
matcher: "/admin/tax-rates",
|
||||||
middlewares: [transformBody(AdminPostTaxRatesReq)],
|
middlewares: [transformBody(AdminPostTaxRatesReq)],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
matcher: "/admin/tax-rates/:id",
|
||||||
|
middlewares: [transformBody(AdminPostTaxRatesTaxRateReq)],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
matcher: "/admin/tax-rates/:id",
|
matcher: "/admin/tax-rates/:id",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// HEAD
|
|
||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
@@ -52,3 +51,29 @@ export class AdminPostTaxRatesReq {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AdminPostTaxRatesTaxRateReq {
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
rate?: number | null
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
code?: string | null
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
is_default?: boolean
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
is_combinable?: boolean
|
||||||
|
|
||||||
|
@IsObject()
|
||||||
|
@IsOptional()
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
@@ -196,6 +196,27 @@ export default class TaxModuleService<
|
|||||||
return await this.taxRateService_.update({ selector, data }, sharedContext)
|
return await this.taxRateService_.update({ selector, data }, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async upsert(
|
||||||
|
data: TaxTypes.UpsertTaxRateDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<TaxTypes.TaxRateDTO[]>
|
||||||
|
async upsert(
|
||||||
|
data: TaxTypes.UpsertTaxRateDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<TaxTypes.TaxRateDTO>
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async upsert(
|
||||||
|
data: TaxTypes.UpsertTaxRateDTO | TaxTypes.UpsertTaxRateDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TaxTypes.TaxRateDTO | TaxTypes.TaxRateDTO[]> {
|
||||||
|
const result = await this.taxRateService_.upsert(data, sharedContext)
|
||||||
|
const serialized = await this.baseRepository_.serialize<
|
||||||
|
TaxTypes.TaxRateDTO[]
|
||||||
|
>(result, { populate: true })
|
||||||
|
return Array.isArray(data) ? serialized : serialized[0]
|
||||||
|
}
|
||||||
|
|
||||||
createTaxRegions(
|
createTaxRegions(
|
||||||
data: TaxTypes.CreateTaxRegionDTO,
|
data: TaxTypes.CreateTaxRegionDTO,
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ export interface CreateTaxRateDTO {
|
|||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpsertTaxRateDTO {
|
||||||
|
id?: string
|
||||||
|
rate?: number | null
|
||||||
|
code?: string | null
|
||||||
|
name?: string
|
||||||
|
is_default?: boolean
|
||||||
|
created_by?: string | null
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateTaxRateDTO {
|
export interface UpdateTaxRateDTO {
|
||||||
rate?: number | null
|
rate?: number | null
|
||||||
code?: string | null
|
code?: string | null
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
CreateTaxRateDTO,
|
CreateTaxRateDTO,
|
||||||
CreateTaxRegionDTO,
|
CreateTaxRegionDTO,
|
||||||
UpdateTaxRateDTO,
|
UpdateTaxRateDTO,
|
||||||
|
UpsertTaxRateDTO,
|
||||||
} from "./mutations"
|
} from "./mutations"
|
||||||
|
|
||||||
export interface ITaxModuleService extends IModuleService {
|
export interface ITaxModuleService extends IModuleService {
|
||||||
@@ -63,6 +64,12 @@ export interface ITaxModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<TaxRateDTO[]>
|
): Promise<TaxRateDTO[]>
|
||||||
|
|
||||||
|
upsert(data: UpsertTaxRateDTO, sharedContext?: Context): Promise<TaxRateDTO>
|
||||||
|
upsert(
|
||||||
|
data: UpsertTaxRateDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<TaxRateDTO[]>
|
||||||
|
|
||||||
delete(taxRateIds: string[], sharedContext?: Context): Promise<void>
|
delete(taxRateIds: string[], sharedContext?: Context): Promise<void>
|
||||||
delete(taxRateId: string, sharedContext?: Context): Promise<void>
|
delete(taxRateId: string, sharedContext?: Context): Promise<void>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user