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 new file mode 100644 index 0000000000..d7c9922114 --- /dev/null +++ b/integration-tests/http/__tests__/tax-region/admin/tax-region.spec.ts @@ -0,0 +1,106 @@ +import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +import { createAdminUser } from "../../../../helpers/create-admin-user" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { + headers: { "x-medusa-access-token": "test_token" }, +} + +medusaIntegrationTestRunner({ + env, + testSuite: ({ dbConnection, getContainer, api }) => { + describe("/admin/tax-regions", () => { + beforeEach(async () => { + await createAdminUser(dbConnection, adminHeaders, getContainer()) + }) + + describe("POST /admin/tax-regions/:id", () => { + let taxRegion + + beforeEach(async () => { + taxRegion = ( + await api.post( + "/admin/tax-regions", + { + country_code: "us", + province_code: "tx", + metadata: { test: "created" }, + }, + adminHeaders + ) + ).data.tax_region + }) + + it("should successfully update a tax region's fieleds", async () => { + let taxRegionResponse = await api.post( + `/admin/tax-regions/${taxRegion.id}`, + { + province_code: "ny", + metadata: { test: "updated" }, + }, + adminHeaders + ) + + expect(taxRegionResponse.status).toEqual(200) + expect(taxRegionResponse.data.tax_region).toEqual( + expect.objectContaining({ + id: expect.any(String), + province_code: "ny", + metadata: { test: "updated" }, + }) + ) + + taxRegionResponse = await api.post( + `/admin/tax-regions/${taxRegion.id}`, + { metadata: { test: "updated 2" } }, + adminHeaders + ) + + expect(taxRegionResponse.status).toEqual(200) + expect(taxRegionResponse.data.tax_region).toEqual( + expect.objectContaining({ + id: expect.any(String), + province_code: "ny", + metadata: { test: "updated 2" }, + }) + ) + + taxRegionResponse = await api.post( + `/admin/tax-regions/${taxRegion.id}`, + { province_code: "ca" }, + adminHeaders + ) + + expect(taxRegionResponse.status).toEqual(200) + expect(taxRegionResponse.data.tax_region).toEqual( + expect.objectContaining({ + id: expect.any(String), + province_code: "ca", + metadata: { test: "updated 2" }, + }) + ) + }) + + it("should throw if tax region does not exist", async () => { + const { + response: { status, data }, + } = await api + .post( + `/admin/tax-regions/does-not-exist`, + { province_code: "ny", metadata: { test: "updated" } }, + adminHeaders + ) + .catch((err) => err) + + expect(status).toEqual(404) + expect(data).toEqual({ + message: 'TaxRegion with id "does-not-exist" not found', + type: "not_found", + }) + }) + }) + }) + }, +}) diff --git a/packages/core/core-flows/src/tax/steps/update-tax-regions.ts b/packages/core/core-flows/src/tax/steps/update-tax-regions.ts new file mode 100644 index 0000000000..361c4eb91b --- /dev/null +++ b/packages/core/core-flows/src/tax/steps/update-tax-regions.ts @@ -0,0 +1,58 @@ +import { + ITaxModuleService, + UpdateTaxRegionDTO, +} from "@medusajs/framework/types" +import { + Modules, + getSelectsAndRelationsFromObjectArray, + removeUndefined, +} from "@medusajs/framework/utils" +import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" + +export const updateTaxRegionsStepId = "update-tax-regions" +/** + * This step updates tax regions + */ +export const updateTaxRegionsStep = createStep( + updateTaxRegionsStepId, + async (data: UpdateTaxRegionDTO[], { container }) => { + const service = container.resolve(Modules.TAX) + const { selects, relations } = getSelectsAndRelationsFromObjectArray(data) + + const prevData = await service.listTaxRegions( + { id: data.map((d) => d.id) }, + { + select: selects, + relations, + } + ) + + const updateData = removeUndefined( + data.map((d) => ({ + id: d.id, + province_code: d.province_code, + metadata: d.metadata, + })) + ) + + const taxRegions = await service.updateTaxRegions(updateData) + + return new StepResponse(taxRegions, prevData) + }, + async (prevData, { container }) => { + if (!prevData?.length) { + return + } + + const service = container.resolve(Modules.TAX) + const updateData = removeUndefined( + prevData.map((d) => ({ + id: d.id, + province_code: d.province_code, + metadata: d.metadata, + })) + ) + + await service.updateTaxRegions(updateData) + } +) diff --git a/packages/core/core-flows/src/tax/workflows/index.ts b/packages/core/core-flows/src/tax/workflows/index.ts index 962cb32961..6b66e49197 100644 --- a/packages/core/core-flows/src/tax/workflows/index.ts +++ b/packages/core/core-flows/src/tax/workflows/index.ts @@ -1,8 +1,9 @@ -export * from "./create-tax-regions" -export * from "./delete-tax-regions" -export * from "./create-tax-rates" -export * from "./update-tax-rates" -export * from "./delete-tax-rates" -export * from "./set-tax-rate-rules" export * from "./create-tax-rate-rules" +export * from "./create-tax-rates" +export * from "./create-tax-regions" export * from "./delete-tax-rate-rules" +export * from "./delete-tax-rates" +export * from "./delete-tax-regions" +export * from "./set-tax-rate-rules" +export * from "./update-tax-rates" +export * from "./update-tax-regions" diff --git a/packages/core/core-flows/src/tax/workflows/update-tax-regions.ts b/packages/core/core-flows/src/tax/workflows/update-tax-regions.ts new file mode 100644 index 0000000000..c61b1bfc5e --- /dev/null +++ b/packages/core/core-flows/src/tax/workflows/update-tax-regions.ts @@ -0,0 +1,20 @@ +import { TaxRegionDTO, UpdateTaxRegionDTO } from "@medusajs/framework/types" +import { + WorkflowData, + WorkflowResponse, + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { updateTaxRegionsStep } from "../steps/update-tax-regions" + +export const updateTaxRegionsWorkflowId = "update-tax-regions" +/** + * This workflow updates one or more tax regions. + */ +export const updateTaxRegionsWorkflow = createWorkflow( + updateTaxRegionsWorkflowId, + ( + input: WorkflowData + ): WorkflowResponse => { + return new WorkflowResponse(updateTaxRegionsStep(input)) + } +) diff --git a/packages/core/types/src/tax/mutations.ts b/packages/core/types/src/tax/mutations.ts index 0d4a73c561..d29d122533 100644 --- a/packages/core/types/src/tax/mutations.ts +++ b/packages/core/types/src/tax/mutations.ts @@ -206,6 +206,26 @@ export interface CreateTaxRegionDTO { } } +/** + * The tax region to be updated. + */ +export interface UpdateTaxRegionDTO { + /** + * The id of the tax region to update + */ + id: string + + /** + * The province code of the tax region. + */ + province_code?: string | null + + /** + * Holds custom data in key-value pairs. + */ + metadata?: MetadataType +} + /** * The tax rate rule to be created. */ diff --git a/packages/core/types/src/tax/service.ts b/packages/core/types/src/tax/service.ts index 2f9773eb1b..c300d2a203 100644 --- a/packages/core/types/src/tax/service.ts +++ b/packages/core/types/src/tax/service.ts @@ -20,6 +20,7 @@ import { CreateTaxRateRuleDTO, CreateTaxRegionDTO, UpdateTaxRateDTO, + UpdateTaxRegionDTO, UpsertTaxRateDTO, } from "./mutations" @@ -407,6 +408,47 @@ export interface ITaxModuleService extends IModuleService { sharedContext?: Context ): Promise + /** + * This method updates a tax region. + * + * @param {UpdateTaxRegionDTO} data - The tax region to be updated. + * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. + * @returns {Promise} The updated tax region. + * + * @example + * const taxRegion = await taxModule.updateTaxRegions({ + * province_code: "be", + * }) + */ + updateTaxRegions( + data: UpdateTaxRegionDTO, + sharedContext?: Context + ): Promise + + /** + * This method updates tax regions. + * + * @param {UpdateTaxRegionDTO[]} data - The tax regions to be updated. + * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. + * @returns {Promise} The updated tax regions. + * + * @example + * const taxRegions = await taxModule.updateTaxRegions([ + * { + * id: "tx-1", + * province_code: "be", + * }, + * { + * id: "tx-2", + * province_code: "ca", + * }, + * ]) + */ + updateTaxRegions( + data: UpdateTaxRegionDTO[], + sharedContext?: Context + ): Promise + /** * This method deletes tax regions by their IDs. * diff --git a/packages/medusa/src/api/admin/tax-regions/[id]/route.ts b/packages/medusa/src/api/admin/tax-regions/[id]/route.ts index c503cc37c7..3385eca929 100644 --- a/packages/medusa/src/api/admin/tax-regions/[id]/route.ts +++ b/packages/medusa/src/api/admin/tax-regions/[id]/route.ts @@ -1,13 +1,17 @@ -import { deleteTaxRegionsWorkflow } from "@medusajs/core-flows" import { - ContainerRegistrationKeys, - remoteQueryObjectFromString, -} from "@medusajs/framework/utils" + deleteTaxRegionsWorkflow, + updateTaxRegionsWorkflow, +} from "@medusajs/core-flows" import { AuthenticatedMedusaRequest, MedusaResponse, } from "@medusajs/framework/http" -import { HttpTypes } from "@medusajs/framework/types" +import { HttpTypes, RemoteQueryFunction } from "@medusajs/framework/types" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/framework/utils" +import { AdminUpdateTaxRegionType } from "../validators" export const GET = async ( req: AuthenticatedMedusaRequest, @@ -27,6 +31,38 @@ export const GET = async ( res.status(200).json({ tax_region: taxRegion }) } +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + const query = req.scope.resolve( + ContainerRegistrationKeys.QUERY + ) + + await updateTaxRegionsWorkflow(req.scope).run({ + input: [ + { + id, + ...req.validatedBody, + }, + ], + }) + + const { + data: [tax_region], + } = await query.graph( + { + entity: "tax_region", + fields: req.remoteQueryConfig.fields, + filters: { id }, + }, + { throwIfKeyNotFound: true } + ) + + return res.json({ tax_region }) +} + export const DELETE = async ( req: AuthenticatedMedusaRequest, res: MedusaResponse diff --git a/packages/medusa/src/api/admin/tax-regions/middlewares.ts b/packages/medusa/src/api/admin/tax-regions/middlewares.ts index b060c0814f..7895fc08ec 100644 --- a/packages/medusa/src/api/admin/tax-regions/middlewares.ts +++ b/packages/medusa/src/api/admin/tax-regions/middlewares.ts @@ -4,13 +4,14 @@ import { AdminCreateTaxRegion, AdminGetTaxRegionParams, AdminGetTaxRegionsParams, + AdminUpdateTaxRegion, } from "./validators" -import { MiddlewareRoute } from "@medusajs/framework/http" import { validateAndTransformBody, validateAndTransformQuery, } from "@medusajs/framework" +import { MiddlewareRoute } from "@medusajs/framework/http" export const adminTaxRegionRoutesMiddlewares: MiddlewareRoute[] = [ { @@ -20,7 +21,18 @@ export const adminTaxRegionRoutesMiddlewares: MiddlewareRoute[] = [ validateAndTransformBody(AdminCreateTaxRegion), validateAndTransformQuery( AdminGetTaxRegionsParams, - QueryConfig.listTransformQueryConfig + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: "POST", + matcher: "/admin/tax-regions/:id", + middlewares: [ + validateAndTransformBody(AdminUpdateTaxRegion), + validateAndTransformQuery( + AdminGetTaxRegionsParams, + QueryConfig.retrieveTransformQueryConfig ), ], }, diff --git a/packages/medusa/src/api/admin/tax-regions/validators.ts b/packages/medusa/src/api/admin/tax-regions/validators.ts index 0da18fd3f6..590713e418 100644 --- a/packages/medusa/src/api/admin/tax-regions/validators.ts +++ b/packages/medusa/src/api/admin/tax-regions/validators.ts @@ -54,3 +54,9 @@ export const AdminCreateTaxRegion = z.object({ .optional(), metadata: z.record(z.unknown()).nullish(), }) + +export type AdminUpdateTaxRegionType = z.infer +export const AdminUpdateTaxRegion = z.object({ + province_code: z.string().nullish(), + metadata: z.record(z.unknown()).nullish(), +})