feat(dashboard, core-flows, js-sdk, types, medusa): listing order's shipping options (#13242)

* feat(dashboard, core-flows,js-sdk,types,medusa): listing order's shipping option

* fix: typo

* chore: migrate claim form

* fix: cleanup rule logic

* feat: add test case, rm params

* fix: expand location name
This commit is contained in:
Frane Polić
2025-08-21 13:01:27 +02:00
committed by GitHub
parent 9b38b750de
commit 492e018957
17 changed files with 618 additions and 71 deletions
+9
View File
@@ -0,0 +1,9 @@
---
"@medusajs/dashboard": patch
"@medusajs/core-flows": patch
"@medusajs/js-sdk": patch
"@medusajs/types": patch
"@medusajs/medusa": patch
---
feat(dashboard,core-flows,js-sdk,types,medusa): listing order's shipping option
@@ -0,0 +1,119 @@
import {
AdminShippingProfile,
AdminStockLocation,
AdminSalesChannel,
MedusaContainer,
} from "@medusajs/types"
import { adminHeaders } from "../../../helpers/create-admin-user"
export async function createShippingOptionSeeder({
api,
container,
salesChannelOverride,
stockLocationOverride,
shippingProfileOverride,
countries = ["us"],
}: {
api: any
container: MedusaContainer
salesChannelOverride?: AdminSalesChannel
stockLocationOverride?: AdminStockLocation
shippingProfileOverride?: AdminShippingProfile
countries?: string[]
}) {
const salesChannel =
salesChannelOverride ??
(
await api.post(
"/admin/sales-channels",
{ name: "first channel", description: "channel" },
adminHeaders
)
).data.sales_channel
const stockLocation =
stockLocationOverride ??
(
await api.post(
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
const shippingProfile =
shippingProfileOverride ??
(
await api.post(
`/admin/shipping-profiles`,
{ name: `test-${stockLocation.id}`, type: "default" },
adminHeaders
)
).data.shipping_profile
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{
name: `Test-${shippingProfile.id}`,
type: "test-type",
},
adminHeaders
)
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: `Test-${shippingProfile.id}`,
geo_zones: countries.map((country) => ({
type: "country",
country_code: country,
})),
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
const shippingOption = (
await api.post(
`/admin/shipping-options?fields=+service_zone.fulfillment_set.*,service_zone.geo_zones.*,service_zone.fulfillment_set.location*`,
{
name: `Test shipping option ${fulfillmentSet.id}`,
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: [],
},
adminHeaders
)
).data.shipping_option
return {
salesChannel,
stockLocation,
shippingProfile,
fulfillmentSet,
shippingOption,
}
}
@@ -8,6 +8,8 @@ import {
} from "../../../../helpers/create-admin-user" } from "../../../../helpers/create-admin-user"
import { setupTaxStructure } from "../../../../modules/__tests__/fixtures" import { setupTaxStructure } from "../../../../modules/__tests__/fixtures"
import { createOrderSeeder } from "../../fixtures/order" import { createOrderSeeder } from "../../fixtures/order"
import { createShippingOptionSeeder } from "../../fixtures/shipping"
import { AdminShippingOption } from "@medusajs/types"
jest.setTimeout(300000) jest.setTimeout(300000)
@@ -73,7 +75,10 @@ medusaIntegrationTestRunner({
}) })
it("should search orders by shipping address", async () => { it("should search orders by shipping address", async () => {
let response = await api.get(`/admin/orders?fields=+shipping_address.address_1,+shipping_address.address_2`, adminHeaders) let response = await api.get(
`/admin/orders?fields=+shipping_address.address_1,+shipping_address.address_2`,
adminHeaders
)
expect(response.data.orders).toHaveLength(1) expect(response.data.orders).toHaveLength(1)
expect(response.data.orders).toEqual([ expect(response.data.orders).toEqual([
@@ -82,7 +87,10 @@ medusaIntegrationTestRunner({
}), }),
]) ])
response = await api.get(`/admin/orders?fields=+shipping_address.address_1,+shipping_address.address_2&q=${order.shipping_address.address_1}`, adminHeaders) response = await api.get(
`/admin/orders?fields=+shipping_address.address_1,+shipping_address.address_2&q=${order.shipping_address.address_1}`,
adminHeaders
)
expect(response.data.orders).toHaveLength(1) expect(response.data.orders).toHaveLength(1)
expect(response.data.orders).toEqual([ expect(response.data.orders).toEqual([
@@ -91,7 +99,10 @@ medusaIntegrationTestRunner({
}), }),
]) ])
response = await api.get(`/admin/orders?q=${order.shipping_address.address_2}`, adminHeaders) response = await api.get(
`/admin/orders?q=${order.shipping_address.address_2}`,
adminHeaders
)
expect(response.data.orders).toHaveLength(1) expect(response.data.orders).toHaveLength(1)
expect(response.data.orders).toEqual([ expect(response.data.orders).toEqual([
@@ -107,7 +118,10 @@ medusaIntegrationTestRunner({
}) })
it("should search orders by billing address", async () => { it("should search orders by billing address", async () => {
let response = await api.get(`/admin/orders?fields=+billing_address.address_1,+billing_address.address_2`, adminHeaders) let response = await api.get(
`/admin/orders?fields=+billing_address.address_1,+billing_address.address_2`,
adminHeaders
)
expect(response.data.orders).toHaveLength(1) expect(response.data.orders).toHaveLength(1)
expect(response.data.orders).toEqual([ expect(response.data.orders).toEqual([
@@ -116,7 +130,10 @@ medusaIntegrationTestRunner({
}), }),
]) ])
response = await api.get(`/admin/orders?fields=+billing_address.address_1,+billing_address.address_2&q=${order.billing_address.address_1}`, adminHeaders) response = await api.get(
`/admin/orders?fields=+billing_address.address_1,+billing_address.address_2&q=${order.billing_address.address_1}`,
adminHeaders
)
expect(response.data.orders).toHaveLength(1) expect(response.data.orders).toHaveLength(1)
expect(response.data.orders).toEqual([ expect(response.data.orders).toEqual([
@@ -125,7 +142,10 @@ medusaIntegrationTestRunner({
}), }),
]) ])
response = await api.get(`/admin/orders?q=${order.billing_address.address_2}`, adminHeaders) response = await api.get(
`/admin/orders?q=${order.billing_address.address_2}`,
adminHeaders
)
expect(response.data.orders).toHaveLength(1) expect(response.data.orders).toHaveLength(1)
expect(response.data.orders).toEqual([ expect(response.data.orders).toEqual([
@@ -2471,6 +2491,76 @@ medusaIntegrationTestRunner({
}) })
}) })
describe("GET /orders/:id/shipping-options", () => {
let so1: AdminShippingOption
let so2: AdminShippingOption
let so3: AdminShippingOption
beforeEach(async () => {
seeder = await createOrderSeeder({ api, container: getContainer() })
order = seeder.order
order = (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
.order
so1 = (
await createShippingOptionSeeder({
api,
container: getContainer(),
salesChannelOverride: seeder.salesChannel,
countries: ["us"],
})
).shippingOption
so2 = (
await createShippingOptionSeeder({
api,
container: getContainer(),
salesChannelOverride: seeder.salesChannel,
countries: ["us", "ca"],
})
).shippingOption
so3 = (
await createShippingOptionSeeder({
api,
container: getContainer(),
salesChannelOverride: seeder.salesChannel,
countries: ["de"],
})
).shippingOption
})
it("should return the shipping options applicable for the order", async () => {
const { data } = await api.get(
`/admin/orders/${order.id}/shipping-options`,
adminHeaders
)
const originalShippingOptionId =
order.shipping_methods[0].shipping_option_id
expect(order.shipping_address.country_code).toEqual("us")
expect(data.shipping_options.length).toEqual(3)
expect(data.shipping_options).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: so1.id,
insufficient_inventory: true,
}),
expect.objectContaining({
id: so2.id,
insufficient_inventory: true, // new SO without location levels for the order item, should have insufficient inventory
}),
expect.objectContaining({
id: originalShippingOptionId,
insufficient_inventory: false, // order is created with this SO, location has to have enough inventory
}),
])
)
})
})
describe("POST /orders/:id/fulfillments/:id/mark-as-delivered", () => { describe("POST /orders/:id/fulfillments/:id/mark-as-delivered", () => {
beforeEach(async () => { beforeEach(async () => {
seeder = await createOrderSeeder({ api, container: getContainer() }) seeder = await createOrderSeeder({ api, container: getContainer() })
@@ -18,6 +18,7 @@ const _orderKeys = queryKeysFactory(ORDERS_QUERY_KEY) as TQueryKey<"orders"> & {
preview: (orderId: string) => any preview: (orderId: string) => any
changes: (orderId: string) => any changes: (orderId: string) => any
lineItems: (orderId: string) => any lineItems: (orderId: string) => any
shippingOptions: (orderId: string) => any
} }
_orderKeys.preview = function (id: string) { _orderKeys.preview = function (id: string) {
@@ -32,6 +33,10 @@ _orderKeys.lineItems = function (id: string) {
return [this.detail(id), "lineItems"] return [this.detail(id), "lineItems"]
} }
_orderKeys.shippingOptions = function (id: string) {
return [this.detail(id), "shippingOptions"]
}
export const ordersQueryKeys = _orderKeys export const ordersQueryKeys = _orderKeys
export const useOrder = ( export const useOrder = (
@@ -125,6 +130,28 @@ export const useOrders = (
return { ...data, ...rest } return { ...data, ...rest }
} }
export const useOrderShippingOptions = (
id: string,
query?: HttpTypes.AdminGetOrderShippingOptionList,
options?: Omit<
UseQueryOptions<
{ shipping_options: HttpTypes.AdminShippingOption[] },
FetchError,
{ shipping_options: HttpTypes.AdminShippingOption[] },
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
const { data, ...rest } = useQuery({
queryFn: async () => sdk.admin.order.listShippingOptions(id, query),
queryKey: ordersQueryKeys.shippingOptions(id),
...options,
})
return { ...data, ...rest }
}
export const useOrderChanges = ( export const useOrderChanges = (
id: string, id: string,
query?: HttpTypes.AdminOrderChangesFilters, query?: HttpTypes.AdminOrderChangesFilters,
@@ -24,13 +24,14 @@ import {
useRemoveClaimOutboundItem, useRemoveClaimOutboundItem,
useUpdateClaimOutboundItems, useUpdateClaimOutboundItems,
} from "../../../../../hooks/api/claims" } from "../../../../../hooks/api/claims"
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
import { OutboundShippingPlaceholder } from "../../../common/placeholders" import { OutboundShippingPlaceholder } from "../../../common/placeholders"
import { AddClaimOutboundItemsTable } from "../add-claim-outbound-items-table" import { AddClaimOutboundItemsTable } from "../add-claim-outbound-items-table"
import { ClaimOutboundItem } from "./claim-outbound-item" import { ClaimOutboundItem } from "./claim-outbound-item"
import { ItemPlaceholder } from "./item-placeholder" import { ItemPlaceholder } from "./item-placeholder"
import { CreateClaimSchemaType } from "./schema" import { CreateClaimSchemaType } from "./schema"
import { useOrderShippingOptions } from "../../../../../hooks/api/orders"
import { getFormattedShippingOptionLocationName } from "../../../../../lib/shipping-options"
type ClaimOutboundSectionProps = { type ClaimOutboundSectionProps = {
order: AdminOrder order: AdminOrder
@@ -58,16 +59,12 @@ export const ClaimOutboundSection = ({
/** /**
* HOOKS * HOOKS
*/ */
const { shipping_options = [] } = useShippingOptions({ const { shipping_options = [] } = useOrderShippingOptions(order.id)
limit: 999,
fields: "*prices,+service_zone.fulfillment_set.location.id",
})
// TODO: filter in the API when boolean filter is supported and fulfillment module support partial rule SO filtering
const outboundShippingOptions = shipping_options.filter( const outboundShippingOptions = shipping_options.filter(
(shippingOption) => (so) =>
!!shippingOption.rules.find( !so.rules?.find((r) => r.attribute === "is_return" && r.value === "true")
(r) => r.attribute === "is_return" && r.value === "false"
)
) )
const { mutateAsync: addOutboundShipping } = useAddClaimOutboundShipping( const { mutateAsync: addOutboundShipping } = useAddClaimOutboundShipping(
@@ -415,7 +412,7 @@ export const ClaimOutboundSection = ({
}} }}
{...field} {...field}
options={outboundShippingOptions.map((so) => ({ options={outboundShippingOptions.map((so) => ({
label: so.name, label: `${so.name} (${getFormattedShippingOptionLocationName(so)})`,
value: so.id, value: so.id,
}))} }))}
disabled={!outboundShippingOptions.length} disabled={!outboundShippingOptions.length}
@@ -23,13 +23,14 @@ import {
useRemoveExchangeOutboundItem, useRemoveExchangeOutboundItem,
useUpdateExchangeOutboundItems, useUpdateExchangeOutboundItems,
} from "../../../../../hooks/api/exchanges" } from "../../../../../hooks/api/exchanges"
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
import { OutboundShippingPlaceholder } from "../../../common/placeholders" import { OutboundShippingPlaceholder } from "../../../common/placeholders"
import { ItemPlaceholder } from "../../../order-create-claim/components/claim-create-form/item-placeholder" import { ItemPlaceholder } from "../../../order-create-claim/components/claim-create-form/item-placeholder"
import { AddExchangeOutboundItemsTable } from "../add-exchange-outbound-items-table" import { AddExchangeOutboundItemsTable } from "../add-exchange-outbound-items-table"
import { ExchangeOutboundItem } from "./exchange-outbound-item" import { ExchangeOutboundItem } from "./exchange-outbound-item"
import { useOrderShippingOptions } from "../../../../../hooks/api/orders"
import { CreateExchangeSchemaType } from "./schema" import { CreateExchangeSchemaType } from "./schema"
import { getFormattedShippingOptionLocationName } from "../../../../../lib/shipping-options"
type ExchangeOutboundSectionProps = { type ExchangeOutboundSectionProps = {
order: AdminOrder order: AdminOrder
@@ -57,16 +58,12 @@ export const ExchangeOutboundSection = ({
/** /**
* HOOKS * HOOKS
*/ */
const { shipping_options = [] } = useShippingOptions({ const { shipping_options = [] } = useOrderShippingOptions(order.id)
limit: 999,
fields: "*prices,+service_zone.fulfillment_set.location.id",
})
// TODO: filter in the API when boolean filter is supported and fulfillment module support partial rule SO filtering
const outboundShippingOptions = shipping_options.filter( const outboundShippingOptions = shipping_options.filter(
(shippingOption) => (so) =>
!!shippingOption.rules.find( !so.rules?.find((r) => r.attribute === "is_return" && r.value === "true")
(r) => r.attribute === "is_return" && r.value === "false"
)
) )
const { mutateAsync: addOutboundShipping } = useAddExchangeOutboundShipping( const { mutateAsync: addOutboundShipping } = useAddExchangeOutboundShipping(
@@ -424,7 +421,7 @@ export const ExchangeOutboundSection = ({
}} }}
{...field} {...field}
options={outboundShippingOptions.map((so) => ({ options={outboundShippingOptions.map((so) => ({
label: so.name, label: `${so.name} (${getFormattedShippingOptionLocationName(so)})`,
value: so.id, value: so.id,
}))} }))}
disabled={!outboundShippingOptions.length} disabled={!outboundShippingOptions.length}
@@ -88,6 +88,7 @@ export type FetchShippingOptionForOrderWorkflowOutput = ShippingOptionDTO & {
is_calculated_price_tax_inclusive: boolean is_calculated_price_tax_inclusive: boolean
} }
} }
export const fetchShippingOptionsForOrderWorkflowId = "fetch-shipping-option" export const fetchShippingOptionsForOrderWorkflowId = "fetch-shipping-option"
/** /**
* This workflow fetches a shipping option for an order. It's used in Return Merchandise Authorization (RMA) flows. It's used * This workflow fetches a shipping option for an order. It's used in Return Merchandise Authorization (RMA) flows. It's used
@@ -87,3 +87,4 @@ export * from "./update-order"
export * from "./update-order-change-actions" export * from "./update-order-change-actions"
export * from "./update-order-changes" export * from "./update-order-changes"
export * from "./update-tax-lines" export * from "./update-tax-lines"
export * from "./list-shipping-options-for-order"
@@ -0,0 +1,205 @@
import {
createWorkflow,
transform,
WorkflowData,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import {
AdditionalData,
ListShippingOptionsForOrderWorkflowInput,
} from "@medusajs/types"
import { useQueryGraphStep, validatePresenceOfStep } from "../../common"
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
export const listShippingOptionsForOrderWorkflowId =
"list-shipping-options-for-order"
/**
* This workflow lists the shipping options of an order. It's executed by the
* [List Shipping Options Store API Route](https://docs.medusajs.com/api/store#shipping-options_getshippingoptions).
*
* :::note
*
* This workflow doesn't retrieve the calculated prices of the shipping options. If you need to retrieve the prices of the shipping options,
* use the {@link listShippingOptionsForOrderWithPricingWorkflow} workflow.
*
* :::
*
* You can use this workflow within your own customizations or custom workflows, allowing you to wrap custom logic around to retrieve the shipping options of an order
* in your custom flows.
*
* @example
* const { result } = await listShippingOptionsForOrderWorkflow(container)
* .run({
* input: {
* order_id: "order_123",
* }
* })
*
* @summary
*
* List a order's shipping options.
*
* :::
*/
export const listShippingOptionsForOrderWorkflow = createWorkflow(
listShippingOptionsForOrderWorkflowId,
(
input: WorkflowData<
ListShippingOptionsForOrderWorkflowInput & AdditionalData
>
) => {
const orderQuery = useQueryGraphStep({
entity: "order",
filters: { id: input.order_id },
fields: [
"id",
"sales_channel_id",
"region_id",
"shipping_address.city",
"shipping_address.country_code",
"shipping_address.province",
"shipping_address.postal_code",
"items.*",
"items.variant.manage_inventory",
"items.variant.inventory_items.inventory_item_id",
"items.variant.inventory_items.inventory.requires_shipping",
"items.variant.inventory_items.inventory.location_levels.*",
],
options: { throwIfKeyNotFound: true },
}).config({ name: "get-order" })
const order = transform(
{ orderQuery },
({ orderQuery }) => orderQuery.data[0]
)
validatePresenceOfStep({
entity: order,
fields: ["sales_channel_id", "region_id"],
})
const scFulfillmentSetQuery = useQueryGraphStep({
entity: "sales_channels",
filters: { id: order.sales_channel_id },
fields: [
"stock_locations.fulfillment_sets.id",
"stock_locations.id",
"stock_locations.name",
"stock_locations.address.*",
],
}).config({ name: "sales_channels-fulfillment-query" })
const scFulfillmentSets = transform(
{ scFulfillmentSetQuery },
({ scFulfillmentSetQuery }) => scFulfillmentSetQuery.data[0]
)
const { fulfillmentSetIds } = transform(
{ scFulfillmentSets },
({ scFulfillmentSets }) => {
const fulfillmentSetIds = new Set<string>()
scFulfillmentSets.stock_locations.forEach((stockLocation) => {
stockLocation.fulfillment_sets.forEach((fulfillmentSet) => {
fulfillmentSetIds.add(fulfillmentSet.id)
})
})
return {
fulfillmentSetIds: Array.from(fulfillmentSetIds),
}
}
)
const queryVariables = transform(
{ fulfillmentSetIds, order },
({ fulfillmentSetIds, order }) => {
return {
filters: {
fulfillment_set_id: fulfillmentSetIds,
address: {
country_code: order.shipping_address?.country_code,
province_code: order.shipping_address?.province,
city: order.shipping_address?.city,
postal_expression: order.shipping_address?.postal_code,
},
},
}
}
)
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_options",
fields: [
"id",
"name",
"price_type",
"service_zone_id",
"shipping_profile_id",
"provider_id",
"data",
"service_zone.fulfillment_set_id",
"service_zone.fulfillment_set.type",
"service_zone.fulfillment_set.location.id",
"service_zone.fulfillment_set.location.name",
"service_zone.fulfillment_set.location.address.*",
"type.id",
"type.label",
"type.description",
"type.code",
"provider.id",
"provider.is_enabled",
"rules.attribute",
"rules.value",
"rules.operator",
],
variables: queryVariables,
}).config({ name: "shipping-options-query" })
const shippingOptionsWithInventory = transform(
{ shippingOptions, order },
({ shippingOptions, order }) =>
shippingOptions.map((shippingOption) => {
const locationId =
shippingOption.service_zone.fulfillment_set.location.id
const itemsAtLocationWithoutAvailableQuantity = order.items.filter(
(item) => {
if (!item.variant?.manage_inventory) {
return false
}
return item.variant.inventory_items.some((inventoryItem) => {
if (!inventoryItem.inventory.requires_shipping) {
return false
}
const level = inventoryItem.inventory.location_levels.find(
(locationLevel) => {
return locationLevel.location_id === locationId
}
)
return !level ? true : level.available_quantity < item.quantity
})
}
)
return {
...shippingOption,
insufficient_inventory:
itemsAtLocationWithoutAvailableQuantity.length > 0,
}
})
)
return new WorkflowResponse(shippingOptionsWithInventory)
}
)
+30
View File
@@ -504,6 +504,36 @@ export class Order {
) )
} }
/**
* This method retrieves a list of shipping options for an order based on the order's shipping address.
*
* This method sends a request to the [List Shipping Options](https://docs.medusajs.com/api/admin#orders_getordersidshipping-options)
* API route.
*
* @param id - The order's ID.
* @param queryParams - Configure the fields to retrieve in each shipping option.
* @param headers - Headers to pass in the request
* @returns The list of shipping options.
*
* @example
* sdk.admin.order.listShippingOptions("order_123")
* .then(({ shipping_options }) => {
* console.log(shipping_options)
* })
*/
async listShippingOptions(
id: string,
queryParams?: FindParams & HttpTypes.AdminGetOrderShippingOptionList,
headers?: ClientHeaders
) {
return await this.client.fetch<{
shipping_options: HttpTypes.AdminShippingOption[]
}>(`/admin/orders/${id}/shipping-options`, {
query: queryParams,
headers,
})
}
/** /**
* This method retrieves a list of changes made on an order, including returns, exchanges, etc... * This method retrieves a list of changes made on an order, including returns, exchanges, etc...
* *
@@ -51,7 +51,7 @@ export type ListShippingOptionsForCartWithPricingWorkflowInput = {
/** /**
* The shipping option's ID. * The shipping option's ID.
*/ */
id: string; id: string
/** /**
* Custom data relevant for the fulfillment provider that processes this shipping option. * Custom data relevant for the fulfillment provider that processes this shipping option.
* It can be data relevant to calculate the shipping option's price. * It can be data relevant to calculate the shipping option's price.
@@ -132,7 +132,14 @@ export type ListShippingOptionsForCartWithPricingWorkflowOutput = {
/** /**
* The shipping option's type. * The shipping option's type.
*/ */
type: Omit<ShippingOptionTypeDTO, "shipping_option_id" | "shipping_option" | "created_at" | "updated_at" | "deleted_at"> type: Omit<
ShippingOptionTypeDTO,
| "shipping_option_id"
| "shipping_option"
| "created_at"
| "updated_at"
| "deleted_at"
>
/** /**
* The associated fulfillment provider details. * The associated fulfillment provider details.
@@ -243,3 +250,24 @@ export type ListShippingOptionsForCartWorkflowInput = {
*/ */
enabled_in_store?: boolean enabled_in_store?: boolean
} }
/**
* The context for retrieving the shipping options.
*/
export type ListShippingOptionsForOrderWorkflowInput = {
/**
* The order's ID.
*/
order_id: string
/**
* Whether to retrieve return shipping options.
* By default, non-return shipping options are retrieved.
*
* @defaultValue false
*/
is_return?: boolean
/**
* Whether to retrieve the shipping option's enabled in the store, which is the default.
*/
enabled_in_store?: boolean
}
@@ -56,3 +56,5 @@ export interface AdminOrderItemsFilters extends FindParams {
order_id?: string[] | string order_id?: string[] | string
version?: number[] | number version?: number[] | number
} }
export interface AdminGetOrderShippingOptionList {}
@@ -0,0 +1,19 @@
import { listShippingOptionsForOrderWorkflow } from "@medusajs/core-flows"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { AdminShippingOption, HttpTypes } from "@medusajs/framework/types"
export const GET = async (
req: MedusaRequest<{}, HttpTypes.AdminGetOrderShippingOptionList>,
res: MedusaResponse<{ shipping_options: AdminShippingOption[] }>
) => {
const { id } = req.params
const workflow = listShippingOptionsForOrderWorkflow(req.scope)
const { result: shipping_options } = await workflow.run({
input: {
order_id: id,
},
})
res.json({ shipping_options })
}
@@ -8,6 +8,7 @@ import {
AdminCancelOrderTransferRequest, AdminCancelOrderTransferRequest,
AdminCompleteOrder, AdminCompleteOrder,
AdminCreateOrderCreditLines, AdminCreateOrderCreditLines,
AdminGetOrderShippingOptionList,
AdminGetOrdersOrderItemsParams, AdminGetOrdersOrderItemsParams,
AdminGetOrdersOrderParams, AdminGetOrdersOrderParams,
AdminGetOrdersParams, AdminGetOrdersParams,
@@ -62,6 +63,16 @@ export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [
), ),
], ],
}, },
{
method: ["GET"],
matcher: "/admin/orders/:id/shipping-options",
middlewares: [
validateAndTransformQuery(
AdminGetOrderShippingOptionList,
QueryConfig.listShippingOptionsQueryConfig
),
],
},
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/orders/:id/changes", matcher: "/admin/orders/:id/changes",
@@ -109,3 +109,8 @@ export const listOrderItemsQueryConfig = {
defaultLimit: 100, defaultLimit: 100,
isList: true, isList: true,
} }
export const listShippingOptionsQueryConfig = {
defaultLimit: 100,
isList: true,
}
@@ -34,6 +34,12 @@ export type AdminGetOrdersOrderItemsParamsType = z.infer<
typeof AdminGetOrdersOrderParams typeof AdminGetOrdersOrderParams
> >
export const AdminGetOrderShippingOptionList = z.object({})
export type AdminGetOrderShippingOptionListType = z.infer<
typeof AdminGetOrderShippingOptionList
>
/** /**
* Parameters used to filter and configure the pagination of the retrieved order. * Parameters used to filter and configure the pagination of the retrieved order.
*/ */