diff --git a/integration-tests/http/__tests__/returns/returns.spec.ts b/integration-tests/http/__tests__/returns/returns.spec.ts new file mode 100644 index 0000000000..e99ee6fcb0 --- /dev/null +++ b/integration-tests/http/__tests__/returns/returns.spec.ts @@ -0,0 +1,278 @@ +import { ModuleRegistrationName, RuleOperator } from "@medusajs/utils" +import { medusaIntegrationTestRunner } from "medusa-test-utils" +import { + adminHeaders, + createAdminUser, +} from "../../../helpers/create-admin-user" + +jest.setTimeout(30000) + +medusaIntegrationTestRunner({ + testSuite: ({ dbConnection, getContainer, api }) => { + let order + let returnShippingOption + let shippingProfile + let fulfillmentSet + + beforeEach(async () => { + const container = getContainer() + await createAdminUser(dbConnection, adminHeaders, container) + + const orderModule = container.resolve(ModuleRegistrationName.ORDER) + + order = await orderModule.createOrders({ + region_id: "test_region_id", + email: "foo@bar.com", + items: [ + { + title: "Custom Item 2", + quantity: 1, + unit_price: 50, + }, + ], + sales_channel_id: "test", + shipping_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + phone: "12345", + }, + billing_address: { + first_name: "Test", + last_name: "Test", + address_1: "Test", + city: "Test", + country_code: "US", + postal_code: "12345", + }, + shipping_methods: [ + { + name: "Test shipping method", + amount: 10, + data: {}, + tax_lines: [ + { + description: "shipping Tax 1", + tax_rate_id: "tax_usa_shipping", + code: "code", + rate: 10, + }, + ], + }, + ], + currency_code: "usd", + customer_id: "joe", + }) + + shippingProfile = ( + await api.post( + `/admin/shipping-profiles`, + { + name: "Test", + type: "default", + }, + adminHeaders + ) + ).data.shipping_profile + + let location = ( + await api.post( + `/admin/stock-locations`, + { + name: "Test location", + }, + adminHeaders + ) + ).data.stock_location + + location = ( + await api.post( + `/admin/stock-locations/${location.id}/fulfillment-sets?fields=*fulfillment_sets`, + { + name: "Test", + type: "test-type", + }, + adminHeaders + ) + ).data.stock_location + + fulfillmentSet = ( + await api.post( + `/admin/fulfillment-sets/${location.fulfillment_sets[0].id}/service-zones`, + { + name: "Test", + geo_zones: [{ type: "country", country_code: "us" }], + }, + adminHeaders + ) + ).data.fulfillment_set + + const shippingOptionPayload = { + name: "Return shipping", + service_zone_id: fulfillmentSet.service_zones[0].id, + shipping_profile_id: shippingProfile.id, + provider_id: "manual_test-provider", + price_type: "flat", + type: { + label: "Test type", + description: "Test description", + code: "test-code", + }, + prices: [ + { + currency_code: "usd", + amount: 1000, + }, + ], + rules: [ + { + operator: RuleOperator.EQ, + attribute: "is_return", + value: "true", + }, + ], + } + + returnShippingOption = ( + await api.post( + "/admin/shipping-options", + shippingOptionPayload, + adminHeaders + ) + ).data.shipping_option + + const item = order.items[0] + + await api.post( + `/admin/orders/${order.id}/fulfillments`, + { + items: [ + { + id: item.id, + quantity: 1, + }, + ], + }, + adminHeaders + ) + }) + + describe("Returns lifecycle", () => { + // Simple lifecyle: + // 1. Initiate return + // 2. Request to return items + // 3. Add return shipping + // 4. Confirm return + it("should initiate a return", async () => { + let result = await api.post( + "/admin/returns", + { + order_id: order.id, + description: "Test", + }, + adminHeaders + ) + + const returnId = result.data.return.id + + expect(result.data.return).toEqual( + expect.objectContaining({ + id: expect.any(String), + order_id: order.id, + display_id: 1, + order_version: 2, + status: "requested", + items: [], + shipping_methods: [], + }) + ) + + const item = order.items[0] + + result = await api.post( + `/admin/returns/${returnId}/request-items`, + { + items: [ + { + id: item.id, + quantity: 1, + }, + ], + }, + adminHeaders + ) + + expect(result.data.return).toEqual( + expect.objectContaining({ + id: expect.any(String), + order_id: order.id, + display_id: 1, + order_version: 2, + status: "requested", + items: [], + shipping_methods: [], + }) + ) + + result = await api.post( + `/admin/returns/${returnId}/shipping-method`, + { + shipping_option_id: returnShippingOption.id, + }, + adminHeaders + ) + + expect(result.data.return).toEqual( + expect.objectContaining({ + id: expect.any(String), + order_id: order.id, + display_id: 1, + order_version: 2, + status: "requested", + items: [], + shipping_methods: [ + expect.objectContaining({ + amount: 1000, + name: "Return shipping", + shipping_option_id: returnShippingOption.id, + }), + ], + }) + ) + + result = await api.post( + `/admin/returns/${returnId}/request`, + {}, + adminHeaders + ) + + expect(result.data.return).toEqual( + expect.objectContaining({ + id: expect.any(String), + order_id: order.id, + display_id: 1, + order_version: 2, + status: "requested", + items: [ + expect.objectContaining({ + quantity: 1, + item_id: item.id, + received_quantity: 0, + }), + ], + shipping_methods: [ + expect.objectContaining({ + amount: 1000, + name: "Return shipping", + shipping_option_id: returnShippingOption.id, + }), + ], + }) + ) + }) + }) + }, +}) diff --git a/integration-tests/modules/__tests__/order/workflows/return/create-return-shipping.spec.ts b/integration-tests/modules/__tests__/order/workflows/return/create-return-shipping.spec.ts index e59bed552d..ea352a248a 100644 --- a/integration-tests/modules/__tests__/order/workflows/return/create-return-shipping.spec.ts +++ b/integration-tests/modules/__tests__/order/workflows/return/create-return-shipping.spec.ts @@ -63,8 +63,8 @@ medusaIntegrationTestRunner({ container ).run({ input: { - returnId: returnOrder.id, - shippingOptionId: shippingOptionId, + return_id: returnOrder.id, + shipping_option_id: shippingOptionId, }, }) @@ -75,10 +75,9 @@ medusaIntegrationTestRunner({ id: expect.any(String), reference: "order_shipping_method", reference_id: expect.any(String), - details: { - order_id: returnOrder.order_id, - return_id: returnOrder.id, - }, + order_id: returnOrder.order_id, + return_id: returnOrder.id, + details: {}, raw_amount: { value: "10", precision: 20 }, applied: false, action: "SHIPPING_ADD", @@ -94,9 +93,9 @@ medusaIntegrationTestRunner({ container ).run({ input: { - returnId: returnOrder.id, - shippingOptionId: shippingOptionId, - customShippingPrice: 20, + return_id: returnOrder.id, + shipping_option_id: shippingOptionId, + custom_price: 20, }, }) @@ -107,10 +106,9 @@ medusaIntegrationTestRunner({ id: expect.any(String), reference: "order_shipping_method", reference_id: expect.any(String), - details: { - order_id: returnOrder.order_id, - return_id: returnOrder.id, - }, + order_id: returnOrder.order_id, + return_id: returnOrder.id, + details: {}, raw_amount: { value: "20", precision: 20 }, applied: false, action: "SHIPPING_ADD", diff --git a/integration-tests/modules/__tests__/order/workflows/return/items.spec.ts b/integration-tests/modules/__tests__/order/workflows/return/items.spec.ts index bd5d12f096..e008187efe 100644 --- a/integration-tests/modules/__tests__/order/workflows/return/items.spec.ts +++ b/integration-tests/modules/__tests__/order/workflows/return/items.spec.ts @@ -83,7 +83,6 @@ medusaIntegrationTestRunner({ reference_id: returnOrder.id, details: { reference_id: item.id, - return_id: returnOrder.id, quantity: 1, }, internal_note: "test", diff --git a/packages/core/core-flows/src/order/steps/confirm-order-changes.ts b/packages/core/core-flows/src/order/steps/confirm-order-changes.ts new file mode 100644 index 0000000000..69125cc1db --- /dev/null +++ b/packages/core/core-flows/src/order/steps/confirm-order-changes.ts @@ -0,0 +1,28 @@ +import { OrderChangeDTO } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/utils" +import { createStep, StepResponse } from "@medusajs/workflows-sdk" + +type ConfirmOrderChangesInput = { + orderId: string + changes: OrderChangeDTO[] +} + +export const confirmOrderChanges = createStep( + "confirm-order-changes", + async (input: ConfirmOrderChangesInput, { container }) => { + const orderModuleService = container.resolve(ModuleRegistrationName.ORDER) + await orderModuleService.confirmOrderChange( + input.changes.map((action) => action.id) + ) + + return new StepResponse(null, input.orderId) + }, + async (orderId, { container }) => { + if (!orderId) { + return + } + + const orderModuleService = container.resolve(ModuleRegistrationName.ORDER) + await orderModuleService.revertLastVersion(orderId) + } +) diff --git a/packages/core/core-flows/src/order/steps/create-return-items.ts b/packages/core/core-flows/src/order/steps/create-return-items.ts new file mode 100644 index 0000000000..3932da546e --- /dev/null +++ b/packages/core/core-flows/src/order/steps/create-return-items.ts @@ -0,0 +1,55 @@ +import { IOrderModuleService, OrderChangeActionDTO } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/utils" +import { createStep, StepResponse } from "@medusajs/workflows-sdk" + +type CreateReturnItemsInput = { + changes: OrderChangeActionDTO[] + returnId: string +} + +export const createReturnItems = createStep( + "create-return-items", + async (input: CreateReturnItemsInput, { container }) => { + const orderModuleService = container.resolve( + ModuleRegistrationName.ORDER + ) + + const returnItems = input.changes.map((item) => { + return { + return_id: item.reference_id, + item_id: item.details?.reference_id, + quantity: item.details?.quantity as number, + note: item.internal_note, + metadata: (item.details?.metadata as Record) ?? {}, + } + }) + + const [prevReturn] = await orderModuleService.listReturns( + { id: input.returnId }, + { + select: ["id"], + relations: ["items"], + } + ) + + const createdReturnItems = await orderModuleService.updateReturns([ + { selector: { id: input.returnId }, data: { items: returnItems } }, + ]) + + return new StepResponse(createdReturnItems, prevReturn) + }, + async (prevData, { container }) => { + if (!prevData) { + return + } + + const orderModuleService = container.resolve( + ModuleRegistrationName.ORDER + ) + + await orderModuleService.updateReturns( + { id: prevData.id }, + { items: prevData.items } + ) + } +) diff --git a/packages/core/core-flows/src/order/workflows/confirm-return-request.ts b/packages/core/core-flows/src/order/workflows/confirm-return-request.ts new file mode 100644 index 0000000000..87d0100403 --- /dev/null +++ b/packages/core/core-flows/src/order/workflows/confirm-return-request.ts @@ -0,0 +1,93 @@ +import { OrderChangeDTO, OrderDTO, ReturnDTO } from "@medusajs/types" +import { ChangeActionType } from "@medusajs/utils" +import { + createStep, + createWorkflow, + transform, + WorkflowData, +} from "@medusajs/workflows-sdk" +import { useRemoteQueryStep } from "../../common" +import { previewOrderChangeStep } from "../steps" +import { confirmOrderChanges } from "../steps/confirm-order-changes" +import { createReturnItems } from "../steps/create-return-items" +import { + throwIfIsCancelled, + throwIfOrderChangeIsNotActive, +} from "../utils/order-validation" + +type WorkflowInput = { + return_id: string +} + +const validationStep = createStep( + "validate-create-return-shipping-method", + async function ({ + order, + orderChange, + orderReturn, + }: { + order: OrderDTO + orderReturn: ReturnDTO + orderChange: OrderChangeDTO + }) { + throwIfIsCancelled(order, "Order") + throwIfIsCancelled(orderReturn, "Return") + throwIfOrderChangeIsNotActive({ orderChange }) + } +) + +export const confirmReturnRequestWorkflowId = "confirm-return-request" +export const confirmReturnRequestWorkflow = createWorkflow( + confirmReturnRequestWorkflowId, + function (input: WorkflowInput): WorkflowData { + const orderReturn: ReturnDTO = useRemoteQueryStep({ + entry_point: "return", + fields: ["id", "status", "order_id"], + variables: { id: input.return_id }, + list: false, + throw_if_key_not_found: true, + }) + + const order: OrderDTO = useRemoteQueryStep({ + entry_point: "orders", + fields: ["id", "version", "items"], + variables: { id: orderReturn.order_id }, + list: false, + throw_if_key_not_found: true, + }).config({ name: "order-query" }) + + const orderChange: OrderChangeDTO = useRemoteQueryStep({ + entry_point: "order_change", + fields: [ + "id", + "actions.id", + "actions.action", + "actions.details", + "actions.reference", + "actions.reference_id", + "actions.internal_note", + ], + variables: { + filters: { + order_id: orderReturn.order_id, + return_id: orderReturn.id, + }, + }, + list: false, + }).config({ name: "order-change-query" }) + + const returnItemActions = transform({ orderChange }, (data) => { + return data.orderChange.actions.filter( + (act) => act.action === ChangeActionType.RETURN_ITEM + ) + }) + + validationStep({ order, orderReturn, orderChange }) + + createReturnItems({ returnId: orderReturn.id, changes: returnItemActions }) + + confirmOrderChanges({ changes: [orderChange], orderId: order.id }) + + return previewOrderChangeStep(order.id) + } +) diff --git a/packages/core/core-flows/src/order/workflows/create-return-shipping-method.ts b/packages/core/core-flows/src/order/workflows/create-return-shipping-method.ts index 50b7cffa57..32b75e886c 100644 --- a/packages/core/core-flows/src/order/workflows/create-return-shipping-method.ts +++ b/packages/core/core-flows/src/order/workflows/create-return-shipping-method.ts @@ -16,7 +16,7 @@ import { createOrderChangeActionsStep } from "../steps/create-order-change-actio import { createOrderShippingMethods } from "../steps/create-order-shipping-methods" import { throwIfIsCancelled, - throwIfOrderChangeIsNotActive + throwIfOrderChangeIsNotActive, } from "../utils/order-validation" const validationStep = createStep( @@ -41,14 +41,14 @@ export const createReturnShippingMethodWorkflowId = export const createReturnShippingMethodWorkflow = createWorkflow( createReturnShippingMethodWorkflowId, function (input: { - returnId: string - shippingOptionId: string - customShippingPrice?: BigNumberInput + return_id: string + shipping_option_id: string + custom_price?: BigNumberInput }): WorkflowData { const orderReturn: ReturnDTO = useRemoteQueryStep({ entry_point: "return", fields: ["id", "status", "order_id"], - variables: { id: input.returnId }, + variables: { id: input.return_id }, list: false, throw_if_key_not_found: true, }) @@ -70,7 +70,7 @@ export const createReturnShippingMethodWorkflow = createWorkflow( "calculated_price.is_calculated_price_tax_inclusive", ], variables: { - id: input.shippingOptionId, + id: input.shipping_option_id, calculated_price: { context: { currency_code: order.currency_code }, }, @@ -102,7 +102,9 @@ export const createReturnShippingMethodWorkflow = createWorkflow( const orderChange: OrderChangeDTO = useRemoteQueryStep({ entry_point: "order_change", fields: ["id", "status"], - variables: { order_id: orderReturn.order_id }, + variables: { + filters: { order_id: orderReturn.order_id, return_id: orderReturn.id }, + }, list: false, }).config({ name: "order-change-query" }) @@ -114,7 +116,7 @@ export const createReturnShippingMethodWorkflow = createWorkflow( returnId: orderReturn.id, shippingOption: shippingOptions[0], methodId: createdMethods[0].id, - customPrice: input.customShippingPrice, + customPrice: input.custom_price, }, ({ shippingOption, returnId, orderId, methodId, customPrice }) => { const methodPrice = @@ -125,10 +127,8 @@ export const createReturnShippingMethodWorkflow = createWorkflow( reference: "order_shipping_method", reference_id: methodId, amount: methodPrice, - details: { - order_id: orderId, - return_id: returnId, - }, + order_id: orderId, + return_id: returnId, } } ) diff --git a/packages/core/core-flows/src/order/workflows/index.ts b/packages/core/core-flows/src/order/workflows/index.ts index 1d85e1d748..93e43c71eb 100644 --- a/packages/core/core-flows/src/order/workflows/index.ts +++ b/packages/core/core-flows/src/order/workflows/index.ts @@ -9,6 +9,7 @@ export * from "./cancel-order-fulfillment" export * from "./cancel-return" export * from "./claim-request-item-return" export * from "./complete-orders" +export * from "./confirm-return-request" export * from "./create-complete-return" export * from "./create-fulfillment" export * from "./create-order-change" diff --git a/packages/core/core-flows/src/order/workflows/request-item-return.ts b/packages/core/core-flows/src/order/workflows/request-item-return.ts index e374bea1c1..16b4b990ac 100644 --- a/packages/core/core-flows/src/order/workflows/request-item-return.ts +++ b/packages/core/core-flows/src/order/workflows/request-item-return.ts @@ -18,7 +18,6 @@ import { throwIfIsCancelled, throwIfItemsDoesNotExistsInOrder, throwIfOrderChangeIsNotActive, - throwIfOrderIsCancelled, } from "../utils/order-validation" const validationStep = createStep( @@ -34,7 +33,7 @@ const validationStep = createStep( orderChange: OrderChangeDTO items: OrderWorkflow.RequestItemReturnWorkflowInput["items"] }) { - throwIfOrderIsCancelled({ order }) + throwIfIsCancelled(order, "Order") throwIfIsCancelled(orderReturn, "Return") throwIfOrderChangeIsNotActive({ orderChange }) throwIfItemsDoesNotExistsInOrder({ order, inputItems: items }) @@ -65,8 +64,10 @@ export const requestItemReturnWorkflow = createWorkflow( const orderChange: OrderChangeDTO = useRemoteQueryStep({ entry_point: "order_change", - fields: ["id", "status"], - variables: { order_id: orderReturn.order_id }, + fields: ["id", "status", "order_id", "return_id"], + variables: { + filters: { order_id: orderReturn.order_id, return_id: orderReturn.id }, + }, list: false, }).config({ name: "order-change-query" }) @@ -86,7 +87,6 @@ export const requestItemReturnWorkflow = createWorkflow( reference_id: orderReturn.id, details: { reference_id: item.id, - return_id: orderReturn.id, quantity: item.quantity, metadata: item.metadata, }, diff --git a/packages/core/js-sdk/src/admin/index.ts b/packages/core/js-sdk/src/admin/index.ts index 006eaa09ae..6ba91070da 100644 --- a/packages/core/js-sdk/src/admin/index.ts +++ b/packages/core/js-sdk/src/admin/index.ts @@ -14,6 +14,7 @@ import { ProductCollection } from "./product-collection" import { ProductTag } from "./product-tag" import { ProductType } from "./product-type" import { Region } from "./region" +import { Return } from "./return" import { SalesChannel } from "./sales-channel" import { ShippingOption } from "./shipping-option" import { ShippingProfile } from "./shipping-profile" @@ -47,6 +48,7 @@ export class Admin { public taxRegion: TaxRegion public store: Store public productTag: ProductTag + public return: Return constructor(client: Client) { this.invite = new Invite(client) @@ -72,5 +74,6 @@ export class Admin { this.taxRegion = new TaxRegion(client) this.store = new Store(client) this.productTag = new ProductTag(client) + this.return = new Return(client) } } diff --git a/packages/core/js-sdk/src/admin/return.ts b/packages/core/js-sdk/src/admin/return.ts new file mode 100644 index 0000000000..b72491b8c4 --- /dev/null +++ b/packages/core/js-sdk/src/admin/return.ts @@ -0,0 +1,77 @@ +import { HttpTypes } from "@medusajs/types" +import { Client } from "../client" +import { ClientHeaders } from "../types" + +export class Return { + private client: Client + constructor(client: Client) { + this.client = client + } + + async initiateRequest( + body: HttpTypes.AdminInitiateReturnRequest, + query?: HttpTypes.SelectParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/returns`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async addReturnItem( + id: string, + body: HttpTypes.AdminAddReturnItems, + query?: HttpTypes.SelectParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/returns/${id}/request-items`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async addReturnShipping( + id: string, + body: HttpTypes.AdminAddReturnShipping, + query?: HttpTypes.SelectParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/returns/${id}/shipping-method`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async confirmRequest( + id: string, + body: HttpTypes.AdminConfirmReturnRequest, + query?: HttpTypes.SelectParams, + headers?: ClientHeaders + ) { + return await this.client.fetch( + `/admin/returns/${id}/request`, + { + method: "POST", + headers, + body, + query, + } + ) + } +} diff --git a/packages/core/types/src/http/index.ts b/packages/core/types/src/http/index.ts index 2ba9b976f5..fae4cec70d 100644 --- a/packages/core/types/src/http/index.ts +++ b/packages/core/types/src/http/index.ts @@ -23,6 +23,7 @@ export * from "./product-type" export * from "./promotion" export * from "./region" export * from "./reservation" +export * from "./return" export * from "./sales-channel" export * from "./shipping-option" export * from "./shipping-profile" @@ -31,3 +32,4 @@ export * from "./store" export * from "./tax-rate" export * from "./tax-region" export * from "./user" + diff --git a/packages/core/types/src/http/return/admin.ts b/packages/core/types/src/http/return/admin.ts new file mode 100644 index 0000000000..95c2c0b192 --- /dev/null +++ b/packages/core/types/src/http/return/admin.ts @@ -0,0 +1,54 @@ +export interface BaseReturnItem { + id: string + quantity: number + received_quantity: number + reason_id?: string + note?: string + item_id: string + return_id: string + metadata?: Record +} + +export interface AdminReturnResponse { + id: string + order_id: string + status?: string + exchange_id?: string + claim_id?: string + order_version: number + display_id: number + no_notification?: boolean + refund_amount?: number + items: BaseReturnItem[] +} + +export interface AdminInitiateReturnRequest { + order_id: string + location_id?: string + description?: string + internal_note?: string + no_notification?: boolean + metadata?: Record +} + +export interface AdminAddReturnItem { + id: string + quantity: number + description?: string + internal_note?: string + metadata?: Record +} + +export interface AdminAddReturnItems { + items: AdminAddReturnItem[] +} +export interface AdminAddReturnShipping { + shipping_option_id: string + custom_price?: number + description?: string + internal_note?: string + metadata?: Record +} +export interface AdminConfirmReturnRequest { + no_notification?: boolean +} diff --git a/packages/core/types/src/http/return/index.ts b/packages/core/types/src/http/return/index.ts new file mode 100644 index 0000000000..26b8eb9dad --- /dev/null +++ b/packages/core/types/src/http/return/index.ts @@ -0,0 +1 @@ +export * from "./admin" diff --git a/packages/core/types/src/order/common.ts b/packages/core/types/src/order/common.ts index deb0223d8c..f7ccde7e01 100644 --- a/packages/core/types/src/order/common.ts +++ b/packages/core/types/src/order/common.ts @@ -1139,6 +1139,7 @@ export interface OrderDTO { type ReturnStatus = "requested" | "received" | "partially_received" | "canceled" export interface ReturnDTO extends Omit { + id: string status: ReturnStatus refund_amount?: BigNumberValue order_id: string diff --git a/packages/core/types/src/order/mutations.ts b/packages/core/types/src/order/mutations.ts index 06ef8740b3..4a5af2c171 100644 --- a/packages/core/types/src/order/mutations.ts +++ b/packages/core/types/src/order/mutations.ts @@ -444,6 +444,13 @@ export interface UpdateReturnDTO { claim_id?: string exchange_id?: string metadata?: Record | null + items?: { + quantity: BigNumberInput + internal_note?: string | null + note?: string | null + reason_id?: string | null + metadata?: Record | null + }[] } export interface UpdateOrderClaimDTO { diff --git a/packages/medusa/src/api/admin/returns/[id]/request-items/route.ts b/packages/medusa/src/api/admin/returns/[id]/request-items/route.ts new file mode 100644 index 0000000000..9f590f3267 --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/request-items/route.ts @@ -0,0 +1,40 @@ +import { requestItemReturnWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminPostReturnsRequestItemsReqSchemaType } from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + await requestItemReturnWorkflow(req.scope).run({ + input: { ...req.validatedBody, return_id: id }, + }) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + + const [orderReturn] = await remoteQuery(queryObject) + + res.json({ + return: orderReturn, + }) +} diff --git a/packages/medusa/src/api/admin/returns/[id]/request/route.ts b/packages/medusa/src/api/admin/returns/[id]/request/route.ts new file mode 100644 index 0000000000..6b14fae184 --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/request/route.ts @@ -0,0 +1,40 @@ +import { confirmReturnRequestWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminPostReturnsConfirmRequestReqSchemaType } from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + await confirmReturnRequestWorkflow(req.scope).run({ + input: { return_id: id }, + }) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + + const [orderReturn] = await remoteQuery(queryObject) + + res.json({ + return: orderReturn, + }) +} diff --git a/packages/medusa/src/api/admin/returns/[id]/shipping-method/route.ts b/packages/medusa/src/api/admin/returns/[id]/shipping-method/route.ts new file mode 100644 index 0000000000..62236b6eef --- /dev/null +++ b/packages/medusa/src/api/admin/returns/[id]/shipping-method/route.ts @@ -0,0 +1,40 @@ +import { createReturnShippingMethodWorkflow } from "@medusajs/core-flows" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminPostReturnsShippingReqSchemaType } from "../../validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { id } = req.params + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + await createReturnShippingMethodWorkflow(req.scope).run({ + input: { ...req.validatedBody, return_id: id }, + }) + + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + + const [orderReturn] = await remoteQuery(queryObject) + + res.json({ + return: orderReturn, + }) +} diff --git a/packages/medusa/src/api/admin/returns/middlewares.ts b/packages/medusa/src/api/admin/returns/middlewares.ts index 77f269af9c..26009ee77b 100644 --- a/packages/medusa/src/api/admin/returns/middlewares.ts +++ b/packages/medusa/src/api/admin/returns/middlewares.ts @@ -5,7 +5,10 @@ import * as QueryConfig from "./query-config" import { AdminGetOrdersOrderParams, AdminGetOrdersParams, + AdminPostReturnsConfirmRequestReqSchema, AdminPostReturnsReqSchema, + AdminPostReturnsRequestItemsReqSchema, + AdminPostReturnsShippingReqSchema, } from "./validators" export const adminReturnRoutesMiddlewares: MiddlewareRoute[] = [ @@ -40,4 +43,37 @@ export const adminReturnRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/admin/returns/:id/request-items", + middlewares: [ + validateAndTransformBody(AdminPostReturnsRequestItemsReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/returns/:id/shipping-method", + middlewares: [ + validateAndTransformBody(AdminPostReturnsShippingReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/returns/:id/request", + middlewares: [ + validateAndTransformBody(AdminPostReturnsConfirmRequestReqSchema), + validateAndTransformQuery( + AdminGetOrdersOrderParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, ] diff --git a/packages/medusa/src/api/admin/returns/query-config.ts b/packages/medusa/src/api/admin/returns/query-config.ts index df08f01b4f..22b10e367d 100644 --- a/packages/medusa/src/api/admin/returns/query-config.ts +++ b/packages/medusa/src/api/admin/returns/query-config.ts @@ -1,62 +1,25 @@ -export const defaultAdminOrderFields = [ +export const defaultAdminReturnFields = [ "id", + "order_id", + "exchange_id", + "claim_id", "display_id", + "order_version", "status", - "version", - "summary", - "metadata", - "created_at", - "updated_at", -] - -export const defaultAdminRetrieveOrderFields = [ - "id", - "display_id", - "status", - "version", - "summary", - "total", - "subtotal", - "tax_total", - "discount_total", - "discount_tax_total", - "original_total", - "original_tax_total", - "item_total", - "item_subtotal", - "item_tax_total", - "original_item_total", - "original_item_subtotal", - "original_item_tax_total", - "shipping_total", - "shipping_subtotal", - "shipping_tax_total", - "original_shipping_tax_total", - "original_shipping_tax_subtotal", - "original_shipping_total", + "refund_amount", "created_at", "updated_at", "*items", - "*items.tax_lines", - "*items.adjustments", - "*items.variant", - "*items.variant.product", - "*items.detail", - "*shipping_address", - "*billing_address", "*shipping_methods", - "*shipping_methods.tax_lines", - "*shipping_methods.adjustments", - "*payment_collections", ] export const retrieveTransformQueryConfig = { - defaultFields: defaultAdminRetrieveOrderFields, + defaultFields: defaultAdminReturnFields, isList: false, } export const listTransformQueryConfig = { - defaults: defaultAdminOrderFields, + defaults: defaultAdminReturnFields, defaultLimit: 20, isList: true, } diff --git a/packages/medusa/src/api/admin/returns/route.ts b/packages/medusa/src/api/admin/returns/route.ts index 1c707cfca9..880732135b 100644 --- a/packages/medusa/src/api/admin/returns/route.ts +++ b/packages/medusa/src/api/admin/returns/route.ts @@ -41,11 +41,25 @@ export const POST = async ( res: MedusaResponse ) => { const input = req.validatedBody as AdminPostReturnsReqSchemaType + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const workflow = beginReturnOrderWorkflow(req.scope) const { result } = await workflow.run({ input, }) - res.status(200).json({ return: result }) + const queryObject = remoteQueryObjectFromString({ + entryPoint: "return", + variables: { + id: result.return_id, + filters: { + ...req.filterableFields, + }, + }, + fields: req.remoteQueryConfig.fields, + }) + + const [orderReturn] = await remoteQuery(queryObject) + + res.status(200).json({ return: orderReturn }) } diff --git a/packages/medusa/src/api/admin/returns/validators.ts b/packages/medusa/src/api/admin/returns/validators.ts index 576784701d..bf01628a80 100644 --- a/packages/medusa/src/api/admin/returns/validators.ts +++ b/packages/medusa/src/api/admin/returns/validators.ts @@ -47,8 +47,10 @@ const ItemSchema = z.object({ export const AdminPostReturnsReqSchema = z.object({ order_id: z.string(), + location_id: z.string().optional(), description: z.string().optional(), internal_note: z.string().optional(), + no_notification: z.boolean().optional(), metadata: z.record(z.unknown()).nullish(), }) export type AdminPostReturnsReqSchemaType = z.infer< @@ -93,3 +95,39 @@ export const AdminPostCancelReturnReqSchema = z.object({ export type AdminPostCancelReturnReqSchemaType = z.infer< typeof AdminPostReceiveReturnsReqSchema > + +export const AdminPostReturnsShippingReqSchema = z.object({ + shipping_option_id: z.string(), + custom_price: z.number().optional(), + description: z.string().optional(), + internal_note: z.string().optional(), + metadata: z.record(z.unknown()).optional(), +}) + +export type AdminPostReturnsShippingReqSchemaType = z.infer< + typeof AdminPostReturnsShippingReqSchema +> + +export const AdminPostReturnsRequestItemsReqSchema = z.object({ + items: z.array( + z.object({ + id: z.string(), + quantity: z.number(), + description: z.string().optional(), + internal_note: z.string().optional(), + metadata: z.record(z.unknown()).optional(), + }) + ), +}) + +export type AdminPostReturnsRequestItemsReqSchemaType = z.infer< + typeof AdminPostReturnsRequestItemsReqSchema +> + +export const AdminPostReturnsConfirmRequestReqSchema = z.object({ + no_notification: z.boolean().optional(), +}) + +export type AdminPostReturnsConfirmRequestReqSchemaType = z.infer< + typeof AdminPostReturnsConfirmRequestReqSchema +>