chore(order): cancel order (#7586)

This commit is contained in:
Carlos R. L. Rodrigues
2024-06-03 12:31:33 -03:00
committed by GitHub
parent fdd9022376
commit 122186a78d
42 changed files with 945 additions and 116 deletions

View File

@@ -1,35 +0,0 @@
import { FulfillmentWorkflow } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { updateFulfillmentWorkflow } from "../workflows/update-fulfillment"
export const updateFulfillmentWorkflowStepId = "update-fulfillment-workflow"
export const updateFulfillmentWorkflowStep = createStep(
updateFulfillmentWorkflowStepId,
async (
data: FulfillmentWorkflow.UpdateFulfillmentWorkflowInput,
{ container }
) => {
const {
transaction,
result: updated,
errors,
} = await updateFulfillmentWorkflow(container).run({
input: data,
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
return new StepResponse(updated, transaction)
},
async (transaction, { container }) => {
if (!transaction) {
return
}
await updateFulfillmentWorkflow(container).cancel({ transaction })
}
)

View File

@@ -5,7 +5,7 @@ import {
transform,
} from "@medusajs/workflows-sdk"
import { validateShipmentStep } from "../steps"
import { updateFulfillmentWorkflowStep } from "../steps/update-fulfillment-workflow"
import { updateFulfillmentWorkflow } from "./update-fulfillment"
export const createShipmentWorkflowId = "create-shipment-workflow"
export const createShipmentWorkflow = createWorkflow(
@@ -20,6 +20,8 @@ export const createShipmentWorkflow = createWorkflow(
shipped_at: new Date(),
}))
updateFulfillmentWorkflowStep(update)
updateFulfillmentWorkflow.runAsStep({
input: update,
})
}
)

View File

@@ -0,0 +1,50 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IOrderModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type CompleteOrdersStepInput = {
orderIds: string[]
}
export const cancelOrdersStepId = "cancel-orders"
export const cancelOrdersStep = createStep(
cancelOrdersStepId,
async (data: CompleteOrdersStepInput, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
const orders = await service.list(
{
id: data.orderIds,
},
{
select: ["id", "status"],
}
)
const canceled = await service.cancel(data.orderIds)
return new StepResponse(
canceled,
canceled.map((order) => {
const prevData = orders.find((o) => o.id === order.id)!
return {
id: order.id,
status: prevData.status,
canceled_at: null,
}
})
)
},
async (canceled, { container }) => {
if (!canceled?.length) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.update(canceled)
}
)

View File

@@ -14,13 +14,23 @@ export const completeOrdersStep = createStep(
ModuleRegistrationName.ORDER
)
const orders = await service.list(
{
id: data.orderIds,
},
{
select: ["id", "status"],
}
)
const completed = await service.completeOrder(data.orderIds)
return new StepResponse(
completed,
completed.map((store) => {
completed.map((order) => {
const prevData = orders.find((o) => o.id === order.id)!
return {
id: store.id,
status: store.status,
id: order.id,
status: prevData.status,
}
})
)

View File

@@ -1,4 +1,5 @@
export * from "./archive-orders"
export * from "./cancel-orders"
export * from "./complete-orders"
export * from "./create-orders"
export * from "./get-item-tax-lines"

View File

@@ -5,7 +5,7 @@ export function throwIfOrderIsCancelled({ order }: { order: OrderDTO }) {
if (order.status === OrderStatus.CANCELED) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order with id ${order.id} has been cancelled.`
`Order with id ${order.id} has been canceled.`
)
}
}

View File

@@ -0,0 +1,130 @@
import {
FulfillmentDTO,
OrderDTO,
OrderWorkflow,
PaymentCollectionDTO,
} from "@medusajs/types"
import { MedusaError, deepFlatMap } from "@medusajs/utils"
import {
WorkflowData,
createStep,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../common"
import { cancelPaymentStep } from "../../payment/steps"
import { deleteReservationsByLineItemsStep } from "../../reservation/steps"
import { cancelOrdersStep } from "../steps/cancel-orders"
import { throwIfOrderIsCancelled } from "../utils/order-validation"
const validateOrder = createStep(
"validate-order",
({
order,
}: {
order: OrderDTO
input: OrderWorkflow.CancelOrderWorkflowInput
}) => {
const order_ = order as OrderDTO & {
payment_collections: PaymentCollectionDTO[]
fulfillments: FulfillmentDTO[]
}
throwIfOrderIsCancelled({ order })
let refunds = 0
let captures = 0
deepFlatMap(order_, "payment_collections.payments", ({ payments }) => {
refunds += payments?.refunds?.length ?? 0
captures += payments?.captures?.length ?? 0
})
if (captures > 0) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Order with payment capture(s) cannot be canceled"
)
}
if (refunds > 0) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Order with payment refund(s) cannot be canceled"
)
}
const throwErrorIf = (
arr: unknown[],
pred: (obj: any) => boolean,
type: string
) => {
if (arr?.filter(pred).length) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`All ${type} must be canceled before canceling an order`
)
}
}
const notCanceled = (o) => !o.canceled_at
throwErrorIf(order_.fulfillments, notCanceled, "fulfillments")
/*
TODO: relationship between order and returns, swaps, claims
throwErrorIf(
order_.returns,
(ret) => ret.status !== "canceled",
"returns"
)
throwErrorIf(order_.swaps, notCanceled, "swaps")
throwErrorIf(order_.claims, notCanceled, "claims")
*/
}
)
export const cancelOrderWorkflowId = "cancel-order"
export const cancelOrderWorkflow = createWorkflow(
cancelOrderWorkflowId,
(
input: WorkflowData<OrderWorkflow.CancelOrderWorkflowInput>
): WorkflowData<void> => {
const order: OrderDTO & { fulfillments: FulfillmentDTO[] } =
useRemoteQueryStep({
entry_point: "orders",
fields: [
"id",
"status",
"items.id",
"fulfillments.canceled_at",
"payment_collections.payments.id",
"payment_collections.payments.refunds.id",
"payment_collections.payments.captures.id",
],
variables: { id: input.order_id },
list: false,
throw_if_key_not_found: true,
})
validateOrder({ order, input })
const lineItemIds = transform({ order }, ({ order }) => {
return order.items?.map((i) => i.id)
})
deleteReservationsByLineItemsStep(lineItemIds)
const paymentIds = transform({ order }, ({ order }) => {
return deepFlatMap(
order,
"payment_collections.payments",
({ payments }) => {
return payments?.id
}
)
})
cancelPaymentStep({ paymentIds })
cancelOrdersStep({ orderIds: [order.id] })
}
)

View File

@@ -1,4 +1,5 @@
export * from "./archive-orders"
export * from "./cancel-order"
export * from "./cancel-order-fulfillment"
export * from "./complete-orders"
export * from "./create-fulfillment"

View File

@@ -0,0 +1,35 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPaymentModuleService, Logger } from "@medusajs/types"
import { ContainerRegistrationKeys, promiseAll } from "@medusajs/utils"
import { createStep } from "@medusajs/workflows-sdk"
type StepInput = {
paymentIds: string | string[]
}
export const cancelPaymentStepId = "cancel-payment-step"
export const cancelPaymentStep = createStep(
cancelPaymentStepId,
async (input: StepInput, { container }) => {
const logger = container.resolve<Logger>(ContainerRegistrationKeys.LOGGER)
const paymentModule = container.resolve<IPaymentModuleService>(
ModuleRegistrationName.PAYMENT
)
const paymentIds = Array.isArray(input.paymentIds)
? input.paymentIds
: [input.paymentIds]
const promises: Promise<any>[] = []
for (const id of paymentIds) {
promises.push(
paymentModule.cancelPayment(id).catch((e) => {
logger.error(
`Error was thrown trying to cancel payment - ${id} - ${e}`
)
})
)
}
await promiseAll(promises)
}
)

View File

@@ -1,3 +1,3 @@
export * from "./cancel-payment"
export * from "./capture-payment"
export * from "./refund-payment"

View File

@@ -0,0 +1,30 @@
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IInventoryServiceNext } from "@medusajs/types"
export const deleteReservationsByLineItemsStepId =
"delete-reservations-by-line-items"
export const deleteReservationsByLineItemsStep = createStep(
deleteReservationsByLineItemsStepId,
async (ids: string[], { container }) => {
const service = container.resolve<IInventoryServiceNext>(
ModuleRegistrationName.INVENTORY
)
await service.deleteReservationItemsByLineItem(ids)
return new StepResponse(void 0, ids)
},
async (prevIds, { container }) => {
if (!prevIds?.length) {
return
}
const service = container.resolve<IInventoryServiceNext>(
ModuleRegistrationName.INVENTORY
)
await service.restoreReservationItemsByLineItem(prevIds)
}
)

View File

@@ -1,7 +1,7 @@
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { IInventoryServiceNext } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IInventoryServiceNext } from "@medusajs/types"
export const deleteReservationsStepId = "delete-reservations"
export const deleteReservationsStep = createStep(

View File

@@ -1,3 +1,4 @@
export * from "./create-reservations"
export * from "./delete-reservations"
export * from "./delete-reservations-by-line-items"
export * from "./update-reservations"

View File

@@ -0,0 +1,14 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteReservationsByLineItemsStep } from "../steps"
type WorkflowInput = { ids: string[] }
export const deleteReservationsByLineItemsWorkflowId =
"delete-reservations-by-line-items"
export const deleteReservationsByLineItemsWorkflow = createWorkflow(
deleteReservationsByLineItemsWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
return deleteReservationsByLineItemsStep(input.ids)
}
)

View File

@@ -1,3 +1,4 @@
export * from "./create-reservations"
export * from "./delete-reservations"
export * from "./delete-reservations-by-line-items"
export * from "./update-reservations"