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ć
2024-12-18 10:16:26 +01:00
committed by GitHub
parent f3eca7734e
commit bde4b82194
20 changed files with 1850 additions and 487 deletions

View File

@@ -1,16 +1,18 @@
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"
export interface ValidateShippingMethodsDataInput {
context: Record<string, unknown>
options_to_validate: {
id: string
provider_id: string
option_data: Record<string, unknown>
method_data: Record<string, unknown>
}[]
}
export type ValidateShippingMethodsDataInput = {
id: string
provider_id: string
option_data: Record<string, unknown>
method_data: Record<string, unknown>
context: CartDTO & { from_location: StockLocationDTO; [k: string]: unknown }
}[]
export const validateAndReturnShippingMethodsDataStepId =
"validate-and-return-shipping-methods-data"
@@ -20,9 +22,9 @@ export const validateAndReturnShippingMethodsDataStepId =
export const validateAndReturnShippingMethodsDataStep = createStep(
validateAndReturnShippingMethodsDataStepId,
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)
}
@@ -31,12 +33,12 @@ export const validateAndReturnShippingMethodsDataStep = createStep(
)
const validatedData = await promiseAll(
options_to_validate.map(async (option) => {
optionsToValidate.map(async (option) => {
const validated = await fulfillmentModule.validateFulfillmentData(
option.provider_id,
option.option_data,
option.method_data,
data.context
option.context
)
return {

View File

@@ -68,8 +68,8 @@ export const addShippingMethodToCartWorkflow = createWorkflow(
validateCartShippingOptionsPriceStep({ shippingOptions })
const validateShippingMethodsDataInput = transform(
{ input, shippingOptions },
({ input, shippingOptions }) => {
{ input, shippingOptions, cart },
({ input, shippingOptions, cart }) => {
return input.options.map((inputOption) => {
const shippingOption = shippingOptions.find(
(so) => so.id === inputOption.id
@@ -80,15 +80,18 @@ export const addShippingMethodToCartWorkflow = createWorkflow(
provider_id: shippingOption?.provider_id,
option_data: shippingOption?.data ?? {},
method_data: inputOption.data ?? {},
context: {
...cart,
from_location: shippingOption?.stock_location ?? {},
},
}
})
}
)
const validatedMethodData = validateAndReturnShippingMethodsDataStep({
options_to_validate: validateShippingMethodsDataInput,
context: {}, // TODO: Add cart, when we have a better idea about what's appropriate to pass
})
const validatedMethodData = validateAndReturnShippingMethodsDataStep(
validateShippingMethodsDataInput
)
const shippingMethodInput = transform(
{ input, shippingOptions, validatedMethodData },

View File

@@ -1,4 +1,3 @@
import { deepFlatMap } from "@medusajs/framework/utils"
import {
createWorkflow,
transform,
@@ -41,7 +40,12 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
const scFulfillmentSetQuery = useQueryGraphStep({
entity: "sales_channels",
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" })
const scFulfillmentSets = transform(
@@ -49,22 +53,23 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
({ scFulfillmentSetQuery }) => scFulfillmentSetQuery.data[0]
)
const fulfillmentSetIds = transform(
{ options: scFulfillmentSets },
(data) => {
const { fulfillmentSetIds, fulfillmentSetLocationMap } = transform(
{ scFulfillmentSets },
({ scFulfillmentSets }) => {
const fulfillmentSetIds = new Set<string>()
const fulfillmentSetLocationMap = {}
deepFlatMap(
data.options,
"stock_locations.fulfillment_sets",
({ fulfillment_sets: fulfillmentSet }) => {
if (fulfillmentSet?.id) {
fulfillmentSetIds.add(fulfillmentSet.id)
}
}
)
scFulfillmentSets.stock_locations.forEach((stockLocation) => {
stockLocation.fulfillment_sets.forEach((fulfillmentSet) => {
fulfillmentSetLocationMap[fulfillmentSet.id] = stockLocation
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",
"provider_id",
"data",
"service_zone.fulfillment_set_id",
"type.id",
"type.label",
@@ -124,15 +130,19 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
}).config({ name: "shipping-options-query" })
const shippingOptionsWithPrice = transform(
{ shippingOptions },
({ shippingOptions }) =>
{ shippingOptions, fulfillmentSetLocationMap },
({ shippingOptions, fulfillmentSetLocationMap }) =>
shippingOptions.map((shippingOption) => {
const price = shippingOption.calculated_price
const fulfillmentSetId =
shippingOption.service_zone.fulfillment_set_id
const stockLocation = fulfillmentSetLocationMap[fulfillmentSetId]
return {
...shippingOption,
amount: price?.calculated_amount,
is_tax_inclusive: !!price?.is_calculated_price_tax_inclusive,
stock_location: stockLocation,
}
})
)