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:
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": {
|
||||||
|
|||||||
+51
-4
@@ -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 />
|
||||||
|
|||||||
+20
-1
@@ -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">
|
||||||
|
|||||||
+1
@@ -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
-48
@@ -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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user