From 6279fb3c67fb70cdb3b08a90b115546e586ebe8d Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Thu, 29 Feb 2024 12:03:18 +0100 Subject: [PATCH] feat(tax): add support for updating tax rates (#6537) --- .../plugins/__tests__/tax/admin/tax.spec.ts | 90 +++++++++++++++++++ packages/core-flows/src/tax/steps/index.ts | 1 + .../src/tax/steps/update-tax-rates.ts | 47 ++++++++++ .../core-flows/src/tax/workflows/index.ts | 1 + .../src/tax/workflows/update-tax-rates.ts | 22 +++++ .../src/api-v2/admin/tax-rates/[id]/route.ts | 31 +++++++ .../src/api-v2/admin/tax-rates/middlewares.ts | 6 ++ .../src/api-v2/admin/tax-rates/validators.ts | 27 +++++- .../tax/src/services/tax-module-service.ts | 21 +++++ packages/types/src/tax/mutations.ts | 10 +++ packages/types/src/tax/service.ts | 7 ++ 11 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 packages/core-flows/src/tax/steps/update-tax-rates.ts create mode 100644 packages/core-flows/src/tax/workflows/update-tax-rates.ts diff --git a/integration-tests/plugins/__tests__/tax/admin/tax.spec.ts b/integration-tests/plugins/__tests__/tax/admin/tax.spec.ts index ca5ccaa84b..703cce2a22 100644 --- a/integration-tests/plugins/__tests__/tax/admin/tax.spec.ts +++ b/integration-tests/plugins/__tests__/tax/admin/tax.spec.ts @@ -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, + }, + }) + }) }) diff --git a/packages/core-flows/src/tax/steps/index.ts b/packages/core-flows/src/tax/steps/index.ts index 38a9f46a3a..934a5c97c7 100644 --- a/packages/core-flows/src/tax/steps/index.ts +++ b/packages/core-flows/src/tax/steps/index.ts @@ -1,2 +1,3 @@ export * from "./create-tax-regions" export * from "./create-tax-rates" +export * from "./update-tax-rates" diff --git a/packages/core-flows/src/tax/steps/update-tax-rates.ts b/packages/core-flows/src/tax/steps/update-tax-rates.ts new file mode 100644 index 0000000000..36dfbed8cf --- /dev/null +++ b/packages/core-flows/src/tax/steps/update-tax-rates.ts @@ -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( + 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( + ModuleRegistrationName.TAX + ) + + await service.upsert(prevData) + } +) diff --git a/packages/core-flows/src/tax/workflows/index.ts b/packages/core-flows/src/tax/workflows/index.ts index 38a9f46a3a..934a5c97c7 100644 --- a/packages/core-flows/src/tax/workflows/index.ts +++ b/packages/core-flows/src/tax/workflows/index.ts @@ -1,2 +1,3 @@ export * from "./create-tax-regions" export * from "./create-tax-rates" +export * from "./update-tax-rates" diff --git a/packages/core-flows/src/tax/workflows/update-tax-rates.ts b/packages/core-flows/src/tax/workflows/update-tax-rates.ts new file mode 100644 index 0000000000..16a10b59f3 --- /dev/null +++ b/packages/core-flows/src/tax/workflows/update-tax-rates.ts @@ -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): WorkflowData => { + return updateTaxRatesStep(input) + } +) diff --git a/packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts b/packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts index 3b2c4d45b1..1f5e24a27f 100644 --- a/packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts +++ b/packages/medusa/src/api-v2/admin/tax-rates/[id]/route.ts @@ -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, + 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, diff --git a/packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts b/packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts index 87284bd632..ffdbe51d64 100644 --- a/packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts +++ b/packages/medusa/src/api-v2/admin/tax-rates/middlewares.ts @@ -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", diff --git a/packages/medusa/src/api-v2/admin/tax-rates/validators.ts b/packages/medusa/src/api-v2/admin/tax-rates/validators.ts index a7fc762444..938f2a6d15 100644 --- a/packages/medusa/src/api-v2/admin/tax-rates/validators.ts +++ b/packages/medusa/src/api-v2/admin/tax-rates/validators.ts @@ -1,4 +1,3 @@ -// HEAD import { Type } from "class-transformer" import { IsBoolean, @@ -52,3 +51,29 @@ export class AdminPostTaxRatesReq { @IsOptional() metadata?: Record } + +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 +} diff --git a/packages/tax/src/services/tax-module-service.ts b/packages/tax/src/services/tax-module-service.ts index bf5eca4f69..626f3751cb 100644 --- a/packages/tax/src/services/tax-module-service.ts +++ b/packages/tax/src/services/tax-module-service.ts @@ -196,6 +196,27 @@ export default class TaxModuleService< return await this.taxRateService_.update({ selector, data }, sharedContext) } + async upsert( + data: TaxTypes.UpsertTaxRateDTO[], + sharedContext?: Context + ): Promise + async upsert( + data: TaxTypes.UpsertTaxRateDTO, + sharedContext?: Context + ): Promise + + @InjectTransactionManager("baseRepository_") + async upsert( + data: TaxTypes.UpsertTaxRateDTO | TaxTypes.UpsertTaxRateDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + 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 diff --git a/packages/types/src/tax/mutations.ts b/packages/types/src/tax/mutations.ts index 9414e8eebd..39e302e3ac 100644 --- a/packages/types/src/tax/mutations.ts +++ b/packages/types/src/tax/mutations.ts @@ -9,6 +9,16 @@ export interface CreateTaxRateDTO { metadata?: Record } +export interface UpsertTaxRateDTO { + id?: string + rate?: number | null + code?: string | null + name?: string + is_default?: boolean + created_by?: string | null + metadata?: Record | null +} + export interface UpdateTaxRateDTO { rate?: number | null code?: string | null diff --git a/packages/types/src/tax/service.ts b/packages/types/src/tax/service.ts index aa8e925067..b56477f1e8 100644 --- a/packages/types/src/tax/service.ts +++ b/packages/types/src/tax/service.ts @@ -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 + upsert(data: UpsertTaxRateDTO, sharedContext?: Context): Promise + upsert( + data: UpsertTaxRateDTO[], + sharedContext?: Context + ): Promise + delete(taxRateIds: string[], sharedContext?: Context): Promise delete(taxRateId: string, sharedContext?: Context): Promise