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:
1 parent
9b38b750de
commit
492e018957
16 files changed
+575
-28
No files matched your search
@@ -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"
|
||||
import { setupTaxStructure } from "../../../../modules/__tests__/fixtures"
|
||||
import { createOrderSeeder } from "../../fixtures/order"
|
||||
import { createShippingOptionSeeder } from "../../fixtures/shipping"
|
||||
import { AdminShippingOption } from "@medusajs/types"
|
||||
|
||||
jest.setTimeout(300000)
|
||||
|
||||
@@ -73,7 +75,10 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
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).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).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).toEqual([
|
||||
@@ -107,7 +118,10 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
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).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).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).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", () => {
|
||||
beforeEach(async () => {
|
||||
seeder = await createOrderSeeder({ api, container: getContainer() })
|
||||
|
||||
@@ -18,6 +18,7 @@ const _orderKeys = queryKeysFactory(ORDERS_QUERY_KEY) as TQueryKey<"orders"> & {
|
||||
preview: (orderId: string) => any
|
||||
changes: (orderId: string) => any
|
||||
lineItems: (orderId: string) => any
|
||||
shippingOptions: (orderId: string) => any
|
||||
}
|
||||
|
||||
_orderKeys.preview = function (id: string) {
|
||||
@@ -32,6 +33,10 @@ _orderKeys.lineItems = function (id: string) {
|
||||
return [this.detail(id), "lineItems"]
|
||||
}
|
||||
|
||||
_orderKeys.shippingOptions = function (id: string) {
|
||||
return [this.detail(id), "shippingOptions"]
|
||||
}
|
||||
|
||||
export const ordersQueryKeys = _orderKeys
|
||||
|
||||
export const useOrder = (
|
||||
@@ -125,6 +130,28 @@ export const useOrders = (
|
||||
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 = (
|
||||
id: string,
|
||||
query?: HttpTypes.AdminOrderChangesFilters,
|
||||
|
||||
+7
-10
@@ -24,13 +24,14 @@ import {
|
||||
useRemoveClaimOutboundItem,
|
||||
useUpdateClaimOutboundItems,
|
||||
} from "../../../../../hooks/api/claims"
|
||||
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { OutboundShippingPlaceholder } from "../../../common/placeholders"
|
||||
import { AddClaimOutboundItemsTable } from "../add-claim-outbound-items-table"
|
||||
import { ClaimOutboundItem } from "./claim-outbound-item"
|
||||
import { ItemPlaceholder } from "./item-placeholder"
|
||||
import { CreateClaimSchemaType } from "./schema"
|
||||
import { useOrderShippingOptions } from "../../../../../hooks/api/orders"
|
||||
import { getFormattedShippingOptionLocationName } from "../../../../../lib/shipping-options"
|
||||
|
||||
type ClaimOutboundSectionProps = {
|
||||
order: AdminOrder
|
||||
@@ -58,16 +59,12 @@ export const ClaimOutboundSection = ({
|
||||
/**
|
||||
* HOOKS
|
||||
*/
|
||||
const { shipping_options = [] } = useShippingOptions({
|
||||
limit: 999,
|
||||
fields: "*prices,+service_zone.fulfillment_set.location.id",
|
||||
})
|
||||
const { shipping_options = [] } = useOrderShippingOptions(order.id)
|
||||
|
||||
// TODO: filter in the API when boolean filter is supported and fulfillment module support partial rule SO filtering
|
||||
const outboundShippingOptions = shipping_options.filter(
|
||||
(shippingOption) =>
|
||||
!!shippingOption.rules.find(
|
||||
(r) => r.attribute === "is_return" && r.value === "false"
|
||||
)
|
||||
(so) =>
|
||||
!so.rules?.find((r) => r.attribute === "is_return" && r.value === "true")
|
||||
)
|
||||
|
||||
const { mutateAsync: addOutboundShipping } = useAddClaimOutboundShipping(
|
||||
@@ -415,7 +412,7 @@ export const ClaimOutboundSection = ({
|
||||
}}
|
||||
{...field}
|
||||
options={outboundShippingOptions.map((so) => ({
|
||||
label: so.name,
|
||||
label: `${so.name} (${getFormattedShippingOptionLocationName(so)})`,
|
||||
value: so.id,
|
||||
}))}
|
||||
disabled={!outboundShippingOptions.length}
|
||||
|
||||
+7
-10
@@ -23,13 +23,14 @@ import {
|
||||
useRemoveExchangeOutboundItem,
|
||||
useUpdateExchangeOutboundItems,
|
||||
} from "../../../../../hooks/api/exchanges"
|
||||
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { OutboundShippingPlaceholder } from "../../../common/placeholders"
|
||||
import { ItemPlaceholder } from "../../../order-create-claim/components/claim-create-form/item-placeholder"
|
||||
import { AddExchangeOutboundItemsTable } from "../add-exchange-outbound-items-table"
|
||||
import { ExchangeOutboundItem } from "./exchange-outbound-item"
|
||||
import { useOrderShippingOptions } from "../../../../../hooks/api/orders"
|
||||
import { CreateExchangeSchemaType } from "./schema"
|
||||
import { getFormattedShippingOptionLocationName } from "../../../../../lib/shipping-options"
|
||||
|
||||
type ExchangeOutboundSectionProps = {
|
||||
order: AdminOrder
|
||||
@@ -57,16 +58,12 @@ export const ExchangeOutboundSection = ({
|
||||
/**
|
||||
* HOOKS
|
||||
*/
|
||||
const { shipping_options = [] } = useShippingOptions({
|
||||
limit: 999,
|
||||
fields: "*prices,+service_zone.fulfillment_set.location.id",
|
||||
})
|
||||
const { shipping_options = [] } = useOrderShippingOptions(order.id)
|
||||
|
||||
// TODO: filter in the API when boolean filter is supported and fulfillment module support partial rule SO filtering
|
||||
const outboundShippingOptions = shipping_options.filter(
|
||||
(shippingOption) =>
|
||||
!!shippingOption.rules.find(
|
||||
(r) => r.attribute === "is_return" && r.value === "false"
|
||||
)
|
||||
(so) =>
|
||||
!so.rules?.find((r) => r.attribute === "is_return" && r.value === "true")
|
||||
)
|
||||
|
||||
const { mutateAsync: addOutboundShipping } = useAddExchangeOutboundShipping(
|
||||
@@ -424,7 +421,7 @@ export const ExchangeOutboundSection = ({
|
||||
}}
|
||||
{...field}
|
||||
options={outboundShippingOptions.map((so) => ({
|
||||
label: so.name,
|
||||
label: `${so.name} (${getFormattedShippingOptionLocationName(so)})`,
|
||||
value: so.id,
|
||||
}))}
|
||||
disabled={!outboundShippingOptions.length}
|
||||
|
||||
@@ -88,6 +88,7 @@ export type FetchShippingOptionForOrderWorkflowOutput = ShippingOptionDTO & {
|
||||
is_calculated_price_tax_inclusive: boolean
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -87,3 +87,4 @@ export * from "./update-order"
|
||||
export * from "./update-order-change-actions"
|
||||
export * from "./update-order-changes"
|
||||
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)
|
||||
}
|
||||
)
|
||||
@@ -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...
|
||||
*
|
||||
|
||||
@@ -51,7 +51,7 @@ export type ListShippingOptionsForCartWithPricingWorkflowInput = {
|
||||
/**
|
||||
* The shipping option's ID.
|
||||
*/
|
||||
id: string;
|
||||
id: string
|
||||
/**
|
||||
* Custom data relevant for the fulfillment provider that processes this shipping option.
|
||||
* It can be data relevant to calculate the shipping option's price.
|
||||
@@ -132,7 +132,14 @@ export type ListShippingOptionsForCartWithPricingWorkflowOutput = {
|
||||
/**
|
||||
* 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.
|
||||
@@ -243,3 +250,24 @@ export type ListShippingOptionsForCartWorkflowInput = {
|
||||
*/
|
||||
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
|
||||
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,
|
||||
AdminCompleteOrder,
|
||||
AdminCreateOrderCreditLines,
|
||||
AdminGetOrderShippingOptionList,
|
||||
AdminGetOrdersOrderItemsParams,
|
||||
AdminGetOrdersOrderParams,
|
||||
AdminGetOrdersParams,
|
||||
@@ -62,6 +63,16 @@ export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/orders/:id/shipping-options",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
AdminGetOrderShippingOptionList,
|
||||
QueryConfig.listShippingOptionsQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/orders/:id/changes",
|
||||
|
||||
@@ -109,3 +109,8 @@ export const listOrderItemsQueryConfig = {
|
||||
defaultLimit: 100,
|
||||
isList: true,
|
||||
}
|
||||
|
||||
export const listShippingOptionsQueryConfig = {
|
||||
defaultLimit: 100,
|
||||
isList: true,
|
||||
}
|
||||
@@ -34,6 +34,12 @@ export type AdminGetOrdersOrderItemsParamsType = z.infer<
|
||||
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.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user