feat: List shipping options for cart (#6677)
This commit is contained in:
1 parent
db26dafa85
commit
25d176b851
23 files changed
+855
-142
No files matched your search
@@ -3,6 +3,7 @@ import { IPricingModuleService, PricingContext } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
MedusaError,
|
||||
arrayDifference,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
@@ -12,7 +13,7 @@ interface StepInput {
|
||||
context?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const getShippingOptionPriceSetsStepId = "get-variant-price-sets"
|
||||
export const getShippingOptionPriceSetsStepId = "get-shipping-option-price-sets"
|
||||
export const getShippingOptionPriceSetsStep = createStep(
|
||||
getShippingOptionPriceSetsStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
@@ -38,26 +39,22 @@ export const getShippingOptionPriceSetsStep = createStep(
|
||||
|
||||
const optionPriceSets = await remoteQuery(query)
|
||||
|
||||
const notFound: string[] = []
|
||||
const priceSetIds: string[] = []
|
||||
const optionsMissingPrices = arrayDifference(
|
||||
data.optionIds,
|
||||
optionPriceSets.map((v) => v.shipping_option_id)
|
||||
)
|
||||
|
||||
optionPriceSets.forEach((v) => {
|
||||
if (v.price_set_id) {
|
||||
priceSetIds.push(v.price_set_id)
|
||||
} else {
|
||||
notFound.push(v.shipping_option_id)
|
||||
}
|
||||
})
|
||||
|
||||
if (notFound.length) {
|
||||
if (optionsMissingPrices.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Shipping options with IDs ${notFound.join(", ")} do not have a price`
|
||||
`Shipping options with IDs ${optionsMissingPrices.join(
|
||||
", "
|
||||
)} do not have a price`
|
||||
)
|
||||
}
|
||||
|
||||
const calculatedPriceSets = await pricingModuleService.calculatePrices(
|
||||
{ id: priceSetIds },
|
||||
{ id: optionPriceSets.map((v) => v.price_set_id) },
|
||||
{ context: data.context as PricingContext["context"] }
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from "./add-shipping-method-to-cart"
|
||||
export * from "./add-to-cart"
|
||||
export * from "./create-carts"
|
||||
export * from "./create-payment-collection-for-cart"
|
||||
export * from "./list-shipping-options-for-cart"
|
||||
export * from "./refresh-payment-collection"
|
||||
export * from "./update-cart"
|
||||
export * from "./update-cart-promotions"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { ListShippingOptionsForCartWorkflowInputDTO } from "@medusajs/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
|
||||
import { listShippingOptionsForContextStep } from "../../../shipping-options"
|
||||
import { getShippingOptionPriceSetsStep } from "../steps"
|
||||
|
||||
export const listShippingOptionsForCartWorkflowId =
|
||||
"list-shipping-options-for-cart"
|
||||
export const listShippingOptionsForCartWorkflow = createWorkflow(
|
||||
listShippingOptionsForCartWorkflowId,
|
||||
(input: WorkflowData<ListShippingOptionsForCartWorkflowInputDTO>) => {
|
||||
const scLocationFulfillmentSets = useRemoteQueryStep({
|
||||
entry_point: "sales_channels",
|
||||
fields: ["stock_locations.fulfillment_sets.id"],
|
||||
variables: { id: input.sales_channel_id },
|
||||
})
|
||||
|
||||
const listOptionsInput = transform(
|
||||
{ scLocationFulfillmentSets, input },
|
||||
(data) => {
|
||||
const fulfillmentSetIds = data.scLocationFulfillmentSets
|
||||
.map((sc) =>
|
||||
sc.stock_locations.map((loc) =>
|
||||
loc.fulfillment_sets.map(({ id }) => id)
|
||||
)
|
||||
)
|
||||
.flat(2)
|
||||
|
||||
return {
|
||||
context: {
|
||||
fulfillment_set_id: fulfillmentSetIds,
|
||||
service_zone: {
|
||||
geo_zones: {
|
||||
city: data.input.shipping_address?.city,
|
||||
country_code: data.input.shipping_address?.country_code,
|
||||
province_code: data.input.shipping_address?.province,
|
||||
},
|
||||
},
|
||||
},
|
||||
config: {
|
||||
select: [
|
||||
"id",
|
||||
"name",
|
||||
"price_type",
|
||||
"service_zone_id",
|
||||
"shipping_profile_id",
|
||||
"provider_id",
|
||||
"data",
|
||||
"amount",
|
||||
],
|
||||
relations: ["type", "provider"],
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const options = listShippingOptionsForContextStep(listOptionsInput)
|
||||
|
||||
const optionIds = transform({ options }, (data) =>
|
||||
data.options.map((option) => option.id)
|
||||
)
|
||||
|
||||
// TODO: Separate shipping options based on price_type, flat_rate vs calculated
|
||||
const priceSets = getShippingOptionPriceSetsStep({
|
||||
optionIds,
|
||||
context: {
|
||||
currency_code: input.currency_code,
|
||||
},
|
||||
})
|
||||
|
||||
const shippingOptionsWithPrice = transform(
|
||||
{ priceSets, options },
|
||||
(data) => {
|
||||
const options = data.options.map((option) => {
|
||||
const price = data.priceSets?.[option.id].calculated_amount
|
||||
|
||||
return {
|
||||
...option,
|
||||
amount: price,
|
||||
}
|
||||
})
|
||||
|
||||
return options
|
||||
}
|
||||
)
|
||||
|
||||
return shippingOptionsWithPrice
|
||||
}
|
||||
)
|
||||
@@ -9,6 +9,7 @@ export * from "./payment"
|
||||
export * from "./product"
|
||||
export * from "./promotion"
|
||||
export * from "./region"
|
||||
export * from "./shipping-options"
|
||||
export * from "./store"
|
||||
export * from "./tax"
|
||||
export * from "./user"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./steps"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./list-shipping-options-for-context"
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
FindConfig,
|
||||
IFulfillmentModuleService,
|
||||
ShippingOptionDTO,
|
||||
} from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
context: Record<string, unknown>
|
||||
config?: FindConfig<ShippingOptionDTO>
|
||||
}
|
||||
|
||||
export const listShippingOptionsForContextStepId =
|
||||
"list-shipping-options-for-context"
|
||||
export const listShippingOptionsForContextStep = createStep(
|
||||
listShippingOptionsForContextStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
const fulfillmentService = container.resolve<IFulfillmentModuleService>(
|
||||
ModuleRegistrationName.FULFILLMENT
|
||||
)
|
||||
|
||||
const shippingOptions = await fulfillmentService.listShippingOptions(
|
||||
data.context,
|
||||
data.config
|
||||
)
|
||||
|
||||
return new StepResponse(shippingOptions)
|
||||
}
|
||||
)
|
||||
@@ -10,3 +10,4 @@ export { default as ShippingOption } from "./shipping-option"
|
||||
export { default as ShippingOptionRule } from "./shipping-option-rule"
|
||||
export { default as ShippingOptionType } from "./shipping-option-type"
|
||||
export { default as ShippingProfile } from "./shipping-profile"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { LINKS } from "../links"
|
||||
|
||||
export const FulfillmentSetLocation: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.FulfillmentSetLocation,
|
||||
isLink: true,
|
||||
databaseConfig: {
|
||||
tableName: "fulfillment_set_location",
|
||||
idPrefix: "fsloc",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: ["fulfillment_set_location", "fulfillment_set_locations"],
|
||||
args: {
|
||||
entity: "LinkFulfillmentSetLocation",
|
||||
},
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "fulfillment_set_id", "stock_location_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
primaryKey: "id",
|
||||
foreignKey: "fulfillment_set_id",
|
||||
alias: "fulfillment_set",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.STOCK_LOCATION,
|
||||
primaryKey: "id",
|
||||
foreignKey: "stock_location_id",
|
||||
alias: "location",
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: Modules.FULFILLMENT,
|
||||
fieldAlias: {
|
||||
stock_locations: "locations_link.location",
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.FulfillmentSetLocation,
|
||||
primaryKey: "fulfillment_set_id",
|
||||
foreignKey: "id",
|
||||
alias: "locations_link",
|
||||
isList: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.STOCK_LOCATION,
|
||||
relationship: {
|
||||
serviceName: LINKS.FulfillmentSetLocation,
|
||||
primaryKey: "stock_location_id",
|
||||
foreignKey: "id",
|
||||
alias: "fulfillment_set_link",
|
||||
isList: true,
|
||||
},
|
||||
fieldAlias: {
|
||||
fulfillment_sets: "fulfillment_set_link.fulfillment_set",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export * from "./cart-payment-collection"
|
||||
export * from "./cart-promotion"
|
||||
export * from "./cart-region"
|
||||
export * from "./cart-sales-channel"
|
||||
export * from "./fulfillment-set-location"
|
||||
export * from "./inventory-level-stock-location"
|
||||
export * from "./order-sales-channel"
|
||||
export * from "./product-sales-channel"
|
||||
|
||||
@@ -17,7 +17,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
|
||||
},
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "sales_channel_id", "location_id"],
|
||||
primaryKeys: ["id", "sales_channel_id", "stock_location_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
@@ -28,7 +28,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
|
||||
{
|
||||
serviceName: Modules.STOCK_LOCATION,
|
||||
primaryKey: "id",
|
||||
foreignKey: "location_id",
|
||||
foreignKey: "stock_location_id",
|
||||
alias: "location",
|
||||
},
|
||||
],
|
||||
@@ -36,7 +36,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
|
||||
{
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
fieldAlias: {
|
||||
locations: "locations_link.location",
|
||||
stock_locations: "locations_link.location",
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.SalesChannelLocation,
|
||||
@@ -53,7 +53,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.SalesChannelLocation,
|
||||
primaryKey: "location_id",
|
||||
primaryKey: "stock_location_id",
|
||||
foreignKey: "id",
|
||||
alias: "sales_channels_link",
|
||||
isList: true,
|
||||
|
||||
@@ -44,6 +44,12 @@ export const LINKS = {
|
||||
Modules.STOCK_LOCATION,
|
||||
"location_id"
|
||||
),
|
||||
FulfillmentSetLocation: composeLinkName(
|
||||
Modules.FULFILLMENT,
|
||||
"fulfillment_set_id",
|
||||
Modules.STOCK_LOCATION,
|
||||
"location_id"
|
||||
),
|
||||
|
||||
// Internal services
|
||||
ProductShippingProfile: composeLinkName(
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { listShippingOptionsForCartWorkflow } from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
|
||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
const { cart_id } = req.params
|
||||
|
||||
const cartService = req.scope.resolve<ICartModuleService>(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
const cart = await cartService.retrieve(cart_id, {
|
||||
select: [
|
||||
"id",
|
||||
"sales_channel_id",
|
||||
"subtotal",
|
||||
"currency_code",
|
||||
"shipping_address.city",
|
||||
"shipping_address.country_code",
|
||||
"shipping_address.province",
|
||||
],
|
||||
relations: ["shipping_address"],
|
||||
})
|
||||
|
||||
const shippingOptions = await listShippingOptionsForCartWorkflow(
|
||||
req.scope
|
||||
).run({
|
||||
input: {
|
||||
cart_id: cart.id,
|
||||
sales_channel_id: cart.sales_channel_id,
|
||||
currency_code: cart.currency_code,
|
||||
shipping_address: {
|
||||
city: cart.shipping_address?.city,
|
||||
country_code: cart.shipping_address?.country_code,
|
||||
province: cart.shipping_address?.province,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
res.json({ shipping_options: shippingOptions })
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
|
||||
export const storeShippingOptionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/shipping-options/:cart_id",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
export const defaultStoreShippingOptionsFields = [
|
||||
"id",
|
||||
"name",
|
||||
"price_type",
|
||||
"service_zone_id",
|
||||
"shipping_profile_id",
|
||||
"fulfillment_provider_id",
|
||||
"shipping_option_type_id",
|
||||
]
|
||||
|
||||
export const listTransformQueryConfig = {
|
||||
defaultLimit: 20,
|
||||
isList: true,
|
||||
}
|
||||
@@ -777,7 +777,7 @@ export class RemoteJoiner {
|
||||
// remove alias from fields
|
||||
const parentPath = [BASE_PATH, ...currentPath].join(".")
|
||||
const parentExpands = parsedExpands.get(parentPath)
|
||||
parentExpands.fields = parentExpands.fields.filter(
|
||||
parentExpands.fields = parentExpands.fields?.filter(
|
||||
(field) => field !== property
|
||||
)
|
||||
|
||||
@@ -787,12 +787,13 @@ export class RemoteJoiner {
|
||||
)
|
||||
)
|
||||
|
||||
const parentFieldAlias = fullPath[Math.max(fullPath.length - 2, 0)]
|
||||
implodeMapping.push({
|
||||
location: [...currentPath],
|
||||
property,
|
||||
path: fullPath,
|
||||
isList: !!serviceConfig.relationships?.find(
|
||||
(relationship) => relationship.alias === fullPath[0]
|
||||
(relationship) => relationship.alias === parentFieldAlias
|
||||
)?.isList,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import { StockLocation } from "./models"
|
||||
import moduleSchema from "./schema"
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CustomerDTO } from "../customer"
|
||||
import { ShippingOptionDTO } from "../fulfillment"
|
||||
import { ProductDTO } from "../product"
|
||||
import { RegionDTO } from "../region"
|
||||
import { CartDTO, CartLineItemDTO } from "./common"
|
||||
@@ -101,3 +102,18 @@ export interface CartWorkflowDTO extends CartDTO {
|
||||
product?: ProductDTO
|
||||
region?: RegionDTO
|
||||
}
|
||||
|
||||
export interface ListShippingOptionsForCartWorkflowInputDTO {
|
||||
cart_id: string
|
||||
sales_channel_id?: string
|
||||
currency_code: string
|
||||
shipping_address: {
|
||||
city?: string
|
||||
country_code?: string
|
||||
province?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface PricedShippingOptionDTO extends ShippingOptionDTO {
|
||||
amount: number
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { FilterableServiceZoneProps, ServiceZoneDTO } from "./service-zone"
|
||||
import { ShippingProfileDTO } from "./shipping-profile"
|
||||
import { BaseFilterable, OperatorMap } from "../../dal"
|
||||
import { FulfillmentDTO } from "./fulfillment"
|
||||
import { FulfillmentProviderDTO } from "./fulfillment-provider"
|
||||
import {
|
||||
FilterableShippingOptionTypeProps,
|
||||
ShippingOptionTypeDTO,
|
||||
} from "./shipping-option-type"
|
||||
import { FilterableServiceZoneProps, ServiceZoneDTO } from "./service-zone"
|
||||
import {
|
||||
FilterableShippingOptionRuleProps,
|
||||
ShippingOptionRuleDTO,
|
||||
} from "./shipping-option-rule"
|
||||
import { BaseFilterable, OperatorMap } from "../../dal"
|
||||
import { FulfillmentDTO } from "./fulfillment"
|
||||
import {
|
||||
FilterableShippingOptionTypeProps,
|
||||
ShippingOptionTypeDTO,
|
||||
} from "./shipping-option-type"
|
||||
import { ShippingProfileDTO } from "./shipping-profile"
|
||||
|
||||
export type ShippingOptionPriceType = "calculated" | "flat"
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface FilterableShippingOptionProps
|
||||
id?: string | string[] | OperatorMap<string | string[]>
|
||||
name?: string | string[] | OperatorMap<string | string[]>
|
||||
fulfillment_set_id?: string | string[] | OperatorMap<string | string[]>
|
||||
shipping_profile_id?: string | string[] | OperatorMap<string | string[]>
|
||||
fulfillment_set_type?: string | string[] | OperatorMap<string | string[]>
|
||||
price_type?:
|
||||
| ShippingOptionPriceType
|
||||
|
||||
Reference in new issue
Block a user