feat(core-flows, types): update shipping methods upon cart ops (#10382)
* feat(core-flows,framework,medusa): list shipping options pass in cart as pricing context * chore: add test for shipping options returning free shipping * feat(core-flows, types): update shipping methods upon cart ops * chore: fix specs * chore: fix bugs + specs * Update update-shipping-methods.ts Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> * Update mutations.ts Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> * chore: undo refresh changes * chore: merge with latest * chore: address PR comments * chore: fix conflicts * chore: fix specs * chore: address reviews --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
@@ -14,7 +14,6 @@ export * from "./get-promotion-codes-to-apply"
|
||||
export * from "./get-variant-price-sets"
|
||||
export * from "./get-variants"
|
||||
export * from "./prepare-adjustments-from-promotion-actions"
|
||||
export * from "./refresh-cart-shipping-methods"
|
||||
export * from "./remove-line-item-adjustments"
|
||||
export * from "./remove-shipping-method-adjustments"
|
||||
export * from "./remove-shipping-method-from-cart"
|
||||
@@ -27,4 +26,3 @@ export * from "./update-line-items"
|
||||
export * from "./validate-cart-payments"
|
||||
export * from "./validate-cart-shipping-options"
|
||||
export * from "./validate-variant-prices"
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import {
|
||||
CartDTO,
|
||||
ICartModuleService,
|
||||
IFulfillmentModuleService,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules, arrayDifference } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export interface RefreshCartShippingMethodsStepInput {
|
||||
cart: CartDTO
|
||||
}
|
||||
|
||||
export const refreshCartShippingMethodsStepId = "refresh-cart-shipping-methods"
|
||||
/**
|
||||
* This step refreshes the shipping methods of a cart.
|
||||
*/
|
||||
export const refreshCartShippingMethodsStep = createStep(
|
||||
refreshCartShippingMethodsStepId,
|
||||
async (data: RefreshCartShippingMethodsStepInput, { container }) => {
|
||||
const { cart } = data
|
||||
const { shipping_methods: shippingMethods = [] } = cart
|
||||
|
||||
if (!shippingMethods?.length) {
|
||||
return new StepResponse(void 0, [])
|
||||
}
|
||||
|
||||
const fulfillmentModule = container.resolve<IFulfillmentModuleService>(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
|
||||
const cartModule = container.resolve<ICartModuleService>(Modules.CART)
|
||||
|
||||
const shippingOptionIds: string[] = shippingMethods.map(
|
||||
(sm) => sm.shipping_option_id!
|
||||
)
|
||||
|
||||
const validShippingOptions =
|
||||
await fulfillmentModule.listShippingOptionsForContext(
|
||||
{
|
||||
id: shippingOptionIds,
|
||||
context: { ...cart, is_return: "false", enabled_in_store: "true" },
|
||||
address: {
|
||||
country_code: cart.shipping_address?.country_code,
|
||||
province_code: cart.shipping_address?.province,
|
||||
city: cart.shipping_address?.city,
|
||||
postal_expression: cart.shipping_address?.postal_code,
|
||||
},
|
||||
},
|
||||
{ relations: ["rules"] }
|
||||
)
|
||||
|
||||
const validShippingOptionIds = validShippingOptions.map((o) => o.id)
|
||||
const invalidShippingOptionIds = arrayDifference(
|
||||
shippingOptionIds,
|
||||
validShippingOptionIds
|
||||
)
|
||||
|
||||
const shippingMethodsToDelete = shippingMethods
|
||||
.filter((sm) => invalidShippingOptionIds.includes(sm.shipping_option_id!))
|
||||
.map((sm) => sm.id)
|
||||
|
||||
await cartModule.softDeleteShippingMethods(shippingMethodsToDelete)
|
||||
|
||||
return new StepResponse(void 0, shippingMethodsToDelete)
|
||||
},
|
||||
async (shippingMethodsToRestore, { container }) => {
|
||||
if (shippingMethodsToRestore?.length) {
|
||||
const cartModule = container.resolve<ICartModuleService>(Modules.CART)
|
||||
|
||||
await cartModule.restoreShippingMethods(shippingMethodsToRestore)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -16,6 +16,10 @@ export const removeShippingMethodFromCartStep = createStep(
|
||||
async (data: RemoveShippingMethodFromCartStepInput, { container }) => {
|
||||
const cartService = container.resolve<ICartModuleService>(Modules.CART)
|
||||
|
||||
if (!data?.shipping_method_ids?.length) {
|
||||
return new StepResponse(null, [])
|
||||
}
|
||||
|
||||
const methods = await cartService.softDeleteShippingMethods(
|
||||
data.shipping_method_ids
|
||||
)
|
||||
@@ -23,7 +27,7 @@ export const removeShippingMethodFromCartStep = createStep(
|
||||
return new StepResponse(methods, data.shipping_method_ids)
|
||||
},
|
||||
async (ids, { container }) => {
|
||||
if (!ids) {
|
||||
if (!ids?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
ICartModuleService,
|
||||
UpdateShippingMethodDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
Modules,
|
||||
getSelectsAndRelationsFromObjectArray,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export const updateShippingMethodsStepId = "update-shipping-methods-step"
|
||||
/**
|
||||
* This step updates a cart's shipping methods.
|
||||
*/
|
||||
export const updateShippingMethodsStep = createStep(
|
||||
updateShippingMethodsStepId,
|
||||
async (data: UpdateShippingMethodDTO[], { container }) => {
|
||||
if (!data?.length) {
|
||||
return new StepResponse([], [])
|
||||
}
|
||||
|
||||
const cartModule = container.resolve<ICartModuleService>(Modules.CART)
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
|
||||
|
||||
const dataBeforeUpdate = await cartModule.listShippingMethods(
|
||||
{ id: data.map((d) => d.id!) },
|
||||
{ select: selects, relations }
|
||||
)
|
||||
|
||||
const updatedItems = await cartModule.updateShippingMethods(data)
|
||||
|
||||
return new StepResponse(updatedItems, dataBeforeUpdate)
|
||||
},
|
||||
async (dataBeforeUpdate, { container }) => {
|
||||
if (!dataBeforeUpdate?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const cartModule: ICartModuleService = container.resolve(Modules.CART)
|
||||
|
||||
await cartModule.updateShippingMethods(dataBeforeUpdate)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
import { isDefined, MedusaError } from "@medusajs/framework/utils"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export const validateCartShippingOptionsStepId =
|
||||
"validate-cart-shipping-options"
|
||||
/**
|
||||
* This step validates shipping options to ensure they have a price.
|
||||
*/
|
||||
export const validateCartShippingOptionsPriceStep = createStep(
|
||||
"validate-cart-shipping-options-price",
|
||||
async (data: { shippingOptions: any[] }, { container }) => {
|
||||
const { shippingOptions = [] } = data
|
||||
const optionsMissingPrices: string[] = []
|
||||
|
||||
for (const shippingOption of shippingOptions) {
|
||||
const { calculated_price, ...options } = shippingOption
|
||||
|
||||
if (
|
||||
shippingOption?.id &&
|
||||
!isDefined(calculated_price?.calculated_amount)
|
||||
) {
|
||||
optionsMissingPrices.push(options.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (optionsMissingPrices.length) {
|
||||
const ids = optionsMissingPrices.join(", ")
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Shipping options with IDs ${ids} do not have a price`
|
||||
)
|
||||
}
|
||||
|
||||
return new StepResponse(void 0)
|
||||
}
|
||||
)
|
||||
@@ -3,6 +3,7 @@ export const cartFieldsForRefreshSteps = [
|
||||
"currency_code",
|
||||
"quantity",
|
||||
"subtotal",
|
||||
"total",
|
||||
"item_subtotal",
|
||||
"shipping_subtotal",
|
||||
"region_id",
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
} from "../steps"
|
||||
import { validateCartStep } from "../steps/validate-cart"
|
||||
import { validateAndReturnShippingMethodsDataStep } from "../steps/validate-shipping-methods-data"
|
||||
import { validateCartShippingOptionsPriceStep } from "../steps/validate-shipping-options-price"
|
||||
import { cartFieldsForRefreshSteps } from "../utils/fields"
|
||||
import { listShippingOptionsForCartWorkflow } from "./list-shipping-options-for-cart"
|
||||
import { updateCartPromotionsWorkflow } from "./update-cart-promotions"
|
||||
import { updateTaxLinesWorkflow } from "./update-tax-lines"
|
||||
|
||||
@@ -54,30 +56,24 @@ export const addShippingMethodToCartWorkflow = createWorkflow(
|
||||
shippingOptionsContext: { is_return: "false", enabled_in_store: "true" },
|
||||
})
|
||||
|
||||
const shippingOptions = useRemoteQueryStep({
|
||||
entry_point: "shipping_option",
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"calculated_price.calculated_amount",
|
||||
"calculated_price.is_calculated_price_tax_inclusive",
|
||||
"provider_id",
|
||||
],
|
||||
variables: {
|
||||
id: optionIds,
|
||||
calculated_price: {
|
||||
context: { currency_code: cart.currency_code },
|
||||
},
|
||||
const shippingOptions = listShippingOptionsForCartWorkflow.runAsStep({
|
||||
input: {
|
||||
option_ids: optionIds,
|
||||
cart_id: cart.id,
|
||||
is_return: false,
|
||||
},
|
||||
}).config({ name: "fetch-shipping-option" })
|
||||
})
|
||||
|
||||
validateCartShippingOptionsPriceStep({ shippingOptions })
|
||||
|
||||
const validateShippingMethodsDataInput = transform(
|
||||
{ input, shippingOptions },
|
||||
(data) => {
|
||||
return data.input.options.map((inputOption) => {
|
||||
const shippingOption = data.shippingOptions.find(
|
||||
({ input, shippingOptions }) => {
|
||||
return input.options.map((inputOption) => {
|
||||
const shippingOption = shippingOptions.find(
|
||||
(so) => so.id === inputOption.id
|
||||
)
|
||||
|
||||
return {
|
||||
id: inputOption.id,
|
||||
provider_id: shippingOption?.provider_id,
|
||||
|
||||
@@ -8,27 +8,20 @@ import {
|
||||
parallelize,
|
||||
transform,
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { emitEventStep } from "../../common/steps/emit-event"
|
||||
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
|
||||
import {
|
||||
createLineItemsStep,
|
||||
getLineItemActionsStep,
|
||||
refreshCartShippingMethodsStep,
|
||||
updateLineItemsStep,
|
||||
} from "../steps"
|
||||
import { validateCartStep } from "../steps/validate-cart"
|
||||
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
|
||||
import {
|
||||
cartFieldsForRefreshSteps,
|
||||
productVariantsFields,
|
||||
} from "../utils/fields"
|
||||
import { productVariantsFields } from "../utils/fields"
|
||||
import { prepareLineItemData } from "../utils/prepare-line-item-data"
|
||||
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
|
||||
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
|
||||
import { updateCartPromotionsWorkflow } from "./update-cart-promotions"
|
||||
import { updateTaxLinesWorkflow } from "./update-tax-lines"
|
||||
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
|
||||
|
||||
export const addToCartWorkflowId = "add-to-cart"
|
||||
/**
|
||||
@@ -44,6 +37,7 @@ export const addToCartWorkflow = createWorkflow(
|
||||
})
|
||||
|
||||
// TODO: This is on par with the context used in v1.*, but we can be more flexible.
|
||||
// TODO: create a common workflow to fetch variants and its prices
|
||||
const pricingContext = transform({ cart: input.cart }, (data) => {
|
||||
return {
|
||||
currency_code: data.cart.currency_code,
|
||||
@@ -100,7 +94,7 @@ export const addToCartWorkflow = createWorkflow(
|
||||
},
|
||||
})
|
||||
|
||||
const [createdItems, updatedItems] = parallelize(
|
||||
parallelize(
|
||||
createLineItemsStep({
|
||||
id: input.cart.id,
|
||||
items: itemsToCreate,
|
||||
@@ -111,43 +105,13 @@ export const addToCartWorkflow = createWorkflow(
|
||||
})
|
||||
)
|
||||
|
||||
const items = transform({ createdItems, updatedItems }, (data) => {
|
||||
return [...(data.createdItems || []), ...(data.updatedItems || [])]
|
||||
refreshCartItemsWorkflow.runAsStep({
|
||||
input: { cart_id: input.cart.id },
|
||||
})
|
||||
|
||||
const cart = useRemoteQueryStep({
|
||||
entry_point: "cart",
|
||||
fields: cartFieldsForRefreshSteps,
|
||||
variables: { id: input.cart.id },
|
||||
list: false,
|
||||
}).config({ name: "refetch–cart" })
|
||||
|
||||
parallelize(
|
||||
refreshCartShippingMethodsStep({ cart }),
|
||||
emitEventStep({
|
||||
eventName: CartWorkflowEvents.UPDATED,
|
||||
data: { id: input.cart.id },
|
||||
})
|
||||
)
|
||||
|
||||
updateTaxLinesWorkflow.runAsStep({
|
||||
input: {
|
||||
cart_id: input.cart.id,
|
||||
},
|
||||
emitEventStep({
|
||||
eventName: CartWorkflowEvents.UPDATED,
|
||||
data: { id: input.cart.id },
|
||||
})
|
||||
|
||||
updateCartPromotionsWorkflow.runAsStep({
|
||||
input: {
|
||||
cart_id: input.cart.id,
|
||||
},
|
||||
})
|
||||
|
||||
refreshPaymentCollectionForCartWorkflow.runAsStep({
|
||||
input: {
|
||||
cart_id: input.cart.id,
|
||||
},
|
||||
})
|
||||
|
||||
return new WorkflowResponse(items)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { deepFlatMap, isPresent, MedusaError } from "@medusajs/framework/utils"
|
||||
import { deepFlatMap } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createWorkflow,
|
||||
transform,
|
||||
when,
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
@@ -16,7 +15,14 @@ export const listShippingOptionsForCartWorkflowId =
|
||||
*/
|
||||
export const listShippingOptionsForCartWorkflow = createWorkflow(
|
||||
listShippingOptionsForCartWorkflowId,
|
||||
(input: WorkflowData<{ cart_id: string; is_return?: boolean }>) => {
|
||||
(
|
||||
input: WorkflowData<{
|
||||
cart_id: string
|
||||
option_ids?: string[]
|
||||
is_return?: boolean
|
||||
enabled_in_store?: boolean
|
||||
}>
|
||||
) => {
|
||||
const cartQuery = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
filters: { id: input.cart_id },
|
||||
@@ -28,6 +34,8 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
|
||||
"shipping_address.city",
|
||||
"shipping_address.country_code",
|
||||
"shipping_address.province",
|
||||
"shipping_address.postal_code",
|
||||
"item_total",
|
||||
"total",
|
||||
],
|
||||
options: { throwIfKeyNotFound: true },
|
||||
@@ -70,42 +78,31 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
|
||||
}
|
||||
)
|
||||
|
||||
const customerGroupIds = when(
|
||||
"get-customer-group",
|
||||
{ cart },
|
||||
({ cart }) => {
|
||||
return !!cart.id
|
||||
}
|
||||
).then(() => {
|
||||
const customerQuery = useQueryGraphStep({
|
||||
entity: "customer",
|
||||
filters: { id: cart.customer_id },
|
||||
fields: ["groups.id"],
|
||||
}).config({ name: "get-customer" })
|
||||
const queryVariables = transform(
|
||||
{ input, fulfillmentSetIds, cart },
|
||||
({ input, fulfillmentSetIds, cart }) => ({
|
||||
id: input.option_ids,
|
||||
|
||||
return transform({ customerQuery }, ({ customerQuery }) => {
|
||||
const customer = customerQuery.data[0]
|
||||
context: {
|
||||
is_return: input.is_return ?? false,
|
||||
enabled_in_store: input.enabled_in_store ?? true,
|
||||
},
|
||||
|
||||
if (!isPresent(customer)) {
|
||||
return []
|
||||
}
|
||||
filters: {
|
||||
fulfillment_set_id: fulfillmentSetIds,
|
||||
|
||||
const { groups = [] } = customer
|
||||
address: {
|
||||
country_code: cart.shipping_address?.country_code,
|
||||
province_code: cart.shipping_address?.province,
|
||||
city: cart.shipping_address?.city,
|
||||
postal_expression: cart.shipping_address?.postal_code,
|
||||
},
|
||||
},
|
||||
|
||||
return groups.map((group) => group.id)
|
||||
})
|
||||
})
|
||||
|
||||
const pricingContext = transform(
|
||||
{ cart, customerGroupIds },
|
||||
({ cart, customerGroupIds }) => ({
|
||||
...cart,
|
||||
customer_group_id: customerGroupIds,
|
||||
calculated_price: { context: cart },
|
||||
})
|
||||
)
|
||||
|
||||
const isReturn = transform({ input }, ({ input }) => !!input.is_return)
|
||||
|
||||
const shippingOptions = useRemoteQueryStep({
|
||||
entry_point: "shipping_options",
|
||||
fields: [
|
||||
@@ -116,7 +113,6 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
|
||||
"shipping_profile_id",
|
||||
"provider_id",
|
||||
"data",
|
||||
"amount",
|
||||
|
||||
"type.id",
|
||||
"type.label",
|
||||
@@ -132,55 +128,22 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
|
||||
|
||||
"calculated_price.*",
|
||||
],
|
||||
variables: {
|
||||
context: {
|
||||
is_return: isReturn,
|
||||
enabled_in_store: "true",
|
||||
},
|
||||
filters: {
|
||||
fulfillment_set_id: fulfillmentSetIds,
|
||||
address: {
|
||||
city: cart.shipping_address?.city,
|
||||
country_code: cart.shipping_address?.country_code,
|
||||
province_code: cart.shipping_address?.province,
|
||||
},
|
||||
},
|
||||
|
||||
calculated_price: {
|
||||
context: pricingContext,
|
||||
},
|
||||
},
|
||||
variables: queryVariables,
|
||||
}).config({ name: "shipping-options-query" })
|
||||
|
||||
const shippingOptionsWithPrice = transform({ shippingOptions }, (data) => {
|
||||
const optionsMissingPrices: string[] = []
|
||||
const shippingOptionsWithPrice = transform(
|
||||
{ shippingOptions },
|
||||
({ shippingOptions }) =>
|
||||
shippingOptions.map((shippingOption) => {
|
||||
const price = shippingOption.calculated_price
|
||||
|
||||
const options = data.shippingOptions.map((shippingOption) => {
|
||||
const { calculated_price, ...options } = shippingOption ?? {}
|
||||
|
||||
if (options?.id && !isPresent(calculated_price?.calculated_amount)) {
|
||||
optionsMissingPrices.push(options.id)
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
amount: calculated_price?.calculated_amount,
|
||||
is_tax_inclusive:
|
||||
!!calculated_price?.is_calculated_price_tax_inclusive,
|
||||
}
|
||||
})
|
||||
|
||||
if (optionsMissingPrices.length) {
|
||||
const ids = optionsMissingPrices.join(", ")
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Shipping options with IDs ${ids} do not have a price`
|
||||
)
|
||||
}
|
||||
|
||||
return options
|
||||
})
|
||||
return {
|
||||
...shippingOption,
|
||||
amount: price?.calculated_amount,
|
||||
is_tax_inclusive: !!price?.is_calculated_price_tax_inclusive,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return new WorkflowResponse(shippingOptionsWithPrice)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import {
|
||||
WorkflowResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
|
||||
import { refreshCartShippingMethodsStep, updateLineItemsStep } from "../steps"
|
||||
import { updateLineItemsStep } from "../steps"
|
||||
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
|
||||
import {
|
||||
cartFieldsForRefreshSteps,
|
||||
productVariantsFields,
|
||||
} from "../utils/fields"
|
||||
import { prepareLineItemData } from "../utils/prepare-line-item-data"
|
||||
import { refreshCartShippingMethodsWorkflow } from "./refresh-cart-shipping-methods"
|
||||
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
|
||||
import { updateCartPromotionsWorkflow } from "./update-cart-promotions"
|
||||
import { updateTaxLinesWorkflow } from "./update-tax-lines"
|
||||
@@ -100,7 +101,9 @@ export const refreshCartItemsWorkflow = createWorkflow(
|
||||
list: false,
|
||||
}).config({ name: "refetch–cart" })
|
||||
|
||||
refreshCartShippingMethodsStep({ cart: refetchedCart })
|
||||
refreshCartShippingMethodsWorkflow.runAsStep({
|
||||
input: { cart_id: cart.id },
|
||||
})
|
||||
|
||||
updateTaxLinesWorkflow.runAsStep({
|
||||
input: { cart_id: cart.id },
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { isDefined, isPresent } from "@medusajs/framework/utils"
|
||||
import {
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
transform,
|
||||
when,
|
||||
WorkflowData,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useQueryGraphStep } from "../../common"
|
||||
import { removeShippingMethodFromCartStep } from "../steps"
|
||||
import { updateShippingMethodsStep } from "../steps/update-shipping-methods"
|
||||
import { listShippingOptionsForCartWorkflow } from "./list-shipping-options-for-cart"
|
||||
|
||||
export const refreshCartShippingMethodsWorkflowId =
|
||||
"refresh-cart-shipping-methods"
|
||||
/**
|
||||
* This workflow refreshes a cart's shipping methods
|
||||
*/
|
||||
export const refreshCartShippingMethodsWorkflow = createWorkflow(
|
||||
refreshCartShippingMethodsWorkflowId,
|
||||
(input: WorkflowData<{ cart_id: string }>) => {
|
||||
const cartQuery = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
filters: { id: input.cart_id },
|
||||
fields: [
|
||||
"id",
|
||||
"sales_channel_id",
|
||||
"currency_code",
|
||||
"region_id",
|
||||
"shipping_methods.*",
|
||||
"shipping_address.city",
|
||||
"shipping_address.country_code",
|
||||
"shipping_address.province",
|
||||
"shipping_methods.shipping_option_id",
|
||||
"total",
|
||||
],
|
||||
options: { throwIfKeyNotFound: true },
|
||||
}).config({ name: "get-cart" })
|
||||
|
||||
const cart = transform({ cartQuery }, ({ cartQuery }) => cartQuery.data[0])
|
||||
const shippingOptionIds: string[] = transform({ cart }, ({ cart }) =>
|
||||
(cart.shipping_methods || [])
|
||||
.map((shippingMethod) => shippingMethod.shipping_option_id)
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
when({ shippingOptionIds }, ({ shippingOptionIds }) => {
|
||||
return !!shippingOptionIds?.length
|
||||
}).then(() => {
|
||||
const shippingOptions = listShippingOptionsForCartWorkflow.runAsStep({
|
||||
input: {
|
||||
option_ids: shippingOptionIds,
|
||||
cart_id: cart.id,
|
||||
is_return: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Creates an object on which shipping methods to remove or update depending
|
||||
// on the validity of the shipping options for the cart
|
||||
const shippingMethodsData = transform(
|
||||
{ cart, shippingOptions },
|
||||
({ cart, shippingOptions }) => {
|
||||
const { shipping_methods: shippingMethods = [] } = cart
|
||||
|
||||
const validShippingMethods = shippingMethods.filter(
|
||||
(shippingMethod) => {
|
||||
// Fetch the available shipping options for the cart context and find the one associated
|
||||
// with the current shipping method
|
||||
const shippingOption = shippingOptions.find(
|
||||
(shippingOption) =>
|
||||
shippingOption.id === shippingMethod.shipping_option_id
|
||||
)
|
||||
|
||||
const shippingOptionPrice =
|
||||
shippingOption?.calculated_price?.calculated_amount
|
||||
|
||||
// The shipping method is only valid if both the shipping option and the price is found
|
||||
// for the context of the cart. The invalid options will lead to a deleted shipping method
|
||||
if (isPresent(shippingOption) && isDefined(shippingOptionPrice)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
)
|
||||
|
||||
const shippingMethodIds = shippingMethods.map((sm) => sm.id)
|
||||
const validShippingMethodIds = validShippingMethods.map((sm) => sm.id)
|
||||
const invalidShippingMethodIds = shippingMethodIds.filter(
|
||||
(id) => !validShippingMethodIds.includes(id)
|
||||
)
|
||||
|
||||
const shippingMethodsToUpdate = validShippingMethods.map(
|
||||
(shippingMethod) => {
|
||||
const shippingOption = shippingOptions.find(
|
||||
(s) => s.id === shippingMethod.shipping_option_id
|
||||
)!
|
||||
|
||||
return {
|
||||
id: shippingMethod.id,
|
||||
shipping_option_id: shippingOption.id,
|
||||
amount: shippingOption.calculated_price.calculated_amount,
|
||||
is_tax_inclusive:
|
||||
shippingOption.calculated_price
|
||||
.is_calculated_price_tax_inclusive,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
shippingMethodsToRemove: invalidShippingMethodIds,
|
||||
shippingMethodsToUpdate,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
parallelize(
|
||||
removeShippingMethodFromCartStep({
|
||||
shipping_method_ids: shippingMethodsData.shippingMethodsToRemove,
|
||||
}),
|
||||
updateShippingMethodsStep(shippingMethodsData.shippingMethodsToUpdate)
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -1,28 +1,16 @@
|
||||
import { UpdateLineItemInCartWorkflowInputDTO } from "@medusajs/framework/types"
|
||||
import { CartWorkflowEvents } from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
transform,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { emitEventStep } from "../../common/steps/emit-event"
|
||||
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
|
||||
import { updateLineItemsStepWithSelector } from "../../line-item/steps"
|
||||
import { refreshCartShippingMethodsStep } from "../steps"
|
||||
import { validateCartStep } from "../steps/validate-cart"
|
||||
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
|
||||
import {
|
||||
cartFieldsForRefreshSteps,
|
||||
productVariantsFields,
|
||||
} from "../utils/fields"
|
||||
import { productVariantsFields } from "../utils/fields"
|
||||
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
|
||||
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
|
||||
import { updateCartPromotionsWorkflow } from "./update-cart-promotions"
|
||||
|
||||
// TODO: The UpdateLineItemsWorkflow are missing the following steps:
|
||||
// - Validate shipping methods for new items (fulfillment module)
|
||||
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
|
||||
|
||||
export const updateLineItemInCartWorkflowId = "update-line-item-in-cart"
|
||||
/**
|
||||
@@ -89,35 +77,10 @@ export const updateLineItemInCartWorkflow = createWorkflow(
|
||||
}
|
||||
})
|
||||
|
||||
const result = updateLineItemsStepWithSelector(lineItemUpdate)
|
||||
updateLineItemsStepWithSelector(lineItemUpdate)
|
||||
|
||||
const cart = useRemoteQueryStep({
|
||||
entry_point: "cart",
|
||||
fields: cartFieldsForRefreshSteps,
|
||||
variables: { id: input.cart.id },
|
||||
list: false,
|
||||
}).config({ name: "refetch–cart" })
|
||||
|
||||
refreshCartShippingMethodsStep({ cart })
|
||||
|
||||
updateCartPromotionsWorkflow.runAsStep({
|
||||
input: {
|
||||
cart_id: input.cart.id,
|
||||
},
|
||||
refreshCartItemsWorkflow.runAsStep({
|
||||
input: { cart_id: input.cart.id },
|
||||
})
|
||||
|
||||
parallelize(
|
||||
refreshPaymentCollectionForCartWorkflow.runAsStep({
|
||||
input: { cart_id: input.cart.id },
|
||||
}),
|
||||
emitEventStep({
|
||||
eventName: CartWorkflowEvents.UPDATED,
|
||||
data: { id: input.cart.id },
|
||||
})
|
||||
)
|
||||
|
||||
const updatedItem = transform({ result }, (data) => data.result?.[0])
|
||||
|
||||
return new WorkflowResponse(updatedItem)
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user