feat(medusa, types, utils, core-flows, order) request & accept order transfer (#10106)

**What**
- add request order transfer workflow
- add admin endpoint for transferring an order to a customer
- accept order transfer storefront endpoint
- accept transfer workflow
- changes in the order module to introduce new change and action types

---

**Note**
- we return 400 instead 409 currently if there is already an active order edit, I will revisit this in a followup
- endpoint for requesting order transfer from the storefront will be added in a separate PR

---

RESOLVES CMRC-701
RESOLVES CMRC-703
RESOLVES CMRC-704
RESOLVES CMRC-705
This commit is contained in:
Frane Polić
2024-11-19 09:53:22 +01:00
committed by GitHub
parent b1b7a4abf1
commit 36460a3a07
21 changed files with 660 additions and 4 deletions

View File

@@ -78,3 +78,5 @@ export * from "./return/update-return-shipping-method"
export * from "./update-order-change-actions"
export * from "./update-order-changes"
export * from "./update-tax-lines"
export * from "./transfer/request-order-transfer"
export * from "./transfer/accept-order-transfer"

View File

@@ -0,0 +1,109 @@
import {
OrderChangeDTO,
OrderDTO,
OrderWorkflow,
} from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
} from "@medusajs/framework/workflows-sdk"
import { OrderPreviewDTO } from "@medusajs/types"
import { useRemoteQueryStep } from "../../../common"
import { throwIfOrderIsCancelled } from "../../utils/order-validation"
import { previewOrderChangeStep } from "../../steps"
import {
ChangeActionType,
MedusaError,
OrderChangeStatus,
} from "@medusajs/utils"
import { confirmOrderChanges } from "../../steps/confirm-order-changes"
/**
* This step validates that an order transfer can be accepted.
*/
export const acceptOrderTransferValidationStep = createStep(
"accept-order-transfer-validation",
async function ({
token,
order,
orderChange,
}: {
token: string
order: OrderDTO
orderChange: OrderChangeDTO
}) {
throwIfOrderIsCancelled({ order })
if (!orderChange || orderChange.change_type !== "transfer") {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order ${order.id} does not have an order transfer request.`
)
}
const transferCustomerAction = orderChange.actions.find(
(a) => a.action === ChangeActionType.TRANSFER_CUSTOMER
)
if (!token.length || token !== transferCustomerAction?.details!.token) {
throw new MedusaError(MedusaError.Types.NOT_ALLOWED, "Invalid token.")
}
}
)
export const acceptOrderTransferWorkflowId = "accept-order-transfer-workflow"
/**
* This workflow accepts an order transfer.
*/
export const acceptOrderTransferWorkflow = createWorkflow(
acceptOrderTransferWorkflowId,
function (
input: WorkflowData<OrderWorkflow.AcceptOrderTransferWorkflowInput>
): WorkflowResponse<OrderPreviewDTO> {
const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders",
fields: ["id", "email", "status", "customer_id"],
variables: { id: input.order_id },
list: false,
throw_if_key_not_found: true,
})
const orderChange: OrderChangeDTO = useRemoteQueryStep({
entry_point: "order_change",
fields: [
"id",
"status",
"change_type",
"actions.id",
"actions.order_id",
"actions.action",
"actions.details",
"actions.reference",
"actions.reference_id",
"actions.internal_note",
],
variables: {
filters: {
order_id: input.order_id,
status: [OrderChangeStatus.REQUESTED],
},
},
list: false,
}).config({ name: "order-change-query" })
acceptOrderTransferValidationStep({
order,
orderChange,
token: input.token,
})
confirmOrderChanges({
changes: [orderChange],
orderId: order.id,
})
return new WorkflowResponse(previewOrderChangeStep(input.order_id))
}
)

View File

@@ -0,0 +1,136 @@
import { OrderDTO, OrderWorkflow } from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
import { CustomerDTO, OrderPreviewDTO } from "@medusajs/types"
import { v4 as uid } from "uuid"
import { emitEventStep, useRemoteQueryStep } from "../../../common"
import { createOrderChangeStep } from "../../steps/create-order-change"
import { throwIfOrderIsCancelled } from "../../utils/order-validation"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import {
ChangeActionType,
MedusaError,
OrderChangeStatus,
OrderWorkflowEvents,
} from "@medusajs/utils"
import { previewOrderChangeStep, updateOrderChangesStep } from "../../steps"
/**
* This step validates that an order transfer can be requested.
*/
export const requestOrderTransferValidationStep = createStep(
"request-order-transfer-validation",
async function ({
order,
customer,
}: {
order: OrderDTO
customer: CustomerDTO
}) {
throwIfOrderIsCancelled({ order })
if (!customer.has_account) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot transfer order: ${order.id} to a guest customer account: ${customer.email}`
)
}
if (order.customer_id === customer.id) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order: ${order.id} already belongs to customer: ${customer.id}`
)
}
}
)
export const requestOrderTransferWorkflowId = "request-order-transfer-workflow"
/**
* This workflow requests an order transfer.
*/
export const requestOrderTransferWorkflow = createWorkflow(
requestOrderTransferWorkflowId,
function (
input: WorkflowData<OrderWorkflow.RequestOrderTransferWorkflowInput>
): WorkflowResponse<OrderPreviewDTO> {
const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders",
fields: ["id", "email", "status", "customer_id"],
variables: { id: input.order_id },
list: false,
throw_if_key_not_found: true,
})
const customer: CustomerDTO = useRemoteQueryStep({
entry_point: "customers",
fields: ["id", "email", "has_account"],
variables: { id: input.customer_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "customer-query" })
requestOrderTransferValidationStep({ order, customer })
const orderChangeInput = transform({ input }, ({ input }) => {
return {
change_type: "transfer" as const,
order_id: input.order_id,
created_by: input.logged_in_user,
description: input.description,
internal_note: input.internal_note,
}
})
const change = createOrderChangeStep(orderChangeInput)
const actionInput = transform(
{ order, input, change },
({ order, input, change }) => [
{
order_change_id: change.id,
order_id: input.order_id,
action: ChangeActionType.TRANSFER_CUSTOMER,
version: change.version,
reference: "customer",
reference_id: input.customer_id,
details: {
token: uid(),
original_email: order.email,
},
},
]
)
createOrderChangeActionsWorkflow.runAsStep({
input: actionInput,
})
const updateOrderChangeInput = transform(
{ input, change },
({ input, change }) => [
{
id: change.id,
status: OrderChangeStatus.REQUESTED,
requested_by: input.logged_in_user,
requested_at: new Date(),
},
]
)
updateOrderChangesStep(updateOrderChangeInput)
emitEventStep({
eventName: OrderWorkflowEvents.TRANSFER_REQUESTED,
data: { id: input.order_id },
})
return new WorkflowResponse(previewOrderChangeStep(input.order_id))
}
)