feat(core-flows,dashboard,js-sdk,medusa,types): support Fulfillment Options (#10622)

**What**
- add a list point for fetching fulfillment options for a provider
- add FO support on SO create & update on dashboard
- pass `cart` and `stockLocation` to `validateFufillmentData` context

---

CLOSES CMRC-789
CLOSES CMRC-790
This commit is contained in:
Frane Polić authored and GitHub committed 2024-12-18 09:16:26 +00:00
1 parent f3eca7734e
commit bde4b82194
20 files changed
+1840 -477

No files matched your search

@@ -9,6 +9,12 @@ export const fulfillmentProvidersQueryKeys = queryKeysFactory(
FULFILLMENT_PROVIDERS_QUERY_KEY FULFILLMENT_PROVIDERS_QUERY_KEY
) )
const FULFILLMENT_PROVIDER_OPTIONS_QUERY_KEY =
"fulfillment_provider_options" as const
export const fulfillmentProviderOptionsQueryKeys = queryKeysFactory(
FULFILLMENT_PROVIDER_OPTIONS_QUERY_KEY
)
export const useFulfillmentProviders = ( export const useFulfillmentProviders = (
query?: HttpTypes.AdminFulfillmentProviderListParams, query?: HttpTypes.AdminFulfillmentProviderListParams,
options?: Omit< options?: Omit<
@@ -29,3 +35,25 @@ export const useFulfillmentProviders = (
return { ...data, ...rest } return { ...data, ...rest }
} }
export const useFulfillmentProviderOptions = (
providerId: string,
options?: Omit<
UseQueryOptions<
HttpTypes.AdminFulfillmentProviderOptionsListResponse,
FetchError,
HttpTypes.AdminFulfillmentProviderOptionsListResponse,
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
const { data, ...rest } = useQuery({
queryFn: () =>
sdk.admin.fulfillmentProvider.listFulfillmentOptions(providerId),
queryKey: fulfillmentProviderOptionsQueryKeys.list(providerId),
...options,
})
return { ...data, ...rest }
}
@@ -20,7 +20,7 @@ type ComboboxQueryParams = {
export const useComboboxData = < export const useComboboxData = <
TResponse extends ComboboxExternalData, TResponse extends ComboboxExternalData,
TParams extends ComboboxQueryParams TParams extends ComboboxQueryParams,
>({ >({
queryKey, queryKey,
queryFn, queryFn,
@@ -50,7 +50,6 @@ export const useComboboxData = <
enabled: !!defaultValue, enabled: !!defaultValue,
}) })
const { data, ...rest } = useInfiniteQuery({ const { data, ...rest } = useInfiniteQuery({
queryKey: [...queryKey, query], queryKey: [...queryKey, query],
queryFn: async ({ pageParam = 0 }) => { queryFn: async ({ pageParam = 0 }) => {
File diff suppressed because it is too large. Load diff
@@ -1523,7 +1523,8 @@
"hint": "Whether customers can use this option during checkout." "hint": "Whether customers can use this option during checkout."
}, },
"provider": "Fulfillment provider", "provider": "Fulfillment provider",
"profile": "Shipping profile" "profile": "Shipping profile",
"fulfillmentOption": "Fulfillment option"
} }
}, },
"serviceZones": { "serviceZones": {
@@ -1,8 +1,9 @@
import { Heading, Input, RadioGroup, Text } from "@medusajs/ui" import { Heading, Input, RadioGroup, Select, Text } from "@medusajs/ui"
import { UseFormReturn } from "react-hook-form" import { UseFormReturn } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { Divider } from "../../../../../components/common/divider" import { Divider } from "../../../../../components/common/divider"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { SwitchBox } from "../../../../../components/common/switch-box" import { SwitchBox } from "../../../../../components/common/switch-box"
@@ -18,6 +19,8 @@ type CreateShippingOptionDetailsFormProps = {
isReturn?: boolean isReturn?: boolean
zone: HttpTypes.AdminServiceZone zone: HttpTypes.AdminServiceZone
locationId: string locationId: string
fulfillmentProviderOptions: HttpTypes.AdminFulfillmentProviderOption[]
selectedProviderId?: string
} }
export const CreateShippingOptionDetailsForm = ({ export const CreateShippingOptionDetailsForm = ({
@@ -25,6 +28,8 @@ export const CreateShippingOptionDetailsForm = ({
isReturn = false, isReturn = false,
zone, zone,
locationId, locationId,
fulfillmentProviderOptions,
selectedProviderId,
}: CreateShippingOptionDetailsFormProps) => { }: CreateShippingOptionDetailsFormProps) => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -134,9 +139,6 @@ export const CreateShippingOptionDetailsForm = ({
) )
}} }}
/> />
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Form.Field <Form.Field
control={form.control} control={form.control}
name="shipping_profile_id" name="shipping_profile_id"
@@ -160,7 +162,9 @@ export const CreateShippingOptionDetailsForm = ({
) )
}} }}
/> />
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Form.Field <Form.Field
control={form.control} control={form.control}
name="provider_id" name="provider_id"
@@ -177,6 +181,10 @@ export const CreateShippingOptionDetailsForm = ({
<Form.Control> <Form.Control>
<Combobox <Combobox
{...field} {...field}
onChange={(e) => {
field.onChange(e)
form.setValue("fulfillment_option_id", "")
}}
options={fulfillmentProviders.options} options={fulfillmentProviders.options}
searchValue={fulfillmentProviders.searchValue} searchValue={fulfillmentProviders.searchValue}
onSearchValueChange={ onSearchValueChange={
@@ -190,6 +198,45 @@ export const CreateShippingOptionDetailsForm = ({
) )
}} }}
/> />
<Form.Field
control={form.control}
name="fulfillment_option_id"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>
{t(
"stockLocations.shippingOptions.fields.fulfillmentOption"
)}
</Form.Label>
<Form.Control>
<Select
{...field}
onValueChange={field.onChange}
disabled={!selectedProviderId}
key={selectedProviderId}
>
<Select.Trigger ref={field.ref}>
<Select.Value />
</Select.Trigger>
<Select.Content>
{fulfillmentProviderOptions
?.filter((fo) => !!fo.is_return === isReturn)
.map((option) => (
<Select.Item value={option.id} key={option.id}>
{option.name || option.id}
</Select.Item>
))}
</Select.Content>
</Select>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</div> </div>
<Divider /> <Divider />
@@ -1,7 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { Button, ProgressStatus, ProgressTabs, toast } from "@medusajs/ui" import { Button, ProgressStatus, ProgressTabs, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form" import { useForm, useWatch } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { useState } from "react" import { useState } from "react"
@@ -20,6 +20,7 @@ import {
CreateShippingOptionDetailsSchema, CreateShippingOptionDetailsSchema,
CreateShippingOptionSchema, CreateShippingOptionSchema,
} from "./schema" } from "./schema"
import { useFulfillmentProviderOptions } from "../../../../../hooks/api"
enum Tab { enum Tab {
DETAILS = "details", DETAILS = "details",
@@ -50,6 +51,7 @@ export function CreateShippingOptionsForm({
enabled_in_store: true, enabled_in_store: true,
shipping_profile_id: "", shipping_profile_id: "",
provider_id: "", provider_id: "",
fulfillment_option_id: "",
region_prices: {}, region_prices: {},
currency_prices: {}, currency_prices: {},
conditional_region_prices: {}, conditional_region_prices: {},
@@ -58,6 +60,16 @@ export function CreateShippingOptionsForm({
resolver: zodResolver(CreateShippingOptionSchema), resolver: zodResolver(CreateShippingOptionSchema),
}) })
const selectedProviderId = useWatch({
control: form.control,
name: "provider_id",
})
const { fulfillment_options: fulfillmentProviderOptions } =
useFulfillmentProviderOptions(selectedProviderId, {
enabled: !!selectedProviderId,
})
const isCalculatedPriceType = const isCalculatedPriceType =
form.watch("price_type") === ShippingOptionPriceType.Calculated form.watch("price_type") === ShippingOptionPriceType.Calculated
@@ -123,6 +135,10 @@ export function CreateShippingOptionsForm({
...conditionalRegionPrices, ...conditionalRegionPrices,
] ]
const fulfillmentOptionData = fulfillmentProviderOptions?.find(
(fo) => fo.id === data.fulfillment_option_id
)!
await mutateAsync( await mutateAsync(
{ {
name: data.name, name: data.name,
@@ -131,6 +147,7 @@ export function CreateShippingOptionsForm({
shipping_profile_id: data.shipping_profile_id, shipping_profile_id: data.shipping_profile_id,
provider_id: data.provider_id, provider_id: data.provider_id,
prices: allPrices, prices: allPrices,
data: fulfillmentOptionData as unknown as Record<string, unknown>,
rules: [ rules: [
{ {
// eslint-disable-next-line // eslint-disable-next-line
@@ -293,6 +310,8 @@ export function CreateShippingOptionsForm({
zone={zone} zone={zone}
isReturn={isReturn} isReturn={isReturn}
locationId={locationId} locationId={locationId}
fulfillmentProviderOptions={fulfillmentProviderOptions || []}
selectedProviderId={selectedProviderId}
/> />
</ProgressTabs.Content> </ProgressTabs.Content>
<ProgressTabs.Content value={Tab.PRICING} className="size-full"> <ProgressTabs.Content value={Tab.PRICING} className="size-full">
@@ -12,6 +12,7 @@ export const CreateShippingOptionDetailsSchema = z.object({
enabled_in_store: z.boolean(), enabled_in_store: z.boolean(),
shipping_profile_id: z.string().min(1), shipping_profile_id: z.string().min(1),
provider_id: z.string().min(1), provider_id: z.string().min(1),
fulfillment_option_id: z.string().min(1),
}) })
export const ShippingOptionConditionalPriceSchema = z.object({ export const ShippingOptionConditionalPriceSchema = z.object({
@@ -2,6 +2,7 @@ import { HttpTypes } from "@medusajs/types"
import { Button, Input, RadioGroup, toast } from "@medusajs/ui" import { Button, Input, RadioGroup, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { zodResolver } from "@hookform/resolvers/zod"
import * as zod from "zod" import * as zod from "zod"
import { Divider } from "../../../../../components/common/divider" import { Divider } from "../../../../../components/common/divider"
@@ -14,7 +15,6 @@ import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-opti
import { useComboboxData } from "../../../../../hooks/use-combobox-data" import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
import { pick } from "../../../../../lib/common" import { pick } from "../../../../../lib/common"
import { formatProvider } from "../../../../../lib/format-provider"
import { isOptionEnabledInStore } from "../../../../../lib/shipping-options" import { isOptionEnabledInStore } from "../../../../../lib/shipping-options"
import { ShippingOptionPriceType } from "../../../common/constants" import { ShippingOptionPriceType } from "../../../common/constants"
@@ -28,7 +28,6 @@ const EditShippingOptionSchema = zod.object({
price_type: zod.nativeEnum(ShippingOptionPriceType), price_type: zod.nativeEnum(ShippingOptionPriceType),
enabled_in_store: zod.boolean().optional(), enabled_in_store: zod.boolean().optional(),
shipping_profile_id: zod.string(), shipping_profile_id: zod.string(),
provider_id: zod.string(),
}) })
export const EditShippingOptionForm = ({ export const EditShippingOptionForm = ({
@@ -49,29 +48,14 @@ export const EditShippingOptionForm = ({
defaultValue: shippingOption.shipping_profile_id, defaultValue: shippingOption.shipping_profile_id,
}) })
const fulfillmentProviders = useComboboxData({
queryFn: (params) =>
sdk.admin.fulfillmentProvider.list({
...params,
stock_location_id: locationId,
}),
queryKey: ["fulfillment_providers"],
getOptions: (data) =>
data.fulfillment_providers.map((provider) => ({
label: formatProvider(provider.id),
value: provider.id,
})),
defaultValue: shippingOption.provider_id,
})
const form = useForm<zod.infer<typeof EditShippingOptionSchema>>({ const form = useForm<zod.infer<typeof EditShippingOptionSchema>>({
defaultValues: { defaultValues: {
name: shippingOption.name, name: shippingOption.name,
price_type: shippingOption.price_type as ShippingOptionPriceType, price_type: shippingOption.price_type as ShippingOptionPriceType,
enabled_in_store: isOptionEnabledInStore(shippingOption), enabled_in_store: isOptionEnabledInStore(shippingOption),
shipping_profile_id: shippingOption.shipping_profile_id, shipping_profile_id: shippingOption.shipping_profile_id,
provider_id: shippingOption.provider_id,
}, },
resolver: zodResolver(EditShippingOptionSchema),
}) })
const { mutateAsync, isPending: isLoading } = useUpdateShippingOptions( const { mutateAsync, isPending: isLoading } = useUpdateShippingOptions(
@@ -101,7 +85,6 @@ export const EditShippingOptionForm = ({
name: values.name, name: values.name,
price_type: values.price_type, price_type: values.price_type,
shipping_profile_id: values.shipping_profile_id, shipping_profile_id: values.shipping_profile_id,
provider_id: values.provider_id,
rules, rules,
}, },
{ {
@@ -209,35 +192,6 @@ export const EditShippingOptionForm = ({
) )
}} }}
/> />
<Form.Field
control={form.control}
name="provider_id"
render={({ field }) => {
return (
<Form.Item>
<Form.Label
tooltip={t(
"stockLocations.fulfillmentProviders.shippingOptionsTooltip"
)}
>
{t("stockLocations.shippingOptions.fields.provider")}
</Form.Label>
<Form.Control>
<Combobox
{...field}
options={fulfillmentProviders.options}
searchValue={fulfillmentProviders.searchValue}
onSearchValueChange={
fulfillmentProviders.onSearchValueChange
}
disabled={fulfillmentProviders.disabled}
/>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</div> </div>
<Divider /> <Divider />
@@ -1,16 +1,18 @@
import { Modules, promiseAll } from "@medusajs/framework/utils" import { Modules, promiseAll } from "@medusajs/framework/utils"
import { IFulfillmentModuleService } from "@medusajs/types" import {
CartDTO,
IFulfillmentModuleService,
StockLocationDTO,
} from "@medusajs/types"
import { createStep, StepResponse } from "@medusajs/workflows-sdk" import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export interface ValidateShippingMethodsDataInput { export type ValidateShippingMethodsDataInput = {
context: Record<string, unknown> id: string
options_to_validate: { provider_id: string
id: string option_data: Record<string, unknown>
provider_id: string method_data: Record<string, unknown>
option_data: Record<string, unknown> context: CartDTO & { from_location: StockLocationDTO; [k: string]: unknown }
method_data: Record<string, unknown> }[]
}[]
}
export const validateAndReturnShippingMethodsDataStepId = export const validateAndReturnShippingMethodsDataStepId =
"validate-and-return-shipping-methods-data" "validate-and-return-shipping-methods-data"
@@ -20,9 +22,9 @@ export const validateAndReturnShippingMethodsDataStepId =
export const validateAndReturnShippingMethodsDataStep = createStep( export const validateAndReturnShippingMethodsDataStep = createStep(
validateAndReturnShippingMethodsDataStepId, validateAndReturnShippingMethodsDataStepId,
async (data: ValidateShippingMethodsDataInput, { container }) => { async (data: ValidateShippingMethodsDataInput, { container }) => {
const { options_to_validate = [] } = data const optionsToValidate = data ?? []
if (!options_to_validate.length) { if (!optionsToValidate.length) {
return new StepResponse(void 0) return new StepResponse(void 0)
} }
@@ -31,12 +33,12 @@ export const validateAndReturnShippingMethodsDataStep = createStep(
) )
const validatedData = await promiseAll( const validatedData = await promiseAll(
options_to_validate.map(async (option) => { optionsToValidate.map(async (option) => {
const validated = await fulfillmentModule.validateFulfillmentData( const validated = await fulfillmentModule.validateFulfillmentData(
option.provider_id, option.provider_id,
option.option_data, option.option_data,
option.method_data, option.method_data,
data.context option.context
) )
return { return {
@@ -68,8 +68,8 @@ export const addShippingMethodToCartWorkflow = createWorkflow(
validateCartShippingOptionsPriceStep({ shippingOptions }) validateCartShippingOptionsPriceStep({ shippingOptions })
const validateShippingMethodsDataInput = transform( const validateShippingMethodsDataInput = transform(
{ input, shippingOptions }, { input, shippingOptions, cart },
({ input, shippingOptions }) => { ({ input, shippingOptions, cart }) => {
return input.options.map((inputOption) => { return input.options.map((inputOption) => {
const shippingOption = shippingOptions.find( const shippingOption = shippingOptions.find(
(so) => so.id === inputOption.id (so) => so.id === inputOption.id
@@ -80,15 +80,18 @@ export const addShippingMethodToCartWorkflow = createWorkflow(
provider_id: shippingOption?.provider_id, provider_id: shippingOption?.provider_id,
option_data: shippingOption?.data ?? {}, option_data: shippingOption?.data ?? {},
method_data: inputOption.data ?? {}, method_data: inputOption.data ?? {},
context: {
...cart,
from_location: shippingOption?.stock_location ?? {},
},
} }
}) })
} }
) )
const validatedMethodData = validateAndReturnShippingMethodsDataStep({ const validatedMethodData = validateAndReturnShippingMethodsDataStep(
options_to_validate: validateShippingMethodsDataInput, validateShippingMethodsDataInput
context: {}, // TODO: Add cart, when we have a better idea about what's appropriate to pass )
})
const shippingMethodInput = transform( const shippingMethodInput = transform(
{ input, shippingOptions, validatedMethodData }, { input, shippingOptions, validatedMethodData },
@@ -1,4 +1,3 @@
import { deepFlatMap } from "@medusajs/framework/utils"
import { import {
createWorkflow, createWorkflow,
transform, transform,
@@ -41,7 +40,12 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
const scFulfillmentSetQuery = useQueryGraphStep({ const scFulfillmentSetQuery = useQueryGraphStep({
entity: "sales_channels", entity: "sales_channels",
filters: { id: cart.sales_channel_id }, filters: { id: cart.sales_channel_id },
fields: ["stock_locations.fulfillment_sets.id"], fields: [
"stock_locations.fulfillment_sets.id",
"stock_locations.id",
"stock_locations.name",
"stock_locations.address.*",
],
}).config({ name: "sales_channels-fulfillment-query" }) }).config({ name: "sales_channels-fulfillment-query" })
const scFulfillmentSets = transform( const scFulfillmentSets = transform(
@@ -49,22 +53,23 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
({ scFulfillmentSetQuery }) => scFulfillmentSetQuery.data[0] ({ scFulfillmentSetQuery }) => scFulfillmentSetQuery.data[0]
) )
const fulfillmentSetIds = transform( const { fulfillmentSetIds, fulfillmentSetLocationMap } = transform(
{ options: scFulfillmentSets }, { scFulfillmentSets },
(data) => { ({ scFulfillmentSets }) => {
const fulfillmentSetIds = new Set<string>() const fulfillmentSetIds = new Set<string>()
const fulfillmentSetLocationMap = {}
deepFlatMap( scFulfillmentSets.stock_locations.forEach((stockLocation) => {
data.options, stockLocation.fulfillment_sets.forEach((fulfillmentSet) => {
"stock_locations.fulfillment_sets", fulfillmentSetLocationMap[fulfillmentSet.id] = stockLocation
({ fulfillment_sets: fulfillmentSet }) => { fulfillmentSetIds.add(fulfillmentSet.id)
if (fulfillmentSet?.id) { })
fulfillmentSetIds.add(fulfillmentSet.id) })
}
}
)
return Array.from(fulfillmentSetIds) return {
fulfillmentSetIds: Array.from(fulfillmentSetIds),
fulfillmentSetLocationMap,
}
} }
) )
@@ -103,6 +108,7 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
"shipping_profile_id", "shipping_profile_id",
"provider_id", "provider_id",
"data", "data",
"service_zone.fulfillment_set_id",
"type.id", "type.id",
"type.label", "type.label",
@@ -124,15 +130,19 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
}).config({ name: "shipping-options-query" }) }).config({ name: "shipping-options-query" })
const shippingOptionsWithPrice = transform( const shippingOptionsWithPrice = transform(
{ shippingOptions }, { shippingOptions, fulfillmentSetLocationMap },
({ shippingOptions }) => ({ shippingOptions, fulfillmentSetLocationMap }) =>
shippingOptions.map((shippingOption) => { shippingOptions.map((shippingOption) => {
const price = shippingOption.calculated_price const price = shippingOption.calculated_price
const fulfillmentSetId =
shippingOption.service_zone.fulfillment_set_id
const stockLocation = fulfillmentSetLocationMap[fulfillmentSetId]
return { return {
...shippingOption, ...shippingOption,
amount: price?.calculated_amount, amount: price?.calculated_amount,
is_tax_inclusive: !!price?.is_calculated_price_tax_inclusive, is_tax_inclusive: !!price?.is_calculated_price_tax_inclusive,
stock_location: stockLocation,
} }
}) })
) )
@@ -74,4 +74,23 @@ export class FulfillmentProvider {
} }
) )
} }
/**
* This method retrieves a list of fulfillment options for a given fulfillment provider. It sends a request to the
* [List Fulfillment Options](https://docs.medusajs.com/api/admin#fulfillment-providers_getfulfillmentprovideroptions)
* API route.
*
* @param id - The ID of the fulfillment provider.
* @param headers - Headers to pass in the request.
* @returns The list of fulfillment options.
*/
async listFulfillmentOptions(id: string, headers?: ClientHeaders) {
return await this.client.fetch<HttpTypes.AdminFulfillmentProviderOptionsListResponse>(
`/admin/fulfillment-providers/${id}/options`,
{
method: "GET",
headers,
}
)
}
} }
+17
View File
@@ -834,6 +834,23 @@ export class Store {
} }
) )
}, },
calculate: async (
id: string,
body: HttpTypes.StoreCalculateShippingOptionPrice,
query?: HttpTypes.SelectParams,
headers?: ClientHeaders
) => {
return await this.client.fetch<HttpTypes.StoreShippingOptionResponse>(
`/store/shipping-options/${id}/calculate`,
{
method: "POST",
headers,
body,
query,
}
)
},
} }
public payment = { public payment = {
@@ -1,3 +1,9 @@
import { BaseFulfillmentProvider } from "../common" import {
BaseFulfillmentProvider,
BaseFulfillmentProviderOption,
} from "../common"
export interface AdminFulfillmentProvider extends BaseFulfillmentProvider {} export interface AdminFulfillmentProvider extends BaseFulfillmentProvider {}
export interface AdminFulfillmentProviderOption
extends BaseFulfillmentProviderOption {}
@@ -1,5 +1,8 @@
import { PaginatedResponse } from "../../common" import { PaginatedResponse } from "../../common"
import { AdminFulfillmentProvider } from "./entities" import {
AdminFulfillmentProvider,
AdminFulfillmentProviderOption,
} from "./entities"
export interface AdminFulfillmentProviderListResponse export interface AdminFulfillmentProviderListResponse
extends PaginatedResponse<{ extends PaginatedResponse<{
@@ -8,3 +11,8 @@ export interface AdminFulfillmentProviderListResponse
*/ */
fulfillment_providers: AdminFulfillmentProvider[] fulfillment_providers: AdminFulfillmentProvider[]
}> {} }> {}
export interface AdminFulfillmentProviderOptionsListResponse
extends PaginatedResponse<{
fulfillment_options: AdminFulfillmentProviderOption[]
}> {}
@@ -8,3 +8,14 @@ export interface BaseFulfillmentProvider {
*/ */
is_enabled: boolean is_enabled: boolean
} }
export interface BaseFulfillmentProviderOption {
/**
* The fulfillment provider option's ID.
*/
id: string
/**
* Whether the fulfillment provider option can be used for returns.
*/
is_return: boolean
}
@@ -0,0 +1,27 @@
import {
AdminFulfillmentProviderOption,
HttpTypes,
} from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<HttpTypes.AdminFulfillmentProviderOptionsListResponse>
) => {
const fulfillmentProviderService = req.scope.resolve(Modules.FULFILLMENT)
const fulfillmentOptions =
await fulfillmentProviderService.retrieveFulfillmentOptions(req.params.id)
res.json({
fulfillment_options:
fulfillmentOptions as unknown as AdminFulfillmentProviderOption[],
count: fulfillmentOptions.length,
limit: fulfillmentOptions.length,
offset: 0,
})
}
@@ -20,4 +20,9 @@ export const adminFulfillmentProvidersRoutesMiddlewares: MiddlewareRoute[] = [
}), }),
], ],
}, },
{
method: ["GET"],
matcher: "/admin/fulfillment-providers/:id/options",
middlewares: [],
},
] ]
@@ -5,6 +5,7 @@ import {
FilterableFulfillmentSetProps, FilterableFulfillmentSetProps,
FindConfig, FindConfig,
FulfillmentDTO, FulfillmentDTO,
FulfillmentOption,
FulfillmentTypes, FulfillmentTypes,
IFulfillmentModuleService, IFulfillmentModuleService,
InternalModuleDeclaration, InternalModuleDeclaration,
@@ -1946,7 +1947,7 @@ export default class FulfillmentModuleService
async retrieveFulfillmentOptions( async retrieveFulfillmentOptions(
providerId: string providerId: string
): Promise<Record<string, any>[]> { ): Promise<FulfillmentOption[]> {
return await this.fulfillmentProviderService_.getFulfillmentOptions( return await this.fulfillmentProviderService_.getFulfillmentOptions(
providerId providerId
) )
@@ -2,6 +2,7 @@ import {
CalculateShippingOptionPriceDTO, CalculateShippingOptionPriceDTO,
Constructor, Constructor,
DAL, DAL,
FulfillmentOption,
FulfillmentTypes, FulfillmentTypes,
IFulfillmentProvider, IFulfillmentProvider,
Logger, Logger,
@@ -81,7 +82,7 @@ export default class FulfillmentProviderService extends ModulesSdkUtils.MedusaIn
async getFulfillmentOptions( async getFulfillmentOptions(
providerId: string providerId: string
): Promise<Record<string, unknown>[]> { ): Promise<FulfillmentOption[]> {
const provider = this.retrieveProviderRegistration(providerId) const provider = this.retrieveProviderRegistration(providerId)
return await provider.getFulfillmentOptions() return await provider.getFulfillmentOptions()
} }