feat(tax): add support for updating tax rates (#6537)
This commit is contained in:
@@ -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-rates"
|
||||
export * from "./update-tax-rates"
|
||||
|
||||
47
packages/core-flows/src/tax/steps/update-tax-rates.ts
Normal file
47
packages/core-flows/src/tax/steps/update-tax-rates.ts
Normal file
@@ -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-rates"
|
||||
export * from "./update-tax-rates"
|
||||
|
||||
22
packages/core-flows/src/tax/workflows/update-tax-rates.ts
Normal file
22
packages/core-flows/src/tax/workflows/update-tax-rates.ts
Normal file
@@ -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 { 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 (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as QueryConfig from "./query-config"
|
||||
import {
|
||||
AdminGetTaxRatesTaxRateParams,
|
||||
AdminPostTaxRatesReq,
|
||||
AdminPostTaxRatesTaxRateReq,
|
||||
} from "./validators"
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
|
||||
@@ -20,6 +21,11 @@ export const adminTaxRateRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
matcher: "/admin/tax-rates",
|
||||
middlewares: [transformBody(AdminPostTaxRatesReq)],
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
matcher: "/admin/tax-rates/:id",
|
||||
middlewares: [transformBody(AdminPostTaxRatesTaxRateReq)],
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
matcher: "/admin/tax-rates/:id",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// HEAD
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsBoolean,
|
||||
@@ -52,3 +51,29 @@ export class AdminPostTaxRatesReq {
|
||||
@IsOptional()
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
data: TaxTypes.CreateTaxRegionDTO,
|
||||
sharedContext?: Context
|
||||
|
||||
@@ -9,6 +9,16 @@ export interface CreateTaxRateDTO {
|
||||
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 {
|
||||
rate?: number | null
|
||||
code?: string | null
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
CreateTaxRateDTO,
|
||||
CreateTaxRegionDTO,
|
||||
UpdateTaxRateDTO,
|
||||
UpsertTaxRateDTO,
|
||||
} from "./mutations"
|
||||
|
||||
export interface ITaxModuleService extends IModuleService {
|
||||
@@ -63,6 +64,12 @@ export interface ITaxModuleService extends IModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<TaxRateDTO[]>
|
||||
|
||||
upsert(data: UpsertTaxRateDTO, sharedContext?: Context): Promise<TaxRateDTO>
|
||||
upsert(
|
||||
data: UpsertTaxRateDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<TaxRateDTO[]>
|
||||
|
||||
delete(taxRateIds: string[], sharedContext?: Context): Promise<void>
|
||||
delete(taxRateId: string, sharedContext?: Context): Promise<void>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user