chore(): start moving some packages to the core directory (#7215)

This commit is contained in:
Adrien de Peretti
2024-05-03 13:37:41 +02:00
committed by GitHub
parent fdee748eed
commit bbccd6481d
1436 changed files with 275 additions and 756 deletions
@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -0,0 +1,33 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreateApiKeyDTO, IApiKeyModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type CreateApiKeysStepInput = {
api_keys: CreateApiKeyDTO[]
}
export const createApiKeysStepId = "create-api-keys"
export const createApiKeysStep = createStep(
createApiKeysStepId,
async (data: CreateApiKeysStepInput, { container }) => {
const service = container.resolve<IApiKeyModuleService>(
ModuleRegistrationName.API_KEY
)
const created = await service.create(data.api_keys)
return new StepResponse(
created,
created.map((apiKey) => apiKey.id)
)
},
async (createdIds, { container }) => {
if (!createdIds?.length) {
return
}
const service = container.resolve<IApiKeyModuleService>(
ModuleRegistrationName.API_KEY
)
await service.delete(createdIds)
}
)
@@ -0,0 +1,17 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IApiKeyModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const deleteApiKeysStepId = "delete-api-keys"
export const deleteApiKeysStep = createStep(
{ name: deleteApiKeysStepId, noCompensation: true },
async (ids: string[], { container }) => {
const service = container.resolve<IApiKeyModuleService>(
ModuleRegistrationName.API_KEY
)
await service.delete(ids)
return new StepResponse(void 0)
},
async () => {}
)
@@ -0,0 +1,6 @@
export * from "./link-sales-channels-to-publishable-key"
export * from "./create-api-keys"
export * from "./delete-api-keys"
export * from "./revoke-api-keys"
export * from "./update-api-keys"
export * from "./validate-sales-channel-exists"
@@ -0,0 +1,62 @@
import { Modules } from "@medusajs/modules-sdk"
import { LinkWorkflowInput } from "@medusajs/types/src"
import { ContainerRegistrationKeys, promiseAll } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const linkSalesChannelsToApiKeyStepId = "link-sales-channels-to-api-key"
export const linkSalesChannelsToApiKeyStep = createStep(
linkSalesChannelsToApiKeyStepId,
async (input: LinkWorkflowInput, { container }) => {
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
if (!input || (!input.add?.length && !input.remove?.length)) {
return
}
const linksToCreate = (input.add ?? []).map((salesChannelId) => {
return {
[Modules.API_KEY]: {
publishable_key_id: input.id,
},
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannelId,
},
}
})
const linksToDismiss = (input.remove ?? []).map((salesChannelId) => {
return {
[Modules.API_KEY]: {
publishable_key_id: input.id,
},
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannelId,
},
}
})
const promises: Promise<any>[] = []
if (linksToCreate.length) {
promises.push(remoteLink.create(linksToCreate))
}
if (linksToDismiss.length) {
promises.push(remoteLink.dismiss(linksToDismiss))
}
await promiseAll(promises)
return new StepResponse(void 0, { linksToCreate, linksToDismiss })
},
async (prevData, { container }) => {
if (!prevData) {
return
}
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
if (prevData.linksToCreate.length) {
await remoteLink.dismiss(prevData.linksToCreate)
}
if (prevData.linksToDismiss.length) {
await remoteLink.create(prevData.linksToDismiss)
}
}
)
@@ -0,0 +1,26 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
FilterableApiKeyProps,
IApiKeyModuleService,
RevokeApiKeyDTO,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type RevokeApiKeysStepInput = {
selector: FilterableApiKeyProps
revoke: RevokeApiKeyDTO
}
export const revokeApiKeysStepId = "revoke-api-keys"
export const revokeApiKeysStep = createStep(
{ name: revokeApiKeysStepId, noCompensation: true },
async (data: RevokeApiKeysStepInput, { container }) => {
const service = container.resolve<IApiKeyModuleService>(
ModuleRegistrationName.API_KEY
)
const apiKeys = await service.revoke(data.selector, data.revoke)
return new StepResponse(apiKeys)
},
async () => {}
)
@@ -0,0 +1,51 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
FilterableApiKeyProps,
IApiKeyModuleService,
UpdateApiKeyDTO,
} from "@medusajs/types"
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type UpdateApiKeysStepInput = {
selector: FilterableApiKeyProps
update: UpdateApiKeyDTO
}
export const updateApiKeysStepId = "update-api-keys"
export const updateApiKeysStep = createStep(
updateApiKeysStepId,
async (data: UpdateApiKeysStepInput, { container }) => {
const service = container.resolve<IApiKeyModuleService>(
ModuleRegistrationName.API_KEY
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
data.update,
])
const prevData = await service.list(data.selector, {
select: selects,
relations,
})
const apiKeys = await service.update(data.selector, data.update)
return new StepResponse(apiKeys, prevData)
},
async (prevData, { container }) => {
if (!prevData?.length) {
return
}
const service = container.resolve<IApiKeyModuleService>(
ModuleRegistrationName.API_KEY
)
await service.upsert(
prevData.map((r) => ({
id: r.id,
title: r.title,
}))
)
}
)
@@ -0,0 +1,37 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ISalesChannelModuleService } from "@medusajs/types"
import { MedusaError, arrayDifference } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
sales_channel_ids: string[]
}
export const validateSalesChannelsExistStepId = "validate-sales-channels-exist"
export const validateSalesChannelsExistStep = createStep(
validateSalesChannelsExistStepId,
async (data: StepInput, { container }) => {
const salesChannelModuleService =
container.resolve<ISalesChannelModuleService>(
ModuleRegistrationName.SALES_CHANNEL
)
const salesChannels = await salesChannelModuleService.list(
{ id: data.sales_channel_ids },
{ select: ["id"] }
)
const salesChannelIds = salesChannels.map((v) => v.id)
const notFound = arrayDifference(data.sales_channel_ids, salesChannelIds)
if (notFound.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sales channels with IDs ${notFound.join(", ")} do not exist`
)
}
return new StepResponse(salesChannelIds)
}
)
@@ -0,0 +1,13 @@
import { ApiKeyDTO, CreateApiKeyDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createApiKeysStep } from "../steps"
type WorkflowInput = { api_keys: CreateApiKeyDTO[] }
export const createApiKeysWorkflowId = "create-api-keys"
export const createApiKeysWorkflow = createWorkflow(
createApiKeysWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<ApiKeyDTO[]> => {
return createApiKeysStep(input)
}
)
@@ -0,0 +1,19 @@
import { Modules } from "@medusajs/modules-sdk"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { removeRemoteLinkStep } from "../../common/steps/remove-remote-links"
import { deleteApiKeysStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const deleteApiKeysWorkflowId = "delete-api-keys"
export const deleteApiKeysWorkflow = createWorkflow(
deleteApiKeysWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
deleteApiKeysStep(input.ids)
// Please note, the ids here should be publishable key IDs
removeRemoteLinkStep({
[Modules.API_KEY]: { publishable_key_id: input.ids },
})
}
)
@@ -0,0 +1,5 @@
export * from "./create-api-keys"
export * from "./delete-api-keys"
export * from "./update-api-keys"
export * from "./revoke-api-keys"
export * from "./link-sales-channels-to-publishable-key"
@@ -0,0 +1,19 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import {
linkSalesChannelsToApiKeyStep,
validateSalesChannelsExistStep,
} from "../steps"
import { LinkWorkflowInput } from "@medusajs/types/src"
export const linkSalesChannelsToApiKeyWorkflowId =
"link-sales-channels-to-api-key"
export const linkSalesChannelsToApiKeyWorkflow = createWorkflow(
linkSalesChannelsToApiKeyWorkflowId,
(input: WorkflowData<LinkWorkflowInput>) => {
validateSalesChannelsExistStep({
sales_channel_ids: input.add ?? [],
})
linkSalesChannelsToApiKeyStep(input)
}
)
@@ -0,0 +1,22 @@
import {
ApiKeyDTO,
FilterableApiKeyProps,
RevokeApiKeyDTO,
} from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { revokeApiKeysStep } from "../steps"
type RevokeApiKeysStepInput = {
selector: FilterableApiKeyProps
revoke: RevokeApiKeyDTO
}
type WorkflowInput = RevokeApiKeysStepInput
export const revokeApiKeysWorkflowId = "revoke-api-keys"
export const revokeApiKeysWorkflow = createWorkflow(
revokeApiKeysWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<ApiKeyDTO[]> => {
return revokeApiKeysStep(input)
}
)
@@ -0,0 +1,22 @@
import {
ApiKeyDTO,
FilterableApiKeyProps,
UpdateApiKeyDTO,
} from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateApiKeysStep } from "../steps"
type UpdateApiKeysStepInput = {
selector: FilterableApiKeyProps
update: UpdateApiKeyDTO
}
type WorkflowInput = UpdateApiKeysStepInput
export const updateApiKeysWorkflowId = "update-api-keys"
export const updateApiKeysWorkflow = createWorkflow(
updateApiKeysWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<ApiKeyDTO[]> => {
return updateApiKeysStep(input)
}
)
@@ -0,0 +1 @@
export * from "./steps"
@@ -0,0 +1 @@
export * from "./set-auth-app-metadata"
@@ -0,0 +1,60 @@
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IAuthModuleService } from "@medusajs/types"
import { isDefined } from "@medusajs/utils"
type StepInput = {
authUserId: string
key: string
value: string
}
export const setAuthAppMetadataStepId = "set-auth-app-metadata"
export const setAuthAppMetadataStep = createStep(
setAuthAppMetadataStepId,
async (data: StepInput, { container }) => {
const service = container.resolve<IAuthModuleService>(
ModuleRegistrationName.AUTH
)
const authUser = await service.retrieve(data.authUserId)
const appMetadata = authUser.app_metadata || {}
if (isDefined(appMetadata[data.key])) {
throw new Error(`Key ${data.key} already exists in app metadata`)
}
appMetadata[data.key] = data.value
await service.update({
id: authUser.id,
app_metadata: appMetadata,
})
return new StepResponse(authUser, { id: authUser.id, key: data.key })
},
async (idAndKey, { container }) => {
if (!idAndKey) {
return
}
const { id, key } = idAndKey
const service = container.resolve<IAuthModuleService>(
ModuleRegistrationName.AUTH
)
const authUser = await service.retrieve(id)
const appMetadata = authUser.app_metadata || {}
if (isDefined(appMetadata[key])) {
delete appMetadata[key]
}
await service.update({
id: authUser.id,
app_metadata: appMetadata,
})
}
)
@@ -0,0 +1,2 @@
export * from "./steps/remove-remote-links"
export * from "./steps/use-remote-query"
@@ -0,0 +1,50 @@
import { DeleteEntityInput, RemoteLink } from "@medusajs/modules-sdk"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ContainerRegistrationKeys } from "@medusajs/utils"
type RemoveRemoteLinksStepInput = DeleteEntityInput | DeleteEntityInput[]
export const removeRemoteLinkStepId = "remove-remote-links"
export const removeRemoteLinkStep = createStep(
removeRemoteLinkStepId,
async (data: RemoveRemoteLinksStepInput, { container }) => {
const entries = Array.isArray(data) ? data : [data]
const grouped: DeleteEntityInput = {}
for (const entry of entries) {
for (const moduleName of Object.keys(entry)) {
grouped[moduleName] ??= {}
for (const linkableKey of Object.keys(entry[moduleName])) {
grouped[moduleName][linkableKey] ??= []
const keys = Array.isArray(entry[moduleName][linkableKey])
? entry[moduleName][linkableKey]
: [entry[moduleName][linkableKey]]
grouped[moduleName][linkableKey] = (
grouped[moduleName][linkableKey] as string[]
).concat(keys as string[])
}
}
}
const link = container.resolve<RemoteLink>(
ContainerRegistrationKeys.REMOTE_LINK
)
await link.delete(grouped)
return new StepResponse(grouped, grouped)
},
async (removedLinks, { container }) => {
if (!removedLinks) {
return
}
const link = container.resolve<RemoteLink>(
ContainerRegistrationKeys.REMOTE_LINK
)
await link.restore(removedLinks)
}
)
@@ -0,0 +1,38 @@
import { remoteQueryObjectFromString } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
entry_point: string
fields: string[]
variables?: Record<string, any>
throw_if_key_not_found?: boolean
throw_if_relation_not_found?: boolean | string[]
list?: boolean
}
export const useRemoteQueryStepId = "use-remote-query"
export const useRemoteQueryStep = createStep(
useRemoteQueryStepId,
async (data: StepInput, { container }) => {
const { list = true, fields, variables, entry_point: entryPoint } = data
const query = container.resolve("remoteQuery")
const queryObject = remoteQueryObjectFromString({
entryPoint,
fields,
variables,
})
const config = {
throwIfKeyNotFound: !!data.throw_if_key_not_found,
throwIfRelationNotFound: data.throw_if_key_not_found
? data.throw_if_relation_not_found
: undefined,
}
const entities = await query(queryObject, undefined, config)
const result = list ? entities : entities[0]
return new StepResponse(result)
}
)
@@ -0,0 +1,2 @@
export * from "./workflows"
export * from "./steps"
@@ -0,0 +1,33 @@
import { CreateCustomerGroupDTO, ICustomerModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export const createCustomerGroupsStepId = "create-customer-groups"
export const createCustomerGroupsStep = createStep(
createCustomerGroupsStepId,
async (data: CreateCustomerGroupDTO[], { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const createdCustomerGroups = await service.createCustomerGroup(data)
return new StepResponse(
createdCustomerGroups,
createdCustomerGroups.map(
(createdCustomerGroups) => createdCustomerGroups.id
)
)
},
async (createdCustomerGroupIds, { container }) => {
if (!createdCustomerGroupIds?.length) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.delete(createdCustomerGroupIds)
}
)
@@ -0,0 +1,28 @@
import { GroupCustomerPair, ICustomerModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export const deleteCustomerGroupCustomersStepId =
"delete-customer-group-customers"
export const deleteCustomerGroupCustomersStep = createStep(
deleteCustomerGroupCustomersStepId,
async (data: GroupCustomerPair[], { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.removeCustomerFromGroup(data)
return new StepResponse(void 0, data)
},
async (groupPairs, { container }) => {
if (!groupPairs?.length) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.addCustomerToGroup(groupPairs)
}
)
@@ -0,0 +1,30 @@
import { ICustomerModuleService } from "@medusajs/types"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
type DeleteCustomerGroupStepInput = string[]
export const deleteCustomerGroupStepId = "delete-customer-groups"
export const deleteCustomerGroupStep = createStep(
deleteCustomerGroupStepId,
async (ids: DeleteCustomerGroupStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.softDeleteCustomerGroups(ids)
return new StepResponse(void 0, ids)
},
async (prevCustomerGroups, { container }) => {
if (!prevCustomerGroups) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.restoreCustomerGroups(prevCustomerGroups)
}
)
@@ -0,0 +1,4 @@
export * from "./update-customer-groups"
export * from "./delete-customer-groups"
export * from "./create-customer-groups"
export * from "./link-customers-customer-group"
@@ -0,0 +1,56 @@
import { ICustomerModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { LinkWorkflowInput } from "@medusajs/types/src"
import { promiseAll } from "@medusajs/utils"
export const linkCustomersToCustomerGroupStepId =
"link-customers-to-customer-group"
export const linkCustomersToCustomerGroupStep = createStep(
linkCustomersToCustomerGroupStepId,
async (data: LinkWorkflowInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const toAdd = (data.add ?? []).map((customerId) => {
return {
customer_id: customerId,
customer_group_id: data.id,
}
})
const toRemove = (data.remove ?? []).map((customerId) => {
return {
customer_id: customerId,
customer_group_id: data.id,
}
})
const promises: Promise<any>[] = []
if (toAdd.length) {
promises.push(service.addCustomerToGroup(toAdd))
}
if (toRemove.length) {
promises.push(service.removeCustomerFromGroup(toRemove))
}
await promiseAll(promises)
return new StepResponse(void 0, { toAdd, toRemove })
},
async (prevData, { container }) => {
if (!prevData) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
if (prevData.toAdd.length) {
await service.removeCustomerFromGroup(prevData.toAdd)
}
if (prevData.toRemove.length) {
await service.addCustomerToGroup(prevData.toRemove)
}
}
)
@@ -0,0 +1,58 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CustomerGroupUpdatableFields,
FilterableCustomerGroupProps,
ICustomerModuleService,
} from "@medusajs/types"
import {
getSelectsAndRelationsFromObjectArray,
promiseAll,
} from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
type UpdateCustomerGroupStepInput = {
selector: FilterableCustomerGroupProps
update: CustomerGroupUpdatableFields
}
export const updateCustomerGroupStepId = "update-customer-groups"
export const updateCustomerGroupsStep = createStep(
updateCustomerGroupStepId,
async (data: UpdateCustomerGroupStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
data.update,
])
const prevCustomerGroups = await service.listCustomerGroups(data.selector, {
select: selects,
relations,
})
const customers = await service.updateCustomerGroups(
data.selector,
data.update
)
return new StepResponse(customers, prevCustomerGroups)
},
async (prevCustomerGroups, { container }) => {
if (!prevCustomerGroups) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await promiseAll(
prevCustomerGroups.map((c) =>
service.updateCustomerGroups(c.id, {
name: c.name,
})
)
)
}
)
@@ -0,0 +1,13 @@
import { CustomerGroupDTO, CreateCustomerGroupDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createCustomerGroupsStep } from "../steps"
type WorkflowInput = { customersData: CreateCustomerGroupDTO[] }
export const createCustomerGroupsWorkflowId = "create-customer-groups"
export const createCustomerGroupsWorkflow = createWorkflow(
createCustomerGroupsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerGroupDTO[]> => {
return createCustomerGroupsStep(input.customersData)
}
)
@@ -0,0 +1,12 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteCustomerGroupStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const deleteCustomerGroupsWorkflowId = "delete-customer-groups"
export const deleteCustomerGroupsWorkflow = createWorkflow(
deleteCustomerGroupsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
return deleteCustomerGroupStep(input.ids)
}
)
@@ -0,0 +1,4 @@
export * from "./update-customer-groups"
export * from "./delete-customer-groups"
export * from "./create-customer-groups"
export * from "./link-customers-customer-group"
@@ -0,0 +1,12 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { linkCustomersToCustomerGroupStep } from "../steps"
import { LinkWorkflowInput } from "@medusajs/types/src"
export const linkCustomersToCustomerGroupWorkflowId =
"link-customers-to-customer-group"
export const linkCustomersToCustomerGroupWorkflow = createWorkflow(
linkCustomersToCustomerGroupWorkflowId,
(input: WorkflowData<LinkWorkflowInput>): WorkflowData<void> => {
return linkCustomersToCustomerGroupStep(input)
}
)
@@ -0,0 +1,20 @@
import {
CustomerGroupDTO,
FilterableCustomerGroupProps,
CustomerGroupUpdatableFields,
} from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateCustomerGroupsStep } from "../steps"
type WorkflowInput = {
selector: FilterableCustomerGroupProps
update: CustomerGroupUpdatableFields
}
export const updateCustomerGroupsWorkflowId = "update-customer-groups"
export const updateCustomerGroupsWorkflow = createWorkflow(
updateCustomerGroupsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerGroupDTO[]> => {
return updateCustomerGroupsStep(input)
}
)
@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -0,0 +1,34 @@
import {
CreateCustomerAddressDTO,
ICustomerModuleService,
} from "@medusajs/types"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export const createCustomerAddressesStepId = "create-customer-addresses"
export const createCustomerAddressesStep = createStep(
createCustomerAddressesStepId,
async (data: CreateCustomerAddressDTO[], { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const addresses = await service.addAddresses(data)
return new StepResponse(
addresses,
addresses.map((address) => address.id)
)
},
async (ids, { container }) => {
if (!ids?.length) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.deleteAddresses(ids)
}
)
@@ -0,0 +1,31 @@
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { CreateCustomerDTO, ICustomerModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export const createCustomersStepId = "create-customers"
export const createCustomersStep = createStep(
createCustomersStepId,
async (data: CreateCustomerDTO[], { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const createdCustomers = await service.create(data)
return new StepResponse(
createdCustomers,
createdCustomers.map((createdCustomers) => createdCustomers.id)
)
},
async (createdCustomerIds, { container }) => {
if (!createdCustomerIds?.length) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.delete(createdCustomerIds)
}
)
@@ -0,0 +1,32 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICustomerModuleService } from "@medusajs/types"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
type DeleteCustomerAddressStepInput = string[]
export const deleteCustomerAddressesStepId = "delete-customer-addresses"
export const deleteCustomerAddressesStep = createStep(
deleteCustomerAddressesStepId,
async (ids: DeleteCustomerAddressStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const existing = await service.listAddresses({
id: ids,
})
await service.deleteAddresses(ids)
return new StepResponse(void 0, existing)
},
async (prevAddresses, { container }) => {
if (!prevAddresses?.length) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.addAddresses(prevAddresses)
}
)
@@ -0,0 +1,30 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICustomerModuleService } from "@medusajs/types"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
type DeleteCustomerStepInput = string[]
export const deleteCustomersStepId = "delete-customers"
export const deleteCustomersStep = createStep(
deleteCustomersStepId,
async (ids: DeleteCustomerStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.softDelete(ids)
return new StepResponse(void 0, ids)
},
async (prevCustomerIds, { container }) => {
if (!prevCustomerIds?.length) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.restore(prevCustomerIds)
}
)
@@ -0,0 +1,8 @@
export * from "./create-customers"
export * from "./update-customers"
export * from "./delete-customers"
export * from "./create-addresses"
export * from "./update-addresses"
export * from "./delete-addresses"
export * from "./maybe-unset-default-billing-addresses"
export * from "./maybe-unset-default-shipping-addresses"
@@ -0,0 +1,61 @@
import {
CreateCustomerAddressDTO,
UpdateCustomerAddressDTO,
FilterableCustomerAddressProps,
ICustomerModuleService,
} from "@medusajs/types"
import { createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { unsetForCreate, unsetForUpdate } from "./utils"
import { isDefined } from "@medusajs/utils"
type StepInput = {
create?: CreateCustomerAddressDTO[]
update?: {
selector: FilterableCustomerAddressProps
update: UpdateCustomerAddressDTO
}
}
export const maybeUnsetDefaultBillingAddressesStepId =
"maybe-unset-default-billing-customer-addresses"
export const maybeUnsetDefaultBillingAddressesStep = createStep(
maybeUnsetDefaultBillingAddressesStepId,
async (data: StepInput, { container }) => {
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
if (isDefined(data.create)) {
return unsetForCreate(
data.create,
customerModuleService,
"is_default_billing"
)
}
if (isDefined(data.update)) {
return unsetForUpdate(
data.update,
customerModuleService,
"is_default_billing"
)
}
throw new Error("Invalid step input")
},
async (addressesToSet, { container }) => {
if (!addressesToSet?.length) {
return
}
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await customerModuleService.updateAddresses(
{ id: addressesToSet },
{ is_default_billing: true }
)
}
)
@@ -0,0 +1,60 @@
import {
CreateCustomerAddressDTO,
UpdateCustomerAddressDTO,
FilterableCustomerAddressProps,
ICustomerModuleService,
} from "@medusajs/types"
import { createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { unsetForCreate, unsetForUpdate } from "./utils"
import { isDefined } from "@medusajs/utils"
type StepInput = {
create?: CreateCustomerAddressDTO[]
update?: {
selector: FilterableCustomerAddressProps
update: UpdateCustomerAddressDTO
}
}
export const maybeUnsetDefaultShippingAddressesStepId =
"maybe-unset-default-shipping-customer-addresses"
export const maybeUnsetDefaultShippingAddressesStep = createStep(
maybeUnsetDefaultShippingAddressesStepId,
async (data: StepInput, { container }) => {
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
if (isDefined(data.create)) {
return unsetForCreate(
data.create,
customerModuleService,
"is_default_shipping"
)
}
if (isDefined(data.update)) {
return unsetForUpdate(
data.update,
customerModuleService,
"is_default_shipping"
)
}
throw new Error("Invalid step input")
},
async (addressesToSet, { container }) => {
if (!addressesToSet?.length) {
return
}
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await customerModuleService.updateAddresses(
{ id: addressesToSet },
{ is_default_shipping: true }
)
}
)
@@ -0,0 +1,54 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
UpdateCustomerAddressDTO,
FilterableCustomerAddressProps,
ICustomerModuleService,
} from "@medusajs/types"
import {
getSelectsAndRelationsFromObjectArray,
promiseAll,
} from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
type UpdateCustomerAddresseStepInput = {
selector: FilterableCustomerAddressProps
update: UpdateCustomerAddressDTO
}
export const updateCustomerAddresseStepId = "update-customer-addresses"
export const updateCustomerAddressesStep = createStep(
updateCustomerAddresseStepId,
async (data: UpdateCustomerAddresseStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
data.update,
])
const prevCustomers = await service.listAddresses(data.selector, {
select: selects,
relations,
})
const customerAddresses = await service.updateAddresses(
data.selector,
data.update
)
return new StepResponse(customerAddresses, prevCustomers)
},
async (prevCustomerAddresses, { container }) => {
if (!prevCustomerAddresses) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await promiseAll(
prevCustomerAddresses.map((c) => service.updateAddresses(c.id, { ...c }))
)
}
)
@@ -0,0 +1,59 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
FilterableCustomerProps,
ICustomerModuleService,
CustomerUpdatableFields,
} from "@medusajs/types"
import {
getSelectsAndRelationsFromObjectArray,
promiseAll,
} from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
type UpdateCustomersStepInput = {
selector: FilterableCustomerProps
update: CustomerUpdatableFields
}
export const updateCustomersStepId = "update-customer"
export const updateCustomersStep = createStep(
updateCustomersStepId,
async (data: UpdateCustomersStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
data.update,
])
const prevCustomers = await service.list(data.selector, {
select: selects,
relations,
})
const customers = await service.update(data.selector, data.update)
return new StepResponse(customers, prevCustomers)
},
async (prevCustomers, { container }) => {
if (!prevCustomers?.length) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await promiseAll(
prevCustomers.map((c) =>
service.update(c.id, {
first_name: c.first_name,
last_name: c.last_name,
email: c.email,
phone: c.phone,
metadata: c.metadata,
})
)
)
}
)
@@ -0,0 +1,2 @@
export * from "./unset-address-for-create"
export * from "./unset-address-for-update"
@@ -0,0 +1,33 @@
import {
CreateCustomerAddressDTO,
ICustomerModuleService,
} from "@medusajs/types"
import { StepResponse } from "@medusajs/workflows-sdk"
export const unsetForCreate = async (
data: CreateCustomerAddressDTO[],
customerService: ICustomerModuleService,
field: "is_default_billing" | "is_default_shipping"
) => {
const customerIds = data.reduce<string[]>((acc, curr) => {
if (curr[field]) {
acc.push(curr.customer_id)
}
return acc
}, [])
const customerDefaultAddresses = await customerService.listAddresses({
customer_id: customerIds,
[field]: true,
})
await customerService.updateAddresses(
{ customer_id: customerIds, [field]: true },
{ [field]: false }
)
return new StepResponse(
void 0,
customerDefaultAddresses.map((address) => address.id)
)
}
@@ -0,0 +1,40 @@
import {
UpdateCustomerAddressDTO,
FilterableCustomerAddressProps,
ICustomerModuleService,
} from "@medusajs/types"
import { StepResponse } from "@medusajs/workflows-sdk"
export const unsetForUpdate = async (
data: {
selector: FilterableCustomerAddressProps
update: UpdateCustomerAddressDTO
},
customerService: ICustomerModuleService,
field: "is_default_billing" | "is_default_shipping"
) => {
if (!data.update[field]) {
return new StepResponse(void 0)
}
const affectedCustomers = await customerService.listAddresses(data.selector, {
select: ["id", "customer_id"],
})
const customerIds = affectedCustomers.map((address) => address.customer_id)
const customerDefaultAddresses = await customerService.listAddresses({
customer_id: customerIds,
[field]: true,
})
await customerService.updateAddresses(
{ customer_id: customerIds, [field]: true },
{ [field]: false }
)
return new StepResponse(
void 0,
customerDefaultAddresses.map((address) => address.id)
)
}
@@ -0,0 +1,31 @@
import { CreateCustomerAddressDTO, CustomerAddressDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import {
createCustomerAddressesStep,
maybeUnsetDefaultBillingAddressesStep,
maybeUnsetDefaultShippingAddressesStep,
} from "../steps"
type WorkflowInput = { addresses: CreateCustomerAddressDTO[] }
export const createCustomerAddressesWorkflowId = "create-customer-addresses"
export const createCustomerAddressesWorkflow = createWorkflow(
createCustomerAddressesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerAddressDTO[]> => {
const unsetInput = transform(input, (data) => ({
create: data.addresses,
}))
parallelize(
maybeUnsetDefaultShippingAddressesStep(unsetInput),
maybeUnsetDefaultBillingAddressesStep(unsetInput)
)
return createCustomerAddressesStep(input.addresses)
}
)
@@ -0,0 +1,31 @@
import { CreateCustomerDTO, CustomerDTO } from "@medusajs/types"
import { createWorkflow, WorkflowData } from "@medusajs/workflows-sdk"
import { createCustomersStep } from "../steps"
import { setAuthAppMetadataStep } from "../../auth/steps"
import { transform } from "@medusajs/workflows-sdk"
type WorkflowInput = {
authUserId: string
customersData: CreateCustomerDTO
}
export const createCustomerAccountWorkflowId = "create-customer-account"
export const createCustomerAccountWorkflow = createWorkflow(
createCustomerAccountWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerDTO> => {
const customers = createCustomersStep([input.customersData])
const customer = transform(
customers,
(customers: CustomerDTO[]) => customers[0]
)
setAuthAppMetadataStep({
authUserId: input.authUserId,
key: "customer_id",
value: customer.id,
})
return customer
}
)
@@ -0,0 +1,13 @@
import { CustomerDTO, CreateCustomerDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createCustomersStep } from "../steps"
type WorkflowInput = { customersData: CreateCustomerDTO[] }
export const createCustomersWorkflowId = "create-customers"
export const createCustomersWorkflow = createWorkflow(
createCustomersWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerDTO[]> => {
return createCustomersStep(input.customersData)
}
)
@@ -0,0 +1,12 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteCustomerAddressesStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const deleteCustomerAddressesWorkflowId = "delete-customer-addresses"
export const deleteCustomerAddressesWorkflow = createWorkflow(
deleteCustomerAddressesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
return deleteCustomerAddressesStep(input.ids)
}
)
@@ -0,0 +1,12 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteCustomersStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const deleteCustomersWorkflowId = "delete-customers"
export const deleteCustomersWorkflow = createWorkflow(
deleteCustomersWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
return deleteCustomersStep(input.ids)
}
)
@@ -0,0 +1,7 @@
export * from "./create-customers"
export * from "./update-customers"
export * from "./delete-customers"
export * from "./create-customer-account"
export * from "./create-addresses"
export * from "./update-addresses"
export * from "./delete-addresses"
@@ -0,0 +1,38 @@
import {
FilterableCustomerAddressProps,
CustomerAddressDTO,
UpdateCustomerAddressDTO,
} from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import {
maybeUnsetDefaultBillingAddressesStep,
maybeUnsetDefaultShippingAddressesStep,
updateCustomerAddressesStep,
} from "../steps"
type WorkflowInput = {
selector: FilterableCustomerAddressProps
update: UpdateCustomerAddressDTO
}
export const updateCustomerAddressesWorkflowId = "update-customer-addresses"
export const updateCustomerAddressesWorkflow = createWorkflow(
updateCustomerAddressesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerAddressDTO[]> => {
const unsetInput = transform(input, (data) => ({
update: data,
}))
parallelize(
maybeUnsetDefaultShippingAddressesStep(unsetInput),
maybeUnsetDefaultBillingAddressesStep(unsetInput)
)
return updateCustomerAddressesStep(input)
}
)
@@ -0,0 +1,22 @@
import {
CustomerDTO,
CustomerUpdatableFields,
FilterableCustomerProps,
} from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateCustomersStep } from "../steps"
type UpdateCustomersStepInput = {
selector: FilterableCustomerProps
update: CustomerUpdatableFields
}
type WorkflowInput = UpdateCustomersStepInput
export const updateCustomersWorkflowId = "update-customers"
export const updateCustomersWorkflow = createWorkflow(
updateCustomersWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerDTO[]> => {
return updateCustomersStep(input)
}
)
@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"
@@ -0,0 +1,49 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreateStoreDTO, IStoreModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createStoresWorkflow } from "../../store"
type CreateDefaultStoreStepInput = {
store: CreateStoreDTO
}
export const createDefaultStoreStepId = "create-default-store"
export const createDefaultStoreStep = createStep(
createDefaultStoreStepId,
async (data: CreateDefaultStoreStepInput, { container }) => {
const storeService = container.resolve(ModuleRegistrationName.STORE)
let shouldDelete = false
let [store] = await storeService.list({}, { take: 1 })
if (!store) {
store = await createStoresWorkflow(container).run({
input: {
stores: [
{
// TODO: Revisit for a more sophisticated approach
...data.store,
supported_currency_codes: ["usd"],
default_currency_code: "usd",
},
],
},
})
shouldDelete = true
}
return new StepResponse(store, { storeId: store.id, shouldDelete })
},
async (data, { container }) => {
if (!data || !data.shouldDelete) {
return
}
const service = container.resolve<IStoreModuleService>(
ModuleRegistrationName.STORE
)
await service.delete(data.storeId)
}
)
@@ -0,0 +1 @@
export * from "./create-default-store"
@@ -0,0 +1,23 @@
import { createWorkflow } from "@medusajs/workflows-sdk"
import { createDefaultSalesChannelStep } from "../../sales-channel"
import { createDefaultStoreStep } from "../steps/create-default-store"
export const createDefaultsWorkflowID = "create-defaults"
export const createDefaultsWorkflow = createWorkflow(
createDefaultsWorkflowID,
(input) => {
const salesChannel = createDefaultSalesChannelStep({
data: {
name: "Default Sales Channel",
description: "Created by Medusa",
},
})
const store = createDefaultStoreStep({
store: {
default_sales_channel_id: salesChannel.id,
},
})
return store
}
)
@@ -0,0 +1 @@
export * from "./create-defaults"
@@ -0,0 +1,3 @@
export * from "./steps"
export * from "./workflows"
@@ -0,0 +1,31 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreateShippingMethodDTO, ICartModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
shipping_methods: CreateShippingMethodDTO[]
}
export const addShippingMethodToCartStepId = "add-shipping-method-to-cart-step"
export const addShippingMethodToCartStep = createStep(
addShippingMethodToCartStepId,
async (data: StepInput, { container }) => {
const cartService = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const methods = await cartService.addShippingMethods(data.shipping_methods)
return new StepResponse(methods, methods)
},
async (methods, { container }) => {
const cartService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
if (!methods?.length) {
return
}
await cartService.deleteShippingMethods(methods.map((m) => m.id))
}
)
@@ -0,0 +1,31 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreateLineItemForCartDTO, ICartModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
items: CreateLineItemForCartDTO[]
}
export const addToCartStepId = "add-to-cart-step"
export const addToCartStep = createStep(
addToCartStepId,
async (data: StepInput, { container }) => {
const cartService = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const items = await cartService.addLineItems(data.items)
return new StepResponse(items, items)
},
async (createdLineItems, { container }) => {
const cartService: ICartModuleService = container.resolve(
ModuleRegistrationName.CART
)
if (!createdLineItems?.length) {
return
}
await cartService.deleteLineItems(createdLineItems.map((c) => c.id))
}
)
@@ -0,0 +1,47 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IInventoryService } from "@medusajs/types"
import { promiseAll } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
interface StepInput {
items: {
inventory_item_id: string
required_quantity: number
quantity: number
location_ids: string[]
}[]
}
export const confirmInventoryStepId = "confirm-inventory-step"
export const confirmInventoryStep = createStep(
confirmInventoryStepId,
async (data: StepInput, { container }) => {
const inventoryService = container.resolve<IInventoryService>(
ModuleRegistrationName.INVENTORY
)
// TODO: Should be bulk
const promises = data.items.map(async (item) => {
const itemQuantity = item.required_quantity * item.quantity
return await inventoryService.confirmInventory(
item.inventory_item_id,
item.location_ids,
itemQuantity
)
})
const inventoryCoverage = await promiseAll(promises)
if (inventoryCoverage.some((hasCoverage) => !hasCoverage)) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Some variant does not have the required inventory`,
MedusaError.Codes.INSUFFICIENT_INVENTORY
)
}
return new StepResponse(null)
}
)
@@ -0,0 +1,31 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreateCartDTO, ICartModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const createCartsStepId = "create-carts"
export const createCartsStep = createStep(
createCartsStepId,
async (data: CreateCartDTO[], { container }) => {
const service = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const createdCarts = await service.create(data)
return new StepResponse(
createdCarts,
createdCarts.map((cart) => cart.id)
)
},
async (createdCartsIds, { container }) => {
if (!createdCartsIds?.length) {
return
}
const service = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
await service.delete(createdCartsIds)
}
)
@@ -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,57 @@
import { CartWorkflowDTO } from "@medusajs/types"
import { OrderStatus } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { createOrdersWorkflow } from "../../../order/workflows/create-orders"
interface StepInput {
cart: CartWorkflowDTO
}
export const createOrderFromCartStepId = "create-order-from-cart"
export const createOrderFromCartStep = createStep(
createOrderFromCartStepId,
async (input: StepInput, { container }) => {
const { cart } = input
const itemAdjustments = (cart.items || [])
?.map((item) => item.adjustments || [])
.flat(1)
const shippingAdjustments = (cart.shipping_methods || [])
?.map((sm) => sm.adjustments || [])
.flat(1)
const promoCodes = [...itemAdjustments, ...shippingAdjustments]
.map((adjustment) => adjustment.code)
.filter((code) => Boolean) as string[]
const { transaction, result } = await createOrdersWorkflow(container).run({
input: {
region_id: cart.region?.id,
customer_id: cart.customer?.id,
sales_channel_id: cart.sales_channel_id,
status: OrderStatus.PENDING,
email: cart.email,
currency_code: cart.currency_code,
shipping_address: cart.shipping_address,
billing_address: cart.billing_address,
// TODO: This should be handle correctly
no_notification: false,
items: cart.items,
shipping_methods: cart.shipping_methods,
metadata: cart.metadata,
promo_codes: promoCodes,
},
})
return new StepResponse(result, { transaction })
},
async (flow, { container }) => {
if (!flow) {
return
}
await createOrdersWorkflow(container).cancel({
transaction: flow.transaction,
})
}
)
@@ -0,0 +1,38 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { BigNumberInput, IPaymentModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = {
region_id: string
currency_code: string
amount: BigNumberInput
metadata?: Record<string, unknown>
}
export const createPaymentCollectionsStepId = "create-payment-collections"
export const createPaymentCollectionsStep = createStep(
createPaymentCollectionsStepId,
async (data: StepInput[], { container }) => {
const service = container.resolve<IPaymentModuleService>(
ModuleRegistrationName.PAYMENT
)
const created = await service.createPaymentCollections(data)
return new StepResponse(
created,
created.map((collection) => collection.id)
)
},
async (createdIds, { container }) => {
if (!createdIds?.length) {
return
}
const service = container.resolve<IPaymentModuleService>(
ModuleRegistrationName.PAYMENT
)
await service.deletePaymentCollections(createdIds)
}
)
@@ -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,31 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IRegionModuleService } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const findOneOrAnyRegionStepId = "find-one-or-any-region"
export const findOneOrAnyRegionStep = createStep(
findOneOrAnyRegionStepId,
async (data: { regionId?: string }, { container }) => {
const service = container.resolve<IRegionModuleService>(
ModuleRegistrationName.REGION
)
if (!data.regionId) {
const regions = await service.list({})
if (!regions?.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"No regions found"
)
}
return new StepResponse(regions[0])
}
const region = await service.retrieve(data.regionId)
return new StepResponse(region)
}
)
@@ -0,0 +1,93 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CustomerDTO, ICustomerModuleService } from "@medusajs/types"
import { validateEmail } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
customerId?: string | null
email?: string | null
}
interface StepOutput {
customer?: CustomerDTO | null
email?: string | null
}
interface StepCompensateInput {
customer?: CustomerDTO
customerWasCreated: boolean
}
export const findOrCreateCustomerStepId = "find-or-create-customer"
export const findOrCreateCustomerStep = createStep(
findOrCreateCustomerStepId,
async (data: StepInput, { container }) => {
if (
typeof data.customerId === undefined &&
typeof data.email === undefined
) {
return new StepResponse(
{
customer: undefined,
email: undefined,
},
{ customerWasCreated: false }
)
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const customerData: StepOutput = {
customer: null,
email: null,
}
let customerWasCreated = false
if (data.customerId) {
const customer = await service.retrieve(data.customerId)
customerData.customer = customer
customerData.email = customer.email
return new StepResponse(customerData, {
customerWasCreated,
})
}
if (data.email) {
const validatedEmail = validateEmail(data.email)
let [customer] = await service.list({
email: validatedEmail,
has_account: false,
})
if (!customer) {
customer = await service.create({ email: validatedEmail })
customerWasCreated = true
}
customerData.customer = customer
customerData.email = customer.email
}
return new StepResponse(customerData, {
customer: customerData.customer,
customerWasCreated,
})
},
async (compData, { container }) => {
const { customer, customerWasCreated } = compData as StepCompensateInput
if (!customerWasCreated || !customer?.id) {
return
}
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await service.delete(customer.id)
}
)
@@ -0,0 +1,38 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ISalesChannelModuleService, SalesChannelDTO } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
salesChannelId?: string | null
}
export const findSalesChannelStepId = "find-sales-channel"
export const findSalesChannelStep = createStep(
findSalesChannelStepId,
async (data: StepInput, { container }) => {
const salesChannelService = container.resolve<ISalesChannelModuleService>(
ModuleRegistrationName.SALES_CHANNEL
)
if (data.salesChannelId === null) {
return new StepResponse(null)
}
let salesChannel: SalesChannelDTO | undefined
if (data.salesChannelId) {
salesChannel = await salesChannelService.retrieve(data.salesChannelId)
} else {
// TODO: Find default sales channel from store
}
if (salesChannel?.is_disabled) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Unable to assign cart to disabled Sales Channel: ${salesChannel.name}`
)
}
return new StepResponse(salesChannel)
}
)
@@ -0,0 +1,39 @@
import { LinkModuleUtils, ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CartDTO, IPromotionModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
cart: CartDTO
}
export const getActionsToComputeFromPromotionsStepId =
"get-actions-to-compute-from-promotions"
export const getActionsToComputeFromPromotionsStep = createStep(
getActionsToComputeFromPromotionsStepId,
async (data: StepInput, { container }) => {
const { cart } = data
const remoteQuery = container.resolve(LinkModuleUtils.REMOTE_QUERY)
const promotionService = container.resolve<IPromotionModuleService>(
ModuleRegistrationName.PROMOTION
)
const existingCartPromotionLinks = await remoteQuery({
cart_promotion: {
__args: { cart_id: [cart.id] },
fields: ["id", "cart_id", "promotion_id", "deleted_at"],
},
})
const existingPromotions = await promotionService.list(
{ id: existingCartPromotionLinks.map((l) => l.promotion_id) },
{ take: null, select: ["code"] }
)
const actionsToCompute = await promotionService.computeActions(
existingPromotions.map((p) => p.code!),
cart as any
)
return new StepResponse(actionsToCompute)
}
)
@@ -0,0 +1,136 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CartLineItemDTO,
CartShippingMethodDTO,
CartWorkflowDTO,
ITaxModuleService,
ItemTaxLineDTO,
ShippingTaxLineDTO,
TaxCalculationContext,
TaxableItemDTO,
TaxableShippingDTO,
} from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
cart: CartWorkflowDTO
items: CartLineItemDTO[]
shipping_methods: CartShippingMethodDTO[]
force_tax_calculation?: boolean
}
function normalizeTaxModuleContext(
cart: CartWorkflowDTO,
forceTaxCalculation: boolean
): TaxCalculationContext | null {
const address = cart.shipping_address
const shouldCalculateTax = forceTaxCalculation || cart.region?.automatic_taxes
if (!shouldCalculateTax) {
return null
}
if (forceTaxCalculation && !address?.country_code) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`country code is required to calculate taxes`
)
}
if (!address?.country_code) {
return null
}
const customer = cart.customer
? {
id: cart.customer.id,
email: cart.customer.email,
customer_groups: cart.customer.groups?.map((g) => g.id) || [],
}
: undefined
return {
address: {
country_code: address.country_code,
province_code: address.province,
address_1: address.address_1,
address_2: address.address_2,
city: address.city,
postal_code: address.postal_code,
},
customer,
// TODO: Should probably come in from order module, defaulting to false
is_return: false,
}
}
function normalizeLineItemsForTax(
cart: CartWorkflowDTO,
items: CartLineItemDTO[]
): TaxableItemDTO[] {
return items.map((item) => ({
id: item.id,
product_id: item.product_id!,
product_name: item.variant_title,
product_sku: item.variant_sku,
product_type: item.product_type,
product_type_id: item.product_type,
quantity: item.quantity,
unit_price: item.unit_price,
currency_code: cart.currency_code,
}))
}
function normalizeLineItemsForShipping(
cart: CartWorkflowDTO,
shippingMethods: CartShippingMethodDTO[]
): TaxableShippingDTO[] {
return shippingMethods.map((shippingMethod) => ({
id: shippingMethod.id,
shipping_option_id: shippingMethod.shipping_option_id!,
unit_price: shippingMethod.amount,
currency_code: cart.currency_code,
}))
}
export const getItemTaxLinesStepId = "get-item-tax-lines"
export const getItemTaxLinesStep = createStep(
getItemTaxLinesStepId,
async (data: StepInput, { container }) => {
const {
cart,
items,
shipping_methods: shippingMethods,
force_tax_calculation: forceTaxCalculation = false,
} = data
const taxService = container.resolve<ITaxModuleService>(
ModuleRegistrationName.TAX
)
const taxContext = normalizeTaxModuleContext(cart, forceTaxCalculation)
if (!taxContext) {
return new StepResponse({
lineItemTaxLines: [],
shippingMethodsTaxLines: [],
})
}
const lineItemTaxLines = (await taxService.getTaxLines(
normalizeLineItemsForTax(cart, items),
taxContext
)) as ItemTaxLineDTO[]
const shippingMethodsTaxLines = (await taxService.getTaxLines(
normalizeLineItemsForShipping(cart, shippingMethods),
taxContext
)) as ShippingTaxLineDTO[]
return new StepResponse({
lineItemTaxLines,
shippingMethodsTaxLines,
})
}
)
@@ -0,0 +1,82 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
variantIds: string[]
context?: Record<string, unknown>
}
export const getVariantPriceSetsStepId = "get-variant-price-sets"
export const getVariantPriceSetsStep = createStep(
getVariantPriceSetsStepId,
async (data: StepInput, { container }) => {
if (!data.variantIds.length) {
return new StepResponse({})
}
const pricingModuleService = container.resolve<IPricingModuleService>(
ModuleRegistrationName.PRICING
)
const remoteQuery = container.resolve("remoteQuery")
const variantPriceSets = await remoteQuery(
{
variant: {
fields: ["id"],
price_set: {
fields: ["id"],
},
},
},
{
variant: {
id: data.variantIds,
},
}
)
const notFound: string[] = []
const priceSetIds: string[] = []
variantPriceSets.forEach((v) => {
if (v.price_set?.id) {
priceSetIds.push(v.price_set.id)
} else {
notFound.push(v.id)
}
})
if (notFound.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variants with IDs ${notFound.join(", ")} do not have a price`
)
}
const calculatedPriceSets = await pricingModuleService.calculatePrices(
{ id: priceSetIds },
{ context: data.context as Record<string, string | number> }
)
const idToPriceSet = new Map<string, Record<string, any>>(
calculatedPriceSets.map((p) => [p.id, p])
)
const variantToCalculatedPriceSets = variantPriceSets.reduce(
(acc, { id, price_set }) => {
const calculatedPriceSet = idToPriceSet.get(price_set?.id)
if (calculatedPriceSet) {
acc[id] = calculatedPriceSet
}
return acc
},
{}
)
return new StepResponse(variantToCalculatedPriceSets)
}
)
@@ -0,0 +1,30 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
FilterableProductVariantProps,
FindConfig,
IProductModuleService,
ProductVariantDTO,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
filter?: FilterableProductVariantProps
config?: FindConfig<ProductVariantDTO>
}
export const getVariantsStepId = "get-variants"
export const getVariantsStep = createStep(
getVariantsStepId,
async (data: StepInput, { container }) => {
const productModuleService = container.resolve<IProductModuleService>(
ModuleRegistrationName.PRODUCT
)
const variants = await productModuleService.listVariants(
data.filter,
data.config
)
return new StepResponse(variants)
}
)
@@ -0,0 +1,25 @@
export * from "./add-shipping-method-to-cart"
export * from "./add-to-cart"
export * from "./confirm-inventory"
export * from "./create-carts"
export * from "./create-line-item-adjustments"
export * from "./create-order-from-cart"
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 "./get-item-tax-lines"
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 "./retrieve-cart"
export * from "./retrieve-cart-with-links"
export * from "./set-tax-lines-for-items"
export * from "./update-cart-promotions"
export * from "./update-carts"
export * from "./validate-cart-shipping-options"
export * from "./validate-variants-existence"
@@ -0,0 +1,42 @@
import { Modules } from "@medusajs/modules-sdk"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type StepInput = {
links: {
cart_id: string
payment_collection_id: string
}[]
}
export const linkCartAndPaymentCollectionsStepId =
"link-cart-payment-collection"
export const linkCartAndPaymentCollectionsStep = createStep(
linkCartAndPaymentCollectionsStepId,
async (data: StepInput, { container }) => {
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
const links = data.links.map((d) => ({
[Modules.CART]: { cart_id: d.cart_id },
[Modules.PAYMENT]: { payment_collection_id: d.payment_collection_id },
}))
await remoteLink.create(links)
return new StepResponse(void 0, data)
},
async (data, { container }) => {
if (!data) {
return
}
const remoteLink = container.resolve(ContainerRegistrationKeys.REMOTE_LINK)
const links = data.links.map((d) => ({
[Modules.CART]: { cart_id: d.cart_id },
[Modules.PAYMENT]: { payment_collection_id: d.payment_collection_id },
}))
await remoteLink.dismiss(links)
}
)
@@ -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,30 @@
import { PromotionActions } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { updateCartPromotionsWorkflow } from "../workflows"
interface StepInput {
id: string
promo_codes?: string[]
action?:
| PromotionActions.ADD
| PromotionActions.REMOVE
| PromotionActions.REPLACE
}
export const refreshCartPromotionsStepId = "refresh-cart-promotions"
export const refreshCartPromotionsStep = createStep(
refreshCartPromotionsStepId,
async (data: StepInput, { container }) => {
const { promo_codes = [], id, action = PromotionActions.ADD } = data
await updateCartPromotionsWorkflow(container).run({
input: {
action,
promoCodes: promo_codes,
cartId: id,
},
})
return new StepResponse(null)
}
)
@@ -0,0 +1,75 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CartDTO,
ICartModuleService,
IFulfillmentModuleService,
} from "@medusajs/types"
import { arrayDifference } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
interface StepInput {
cart: CartDTO
}
export const refreshCartShippingMethodsStepId = "refresh-cart-shipping-methods"
export const refreshCartShippingMethodsStep = createStep(
refreshCartShippingMethodsStepId,
async (data: StepInput, { container }) => {
const { cart } = data
const { shipping_methods: shippingMethods = [] } = cart
if (!shippingMethods?.length) {
return new StepResponse(void 0, [])
}
const fulfillmentModule = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const cartModule = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const shippingOptionIds: string[] = shippingMethods.map(
(sm) => sm.shipping_option_id!
)
const validShippingOptions =
await fulfillmentModule.listShippingOptionsForContext(
{
id: shippingOptionIds,
context: { ...cart },
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>(
ModuleRegistrationName.CART
)
await cartModule.restoreShippingMethods(shippingMethodsToRestore)
}
}
)
@@ -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,32 @@
import { LinkModuleUtils, Modules } from "@medusajs/modules-sdk"
import { CartWorkflowDTO } from "@medusajs/types"
import { isObject, remoteQueryObjectFromString } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
cart_or_cart_id: string | CartWorkflowDTO
fields: string[]
}
export const retrieveCartWithLinksStepId = "retrieve-cart-with-links"
export const retrieveCartWithLinksStep = createStep(
retrieveCartWithLinksStepId,
async (data: StepInput, { container }) => {
const { cart_or_cart_id: cartOrCartId, fields } = data
if (isObject(cartOrCartId)) {
return new StepResponse(cartOrCartId)
}
const id = cartOrCartId
const remoteQuery = container.resolve(LinkModuleUtils.REMOTE_QUERY)
const query = remoteQueryObjectFromString({
entryPoint: Modules.CART,
fields,
})
const [cart] = await remoteQuery(query, { cart: { id } })
return new StepResponse(cart)
}
)
@@ -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 {
id: 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.id, 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)
}
)
@@ -0,0 +1,128 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CartWorkflowDTO,
CreateLineItemTaxLineDTO,
CreateShippingMethodTaxLineDTO,
ICartModuleService,
ItemTaxLineDTO,
ShippingTaxLineDTO,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
cart: CartWorkflowDTO
item_tax_lines: ItemTaxLineDTO[]
shipping_tax_lines: ShippingTaxLineDTO[]
}
export const setTaxLinesForItemsStepId = "set-tax-lines-for-items"
export const setTaxLinesForItemsStep = createStep(
setTaxLinesForItemsStepId,
async (data: StepInput, { container }) => {
const { cart, item_tax_lines, shipping_tax_lines } = data
const cartService = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const getShippingTaxLinesPromise =
await cartService.listShippingMethodTaxLines({
shipping_method_id: shipping_tax_lines.map((t) => t.shipping_line_id),
})
const getItemTaxLinesPromise = await cartService.listLineItemTaxLines({
item_id: item_tax_lines.map((t) => t.line_item_id),
})
const itemsTaxLinesData = normalizeItemTaxLinesForCart(item_tax_lines)
const setItemTaxLinesPromise = itemsTaxLinesData.length
? cartService.setLineItemTaxLines(cart.id, itemsTaxLinesData)
: 0
const shippingTaxLinesData =
normalizeShippingTaxLinesForCart(shipping_tax_lines)
const setShippingTaxLinesPromise = shippingTaxLinesData.length
? await cartService.setShippingMethodTaxLines(
cart.id,
shippingTaxLinesData
)
: 0
const [existingShippingMethodTaxLines, existingLineItemTaxLines] =
await Promise.all([
getShippingTaxLinesPromise,
getItemTaxLinesPromise,
setItemTaxLinesPromise,
setShippingTaxLinesPromise,
])
return new StepResponse(null, {
cart,
existingLineItemTaxLines,
existingShippingMethodTaxLines,
})
},
async (revertData, { container }) => {
if (!revertData) {
return
}
const { cart, existingLineItemTaxLines, existingShippingMethodTaxLines } =
revertData
const cartService = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
if (existingLineItemTaxLines) {
await cartService.setLineItemTaxLines(
cart.id,
existingLineItemTaxLines.map((taxLine) => ({
description: taxLine.description,
tax_rate_id: taxLine.tax_rate_id,
code: taxLine.code,
rate: taxLine.rate,
provider_id: taxLine.provider_id,
item_id: taxLine.item_id,
}))
)
}
await cartService.setShippingMethodTaxLines(
cart.id,
existingShippingMethodTaxLines.map((taxLine) => ({
description: taxLine.description,
tax_rate_id: taxLine.tax_rate_id,
code: taxLine.code,
rate: taxLine.rate,
provider_id: taxLine.provider_id,
shipping_method_id: taxLine.shipping_method_id,
}))
)
}
)
function normalizeItemTaxLinesForCart(
taxLines: ItemTaxLineDTO[]
): CreateLineItemTaxLineDTO[] {
return taxLines.map((taxLine) => ({
description: taxLine.name,
tax_rate_id: taxLine.rate_id,
code: taxLine.code!,
rate: taxLine.rate!,
provider_id: taxLine.provider_id,
item_id: taxLine.line_item_id,
}))
}
function normalizeShippingTaxLinesForCart(
taxLines: ShippingTaxLineDTO[]
): CreateShippingMethodTaxLineDTO[] {
return taxLines.map((taxLine) => ({
description: taxLine.name,
tax_rate_id: taxLine.rate_id,
code: taxLine.code!,
rate: taxLine.rate!,
provider_id: taxLine.provider_id,
shipping_method_id: taxLine.shipping_line_id,
}))
}
@@ -0,0 +1,107 @@
import {
LinkModuleUtils,
ModuleRegistrationName,
Modules,
} from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { PromotionActions } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
id: string
promo_codes?: string[]
action?:
| PromotionActions.ADD
| PromotionActions.REMOVE
| PromotionActions.REPLACE
}
export const updateCartPromotionsStepId = "update-cart-promotions"
export const updateCartPromotionsStep = createStep(
updateCartPromotionsStepId,
async (data: StepInput, { container }) => {
const { promo_codes = [], id, action = PromotionActions.ADD } = data
const remoteLink = container.resolve(LinkModuleUtils.REMOTE_LINK)
const remoteQuery = container.resolve(LinkModuleUtils.REMOTE_QUERY)
const promotionService = container.resolve<IPromotionModuleService>(
ModuleRegistrationName.PROMOTION
)
const existingCartPromotionLinks = await remoteQuery({
cart_promotion: {
__args: { cart_id: [id] },
fields: ["cart_id", "promotion_id"],
},
})
const promotionLinkMap = new Map<string, any>(
existingCartPromotionLinks.map((link) => [link.promotion_id, link])
)
const promotions = await promotionService.list(
{ code: promo_codes },
{ select: ["id"] }
)
const linksToCreate: any[] = []
const linksToDismiss: any[] = []
for (const promotion of promotions) {
const linkObject = {
[Modules.CART]: { cart_id: id },
[Modules.PROMOTION]: { promotion_id: promotion.id },
}
if ([PromotionActions.ADD, PromotionActions.REPLACE].includes(action)) {
linksToCreate.push(linkObject)
}
if (action === PromotionActions.REMOVE) {
const link = promotionLinkMap.get(promotion.id)
if (link) {
linksToDismiss.push(linkObject)
}
}
}
if (action === PromotionActions.REPLACE) {
for (const link of existingCartPromotionLinks) {
linksToDismiss.push({
[Modules.CART]: { cart_id: link.cart_id },
[Modules.PROMOTION]: { promotion_id: link.promotion_id },
})
}
}
const linksToDismissPromise = linksToDismiss.length
? remoteLink.dismiss(linksToDismiss)
: []
const linksToCreatePromise = linksToCreate.length
? remoteLink.create(linksToCreate)
: []
const [_, createdLinks] = await Promise.all([
linksToDismissPromise,
linksToCreatePromise,
])
return new StepResponse(null, {
createdLinkIds: createdLinks.map((link) => link.id),
dismissedLinks: linksToDismiss,
})
},
async (revertData, { container }) => {
const remoteLink = container.resolve(LinkModuleUtils.REMOTE_LINK)
if (revertData?.dismissedLinks?.length) {
await remoteLink.create(revertData.dismissedLinks)
}
if (revertData?.createdLinkIds?.length) {
await remoteLink.delete(revertData.createdLinkIds)
}
}
)
@@ -0,0 +1,53 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
ICartModuleService,
UpdateCartDTO,
UpdateCartWorkflowInputDTO,
} from "@medusajs/types"
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const updateCartsStepId = "update-carts"
export const updateCartsStep = createStep(
updateCartsStepId,
async (data: UpdateCartWorkflowInputDTO[], { container }) => {
const cartModule = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
const cartsBeforeUpdate = await cartModule.list(
{ id: data.map((d) => d.id) },
{ select: selects, relations }
)
const updatedCart = await cartModule.update(data)
return new StepResponse(updatedCart, cartsBeforeUpdate)
},
async (cartsBeforeUpdate, { container }) => {
if (!cartsBeforeUpdate) {
return
}
const cartModule = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const dataToUpdate: UpdateCartDTO[] = []
for (const cart of cartsBeforeUpdate) {
dataToUpdate.push({
id: cart.id,
region_id: cart.region_id,
customer_id: cart.customer_id,
sales_channel_id: cart.sales_channel_id,
email: cart.email,
currency_code: cart.currency_code,
metadata: cart.metadata,
})
}
return await cartModule.update(dataToUpdate)
}
)
@@ -0,0 +1,35 @@
import {
CartLineItemDTO,
CartShippingMethodDTO,
CartWorkflowDTO,
} from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { updateTaxLinesWorkflow } from "../workflows"
interface StepInput {
cart_or_cart_id: CartWorkflowDTO | string
items?: CartLineItemDTO[]
shipping_methods?: CartShippingMethodDTO[]
force_tax_calculation?: boolean
}
export const updateTaxLinesStepId = "update-tax-lines-step"
export const updateTaxLinesStep = createStep(
updateTaxLinesStepId,
async (input: StepInput, { container }) => {
const { transaction } = await updateTaxLinesWorkflow(container).run({
input,
})
return new StepResponse(null, { transaction })
},
async (flow, { container }) => {
if (!flow) {
return
}
await updateTaxLinesWorkflow(container).cancel({
transaction: flow.transaction,
})
}
)
@@ -0,0 +1,53 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CartDTO, IFulfillmentModuleService } from "@medusajs/types"
import { arrayDifference, MedusaError } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
interface StepInput {
cart: CartDTO
option_ids: string[]
}
export const validateCartShippingOptionsStepId =
"validate-cart-shipping-options"
export const validateCartShippingOptionsStep = createStep(
validateCartShippingOptionsStepId,
async (data: StepInput, { container }) => {
const { option_ids: optionIds = [], cart } = data
if (!optionIds.length) {
return new StepResponse(void 0)
}
const fulfillmentModule = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const validShippingOptions =
await fulfillmentModule.listShippingOptionsForContext(
{
id: optionIds,
context: { ...cart },
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 invalidOptionIds = arrayDifference(optionIds, validShippingOptionIds)
if (invalidOptionIds.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Shipping Options are invalid for cart.`
)
}
return new StepResponse(void 0)
}
)
@@ -0,0 +1,38 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IProductModuleService } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
interface StepInput {
variantIds: string[]
}
export const validateVariantsExistStepId = "validate-variants-exist"
export const validateVariantsExistStep = createStep(
validateVariantsExistStepId,
async (data: StepInput, { container }) => {
const productModuleService = container.resolve<IProductModuleService>(
ModuleRegistrationName.PRODUCT
)
const variants = await productModuleService.listVariants(
{ id: data.variantIds },
{ select: ["id"] }
)
const variantIdToData = new Set(variants.map((v) => v.id))
const notFoundVariants = new Set(
[...data.variantIds].filter((x) => !variantIdToData.has(x))
)
if (notFoundVariants.size) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variants with IDs ${[...notFoundVariants].join(", ")} do not exist`
)
}
return new StepResponse(Array.from(variants.map((v) => v.id)))
}
)
@@ -0,0 +1,52 @@
export const cartFieldsForRefreshSteps = [
"region_id",
"currency_code",
"region.*",
"items.*",
"items.tax_lines.*",
"shipping_address.*",
"shipping_methods.*",
"shipping_methods.tax_lines*",
"customer.*",
"customer.groups.*",
]
export const completeCartFields = [
"id",
"currency_code",
"email",
"created_at",
"updated_at",
"total",
"subtotal",
"tax_total",
"discount_total",
"discount_tax_total",
"original_total",
"original_tax_total",
"item_total",
"item_subtotal",
"item_tax_total",
"sales_channel_id",
"original_item_total",
"original_item_subtotal",
"original_item_tax_total",
"shipping_total",
"shipping_subtotal",
"shipping_tax_total",
"original_shipping_tax_total",
"original_shipping_tax_subtotal",
"original_shipping_total",
"items.*",
"items.tax_lines.*",
"items.adjustments.*",
"customer.*",
"shipping_methods.*",
"shipping_methods.tax_lines.*",
"shipping_methods.adjustments.*",
"shipping_address.*",
"billing_address.*",
"region.*",
"payment_collection.*",
"payment_collection.payment_sessions.*",
]
@@ -0,0 +1,66 @@
import { BigNumberInput } from "@medusajs/types"
import { MedusaError } from "medusa-core-utils"
interface ConfirmInventoryPreparationInput {
product_variant_inventory_items: {
variant_id: string
inventory_item_id: string
required_quantity: number
}[]
items: { variant_id?: string; quantity: BigNumberInput }[]
variants: { id: string; manage_inventory?: boolean }[]
location_ids: string[]
}
interface ConfirmInventoryItem {
inventory_item_id: string
required_quantity: number
quantity: number
location_ids: string[]
}
export const prepareConfirmInventoryInput = ({
product_variant_inventory_items,
location_ids,
items,
variants,
}: ConfirmInventoryPreparationInput) => {
if (!product_variant_inventory_items.length) {
return []
}
const variantsMap = new Map<
string,
{ id: string; manage_inventory?: boolean }
>(variants.map((v) => [v.id, v]))
const itemsToConfirm: ConfirmInventoryItem[] = []
items.forEach((item) => {
const variant = variantsMap.get(item.variant_id!)
if (!variant?.manage_inventory) {
return
}
const variantInventoryItem = product_variant_inventory_items.find(
(i) => i.variant_id === item.variant_id
)
if (!variantInventoryItem) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variant ${item.variant_id} does not have any inventory items associated with it.`
)
}
itemsToConfirm.push({
inventory_item_id: variantInventoryItem.inventory_item_id,
required_quantity: variantInventoryItem.required_quantity,
quantity: item.quantity as number, // TODO: update type to BigNumberInput
location_ids: location_ids,
})
})
return itemsToConfirm
}
@@ -0,0 +1,90 @@
import {
BigNumberInput,
CreateOrderAdjustmentDTO,
CreateOrderLineItemTaxLineDTO,
ProductVariantDTO,
} from "@medusajs/types"
interface Input {
quantity: BigNumberInput
metadata?: Record<string, any>
unitPrice: BigNumberInput
variant: ProductVariantDTO
taxLines?: CreateOrderLineItemTaxLineDTO[]
adjustments?: CreateOrderAdjustmentDTO[]
cartId?: string
}
export function prepareLineItemData(data: Input) {
const {
variant,
unitPrice,
quantity,
metadata,
cartId,
taxLines,
adjustments,
} = data
if (!variant.product) {
throw new Error("Variant does not have a product")
}
const lineItem: any = {
quantity,
title: variant.title,
subtitle: variant.product.title,
thumbnail: variant.product.thumbnail,
product_id: variant.product.id,
product_title: variant.product.title,
product_description: variant.product.description,
product_subtitle: variant.product.subtitle,
product_type: variant.product.type?.[0].value ?? null,
product_collection: variant.product.collection?.[0]?.value ?? null,
product_handle: variant.product.handle,
variant_id: variant.id,
variant_sku: variant.sku,
variant_barcode: variant.barcode,
variant_title: variant.title,
unit_price: unitPrice,
metadata,
}
if (taxLines) {
lineItem.tax_lines = prepareTaxLinesData(taxLines)
}
if (adjustments) {
lineItem.adjustments = prepareAdjustmentsData(adjustments)
}
if (cartId) {
lineItem.cart_id = cartId
}
return lineItem
}
export function prepareTaxLinesData(data: CreateOrderLineItemTaxLineDTO[]) {
return data.map((d) => ({
description: d.description,
tax_rate_id: d.tax_rate_id,
code: d.code,
rate: d.rate,
provider_id: d.provider_id,
}))
}
export function prepareAdjustmentsData(data: CreateOrderAdjustmentDTO[]) {
return data.map((d) => ({
code: d.code,
amount: d.amount,
description: d.description,
promotion_id: d.promotion_id,
provider_id: d.promotion_id,
}))
}
@@ -0,0 +1,86 @@
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import {
addShippingMethodToCartStep,
validateCartShippingOptionsStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { cartFieldsForRefreshSteps } from "../utils/fields"
interface AddShippingMethodToCartWorkflowInput {
cart_id: string
options: {
id: string
data?: Record<string, unknown>
}[]
}
export const addShippingMethodToCartWorkflowId = "add-shipping-method-to-cart"
export const addShippingMethodToWorkflow = createWorkflow(
addShippingMethodToCartWorkflowId,
(
input: WorkflowData<AddShippingMethodToCartWorkflowInput>
): WorkflowData<void> => {
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart_id },
list: false,
})
const optionIds = transform({ input }, (data) => {
return (data.input.options ?? []).map((i) => i.id)
})
validateCartShippingOptionsStep({
option_ids: optionIds,
cart,
})
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_option",
fields: ["id", "name", "calculated_price.calculated_amount"],
variables: {
id: optionIds,
calculated_price: {
context: { currency_code: cart.currency_code },
},
},
}).config({ name: "fetch-shipping-option" })
const shippingMethodInput = transform(
{ input, shippingOptions },
(data) => {
const options = (data.input.options ?? []).map((option) => {
const shippingOption = data.shippingOptions.find(
(so) => so.id === option.id
)!
return {
shipping_option_id: shippingOption.id,
amount: shippingOption.calculated_price.calculated_amount,
data: option.data ?? {},
name: shippingOption.name,
cart_id: data.input.cart_id,
}
})
return options
}
)
const shippingMethods = addShippingMethodToCartStep({
shipping_methods: shippingMethodInput,
})
refreshCartPromotionsStep({ id: input.cart_id })
updateTaxLinesStep({
cart_or_cart_id: input.cart_id,
shipping_methods: shippingMethods,
})
}
)
@@ -0,0 +1,180 @@
import {
AddToCartWorkflowInputDTO,
CreateLineItemForCartDTO,
} from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import {
addToCartStep,
confirmInventoryStep,
refreshCartShippingMethodsStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { cartFieldsForRefreshSteps } from "../utils/fields"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
// TODO: The AddToCartWorkflow are missing the following steps:
// - Refresh/delete shipping methods (fulfillment module)
export const addToCartWorkflowId = "add-to-cart"
export const addToCartWorkflow = createWorkflow(
addToCartWorkflowId,
(input: WorkflowData<AddToCartWorkflowInputDTO>) => {
const variantIds = transform({ input }, (data) => {
return (data.input.items ?? []).map((i) => i.variant_id)
})
// TODO: This is on par with the context used in v1.*, but we can be more flexible.
const pricingContext = transform({ cart: input.cart }, (data) => {
return {
currency_code: data.cart.currency_code,
region_id: data.cart.region_id,
customer_id: data.cart.customer_id,
}
})
const variants = useRemoteQueryStep({
entry_point: "variants",
fields: [
"id",
"title",
"sku",
"barcode",
"manage_inventory",
"product.id",
"product.title",
"product.description",
"product.subtitle",
"product.thumbnail",
"product.type",
"product.collection",
"product.handle",
"calculated_price.calculated_amount",
"inventory_items.inventory_item_id",
"inventory_items.required_quantity",
"inventory_items.inventory.location_levels.stock_locations.id",
"inventory_items.inventory.location_levels.stock_locations.name",
"inventory_items.inventory.location_levels.stock_locations.sales_channels.id",
"inventory_items.inventory.location_levels.stock_locations.sales_channels.name",
],
variables: {
id: variantIds,
calculated_price: {
context: pricingContext,
},
},
throw_if_key_not_found: true,
})
const confirmInventoryInput = transform({ input, variants }, (data) => {
const managedVariants = data.variants.filter((v) => v.manage_inventory)
if (!managedVariants.length) {
return { items: [] }
}
const productVariantInventoryItems: any[] = []
const stockLocations = data.variants
.map((v) => v.inventory_items)
.flat()
.map((ii) => {
productVariantInventoryItems.push({
variant_id: ii.variant_id,
inventory_item_id: ii.inventory_item_id,
required_quantity: ii.required_quantity,
})
return ii.inventory.location_levels
})
.flat()
.map((ll) => ll.stock_locations)
.flat()
const salesChannelId = data.input.cart.sales_channel_id
if (salesChannelId) {
const salesChannels = stockLocations
.map((sl) => sl.sales_channels)
.flat()
.filter((sc) => sc.id === salesChannelId)
if (!salesChannels.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sales channel ${salesChannelId} is not associated with any stock locations.`
)
}
}
const priceNotFound: string[] = data.variants
.filter((v) => !v.calculated_price)
.map((v) => v.id)
if (priceNotFound.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variants with IDs ${priceNotFound.join(", ")} do not have a price`
)
}
const items = prepareConfirmInventoryInput({
product_variant_inventory_items: productVariantInventoryItems,
location_ids: stockLocations.map((l) => l.id),
items: data.input.items!,
variants: data.variants.map((v) => ({
id: v.id,
manage_inventory: v.manage_inventory,
})),
})
return { items }
})
confirmInventoryStep(confirmInventoryInput)
const lineItems = transform({ input, variants }, (data) => {
const items = (data.input.items ?? []).map((item) => {
const variant = data.variants.find((v) => v.id === item.variant_id)!
return prepareLineItemData({
variant: variant,
unitPrice: variant.calculated_price.calculated_amount,
quantity: item.quantity,
metadata: item?.metadata ?? {},
cartId: data.input.cart.id,
}) as CreateLineItemForCartDTO
})
return items
})
const items = addToCartStep({ items: lineItems })
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart.id },
list: false,
}).config({ name: "refetchcart" })
refreshCartShippingMethodsStep({ cart })
// TODO: since refreshCartShippingMethodsStep potentially removes cart shipping methods, we need the updated cart here
// for the following 2 steps as they act upon final cart shape
updateTaxLinesStep({ cart_or_cart_id: cart, items })
refreshCartPromotionsStep({ id: input.cart.id })
refreshPaymentCollectionForCartStep({ cart_id: input.cart.id })
return items
}
)
@@ -0,0 +1,43 @@
import { CompleteCartWorkflowInputDTO, OrderDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { createOrderFromCartStep } from "../steps"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { completeCartFields } from "../utils/fields"
/*
- [] Create Tax Lines
- [] Authorize Payment
- fail:
- [] Delete Tax lines
- [] Reserve Item from inventory (if enabled)
- fail:
- [] Delete reservations
- [] Cancel Payment
- [] Create order
*/
export const completeCartWorkflowId = "complete-cart"
export const completeCartWorkflow = createWorkflow(
completeCartWorkflowId,
(
input: WorkflowData<CompleteCartWorkflowInputDTO>
): WorkflowData<OrderDTO> => {
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: completeCartFields,
variables: { id: input.id },
list: false,
})
updateTaxLinesStep({ cart_or_cart_id: cart, force_tax_calculation: true })
const finalCart = useRemoteQueryStep({
entry_point: "cart",
fields: completeCartFields,
variables: { id: input.id },
list: false,
}).config({ name: "final-cart" })
return createOrderFromCartStep({ cart: finalCart })
}
)
@@ -0,0 +1,228 @@
import { CartDTO, CreateCartWorkflowInputDTO } from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import {
confirmInventoryStep,
createCartsStep,
findOneOrAnyRegionStep,
findOrCreateCustomerStep,
findSalesChannelStep,
getVariantPriceSetsStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
// TODO: The createCartWorkflow are missing the following steps:
// - Refresh/delete shipping methods (fulfillment module)
export const createCartWorkflowId = "create-cart"
export const createCartWorkflow = createWorkflow(
createCartWorkflowId,
(input: WorkflowData<CreateCartWorkflowInputDTO>): WorkflowData<CartDTO> => {
const variantIds = transform({ input }, (data) => {
return (data.input.items ?? []).map((i) => i.variant_id)
})
const [salesChannel, region, customerData] = parallelize(
findSalesChannelStep({
salesChannelId: input.sales_channel_id,
}),
findOneOrAnyRegionStep({
regionId: input.region_id,
}),
findOrCreateCustomerStep({
customerId: input.customer_id,
email: input.email,
})
)
// TODO: This is on par with the context used in v1.*, but we can be more flexible.
const pricingContext = transform(
{ input, region, customerData },
(data) => {
return {
currency_code: data.input.currency_code ?? data.region.currency_code,
region_id: data.region.id,
customer_id: data.customerData.customer?.id,
}
}
)
const variants = useRemoteQueryStep({
entry_point: "variants",
fields: [
"id",
"title",
"sku",
"manage_inventory",
"barcode",
"product.id",
"product.title",
"product.description",
"product.subtitle",
"product.thumbnail",
"product.type",
"product.collection",
"product.handle",
"calculated_price.calculated_amount",
"inventory_items.inventory_item_id",
"inventory_items.required_quantity",
"inventory_items.inventory.location_levels.stock_locations.id",
"inventory_items.inventory.location_levels.stock_locations.name",
"inventory_items.inventory.location_levels.stock_locations.sales_channels.id",
"inventory_items.inventory.location_levels.stock_locations.sales_channels.name",
],
variables: {
id: variantIds,
calculated_price: {
context: pricingContext,
},
},
throw_if_key_not_found: true,
})
const confirmInventoryInput = transform(
{ input, salesChannel, variants },
(data) => {
const managedVariants = data.variants.filter((v) => v.manage_inventory)
if (!managedVariants.length) {
return { items: [] }
}
const productVariantInventoryItems: any[] = []
const stockLocations = managedVariants
.map((v) => v.inventory_items)
.flat()
.map((ii) => {
productVariantInventoryItems.push({
variant_id: ii.variant_id,
inventory_item_id: ii.inventory_item_id,
required_quantity: ii.required_quantity,
})
return ii.inventory.location_levels
})
.flat()
.map((ll) => ll.stock_locations)
.flat()
const salesChannelId = data.salesChannel?.id
if (salesChannelId) {
const salesChannels = stockLocations
.map((sl) => sl.sales_channels)
.flat()
.filter((sc) => sc.id === salesChannelId)
if (!salesChannels.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Sales channel ${salesChannelId} is not associated with any stock locations.`
)
}
}
const priceNotFound: string[] = data.variants
.filter((v) => !v.calculated_price)
.map((v) => v.id)
if (priceNotFound.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variants with IDs ${priceNotFound.join(", ")} do not have a price`
)
}
const items = prepareConfirmInventoryInput({
product_variant_inventory_items: productVariantInventoryItems,
location_ids: stockLocations.map((l) => l.id),
items: data.input.items!,
variants: data.variants.map((v) => ({
id: v.id,
manage_inventory: v.manage_inventory,
})),
})
return { items }
}
)
confirmInventoryStep(confirmInventoryInput)
const priceSets = getVariantPriceSetsStep({
variantIds,
context: pricingContext,
})
const cartInput = transform(
{ input, region, customerData, salesChannel },
(data) => {
const data_ = {
...data.input,
currency_code: data.input.currency_code ?? data.region.currency_code,
region_id: data.region.id,
}
if (data.customerData.customer?.id) {
data_.customer_id = data.customerData.customer.id
data_.email = data.input?.email ?? data.customerData.customer.email
}
if (data.salesChannel?.id) {
data_.sales_channel_id = data.salesChannel.id
}
return data_
}
)
const lineItems = transform({ priceSets, input, variants }, (data) => {
const items = (data.input.items ?? []).map((item) => {
const variant = data.variants.find((v) => v.id === item.variant_id)!
return prepareLineItemData({
variant: variant,
unitPrice: data.priceSets[item.variant_id].calculated_amount,
quantity: item.quantity,
metadata: item?.metadata ?? {},
})
})
return items
})
const cartToCreate = transform({ lineItems, cartInput }, (data) => {
return {
...data.cartInput,
items: data.lineItems,
}
})
const carts = createCartsStep([cartToCreate])
const cart = transform({ carts }, (data) => data.carts?.[0])
refreshCartPromotionsStep({
id: cart.id,
promo_codes: input.promo_codes,
})
updateTaxLinesStep({ cart_or_cart_id: cart.id })
refreshPaymentCollectionForCartStep({
cart_id: cart.id,
})
return cart
}
)
@@ -0,0 +1,38 @@
import {
CartDTO,
CreatePaymentCollectionForCartWorkflowInputDTO,
} from "@medusajs/types"
import {
WorkflowData,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { retrieveCartStep } from "../steps"
import { createPaymentCollectionsStep } from "../steps/create-payment-collection"
import { linkCartAndPaymentCollectionsStep } from "../steps/link-cart-payment-collection"
export const createPaymentCollectionForCartWorkflowId =
"create-payment-collection-for-cart"
export const createPaymentCollectionForCartWorkflow = createWorkflow(
createPaymentCollectionForCartWorkflowId,
(
input: WorkflowData<CreatePaymentCollectionForCartWorkflowInputDTO>
): WorkflowData<CartDTO> => {
const created = createPaymentCollectionsStep([input])
const link = transform({ cartId: input.cart_id, created }, (data) => ({
links: [
{
cart_id: data.cartId,
payment_collection_id: data.created[0].id,
},
],
}))
linkCartAndPaymentCollectionsStep(link)
const cart = retrieveCartStep({ id: input.cart_id })
return cart
}
)
@@ -0,0 +1,11 @@
export * from "./add-shipping-method-to-cart"
export * from "./add-to-cart"
export * from "./complete-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"
export * from "./update-line-item-in-cart"
export * from "./update-tax-lines"
@@ -0,0 +1,103 @@
import { ListShippingOptionsForCartWorkflowInputDTO } from "@medusajs/types"
import { deepFlatMap, MedusaError } from "@medusajs/utils"
import {
createWorkflow,
transform,
WorkflowData,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
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",
"stock_locations.fulfillment_sets.name",
"stock_locations.fulfillment_sets.price_type",
"stock_locations.fulfillment_sets.service_zone_id",
"stock_locations.fulfillment_sets.shipping_profile_id",
"stock_locations.fulfillment_sets.provider_id",
"stock_locations.fulfillment_sets.data",
"stock_locations.fulfillment_sets.amount",
"stock_locations.fulfillment_sets.service_zones.shipping_options.id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.name",
"stock_locations.fulfillment_sets.service_zones.shipping_options.price_type",
"stock_locations.fulfillment_sets.service_zones.shipping_options.service_zone_id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.shipping_profile_id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.provider_id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.data",
"stock_locations.fulfillment_sets.service_zones.shipping_options.amount",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.label",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.description",
"stock_locations.fulfillment_sets.service_zones.shipping_options.type.code",
"stock_locations.fulfillment_sets.service_zones.shipping_options.provider.id",
"stock_locations.fulfillment_sets.service_zones.shipping_options.provider.is_enabled",
"stock_locations.fulfillment_sets.service_zones.shipping_options.calculated_price.calculated_amount",
],
variables: {
id: input.sales_channel_id,
"stock_locations.fulfillment_sets.service_zones.shipping_options": {
context: {
address: {
city: input.shipping_address?.city,
country_code: input.shipping_address?.country_code,
province_code: input.shipping_address?.province,
},
},
},
"stock_locations.fulfillment_sets.service_zones.shipping_options.calculated_price":
{
context: {
currency_code: input.currency_code,
},
},
},
})
const shippingOptionsWithPrice = transform(
{ options: scLocationFulfillmentSets },
(data) => {
const optionsMissingPrices: string[] = []
const options = deepFlatMap(
data.options,
"stock_locations.fulfillment_sets.service_zones.shipping_options.calculated_price",
({ shipping_options }) => {
const { calculated_price, ...options } = shipping_options ?? {}
if (!calculated_price) {
optionsMissingPrices.push(options.id)
}
return {
...options,
amount: calculated_price?.calculated_amount,
}
}
)
if (optionsMissingPrices.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Shipping options with IDs ${optionsMissingPrices.join(
", "
)} do not have a price`
)
}
return options
}
)
return shippingOptionsWithPrice
}
)
@@ -0,0 +1,70 @@
import {
StepResponse,
WorkflowData,
createStep,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import {
deletePaymentSessionStep,
updatePaymentCollectionStep,
} from "../../payment-collection"
type WorklowInput = {
cart_id: string
}
interface StepInput {
cart_id: string
}
// We export a step running the workflow too, so that we can use it as a subworkflow e.g. in the update cart workflows
export const refreshPaymentCollectionForCartStepId =
"refresh-payment-collection-for-cart"
export const refreshPaymentCollectionForCartStep = createStep(
refreshPaymentCollectionForCartStepId,
async (data: StepInput, { container }) => {
await refreshPaymentCollectionForCartWorkflow(container).run({
input: {
cart_id: data.cart_id,
},
})
return new StepResponse(null)
}
)
export const refreshPaymentCollectionForCartWorkflowId =
"refresh-payment-collection-for-cart"
export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
refreshPaymentCollectionForCartWorkflowId,
(input: WorkflowData<WorklowInput>): WorkflowData<void> => {
const carts = useRemoteQueryStep({
entry_point: "cart",
fields: [
"id",
"total",
"currency_code",
"payment_collection.id",
"payment_collection.payment_sessions.id",
],
variables: { id: input.cart_id },
throw_if_key_not_found: true,
})
const cart = transform({ carts }, (data) => data.carts[0])
deletePaymentSessionStep({
payment_session_id: cart.payment_collection.payment_sessions?.[0].id,
})
updatePaymentCollectionStep({
selector: { id: cart.payment_collection.id },
update: {
amount: cart.total,
currency_code: cart.currency_code,
},
})
}
)

Some files were not shown because too many files have changed in this diff Show More