feat: Admin Returns API (#8117)

* feat: Add request item + add shipping APIs

* wip

* finalize workflow

* move steps

* add returns to js-sdk

* few chores

* fix test

* fix another test :)
This commit is contained in:
Oli Juhl
2024-07-15 15:57:06 +02:00
committed by GitHub
parent 53ddea717c
commit 00c7900337
23 changed files with 845 additions and 77 deletions

View File

@@ -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,
}),
],
})
)
})
})
},
})

View File

@@ -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",

View File

@@ -83,7 +83,6 @@ medusaIntegrationTestRunner({
reference_id: returnOrder.id,
details: {
reference_id: item.id,
return_id: returnOrder.id,
quantity: 1,
},
internal_note: "test",

View File

@@ -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)
}
)

View File

@@ -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<IOrderModuleService>(
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<string, unknown>) ?? {},
}
})
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<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await orderModuleService.updateReturns(
{ id: prevData.id },
{ items: prevData.items }
)
}
)

View File

@@ -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<OrderDTO> {
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)
}
)

View File

@@ -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,
}
}
)

View File

@@ -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"

View File

@@ -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,
},

View File

@@ -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)
}
}

View File

@@ -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<HttpTypes.AdminReturnResponse>(
`/admin/returns`,
{
method: "POST",
headers,
body,
query,
}
)
}
async addReturnItem(
id: string,
body: HttpTypes.AdminAddReturnItems,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminReturnResponse>(
`/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<HttpTypes.AdminReturnResponse>(
`/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<HttpTypes.AdminReturnResponse>(
`/admin/returns/${id}/request`,
{
method: "POST",
headers,
body,
query,
}
)
}
}

View File

@@ -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"

View File

@@ -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<string, unknown>
}
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<string, unknown>
}
export interface AdminAddReturnItem {
id: string
quantity: number
description?: string
internal_note?: string
metadata?: Record<string, unknown>
}
export interface AdminAddReturnItems {
items: AdminAddReturnItem[]
}
export interface AdminAddReturnShipping {
shipping_option_id: string
custom_price?: number
description?: string
internal_note?: string
metadata?: Record<string, unknown>
}
export interface AdminConfirmReturnRequest {
no_notification?: boolean
}

View File

@@ -0,0 +1 @@
export * from "./admin"

View File

@@ -1139,6 +1139,7 @@ export interface OrderDTO {
type ReturnStatus = "requested" | "received" | "partially_received" | "canceled"
export interface ReturnDTO extends Omit<OrderDTO, "status" | "version"> {
id: string
status: ReturnStatus
refund_amount?: BigNumberValue
order_id: string

View File

@@ -444,6 +444,13 @@ export interface UpdateReturnDTO {
claim_id?: string
exchange_id?: string
metadata?: Record<string, unknown> | null
items?: {
quantity: BigNumberInput
internal_note?: string | null
note?: string | null
reason_id?: string | null
metadata?: Record<string, unknown> | null
}[]
}
export interface UpdateOrderClaimDTO {

View File

@@ -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<AdminPostReturnsRequestItemsReqSchemaType>,
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,
})
}

View File

@@ -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<AdminPostReturnsConfirmRequestReqSchemaType>,
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,
})
}

View File

@@ -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<AdminPostReturnsShippingReqSchemaType>,
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,
})
}

View File

@@ -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
),
],
},
]

View File

@@ -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,
}

View File

@@ -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 })
}

View File

@@ -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
>