chore(core-flows): Order Exchange - initial workflows (#8374)

PR: 1:n

This is the first PR with a initial skeleton for Order exchange workflows.
This is not yet to be considered functional or reflecting the exact behavior of Order Exchanges.

What:
* organize folder structure of order-related steps and workflows 
* initial worklows for Order exchange - This first draft is just a copy of Claims behavior
This commit is contained in:
Carlos R. L. Rodrigues
2024-08-01 07:27:06 -03:00
committed by GitHub
parent 7569fd1605
commit accf884bb1
38 changed files with 1156 additions and 223 deletions

View File

@@ -1,125 +0,0 @@
import {
beginExchangeOrderWorkflow,
createExchangeReturnShippingMethodWorkflow,
} from "@medusajs/core-flows"
import { OrderDTO, OrderExchangeDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createOrderFixture, prepareDataFixtures } from "../__fixtures__"
jest.setTimeout(50000)
medusaIntegrationTestRunner({
env: { MEDUSA_FF_MEDUSA_V2: true },
testSuite: ({ getContainer }) => {
let container
beforeAll(() => {
container = getContainer()
})
describe("Order change: Create exchange return shipping", () => {
let order: OrderDTO
let fixtures
let exchangeOrder: OrderExchangeDTO
beforeEach(async () => {
fixtures = await prepareDataFixtures({ container })
order = await createOrderFixture({
container,
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
})
await beginExchangeOrderWorkflow(container).run({
input: { order_id: order.id },
throwOnError: true,
})
const remoteQuery = container.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
const remoteQueryObject = remoteQueryObjectFromString({
entryPoint: "order_exchange",
variables: { order_id: order.id },
fields: ["order_id", "id", "status", "order_change_id", "return_id"],
})
;[exchangeOrder] = await remoteQuery(remoteQueryObject)
})
describe("createExchangeReturnShippingMethodWorkflow", () => {
it("should successfully add exchange return shipping to order changes", async () => {
const shippingOptionId = fixtures.shippingOption.id
const { result } = await createExchangeReturnShippingMethodWorkflow(
container
).run({
input: {
exchangeId: exchangeOrder.id,
shippingOptionId: shippingOptionId,
},
})
const orderChange = result?.[0]
expect(orderChange).toEqual(
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
details: {
exchange_id: exchangeOrder.id,
order_id: exchangeOrder.order_id,
return_id: exchangeOrder.return_id,
},
raw_amount: { value: "10", precision: 20 },
applied: false,
action: "SHIPPING_ADD",
amount: 10,
})
)
})
it("should successfully add return shipping with custom price to order changes", async () => {
const shippingOptionId = fixtures.shippingOption.id
const { result } = await createExchangeReturnShippingMethodWorkflow(
container
).run({
input: {
exchangeId: exchangeOrder.id,
shippingOptionId: shippingOptionId,
customShippingPrice: 20,
},
})
const orderChange = result?.[0]
expect(orderChange).toEqual(
expect.objectContaining({
id: expect.any(String),
reference: "order_shipping_method",
reference_id: expect.any(String),
details: {
exchange_id: exchangeOrder.id,
order_id: exchangeOrder.order_id,
return_id: exchangeOrder.return_id,
},
raw_amount: { value: "20", precision: 20 },
applied: false,
action: "SHIPPING_ADD",
amount: 20,
})
)
})
})
})
},
})

View File

@@ -0,0 +1,50 @@
import {
CreateOrderExchangeItemDTO,
IOrderModuleService,
OrderChangeActionDTO,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type CreateOrderExchangeItemsFromActionsInput = {
changes: OrderChangeActionDTO[]
exchangeId: string
}
export const createOrderExchangeItemsFromActionsStep = createStep(
"create-exchange-items-from-change-actions",
async (input: CreateOrderExchangeItemsFromActionsInput, { container }) => {
const orderModuleService = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
const exchangeItems = input.changes.map((item) => {
return {
exchange_id: input.exchangeId,
item_id: item.details?.reference_id! as string,
quantity: item.details?.quantity as number,
note: item.internal_note,
metadata: (item.details?.metadata as Record<string, unknown>) ?? {},
}
}) as CreateOrderExchangeItemDTO[]
const createdExchangeItems =
await orderModuleService.createOrderExchangeItems(exchangeItems)
return new StepResponse(
createdExchangeItems,
createdExchangeItems.map((i) => i.id)
)
},
async (ids, { container }) => {
if (!ids) {
return
}
const orderModuleService = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await orderModuleService.deleteOrderExchangeItems(ids)
}
)

View File

@@ -0,0 +1,28 @@
import { IOrderModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export const deleteExchangesStepId = "delete-exchanges"
export const deleteExchangesStep = createStep(
deleteExchangesStepId,
async (data: { ids: string[] }, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
const deleted = await service.softDeleteOrderExchanges(data.ids)
return new StepResponse(deleted, data.ids)
},
async (ids, { container }) => {
if (!ids) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.restoreOrderExchanges(ids)
}
)

View File

@@ -0,0 +1,55 @@
import { IOrderModuleService, UpdateOrderExchangeDTO } from "@medusajs/types"
import {
ModuleRegistrationName,
getSelectsAndRelationsFromObjectArray,
} from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const updateOrderExchangesStepId = "update-order-exchange"
export const updateOrderExchangesStep = createStep(
updateOrderExchangesStepId,
async (data: UpdateOrderExchangeDTO[], { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data, {
objectFields: ["metadata"],
})
const dataBeforeUpdate = await service.listOrderExchanges(
{ id: data.map((d) => d.id) },
{ relations, select: selects }
)
const updated = await service.updateOrderExchanges(
data.map((dt) => {
const { id, ...rest } = dt
return {
selector: { id },
data: rest,
}
})
)
return new StepResponse(updated, dataBeforeUpdate)
},
async (dataBeforeUpdate, { container }) => {
if (!dataBeforeUpdate?.length) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.updateOrderExchanges(
dataBeforeUpdate.map((dt) => {
const { id, ...rest } = dt
return {
selector: { id },
data: rest,
}
})
)
}
)

View File

@@ -1,34 +1,36 @@
export * from "./archive-orders"
export * from "./cancel-claim"
export * from "./cancel-exchange"
export * from "./cancel-order-change"
export * from "./cancel-orders"
export * from "./cancel-return"
export * from "./claim/cancel-claim"
export * from "./claim/create-claim-items-from-actions"
export * from "./claim/create-claims"
export * from "./claim/delete-claims"
export * from "./complete-orders"
export * from "./create-claim-items-from-actions"
export * from "./create-claims"
export * from "./create-complete-return"
export * from "./create-exchanges"
export * from "./create-line-items"
export * from "./create-order-change"
export * from "./create-order-change-actions"
export * from "./create-orders"
export * from "./create-returns"
export * from "./decline-order-change"
export * from "./delete-claims"
export * from "./delete-line-items"
export * from "./delete-order-change-actions"
export * from "./delete-order-changes"
export * from "./delete-order-shipping-methods"
export * from "./delete-returns"
export * from "./exchange/cancel-exchange"
export * from "./exchange/create-exchange"
export * from "./exchange/create-exchange-items-from-actions"
export * from "./exchange/delete-exchanges"
export * from "./get-item-tax-lines"
export * from "./preview-order-change"
export * from "./register-fulfillment"
export * from "./register-shipment"
export * from "./return/cancel-return"
export * from "./return/create-complete-return"
export * from "./return/create-returns"
export * from "./return/delete-returns"
export * from "./return/update-return-items"
export * from "./return/update-returns"
export * from "./set-tax-lines-for-items"
export * from "./update-order-change-actions"
export * from "./update-order-exchanges"
export * from "./update-return-items"
export * from "./update-returns"
export * from "./update-shipping-methods"
export * from "./update-tax-lines"

View File

@@ -7,7 +7,7 @@ import {
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { createOrderClaimsStep } from "../../steps/create-claims"
import { createOrderClaimsStep } from "../../steps/claim/create-claims"
import { createOrderChangeStep } from "../../steps/create-order-change"
import { throwIfIsCancelled } from "../../utils/order-validation"

View File

@@ -15,10 +15,10 @@ import {
when,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { updateOrderClaimsStep } from "../../steps/claim/update-order-claims"
import { createOrderChangeActionsStep } from "../../steps/create-order-change-actions"
import { createReturnsStep } from "../../steps/create-returns"
import { previewOrderChangeStep } from "../../steps/preview-order-change"
import { updateOrderClaimsStep } from "../../steps/update-order-claims"
import { createReturnsStep } from "../../steps/return/create-returns"
import {
throwIfIsCancelled,
throwIfItemsDoesNotExistsInOrder,

View File

@@ -7,7 +7,6 @@ import {
} from "@medusajs/types"
import { ChangeActionType, Modules, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
@@ -19,9 +18,9 @@ import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { createFulfillmentWorkflow } from "../../../fulfillment/workflows/create-fulfillment"
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
import { previewOrderChangeStep } from "../../steps"
import { createOrderClaimItemsFromActionsStep } from "../../steps/claim/create-claim-items-from-actions"
import { confirmOrderChanges } from "../../steps/confirm-order-changes"
import { createOrderClaimItemsFromActionsStep } from "../../steps/create-claim-items-from-actions"
import { createReturnItemsFromActionsStep } from "../../steps/create-return-items-from-actions"
import { createReturnItemsFromActionsStep } from "../../steps/return/create-return-items-from-actions"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,

View File

@@ -7,8 +7,8 @@ import {
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { createOrderExchangesStep } from "../../steps/create-exchanges"
import { createOrderChangeStep } from "../../steps/create-order-change"
import { createOrderExchangesStep } from "../../steps/exchange/create-exchange"
import { throwIfOrderIsCancelled } from "../../utils/order-validation"
const validationStep = createStep(

View File

@@ -0,0 +1,94 @@
import { OrderChangeDTO, OrderDTO, OrderExchangeDTO } from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
createStep,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import {
deleteExchangesStep,
deleteOrderChangesStep,
deleteOrderShippingMethods,
deleteReturnsStep,
} from "../../steps"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
type WorkflowInput = {
exchange_id: string
}
const validationStep = createStep(
"validate-cancel-begin-order-exchange",
async function ({
order,
orderChange,
orderExchange,
}: {
order: OrderDTO
orderExchange: OrderExchangeDTO
orderChange: OrderChangeDTO
}) {
throwIfIsCancelled(order, "Order")
throwIfIsCancelled(orderExchange, "Exchange")
throwIfOrderChangeIsNotActive({ orderChange })
}
)
export const cancelBeginOrderExchangeWorkflowId = "cancel-begin-order-exchange"
export const cancelBeginOrderExchangeWorkflow = createWorkflow(
cancelBeginOrderExchangeWorkflowId,
function (input: WorkflowInput): WorkflowData<void> {
const orderExchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "status", "order_id", "return_id", "canceled_at"],
variables: { id: input.exchange_id },
list: false,
throw_if_key_not_found: true,
})
const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders",
fields: ["id", "version", "canceled_at"],
variables: { id: orderExchange.order_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "order-query" })
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version", "actions.*"],
variables: {
filters: {
order_id: orderExchange.order_id,
exchange_id: orderExchange.id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
},
list: false,
}).config({ name: "order-change-query" })
validationStep({ order, orderExchange, orderChange })
const shippingToRemove = transform(
{ orderChange, input },
({ orderChange, input }) => {
return (orderChange.actions ?? [])
.filter((a) => a.action === ChangeActionType.SHIPPING_ADD)
.map(({ id }) => id)
}
)
parallelize(
deleteReturnsStep({ ids: [orderExchange.return_id!] }),
deleteExchangesStep({ ids: [orderExchange.id] }),
deleteOrderChangesStep({ ids: [orderChange.id] }),
deleteOrderShippingMethods({ ids: shippingToRemove })
)
}
)

View File

@@ -0,0 +1,82 @@
import {
FulfillmentDTO,
OrderExchangeDTO,
OrderWorkflow,
} from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import {
WorkflowData,
createStep,
createWorkflow,
when,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { cancelOrderExchangeStep } from "../../steps"
import { throwIfIsCancelled } from "../../utils/order-validation"
import { cancelReturnWorkflow } from "../return/cancel-return"
const validateOrder = createStep(
"validate-exchange",
({
orderExchange,
}: {
orderExchange: OrderExchangeDTO
input: OrderWorkflow.CancelOrderExchangeWorkflowInput
}) => {
const orderExchange_ = orderExchange as OrderExchangeDTO & {
fulfillments: FulfillmentDTO[]
}
throwIfIsCancelled(orderExchange, "Exchange")
const throwErrorIf = (
arr: unknown[],
pred: (obj: any) => boolean,
message: string
) => {
if (arr?.some(pred)) {
throw new MedusaError(MedusaError.Types.NOT_ALLOWED, message)
}
}
const notCanceled = (o) => !o.canceled_at
throwErrorIf(
orderExchange_.fulfillments,
notCanceled,
"All fulfillments must be canceled before canceling am exchange"
)
}
)
export const cancelOrderExchangeWorkflowId = "cancel-exchange"
export const cancelOrderExchangeWorkflow = createWorkflow(
cancelOrderExchangeWorkflowId,
(
input: WorkflowData<OrderWorkflow.CancelOrderExchangeWorkflowInput>
): WorkflowData<void> => {
const orderExchange: OrderExchangeDTO & { fulfillments: FulfillmentDTO[] } =
useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "return_id", "canceled_at", "fulfillments.canceled_at"],
variables: { id: input.exchange_id },
list: false,
throw_if_key_not_found: true,
})
validateOrder({ orderExchange, input })
cancelOrderExchangeStep({ exchange_id: orderExchange.id })
when({ orderExchange }, ({ orderExchange }) => {
return !!orderExchange.return_id
}).then(() => {
cancelReturnWorkflow.runAsStep({
input: {
return_id: orderExchange.return_id!,
no_notification: input.no_notification,
},
})
})
}
)

View File

@@ -0,0 +1,361 @@
import {
FulfillmentWorkflow,
OrderChangeActionDTO,
OrderChangeDTO,
OrderDTO,
OrderExchangeDTO,
} from "@medusajs/types"
import { ChangeActionType, Modules, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowResponse,
createStep,
createWorkflow,
parallelize,
transform,
when,
} from "@medusajs/workflows-sdk"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { createFulfillmentWorkflow } from "../../../fulfillment/workflows/create-fulfillment"
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
import { previewOrderChangeStep } from "../../steps"
import { confirmOrderChanges } from "../../steps/confirm-order-changes"
import { createOrderExchangeItemsFromActionsStep } from "../../steps/exchange/create-exchange-items-from-actions"
import { createReturnItemsFromActionsStep } from "../../steps/return/create-return-items-from-actions"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
type WorkflowInput = {
exchange_id: string
}
const validationStep = createStep(
"validate-confirm-exchange-request",
async function ({
order,
orderChange,
orderExchange,
}: {
order: OrderDTO
orderExchange: OrderExchangeDTO
orderChange: OrderChangeDTO
}) {
throwIfIsCancelled(order, "Order")
throwIfIsCancelled(orderExchange, "Exchange")
throwIfOrderChangeIsNotActive({ orderChange })
}
)
function prepareFulfillmentData({
order,
items,
shippingOption,
deliveryAddress,
}: {
order: OrderDTO
items: any[]
shippingOption: {
id: string
provider_id: string
service_zone: {
fulfillment_set: {
location?: {
id: string
address: Record<string, any>
}
}
}
}
deliveryAddress?: Record<string, any>
}) {
const orderItemsMap = new Map<string, Required<OrderDTO>["items"][0]>(
order.items!.map((i) => [i.id, i])
)
const fulfillmentItems = items.map((i) => {
const orderItem = orderItemsMap.get(i.item_id)!
return {
line_item_id: i.item_id,
quantity: i.quantity,
return_quantity: i.quantity,
title: orderItem.variant_title ?? orderItem.title,
sku: orderItem.variant_sku || "",
barcode: orderItem.variant_barcode || "",
} as FulfillmentWorkflow.CreateFulfillmentItemWorkflowDTO
})
const locationId = shippingOption.service_zone.fulfillment_set.location?.id!
// delivery address is the stock location address
const address =
deliveryAddress ??
shippingOption.service_zone.fulfillment_set.location?.address ??
{}
delete address.id
return {
input: {
location_id: locationId,
provider_id: shippingOption.provider_id,
shipping_option_id: shippingOption.id,
items: fulfillmentItems,
delivery_address: address,
order: order,
},
}
}
function transformActionsToItems({ orderChange }) {
const exchangeItems: OrderChangeActionDTO[] = []
const returnItems: OrderChangeActionDTO[] = []
const actions = orderChange.actions ?? []
actions.forEach((item) => {
if (item.action === ChangeActionType.RETURN_ITEM) {
returnItems.push(item)
} else if (item.action === ChangeActionType.ITEM_ADD) {
exchangeItems.push(item)
}
})
return {
exchangeItems: {
changes: exchangeItems,
exchangeId: exchangeItems?.[0]?.exchange_id!,
},
returnItems: {
changes: returnItems,
returnId: returnItems?.[0]?.return_id!,
},
}
}
function extractShippingOption({ orderPreview, orderExchange, returnId }) {
if (!orderPreview.shipping_methods?.length) {
return
}
let returnShippingMethod
let exchangeShippingMethod
for (const shippingMethod of orderPreview.shipping_methods) {
const modifiedShippingMethod_ = shippingMethod as any
if (!modifiedShippingMethod_.actions) {
continue
}
for (const action of modifiedShippingMethod_.actions) {
if (action.action === ChangeActionType.SHIPPING_ADD) {
if (action.return_id === returnId) {
returnShippingMethod = shippingMethod
} else if (action.exchange_id === orderExchange.id) {
exchangeShippingMethod = shippingMethod
}
}
}
}
return {
returnShippingMethod,
exchangeShippingMethod,
}
}
export const confirmExchangeRequestWorkflowId = "confirm-exchange-request"
export const confirmExchangeRequestWorkflow = createWorkflow(
confirmExchangeRequestWorkflowId,
function (input: WorkflowInput): WorkflowResponse<OrderDTO> {
const orderExchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "status", "order_id", "canceled_at"],
variables: { id: input.exchange_id },
list: false,
throw_if_key_not_found: true,
})
const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders",
fields: [
"id",
"version",
"canceled_at",
"items.id",
"items.title",
"items.variant_title",
"items.variant_sku",
"items.variant_barcode",
"shipping_address.*",
],
variables: { id: orderExchange.order_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "order-query" })
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: [
"id",
"actions.id",
"actions.exchange_id",
"actions.return_id",
"actions.action",
"actions.details",
"actions.reference",
"actions.reference_id",
"actions.internal_note",
],
variables: {
filters: {
order_id: orderExchange.order_id,
exchange_id: orderExchange.id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
},
list: false,
}).config({ name: "order-change-query" })
validationStep({ order, orderExchange, orderChange })
const { exchangeItems, returnItems } = transform(
{ orderChange },
transformActionsToItems
)
const orderPreview = previewOrderChangeStep(order.id)
const [createExchangeItems, createdReturnItems] = parallelize(
createOrderExchangeItemsFromActionsStep(exchangeItems),
createReturnItemsFromActionsStep(returnItems),
confirmOrderChanges({ changes: [orderChange], orderId: order.id })
)
const returnId = transform(
{ createdReturnItems },
({ createdReturnItems }) => {
return createdReturnItems?.[0]?.return_id
}
)
const exchangeId = transform(
{ createExchangeItems },
({ createExchangeItems }) => {
return createExchangeItems?.[0]?.exchange_id
}
)
const { returnShippingMethod, exchangeShippingMethod } = transform(
{ orderPreview, orderExchange, returnId },
extractShippingOption
)
when({ exchangeShippingMethod }, ({ exchangeShippingMethod }) => {
return !!exchangeShippingMethod
}).then(() => {
const exchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: [
"id",
"version",
"canceled_at",
"additional_items.id",
"additional_items.title",
"additional_items.variant_title",
"additional_items.variant_sku",
"additional_items.variant_barcode",
],
variables: { id: exchangeId },
list: false,
throw_if_key_not_found: true,
}).config({ name: "exchange-query" })
const exchangeShippingOption = useRemoteQueryStep({
entry_point: "shipping_options",
fields: [
"id",
"provider_id",
"service_zone.fulfillment_set.location.id",
"service_zone.fulfillment_set.location.address.*",
],
variables: {
id: exchangeShippingMethod.shipping_option_id,
},
list: false,
throw_if_key_not_found: true,
}).config({ name: "exchange-shipping-option" })
const fulfillmentData = transform(
{
order,
items: exchange.additional_items! ?? [],
shippingOption: exchangeShippingOption,
deliveryAddress: order.shipping_address,
},
prepareFulfillmentData
)
const fulfillment = createFulfillmentWorkflow.runAsStep(fulfillmentData)
const link = transform({ fulfillment, order }, (data) => {
return [
{
[Modules.ORDER]: { order_id: data.order.id },
[Modules.FULFILLMENT]: { fulfillment_id: data.fulfillment.id },
},
]
})
createRemoteLinkStep(link).config({
name: "exchange-shipping-fulfillment-link",
})
})
when({ returnShippingMethod }, ({ returnShippingMethod }) => {
return !!returnShippingMethod
}).then(() => {
const returnShippingOption = useRemoteQueryStep({
entry_point: "shipping_options",
fields: [
"id",
"provider_id",
"service_zone.fulfillment_set.location.id",
"service_zone.fulfillment_set.location.address.*",
],
variables: {
id: returnShippingMethod.shipping_option_id,
},
list: false,
throw_if_key_not_found: true,
}).config({ name: "exchange-return-shipping-option" })
const fulfillmentData = transform(
{
order,
items: order.items!,
shippingOption: returnShippingOption,
},
prepareFulfillmentData
)
const returnFulfillment =
createReturnFulfillmentWorkflow.runAsStep(fulfillmentData)
const returnLink = transform(
{ returnId, fulfillment: returnFulfillment },
(data) => {
return [
{
[Modules.ORDER]: { return_id: data.returnId },
[Modules.FULFILLMENT]: { fulfillment_id: data.fulfillment.id },
},
]
}
)
createRemoteLinkStep(returnLink).config({
name: "exchange-return-shipping-fulfillment-link",
})
})
return new WorkflowResponse(orderPreview)
}
)

View File

@@ -1,60 +1,63 @@
import {
BigNumberInput,
OrderChangeActionDTO,
OrderChangeDTO,
OrderDTO,
OrderExchangeDTO,
} from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { previewOrderChangeStep } from "../../steps"
import { createOrderChangeActionsStep } from "../../steps/create-order-change-actions"
import { createOrderShippingMethods } from "../../steps/create-order-shipping-methods"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
throwIfOrderIsCancelled,
} from "../../utils/order-validation"
const validationStep = createStep(
"validate-create-exchange-return-shipping-method",
"validate-create-exchange-shipping-method",
async function ({
order,
orderChange,
orderExchange,
}: {
order: OrderDTO
orderExchange: OrderExchangeDTO
orderChange: OrderChangeDTO
}) {
throwIfOrderIsCancelled({ order })
throwIfIsCancelled(order, "Order")
throwIfIsCancelled(orderExchange, "Exchange")
throwIfOrderChangeIsNotActive({ orderChange })
}
)
export const createExchangeReturnShippingMethodWorkflowId =
"create-exchange-return-shipping-method"
export const createExchangeReturnShippingMethodWorkflow = createWorkflow(
createExchangeReturnShippingMethodWorkflowId,
export const createExchangeShippingMethodWorkflowId =
"create-exchange-shipping-method"
export const createExchangeShippingMethodWorkflow = createWorkflow(
createExchangeShippingMethodWorkflowId,
function (input: {
exchangeId: string
shippingOptionId: string
customShippingPrice?: BigNumberInput
}): WorkflowResponse<OrderChangeActionDTO[]> {
return_id?: string
exchange_id?: string
shipping_option_id: string
custom_price?: BigNumberInput
}): WorkflowResponse<OrderDTO> {
const orderExchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "status", "order_id", "return_id"],
variables: { id: input.exchangeId },
fields: ["id", "status", "order_id", "canceled_at"],
variables: { id: input.exchange_id },
list: false,
throw_if_key_not_found: true,
})
const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders",
fields: ["id", "status", "currency_code"],
fields: ["id", "status", "currency_code", "canceled_at"],
variables: { id: orderExchange.order_id },
list: false,
throw_if_key_not_found: true,
@@ -69,41 +72,16 @@ export const createExchangeReturnShippingMethodWorkflow = createWorkflow(
"calculated_price.is_calculated_price_tax_inclusive",
],
variables: {
id: input.shippingOptionId,
id: input.shipping_option_id,
calculated_price: {
context: { currency_code: order.currency_code },
},
},
}).config({ name: "fetch-shipping-option" })
const shippingMethodInput = transform(
{ orderExchange, shippingOptions, input },
(data) => {
const option = data.shippingOptions[0]
return {
shipping_option_id: option.id,
amount:
data.input.customShippingPrice ??
option.calculated_price.calculated_amount,
is_tax_inclusive:
!!option.calculated_price.is_calculated_price_tax_inclusive,
data: option.data ?? {},
name: option.name,
order_id: data.orderExchange.order_id,
return_id: data.orderExchange.return_id,
exchange_id: data.orderExchange.id,
}
}
)
const createdMethods = createOrderShippingMethods({
shipping_methods: [shippingMethodInput],
})
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status"],
fields: ["id", "status", "version"],
variables: {
filters: {
order_id: orderExchange.order_id,
@@ -114,43 +92,78 @@ export const createExchangeReturnShippingMethodWorkflow = createWorkflow(
list: false,
}).config({ name: "order-change-query" })
validationStep({ order, orderChange })
validationStep({ order, orderExchange, orderChange })
const shippingMethodInput = transform(
{
orderExchange,
shippingOptions,
customPrice: input.custom_price,
orderChange,
input,
},
(data) => {
const option = data.shippingOptions[0]
const orderChange = data.orderChange
return {
shipping_option_id: option.id,
amount: data.customPrice ?? option.calculated_price.calculated_amount,
is_tax_inclusive:
!!option.calculated_price.is_calculated_price_tax_inclusive,
data: option.data ?? {},
name: option.name,
version: orderChange.version,
order_id: data.orderExchange.order_id,
return_id: input.return_id,
exchange_id: data.orderExchange.id,
}
}
)
const createdMethods = createOrderShippingMethods({
shipping_methods: [shippingMethodInput],
})
const orderChangeActionInput = transform(
{
orderId: order.id,
returnId: orderExchange.return_id,
exchangeId: orderExchange.id,
shippingOption: shippingOptions[0],
methodId: createdMethods[0].id,
customPrice: input.customShippingPrice,
order,
orderExchange,
shippingOptions,
createdMethods,
customPrice: input.custom_price,
orderChange,
input,
},
({
shippingOption,
exchangeId,
returnId,
orderId,
methodId,
shippingOptions,
orderExchange,
order,
createdMethods,
customPrice,
orderChange,
input,
}) => {
const shippingOption = shippingOptions[0]
const createdMethod = createdMethods[0]
const methodPrice =
customPrice ?? shippingOption.calculated_price.calculated_amount
return {
action: ChangeActionType.SHIPPING_ADD,
reference: "order_shipping_method",
reference_id: methodId,
order_change_id: orderChange.id,
reference_id: createdMethod.id,
amount: methodPrice,
details: {
order_id: orderId,
return_id: returnId,
exchange_id: exchangeId,
},
order_id: order.id,
return_id: input.return_id,
exchange_id: orderExchange.id,
}
}
)
const response = createOrderChangeActionsStep([orderChangeActionInput])
return new WorkflowResponse(response)
createOrderChangeActionsStep([orderChangeActionInput])
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -18,7 +18,6 @@ import { previewOrderChangeStep } from "../../steps/preview-order-change"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
throwIfOrderIsCancelled,
} from "../../utils/order-validation"
import { addOrderLineItemsWorkflow } from "../add-line-items"
@@ -33,7 +32,7 @@ const validationStep = createStep(
orderExchange: OrderExchangeDTO
orderChange: OrderChangeDTO
}) {
throwIfOrderIsCancelled({ order })
throwIfIsCancelled(order, "Order")
throwIfIsCancelled(orderExchange, "Exchange")
throwIfOrderChangeIsNotActive({ orderChange })
}
@@ -102,7 +101,7 @@ export const orderExchangeAddNewItemWorkflow = createWorkflow(
details: {
reference_id: lineItems[index].id,
quantity: item.quantity,
unit_price: item.unit_price,
unit_price: item.unit_price ?? lineItems[index].unit_price,
metadata: item.metadata,
},
}))

View File

@@ -7,23 +7,22 @@ import {
} from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
transform,
when,
WorkflowData,
WorkflowResponse,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { createOrderChangeActionsStep } from "../../steps/create-order-change-actions"
import { createReturnsStep } from "../../steps/create-returns"
import { updateOrderExchangesStep } from "../../steps/exchange/update-order-exchanges"
import { previewOrderChangeStep } from "../../steps/preview-order-change"
import { updateOrderExchangesStep } from "../../steps/update-order-exchanges"
import { createReturnsStep } from "../../steps/return/create-returns"
import {
throwIfIsCancelled,
throwIfItemsDoesNotExistsInOrder,
throwIfOrderChangeIsNotActive,
throwIfOrderIsCancelled,
} from "../../utils/order-validation"
const validationStep = createStep(
@@ -41,7 +40,7 @@ const validationStep = createStep(
orderChange: OrderChangeDTO
items: OrderWorkflow.OrderExchangeRequestItemReturnWorkflowInput["items"]
}) {
throwIfOrderIsCancelled({ order })
throwIfIsCancelled(order, "Order")
throwIfIsCancelled(orderExchange, "Exchange")
throwIfIsCancelled(orderReturn, "Return")
throwIfOrderChangeIsNotActive({ orderChange })
@@ -113,7 +112,10 @@ export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
},
},
list: false,
}).config({ name: "order-change-query" })
}).config({
name: "order-change-query",
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
})
validationStep({
order,
@@ -156,6 +158,7 @@ export const orderExchangeRequestItemReturnWorkflow = createWorkflow(
details: {
reference_id: item.id,
quantity: item.quantity,
reason_id: item.reason_id,
metadata: item.metadata,
},
}))

View File

@@ -0,0 +1,107 @@
import {
OrderChangeActionDTO,
OrderChangeDTO,
OrderDTO,
OrderExchangeDTO,
OrderWorkflow,
} from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import { deleteOrderShippingMethods } from "../../steps"
import { deleteOrderChangeActionsStep } from "../../steps/delete-order-change-actions"
import { previewOrderChangeStep } from "../../steps/preview-order-change"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
const validationStep = createStep(
"validate-remove-exchange-shipping-method",
async function ({
orderChange,
orderExchange,
input,
}: {
input: { exchange_id: string; action_id: string }
orderExchange: OrderExchangeDTO
orderChange: OrderChangeDTO
}) {
throwIfIsCancelled(orderExchange, "Exchange")
throwIfOrderChangeIsNotActive({ orderChange })
const associatedAction = (orderChange.actions ?? []).find(
(a) => a.id === input.action_id
) as OrderChangeActionDTO
if (!associatedAction) {
throw new Error(
`No shipping method found for exchange ${input.exchange_id} in order change ${orderChange.id}`
)
} else if (associatedAction.action !== ChangeActionType.SHIPPING_ADD) {
throw new Error(
`Action ${associatedAction.id} is not adding a shipping method`
)
}
}
)
export const removeExchangeShippingMethodWorkflowId =
"remove-exchange-shipping-method"
export const removeExchangeShippingMethodWorkflow = createWorkflow(
removeExchangeShippingMethodWorkflowId,
function (
input: WorkflowData<OrderWorkflow.DeleteExchangeShippingMethodWorkflowInput>
): WorkflowResponse<OrderDTO> {
const orderExchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "status", "order_id", "canceled_at"],
variables: { id: input.exchange_id },
list: false,
throw_if_key_not_found: true,
})
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version", "actions.*"],
variables: {
filters: {
order_id: orderExchange.order_id,
exchange_id: orderExchange.id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
},
list: false,
}).config({ name: "order-change-query" })
validationStep({ orderExchange, orderChange, input })
const dataToRemove = transform(
{ orderChange, input },
({ orderChange, input }) => {
const associatedAction = (orderChange.actions ?? []).find(
(a) => a.id === input.action_id
) as OrderChangeActionDTO
return {
actionId: associatedAction.id,
shippingMethodId: associatedAction.reference_id,
}
}
)
parallelize(
deleteOrderChangeActionsStep({ ids: [dataToRemove.actionId] }),
deleteOrderShippingMethods({ ids: [dataToRemove.shippingMethodId] })
)
return new WorkflowResponse(previewOrderChangeStep(orderExchange.order_id))
}
)

View File

@@ -0,0 +1,119 @@
import {
OrderChangeActionDTO,
OrderChangeDTO,
OrderDTO,
OrderExchangeDTO,
OrderWorkflow,
} from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import {
previewOrderChangeStep,
updateOrderChangeActionsStep,
} from "../../steps"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
const validationStep = createStep(
"update-exchange-add-item-validation",
async function (
{
order,
orderChange,
orderExchange,
input,
}: {
order: OrderDTO
orderExchange: OrderExchangeDTO
orderChange: OrderChangeDTO
input: OrderWorkflow.UpdateExchangeAddNewItemWorkflowInput
},
context
) {
throwIfIsCancelled(order, "Order")
throwIfIsCancelled(orderExchange, "Exchange")
throwIfOrderChangeIsNotActive({ orderChange })
const associatedAction = (orderChange.actions ?? []).find(
(a) => a.id === input.action_id
) as OrderChangeActionDTO
if (!associatedAction) {
throw new Error(
`No request to add item for exchange ${input.exchange_id} in order change ${orderChange.id}`
)
} else if (associatedAction.action !== ChangeActionType.ITEM_ADD) {
throw new Error(`Action ${associatedAction.id} is not adding an item`)
}
}
)
export const updateExchangeAddItemWorkflowId = "update-exchange-add-item"
export const updateExchangeAddItemWorkflow = createWorkflow(
updateExchangeAddItemWorkflowId,
function (
input: WorkflowData<OrderWorkflow.UpdateExchangeAddNewItemWorkflowInput>
): WorkflowResponse<OrderDTO> {
const orderExchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "status", "order_id", "canceled_at"],
variables: { id: input.exchange_id },
list: false,
throw_if_key_not_found: true,
})
const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders",
fields: ["id", "status", "canceled_at", "items.*"],
variables: { id: orderExchange.order_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "order-query" })
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version", "actions.*"],
variables: {
filters: {
order_id: orderExchange.order_id,
exchange_id: orderExchange.id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
},
list: false,
}).config({ name: "order-change-query" })
validationStep({ order, input, orderExchange, orderChange })
const updateData = transform(
{ orderChange, input },
({ input, orderChange }) => {
const originalAction = (orderChange.actions ?? []).find(
(a) => a.id === input.action_id
) as OrderChangeActionDTO
const data = input.data
return {
id: input.action_id,
details: {
quantity: data.quantity ?? originalAction.details?.quantity,
},
internal_note: data.internal_note,
}
}
)
updateOrderChangeActionsStep([updateData])
return new WorkflowResponse(previewOrderChangeStep(order.id))
}
)

View File

@@ -0,0 +1,122 @@
import {
OrderChangeActionDTO,
OrderChangeDTO,
OrderDTO,
OrderExchangeDTO,
OrderWorkflow,
} from "@medusajs/types"
import { ChangeActionType, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import {
updateOrderChangeActionsStep,
updateOrderShippingMethodsStep,
} from "../../steps"
import { previewOrderChangeStep } from "../../steps/preview-order-change"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,
} from "../../utils/order-validation"
const validationStep = createStep(
"validate-update-exchange-shipping-method",
async function ({
orderChange,
orderExchange,
input,
}: {
input: { exchange_id: string; action_id: string }
orderExchange: OrderExchangeDTO
orderChange: OrderChangeDTO
}) {
throwIfIsCancelled(orderExchange, "Exchange")
throwIfOrderChangeIsNotActive({ orderChange })
const associatedAction = (orderChange.actions ?? []).find(
(a) => a.id === input.action_id
) as OrderChangeActionDTO
if (!associatedAction) {
throw new Error(
`No shipping method found for exchange ${input.exchange_id} in order change ${orderChange.id}`
)
} else if (associatedAction.action !== ChangeActionType.SHIPPING_ADD) {
throw new Error(
`Action ${associatedAction.id} is not adding a shipping method`
)
}
}
)
export const updateExchangeShippingMethodWorkflowId =
"update-exchange-shipping-method"
export const updateExchangeShippingMethodWorkflow = createWorkflow(
updateExchangeShippingMethodWorkflowId,
function (
input: WorkflowData<OrderWorkflow.UpdateExchangeShippingMethodWorkflowInput>
): WorkflowResponse<OrderDTO> {
const orderExchange: OrderExchangeDTO = useRemoteQueryStep({
entry_point: "order_exchange",
fields: ["id", "status", "order_id", "canceled_at"],
variables: { id: input.exchange_id },
list: false,
throw_if_key_not_found: true,
})
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: ["id", "status", "version", "actions.*"],
variables: {
filters: {
order_id: orderExchange.order_id,
exchange_id: orderExchange.id,
status: [OrderChangeStatus.PENDING, OrderChangeStatus.REQUESTED],
},
},
list: false,
}).config({ name: "order-change-query" })
validationStep({ orderExchange, orderChange, input })
const updateData = transform(
{ orderChange, input },
({ input, orderChange }) => {
const originalAction = (orderChange.actions ?? []).find(
(a) => a.id === input.action_id
) as OrderChangeActionDTO
const data = input.data
const action = {
id: originalAction.id,
internal_note: data.internal_note,
}
const shippingMethod = {
id: originalAction.reference_id,
amount: data.custom_price,
metadata: data.metadata,
}
return {
action,
shippingMethod,
}
}
)
parallelize(
updateOrderChangeActionsStep([updateData.action]),
updateOrderShippingMethodsStep([updateData.shippingMethod!])
)
return new WorkflowResponse(previewOrderChangeStep(orderExchange.order_id))
}
)

View File

@@ -26,9 +26,15 @@ export * from "./decline-order-change"
export * from "./delete-order-change"
export * from "./delete-order-change-actions"
export * from "./exchange/begin-order-exchange"
export * from "./exchange/create-exchange-return-shipping-method"
export * from "./exchange/cancel-begin-order-exchange"
export * from "./exchange/cancel-exchange"
export * from "./exchange/confirm-exchange-request"
export * from "./exchange/create-exchange-shipping-method"
export * from "./exchange/exchange-add-new-item"
export * from "./exchange/exchange-request-item-return"
export * from "./exchange/remove-exchange-shipping-method"
export * from "./exchange/update-exchange-add-item"
export * from "./exchange/update-exchange-shipping-method"
export * from "./get-order-detail"
export * from "./get-orders-list"
export * from "./return/begin-receive-return"

View File

@@ -6,7 +6,6 @@ import {
} from "@medusajs/types"
import { ChangeActionType, Modules, OrderChangeStatus } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
@@ -17,7 +16,7 @@ import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { createReturnFulfillmentWorkflow } from "../../../fulfillment/workflows/create-return-fulfillment"
import { previewOrderChangeStep } from "../../steps"
import { confirmOrderChanges } from "../../steps/confirm-order-changes"
import { createReturnItemsFromActionsStep } from "../../steps/create-return-items-from-actions"
import { createReturnItemsFromActionsStep } from "../../steps/return/create-return-items-from-actions"
import {
throwIfIsCancelled,
throwIfOrderChangeIsNotActive,

View File

@@ -16,8 +16,8 @@ import {
} from "@medusajs/workflows-sdk"
import { createRemoteLinkStep, useRemoteQueryStep } from "../../../common"
import { createReturnFulfillmentWorkflow } from "../../../fulfillment"
import { createCompleteReturnStep } from "../../steps/create-complete-return"
import { receiveReturnStep } from "../../steps/receive-return"
import { createCompleteReturnStep } from "../../steps/return/create-complete-return"
import { receiveReturnStep } from "../../steps/return/receive-return"
import {
throwIfItemsDoesNotExistsInOrder,
throwIfOrderIsCancelled,

View File

@@ -8,7 +8,7 @@ import {
import { useRemoteQueryStep } from "../../../common"
import { ReturnDTO } from "@medusajs/types"
import { receiveReturnStep } from "../../steps/receive-return"
import { receiveReturnStep } from "../../steps/return/receive-return"
import {
throwIfIsCancelled,
throwIfItemsDoesNotExistsInReturn,

View File

@@ -25,6 +25,11 @@ export interface OrderClaimAddNewItemWorkflowInput {
items: NewItem[]
}
export interface OrderExchangeAddNewItemWorkflowInput {
exchange_id: string
items: NewItem[]
}
export interface OrderAddLineItemWorkflowInput {
order_id: string
items: NewItem[]
@@ -48,6 +53,15 @@ export interface UpdateClaimAddNewItemWorkflowInput {
}
}
export interface UpdateExchangeAddNewItemWorkflowInput {
exchange_id: string
action_id: string
data: {
quantity?: BigNumberInput
internal_note?: string | null
}
}
export interface OrderExchangeItemWorkflowInput {
exchange_id: string
items: ExistingItem[]
@@ -86,3 +100,8 @@ export interface DeleteOrderClaimItemActionWorkflowInput {
claim_id: string
action_id: string
}
export interface DeleteOrderExchangeItemActionWorkflowInput {
exchange_id: string
action_id: string
}