feat(workflows-sdk,core-flows,medusa,types): add workflow to update promotions to cart (#6474)

what:

- adds API + workflow to add/remove promotions in a cart
- minor fixes in promotions module
- minor type fixes in cart module
- typing fix in workflows-sdk (Thanks @adrien2p)
- fix step result in workflows-sdk (Thanks @adrien2p)

RESOLVES CORE-1768

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Riqwan Thamir
2024-02-26 12:43:57 +00:00
committed by GitHub
co-authored by Adrien de Peretti
parent 63be07031b
commit ac86362e81
30 changed files with 1187 additions and 101 deletions
@@ -0,0 +1,41 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CreateLineItemAdjustmentDTO,
ICartModuleService,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
lineItemAdjustmentsToCreate: CreateLineItemAdjustmentDTO[]
}
export const createLineItemAdjustmentsStepId = "create-line-item-adjustments"
export const createLineItemAdjustmentsStep = createStep(
createLineItemAdjustmentsStepId,
async (data: StepInput, { container }) => {
const { lineItemAdjustmentsToCreate = [] } = data
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
const createdLineItemAdjustments =
await cartModuleService.addLineItemAdjustments(
lineItemAdjustmentsToCreate
)
return new StepResponse(void 0, createdLineItemAdjustments)
},
async (createdLineItemAdjustments, { container }) => {
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
if (!createdLineItemAdjustments?.length) {
return
}
await cartModuleService.softDeleteLineItemAdjustments(
createdLineItemAdjustments.map((c) => c.id)
)
}
)
@@ -0,0 +1,42 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CreateShippingMethodAdjustmentDTO,
ICartModuleService,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
shippingMethodAdjustmentsToCreate: CreateShippingMethodAdjustmentDTO[]
}
export const createShippingMethodAdjustmentsStepId =
"create-shipping-method-adjustments"
export const createShippingMethodAdjustmentsStep = createStep(
createShippingMethodAdjustmentsStepId,
async (data: StepInput, { container }) => {
const { shippingMethodAdjustmentsToCreate = [] } = data
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
const createdShippingMethodAdjustments =
await cartModuleService.addShippingMethodAdjustments(
shippingMethodAdjustmentsToCreate
)
return new StepResponse(void 0, createdShippingMethodAdjustments)
},
async (createdShippingMethodAdjustments, { container }) => {
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
if (!createdShippingMethodAdjustments?.length) {
return
}
await cartModuleService.softDeleteShippingMethodAdjustments(
createdShippingMethodAdjustments.map((c) => c.id)
)
}
)
@@ -0,0 +1,54 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CartDTO, IPromotionModuleService } from "@medusajs/types"
import { deduplicate, isString } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
cart: CartDTO
promoCodes: string[]
removePromotions: boolean
}
export const getActionsToComputeFromPromotionsStepId =
"get-actions-to-compute-from-promotions"
export const getActionsToComputeFromPromotionsStep = createStep(
getActionsToComputeFromPromotionsStepId,
async (data: StepInput, { container }) => {
const promotionModuleService: IPromotionModuleService = container.resolve(
ModuleRegistrationName.PROMOTION
)
const { removePromotions = false, promoCodes = [], cart } = data
const appliedItemPromoCodes = cart.items
?.map((item) => item.adjustments?.map((adjustment) => adjustment.code))
.flat(1)
.filter(isString) as string[]
const appliedShippingMethodPromoCodes = cart.shipping_methods
?.map((shippingMethod) =>
shippingMethod.adjustments?.map((adjustment) => adjustment.code)
)
.flat(1)
.filter(isString) as string[]
let promotionCodesToApply = deduplicate([
...promoCodes,
...appliedItemPromoCodes,
...appliedShippingMethodPromoCodes,
])
if (removePromotions) {
promotionCodesToApply = promotionCodesToApply.filter(
(code) => !promoCodes.includes(code)
)
}
const actionsToCompute = await promotionModuleService.computeActions(
promotionCodesToApply,
cart as any
)
return new StepResponse(actionsToCompute)
}
)
@@ -1,5 +1,12 @@
export * from "./create-carts"
export * from "./create-line-item-adjustments"
export * from "./create-shipping-method-adjustments"
export * from "./find-one-or-any-region"
export * from "./find-or-create-customer"
export * from "./find-sales-channel"
export * from "./get-actions-to-compute-from-promotions"
export * from "./prepare-adjustments-from-promotion-actions"
export * from "./remove-line-item-adjustments"
export * from "./remove-shipping-method-adjustments"
export * from "./retrieve-cart"
export * from "./update-carts"
@@ -0,0 +1,75 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
AddItemAdjustmentAction,
AddShippingMethodAdjustment,
ComputeActions,
IPromotionModuleService,
PromotionDTO,
RemoveItemAdjustmentAction,
RemoveShippingMethodAdjustment,
} from "@medusajs/types"
import { ComputedActions } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
actions: ComputeActions[]
}
export const prepareAdjustmentsFromPromotionActionsStepId =
"prepare-adjustments-from-promotion-actions"
export const prepareAdjustmentsFromPromotionActionsStep = createStep(
prepareAdjustmentsFromPromotionActionsStepId,
async (data: StepInput, { container }) => {
const promotionModuleService: IPromotionModuleService = container.resolve(
ModuleRegistrationName.PROMOTION
)
const { actions = [] } = data
const promotions = await promotionModuleService.list(
{ code: actions.map((a) => a.code) },
{ select: ["id", "code"] }
)
const promotionsMap = new Map<string, PromotionDTO>(
promotions.map((promotion) => [promotion.code!, promotion])
)
const lineItemAdjustmentsToCreate = actions
.filter((a) => a.action === ComputedActions.ADD_ITEM_ADJUSTMENT)
.map((action) => ({
code: action.code,
amount: (action as AddItemAdjustmentAction).amount,
item_id: (action as AddItemAdjustmentAction).item_id,
promotion_id: promotionsMap.get(action.code)?.id,
}))
const lineItemAdjustmentIdsToRemove = actions
.filter((a) => a.action === ComputedActions.REMOVE_ITEM_ADJUSTMENT)
.map((a) => (a as RemoveItemAdjustmentAction).adjustment_id)
const shippingMethodAdjustmentsToCreate = actions
.filter(
(a) => a.action === ComputedActions.ADD_SHIPPING_METHOD_ADJUSTMENT
)
.map((action) => ({
code: action.code,
amount: (action as AddShippingMethodAdjustment).amount,
shipping_method_id: (action as AddShippingMethodAdjustment)
.shipping_method_id,
promotion_id: promotionsMap.get(action.code)?.id,
}))
const shippingMethodAdjustmentIdsToRemove = actions
.filter(
(a) => a.action === ComputedActions.REMOVE_SHIPPING_METHOD_ADJUSTMENT
)
.map((a) => (a as RemoveShippingMethodAdjustment).adjustment_id)
return new StepResponse({
lineItemAdjustmentsToCreate,
lineItemAdjustmentIdsToRemove,
shippingMethodAdjustmentsToCreate,
shippingMethodAdjustmentIdsToRemove,
})
}
)
@@ -0,0 +1,37 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICartModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
lineItemAdjustmentIdsToRemove: string[]
}
export const removeLineItemAdjustmentsStepId = "remove-line-item-adjustments"
export const removeLineItemAdjustmentsStep = createStep(
removeLineItemAdjustmentsStepId,
async (data: StepInput, { container }) => {
const { lineItemAdjustmentIdsToRemove = [] } = data
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
await cartModuleService.softDeleteLineItemAdjustments(
lineItemAdjustmentIdsToRemove
)
return new StepResponse(void 0, lineItemAdjustmentIdsToRemove)
},
async (lineItemAdjustmentIdsToRemove, { container }) => {
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
if (!lineItemAdjustmentIdsToRemove?.length) {
return
}
await cartModuleService.restoreLineItemAdjustments(
lineItemAdjustmentIdsToRemove
)
}
)
@@ -0,0 +1,38 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICartModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
shippingMethodAdjustmentIdsToRemove: string[]
}
export const removeShippingMethodAdjustmentsStepId =
"remove-shipping-method-adjustments"
export const removeShippingMethodAdjustmentsStep = createStep(
removeShippingMethodAdjustmentsStepId,
async (data: StepInput, { container }) => {
const { shippingMethodAdjustmentIdsToRemove = [] } = data
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
await cartModuleService.softDeleteShippingMethodAdjustments(
shippingMethodAdjustmentIdsToRemove
)
return new StepResponse(void 0, shippingMethodAdjustmentIdsToRemove)
},
async (shippingMethodAdjustmentIdsToRemove, { container }) => {
const cartModuleService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
if (!shippingMethodAdjustmentIdsToRemove?.length) {
return
}
await cartModuleService.restoreShippingMethodAdjustments(
shippingMethodAdjustmentIdsToRemove
)
}
)
@@ -0,0 +1,37 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CartDTO, FindConfig, ICartModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
cartId: string
config: FindConfig<CartDTO>
}
export const retrieveCartStepId = "retrieve-cart"
export const retrieveCartStep = createStep(
retrieveCartStepId,
async (data: StepInput, { container }) => {
const cartModuleService = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const cart = await cartModuleService.retrieve(data.cartId, data.config)
// TODO: remove this when cart handles totals calculation
cart.items = cart.items?.map((item) => {
item.subtotal = item.unit_price
return item
})
// TODO: remove this when cart handles totals calculation
cart.shipping_methods = cart.shipping_methods?.map((shipping_method) => {
// TODO: should we align all amounts/prices fields to be unit_price?
shipping_method.subtotal = shipping_method.amount
return shipping_method
})
return new StepResponse(cart)
}
)
@@ -1,3 +1,3 @@
export * from "./create-carts"
export * from "./update-cart-promotions"
export * from "./update-carts"
@@ -0,0 +1,69 @@
import { CartDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
} from "@medusajs/workflows-sdk"
import {
createLineItemAdjustmentsStep,
createShippingMethodAdjustmentsStep,
getActionsToComputeFromPromotionsStep,
prepareAdjustmentsFromPromotionActionsStep,
removeLineItemAdjustmentsStep,
removeShippingMethodAdjustmentsStep,
retrieveCartStep,
} from "../steps"
type WorkflowInput = {
promoCodes: string[]
cartId: string
removePromotions?: boolean
}
export const updateCartPromotionsWorkflowId = "update-cart-promotions"
export const updateCartPromotionsWorkflow = createWorkflow(
updateCartPromotionsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CartDTO> => {
const retrieveCartInput = {
cartId: input.cartId,
config: {
relations: [
"items",
"items.adjustments",
"shipping_methods",
"shipping_methods.adjustments",
],
},
}
const cart = retrieveCartStep(retrieveCartInput)
const actions = getActionsToComputeFromPromotionsStep({
cart,
promoCodes: input.promoCodes,
removePromotions: input.removePromotions || false,
})
const {
lineItemAdjustmentsToCreate,
lineItemAdjustmentIdsToRemove,
shippingMethodAdjustmentsToCreate,
shippingMethodAdjustmentIdsToRemove,
} = prepareAdjustmentsFromPromotionActionsStep({ actions })
parallelize(
removeLineItemAdjustmentsStep({ lineItemAdjustmentIdsToRemove }),
removeShippingMethodAdjustmentsStep({
shippingMethodAdjustmentIdsToRemove,
})
)
parallelize(
createLineItemAdjustmentsStep({ lineItemAdjustmentsToCreate }),
createShippingMethodAdjustmentsStep({ shippingMethodAdjustmentsToCreate })
)
return retrieveCartStep(retrieveCartInput).config({
name: "retrieve-cart-result-step",
})
}
)