feat(core-flows,js-sdk,medusa,types): draft order delete (#12172)

This commit is contained in:
Frane Polić authored and GitHub committed 2025-05-28 14:37:00 +02:00
1 parent 52eebcee6f
commit 9866baa852
11 files changed
+521 -2

No files matched your search

@@ -0,0 +1,26 @@
import { IOrderModuleService } from "@medusajs/framework/types"
import { createStep } from "@medusajs/framework/workflows-sdk"
import { Modules } from "@medusajs/framework/utils"
/**
* The details of canceling the orders.
*/
export type DeleteDraftOrdersStepInput = {
/**
* The IDs of the orders to delete.
*/
orderIds: string[]
}
export const deleteDraftOrdersStepId = "delete-draft-orders"
/**
* This step deletes one or more draft orders.
*/
export const deleteDraftOrdersStep = createStep(
deleteDraftOrdersStepId,
async (data: DeleteDraftOrdersStepInput, { container }) => {
const service = container.resolve<IOrderModuleService>(Modules.ORDER)
await service.deleteOrders(data.orderIds)
}
)
@@ -1 +1,2 @@
export * from "./validate-draft-order"
export * from "./delete-draft-order"
@@ -0,0 +1,85 @@
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
import { OrderDTO } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { removeRemoteLinkStep, useQueryGraphStep } from "../../common"
import { deleteDraftOrdersStep } from "../steps"
/**
* The data to validate the order's cancelation.
*/
export type DeleteDraftOrderStepInput = {
/**
* The order ids to delete.
*/
order_ids: string[]
}
const validateDraftOrdersStep = createStep(
"validate-draft-orders",
async (data: { orders: OrderDTO[] }) => {
if (
data.orders.some(
(order) => order.status !== "draft" || !order.is_draft_order
)
) {
throw new Error("One or more orders are not draft")
}
if (data.orders.some((order) => order.deleted_at)) {
throw new Error("One or more orders are already deleted")
}
}
)
export const deleteDraftOrderWorkflowId = "delete-draft-order"
/**
* This workflow deletes draft orders.
*
* You can also use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around canceling an order.
*
* @example
* const { result } = await deleteDraftOrderWorkflow(container)
* .run({
* input: {
* order_ids: ["order_123", "order_456"],
* }
* })
*
* @summary
*
* Delete draft orders.
*
* @property hooks.orderCanceled - This hook is executed after the order is canceled. You can consume this hook to perform custom actions on the canceled order.
*/
export const deleteDraftOrdersWorkflow = createWorkflow(
deleteDraftOrderWorkflowId,
(input: WorkflowData<DeleteDraftOrderStepInput>) => {
const orderQuery = useQueryGraphStep({
entity: "orders",
fields: ["id", "status", "is_draft_order", "deleted_at"],
filters: { id: input.order_ids },
options: { throwIfKeyNotFound: true },
}).config({ name: "get-draft-order" })
const orders = transform({ orderQuery }, ({ orderQuery }) => {
return orderQuery.data
})
validateDraftOrdersStep({ orders })
removeRemoteLinkStep({
[Modules.ORDER]: { order_id: input.order_ids },
})
deleteDraftOrdersStep({ orderIds: input.order_ids })
return new WorkflowResponse(void 0)
}
)
@@ -15,3 +15,4 @@ export * from "./update-draft-order-action-shipping-method"
export * from "./update-draft-order-item"
export * from "./update-draft-order-shipping-method"
export * from "./remove-draft-order-shipping-method"
export * from "./delete-draft-order"
@@ -162,6 +162,29 @@ export class DraftOrder {
)
}
/**
* This method deletes a draft order. It sends a request to the
* [Delete Draft Order](https://docs.medusajs.com/api/admin#draft-orders_deleteordereditsid) API route.
*
* @param id - The draft order's ID.
* @param headers - Headers to pass in the request.
*
* @example
* sdk.admin.draftOrder.delete("order_123")
* .then(({ id, object, deleted }) => {
* console.log(id, object, deleted)
* })
*/
async delete(id: string, headers?: ClientHeaders) {
return await this.client.fetch<HttpTypes.DeleteResponse<"draft-order">>(
`/admin/draft-orders/${id}`,
{
method: "DELETE",
headers,
}
)
}
/**
* This method updates a draft order. It sends a request to the
* [Update Draft Order](https://docs.medusajs.com/api/admin#draft-orders_postdraftordersid) API route.
+5
View File
@@ -1147,6 +1147,11 @@ export interface OrderDTO {
*/
updated_at: string | Date
/**
* When the order was deleted.
*/
deleted_at?: string | Date
/**
* The original item total of the order.
*/
@@ -1,6 +1,7 @@
import {
getOrderDetailWorkflow,
updateDraftOrderWorkflow,
deleteDraftOrdersWorkflow,
} from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
@@ -54,3 +55,22 @@ export const POST = async (
.status(200)
.json({ draft_order: result.data[0] as HttpTypes.AdminDraftOrder })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const { id } = req.params
await deleteDraftOrdersWorkflow(req.scope).run({
input: {
order_ids: [id],
},
})
res.status(200).json({
id,
object: "draft-order",
deleted: true,
})
}
@@ -0,0 +1,21 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20250522181137 extends Migration {
override async up(): Promise<void> {
this.addSql(
`DELETE FROM "order_summary" WHERE "order_id" NOT IN (SELECT id FROM "order");`
)
this.addSql(`ALTER TABLE "order_summary"
ADD CONSTRAINT
"order_summary_order_id_foreign" FOREIGN KEY ("order_id") REFERENCES "order" ("id")
ON UPDATE CASCADE
ON DELETE CASCADE;`)
}
override async down(): Promise<void> {
this.addSql(
`ALTER TABLE "order_summary" DROP CONSTRAINT IF EXISTS "order_summary_order_id_foreign";`
)
}
}
@@ -728,7 +728,14 @@ export default class OrderModuleService
const creditLinesToCreate: CreateOrderCreditLineDTO[] = []
const createdOrders: InferEntityType<typeof Order>[] = []
for (const { items, shipping_methods, credit_lines, ...order } of data) {
for (const {
items,
shipping_methods,
credit_lines,
shipping_address,
billing_address,
...order
} of data) {
const ord = order as any
const shippingMethods = shipping_methods?.map((sm: any) => {
@@ -844,6 +851,70 @@ export default class OrderModuleService
})
}
@InjectTransactionManager()
// @ts-expect-error
async deleteOrders(
orderIds: string | string[],
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
const ids = Array.isArray(orderIds) ? orderIds : [orderIds]
const orders = await this.orderService_.list(
{ id: ids },
{
select: ["id", "shipping_address_id", "billing_address_id"],
},
sharedContext
)
const orderAddressIds = orders
.map((order) => [order.shipping_address_id, order.billing_address_id])
.flat(1)
const orderChanges = await this.orderChangeService_.list(
{ order_id: ids },
{ select: ["id"] },
sharedContext
)
const orderChangeIds = orderChanges.map((orderChange) => orderChange.id)
const orderItems = await this.orderItemService_.list(
{ order_id: ids },
{ select: ["id", "item_id"] },
sharedContext
)
const lineItemIds = orderItems.map((orderItem) => orderItem.item_id)
const orderShipping = await this.orderShippingService_.list(
{ order_id: ids },
{ select: ["shipping_method_id"] },
sharedContext
)
const orderShippingMethodIds = orderShipping.map(
(orderShipping) => orderShipping.shipping_method_id
)
await promiseAll([
this.orderAddressService_.delete(orderAddressIds, sharedContext),
// Delete order changes & actions
this.orderChangeService_.delete(orderChangeIds, sharedContext),
])
// Delete order, order items, summary, shipping methods and transactions
await super.deleteOrders(ids, sharedContext)
await promiseAll([
this.orderLineItemService_.delete(lineItemIds, sharedContext),
this.orderShippingMethodService_.delete(
orderShippingMethodIds,
sharedContext
),
])
}
// @ts-expect-error
async updateOrders(
data: OrderTypes.UpdateOrderDTO[]