feat(core-flows,medusa,order,types): update orders (#10373)
**What** - add order update endpoint - add workflows and steps for updating orders - add `registerChanges` method to Order module + workflow step --- CLOSES CMRC-633
This commit is contained in:
@@ -199,6 +199,14 @@ export async function createOrderSeeder({
|
|||||||
province: "ny",
|
province: "ny",
|
||||||
postal_code: "94016",
|
postal_code: "94016",
|
||||||
},
|
},
|
||||||
|
billing_address: {
|
||||||
|
address_1: "test billing address 1",
|
||||||
|
address_2: "test billing address 2",
|
||||||
|
city: "ny",
|
||||||
|
country_code: "us",
|
||||||
|
province: "ny",
|
||||||
|
postal_code: "94016",
|
||||||
|
},
|
||||||
sales_channel_id: salesChannel.id,
|
sales_channel_id: salesChannel.id,
|
||||||
items: [
|
items: [
|
||||||
{ quantity: 1, variant_id: product.variants[0].id },
|
{ quantity: 1, variant_id: product.variants[0].id },
|
||||||
|
|||||||
@@ -20,6 +20,282 @@ medusaIntegrationTestRunner({
|
|||||||
await createAdminUser(dbConnection, adminHeaders, container)
|
await createAdminUser(dbConnection, adminHeaders, container)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("POST /orders/:id", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
seeder = await createOrderSeeder({
|
||||||
|
api,
|
||||||
|
container: getContainer(),
|
||||||
|
})
|
||||||
|
order = seeder.order
|
||||||
|
|
||||||
|
order = (
|
||||||
|
await api.get(`/admin/orders/${order.id}?fields=+email`, adminHeaders)
|
||||||
|
).data.order
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update shipping address on an order (by creating a new Address record)", async () => {
|
||||||
|
const addressBefore = order.shipping_address
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/orders/${order.id}`,
|
||||||
|
{
|
||||||
|
shipping_address: {
|
||||||
|
city: "New New York",
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.order.shipping_address.id).not.toEqual(
|
||||||
|
addressBefore.id
|
||||||
|
) // new addres created
|
||||||
|
expect(response.data.order.shipping_address).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
customer_id: addressBefore.customer_id,
|
||||||
|
company: addressBefore.company,
|
||||||
|
first_name: addressBefore.first_name,
|
||||||
|
last_name: addressBefore.last_name,
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
address_2: addressBefore.address_2,
|
||||||
|
city: "New New York",
|
||||||
|
country_code: addressBefore.country_code,
|
||||||
|
province: addressBefore.province,
|
||||||
|
postal_code: addressBefore.postal_code,
|
||||||
|
phone: addressBefore.phone,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderChangesResult = (
|
||||||
|
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
|
||||||
|
).data.order_changes
|
||||||
|
|
||||||
|
expect(orderChangesResult.length).toEqual(1)
|
||||||
|
expect(orderChangesResult[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
change_type: "update_order",
|
||||||
|
status: "confirmed",
|
||||||
|
confirmed_at: expect.any(String),
|
||||||
|
actions: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
applied: true,
|
||||||
|
reference_id: addressBefore.id,
|
||||||
|
reference: "shipping_address",
|
||||||
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
|
details: {
|
||||||
|
city: "New New York",
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to update shipping address if country code has been changed", async () => {
|
||||||
|
const response = await api
|
||||||
|
.post(
|
||||||
|
`/admin/orders/${order.id}`,
|
||||||
|
{
|
||||||
|
shipping_address: {
|
||||||
|
country_code: "HR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.response.status).toBe(400)
|
||||||
|
expect(response.response.data.message).toBe(
|
||||||
|
"Country code cannot be changed"
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderChangesResult = (
|
||||||
|
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
|
||||||
|
).data.order_changes
|
||||||
|
|
||||||
|
expect(orderChangesResult.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update billing address on an order (by creating a new Address record)", async () => {
|
||||||
|
const addressBefore = order.billing_address
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/orders/${order.id}`,
|
||||||
|
{
|
||||||
|
billing_address: {
|
||||||
|
city: "New New York",
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.order.billing_address.id).not.toEqual(
|
||||||
|
addressBefore.id
|
||||||
|
) // new addres created
|
||||||
|
expect(response.data.order.billing_address).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
customer_id: addressBefore.customer_id,
|
||||||
|
company: addressBefore.company,
|
||||||
|
first_name: addressBefore.first_name,
|
||||||
|
last_name: addressBefore.last_name,
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
address_2: addressBefore.address_2,
|
||||||
|
city: "New New York",
|
||||||
|
country_code: addressBefore.country_code,
|
||||||
|
province: addressBefore.province,
|
||||||
|
postal_code: addressBefore.postal_code,
|
||||||
|
phone: addressBefore.phone,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderChangesResult = (
|
||||||
|
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
|
||||||
|
).data.order_changes
|
||||||
|
|
||||||
|
expect(orderChangesResult.length).toEqual(1)
|
||||||
|
expect(orderChangesResult[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
change_type: "update_order",
|
||||||
|
status: "confirmed",
|
||||||
|
confirmed_at: expect.any(String),
|
||||||
|
actions: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
applied: true,
|
||||||
|
reference_id: addressBefore.id,
|
||||||
|
reference: "billing_address",
|
||||||
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
|
details: {
|
||||||
|
city: "New New York",
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to update billing address if country code has been changed", async () => {
|
||||||
|
const response = await api
|
||||||
|
.post(
|
||||||
|
`/admin/orders/${order.id}`,
|
||||||
|
{
|
||||||
|
billing_address: {
|
||||||
|
country_code: "HR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.response.status).toBe(400)
|
||||||
|
expect(response.response.data.message).toBe(
|
||||||
|
"Country code cannot be changed"
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderChangesResult = (
|
||||||
|
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
|
||||||
|
).data.order_changes
|
||||||
|
|
||||||
|
expect(orderChangesResult.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update orders email and shipping address and create 2 change records", async () => {
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/orders/${order.id}?fields=+email,*shipping_address`,
|
||||||
|
{
|
||||||
|
email: "new-email@example.com",
|
||||||
|
shipping_address: {
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.order.email).toBe("new-email@example.com")
|
||||||
|
expect(response.data.order.shipping_address.id).not.toEqual(
|
||||||
|
order.shipping_address.id
|
||||||
|
)
|
||||||
|
expect(response.data.order.shipping_address).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderChangesResult = (
|
||||||
|
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
|
||||||
|
).data.order_changes
|
||||||
|
|
||||||
|
expect(orderChangesResult.length).toEqual(2)
|
||||||
|
expect(orderChangesResult).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
change_type: "update_order",
|
||||||
|
status: "confirmed",
|
||||||
|
confirmed_at: expect.any(String),
|
||||||
|
actions: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
applied: true,
|
||||||
|
reference_id: order.shipping_address.id,
|
||||||
|
reference: "shipping_address",
|
||||||
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
|
details: {
|
||||||
|
address_1: "New Main street 123",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
change_type: "update_order",
|
||||||
|
status: "confirmed",
|
||||||
|
confirmed_at: expect.any(String),
|
||||||
|
actions: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
version: 1,
|
||||||
|
applied: true,
|
||||||
|
reference_id: order.email,
|
||||||
|
reference: "email",
|
||||||
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
|
details: {
|
||||||
|
email: "new-email@example.com",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to update email if it is invalid", async () => {
|
||||||
|
const response = await api
|
||||||
|
.post(
|
||||||
|
`/admin/orders/${order.id}`,
|
||||||
|
{
|
||||||
|
email: "invalid-email",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.response.status).toBe(400)
|
||||||
|
expect(response.response.data.message).toBe("The email is not valid")
|
||||||
|
|
||||||
|
const orderChangesResult = (
|
||||||
|
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
|
||||||
|
).data.order_changes
|
||||||
|
|
||||||
|
expect(orderChangesResult.length).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("POST /orders/:id/fulfillments", () => {
|
describe("POST /orders/:id/fulfillments", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const stockChannelOverride = (
|
const stockChannelOverride = (
|
||||||
|
|||||||
@@ -34,4 +34,5 @@ export * from "./update-order-change-actions"
|
|||||||
export * from "./update-order-changes"
|
export * from "./update-order-changes"
|
||||||
export * from "./update-order-exchanges"
|
export * from "./update-order-exchanges"
|
||||||
export * from "./update-shipping-methods"
|
export * from "./update-shipping-methods"
|
||||||
|
export * from "./update-orders"
|
||||||
|
export * from "./register-order-changes"
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
IOrderModuleService,
|
||||||
|
RegisterOrderChangeDTO,
|
||||||
|
} from "@medusajs/framework/types"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/framework/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||||
|
|
||||||
|
export const registerOrderChangeStepId = "register-order-change"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step registers an order changes.
|
||||||
|
*/
|
||||||
|
export const registerOrderChangesStep = createStep(
|
||||||
|
registerOrderChangeStepId,
|
||||||
|
async (data: RegisterOrderChangeDTO[], { container }) => {
|
||||||
|
const service = container.resolve<IOrderModuleService>(
|
||||||
|
ModuleRegistrationName.ORDER
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderChanges = await service.registerOrderChange(data)
|
||||||
|
|
||||||
|
return new StepResponse(
|
||||||
|
void 0,
|
||||||
|
orderChanges.map((c) => c.id)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async (orderChangeIds, { container }) => {
|
||||||
|
if (!orderChangeIds?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<IOrderModuleService>(
|
||||||
|
ModuleRegistrationName.ORDER
|
||||||
|
)
|
||||||
|
|
||||||
|
await service.deleteOrderChanges(orderChangeIds)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
FilterableOrderProps,
|
||||||
|
IOrderModuleService,
|
||||||
|
UpdateOrderDTO,
|
||||||
|
} from "@medusajs/framework/types"
|
||||||
|
import {
|
||||||
|
Modules,
|
||||||
|
getSelectsAndRelationsFromObjectArray,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||||
|
|
||||||
|
export type UpdateOrdersStepInput = {
|
||||||
|
selector: FilterableOrderProps
|
||||||
|
update: UpdateOrderDTO // TODO: Update to UpdateOrderDTO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateOrdersStepId = "update-orders"
|
||||||
|
/**
|
||||||
|
* This step updates orders matching the specified filters.
|
||||||
|
*/
|
||||||
|
export const updateOrdersStep = createStep(
|
||||||
|
updateOrdersStepId,
|
||||||
|
async (data: UpdateOrdersStepInput, { container }) => {
|
||||||
|
const service = container.resolve<IOrderModuleService>(Modules.ORDER)
|
||||||
|
|
||||||
|
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||||
|
data.update,
|
||||||
|
])
|
||||||
|
|
||||||
|
const prevData = await service.listOrders(data.selector, {
|
||||||
|
select: selects,
|
||||||
|
relations,
|
||||||
|
})
|
||||||
|
|
||||||
|
const orders = await service.updateOrders(data.selector, data.update)
|
||||||
|
|
||||||
|
return new StepResponse(orders, prevData)
|
||||||
|
},
|
||||||
|
async (prevData, { container }) => {
|
||||||
|
if (!prevData?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<IOrderModuleService>(Modules.ORDER)
|
||||||
|
|
||||||
|
await service.updateOrders(prevData as UpdateOrderDTO[])
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -82,3 +82,4 @@ export * from "./transfer/request-order-transfer"
|
|||||||
export * from "./transfer/accept-order-transfer"
|
export * from "./transfer/accept-order-transfer"
|
||||||
export * from "./transfer/cancel-order-transfer"
|
export * from "./transfer/cancel-order-transfer"
|
||||||
export * from "./transfer/decline-order-transfer"
|
export * from "./transfer/decline-order-transfer"
|
||||||
|
export * from "./update-order"
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import { OrderDTO, OrderWorkflow } from "@medusajs/framework/types"
|
||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
WorkflowResponse,
|
||||||
|
createStep,
|
||||||
|
createWorkflow,
|
||||||
|
transform,
|
||||||
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import {
|
||||||
|
OrderPreviewDTO,
|
||||||
|
RegisterOrderChangeDTO,
|
||||||
|
UpdateOrderDTO,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
MedusaError,
|
||||||
|
OrderWorkflowEvents,
|
||||||
|
validateEmail,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
|
|
||||||
|
import { throwIfOrderIsCancelled } from "../utils/order-validation"
|
||||||
|
import {
|
||||||
|
previewOrderChangeStep,
|
||||||
|
registerOrderChangesStep,
|
||||||
|
updateOrdersStep,
|
||||||
|
} from "../steps"
|
||||||
|
import { emitEventStep, useQueryGraphStep } from "../../common"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step validates that an order can be updated with provided input.
|
||||||
|
*/
|
||||||
|
export const updateOrderValidationStep = createStep(
|
||||||
|
"update-order-validation",
|
||||||
|
async function ({
|
||||||
|
order,
|
||||||
|
input,
|
||||||
|
}: {
|
||||||
|
order: OrderDTO
|
||||||
|
input: OrderWorkflow.UpdateOrderWorkflowInput
|
||||||
|
}) {
|
||||||
|
throwIfOrderIsCancelled({ order })
|
||||||
|
|
||||||
|
if (
|
||||||
|
input.shipping_address?.country_code &&
|
||||||
|
order.shipping_address?.country_code !==
|
||||||
|
input.shipping_address?.country_code
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Country code cannot be changed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
input.billing_address?.country_code &&
|
||||||
|
order.billing_address?.country_code !==
|
||||||
|
input.billing_address?.country_code
|
||||||
|
) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Country code cannot be changed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.email) {
|
||||||
|
validateEmail(input.email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const updateOrderWorkflowId = "update-order-workflow"
|
||||||
|
/**
|
||||||
|
* Update order workflow.
|
||||||
|
*/
|
||||||
|
export const updateOrderWorkflow = createWorkflow(
|
||||||
|
updateOrderWorkflowId,
|
||||||
|
function (
|
||||||
|
input: WorkflowData<OrderWorkflow.UpdateOrderWorkflowInput>
|
||||||
|
): WorkflowResponse<OrderPreviewDTO> {
|
||||||
|
const orderQuery = useQueryGraphStep({
|
||||||
|
entity: "order",
|
||||||
|
fields: [
|
||||||
|
"id",
|
||||||
|
"status",
|
||||||
|
"email",
|
||||||
|
"shipping_address.*",
|
||||||
|
"billing_address.*",
|
||||||
|
],
|
||||||
|
filters: { id: input.id },
|
||||||
|
options: { throwIfKeyNotFound: true },
|
||||||
|
}).config({ name: "order-query" })
|
||||||
|
|
||||||
|
const order = transform(
|
||||||
|
{ orderQuery },
|
||||||
|
({ orderQuery }) => orderQuery.data[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
updateOrderValidationStep({ order, input })
|
||||||
|
|
||||||
|
const updateInput = transform({ input, order }, ({ input, order }) => {
|
||||||
|
const update: UpdateOrderDTO = {}
|
||||||
|
|
||||||
|
if (input.shipping_address) {
|
||||||
|
const address = {
|
||||||
|
// we want to create a new address
|
||||||
|
...order.shipping_address,
|
||||||
|
...input.shipping_address,
|
||||||
|
}
|
||||||
|
delete address.id
|
||||||
|
update.shipping_address = address
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.billing_address) {
|
||||||
|
const address = {
|
||||||
|
...order.billing_address,
|
||||||
|
...input.billing_address,
|
||||||
|
}
|
||||||
|
delete address.id
|
||||||
|
update.billing_address = address
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...input, ...update }
|
||||||
|
})
|
||||||
|
|
||||||
|
updateOrdersStep({
|
||||||
|
selector: { id: input.id },
|
||||||
|
update: updateInput,
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderChangeInput = transform({ input, order }, ({ input, order }) => {
|
||||||
|
const changes: RegisterOrderChangeDTO[] = []
|
||||||
|
if (input.shipping_address) {
|
||||||
|
changes.push({
|
||||||
|
change_type: "update_order" as const,
|
||||||
|
order_id: input.id,
|
||||||
|
reference: "shipping_address",
|
||||||
|
reference_id: order.shipping_address?.id, // save previous address id as reference
|
||||||
|
details: input.shipping_address as Record<string, unknown>, // save what changed on the address
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.billing_address) {
|
||||||
|
changes.push({
|
||||||
|
change_type: "update_order" as const,
|
||||||
|
order_id: input.id,
|
||||||
|
reference: "billing_address",
|
||||||
|
reference_id: order.billing_address?.id,
|
||||||
|
details: input.billing_address as Record<string, unknown>,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.email) {
|
||||||
|
changes.push({
|
||||||
|
change_type: "update_order" as const,
|
||||||
|
order_id: input.id,
|
||||||
|
reference: "email",
|
||||||
|
reference_id: order.email,
|
||||||
|
details: { email: input.email },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes
|
||||||
|
})
|
||||||
|
|
||||||
|
registerOrderChangesStep(orderChangeInput)
|
||||||
|
|
||||||
|
emitEventStep({
|
||||||
|
eventName: OrderWorkflowEvents.UPDATED,
|
||||||
|
data: { id: input.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
return new WorkflowResponse(previewOrderChangeStep(input.id))
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -25,6 +25,7 @@ export type ChangeActionType =
|
|||||||
| "WRITE_OFF_ITEM"
|
| "WRITE_OFF_ITEM"
|
||||||
| "REINSTATE_ITEM"
|
| "REINSTATE_ITEM"
|
||||||
| "TRANSFER_CUSTOMER"
|
| "TRANSFER_CUSTOMER"
|
||||||
|
| "UPDATE_ORDER_PROPERTIES"
|
||||||
|
|
||||||
export type OrderChangeStatus =
|
export type OrderChangeStatus =
|
||||||
| "confirmed"
|
| "confirmed"
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ import {
|
|||||||
ReturnDTO,
|
ReturnDTO,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
|
|
||||||
|
type OrderChangeType =
|
||||||
|
| "return_request"
|
||||||
|
| "return_receive"
|
||||||
|
| "exchange"
|
||||||
|
| "claim"
|
||||||
|
| "edit"
|
||||||
|
| "transfer"
|
||||||
|
| "update_order"
|
||||||
|
|
||||||
/** ADDRESS START */
|
/** ADDRESS START */
|
||||||
/**
|
/**
|
||||||
* The data to create or update in the address.
|
* The data to create or update in the address.
|
||||||
@@ -860,13 +869,7 @@ export interface CreateOrderChangeDTO {
|
|||||||
/**
|
/**
|
||||||
* The type of the order change.
|
* The type of the order change.
|
||||||
*/
|
*/
|
||||||
change_type?:
|
change_type?: OrderChangeType
|
||||||
| "return_request"
|
|
||||||
| "return_receive"
|
|
||||||
| "exchange"
|
|
||||||
| "claim"
|
|
||||||
| "edit"
|
|
||||||
| "transfer"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The description of the order change.
|
* The description of the order change.
|
||||||
@@ -1049,6 +1052,57 @@ export interface ConfirmOrderChangeDTO {
|
|||||||
metadata?: Record<string, unknown> | null
|
metadata?: Record<string, unknown> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The details of the order change registration.
|
||||||
|
*/
|
||||||
|
export interface RegisterOrderChangeDTO {
|
||||||
|
/**
|
||||||
|
* The associated order's ID.
|
||||||
|
*/
|
||||||
|
order_id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the order change.
|
||||||
|
*/
|
||||||
|
change_type?: OrderChangeType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The description of the order change.
|
||||||
|
*/
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal note of the order change.
|
||||||
|
*/
|
||||||
|
internal_note?: string | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user or customer that requested the order change.
|
||||||
|
*/
|
||||||
|
requested_by?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds custom data in key-value pairs.
|
||||||
|
*/
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The details of the order change action
|
||||||
|
*/
|
||||||
|
details?: Record<string, unknown>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the data model that this change
|
||||||
|
* references. For example, `shipping_address`.
|
||||||
|
*/
|
||||||
|
reference?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the data model's record referenced.
|
||||||
|
*/
|
||||||
|
reference_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
/** ORDER CHANGE END */
|
/** ORDER CHANGE END */
|
||||||
/** ORDER CHANGE ACTION START */
|
/** ORDER CHANGE ACTION START */
|
||||||
/**
|
/**
|
||||||
@@ -1124,6 +1178,11 @@ export interface CreateOrderChangeActionDTO {
|
|||||||
* quantity, based on the type of this action.
|
* quantity, based on the type of this action.
|
||||||
*/
|
*/
|
||||||
details?: Record<string, unknown>
|
details?: Record<string, unknown>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the action has been applied.
|
||||||
|
*/
|
||||||
|
applied?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ import {
|
|||||||
CreateOrderTransactionDTO,
|
CreateOrderTransactionDTO,
|
||||||
DeclineOrderChangeDTO,
|
DeclineOrderChangeDTO,
|
||||||
ReceiveOrderReturnDTO,
|
ReceiveOrderReturnDTO,
|
||||||
|
RegisterOrderChangeDTO,
|
||||||
RegisterOrderDeliveryDTO,
|
RegisterOrderDeliveryDTO,
|
||||||
RegisterOrderFulfillmentDTO,
|
RegisterOrderFulfillmentDTO,
|
||||||
RegisterOrderShipmentDTO,
|
RegisterOrderShipmentDTO,
|
||||||
@@ -2710,6 +2711,44 @@ export interface IOrderModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<void>
|
): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method registers an order change.
|
||||||
|
*
|
||||||
|
* @param {RegisterOrderChangeDTO} data - The register order change details.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<OrderChangeReturn>} The item and shipping method changes made on the order.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await orderModuleService.registerOrderChange({
|
||||||
|
* order_id: "123",
|
||||||
|
* details: Record<string, unknown>
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
registerOrderChange(
|
||||||
|
data: RegisterOrderChangeDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<OrderChangeDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method registers order changes.
|
||||||
|
*
|
||||||
|
* @param {RegisterOrderChangeDTO[]} data - The register order changes details.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<OrderChangeReturn[]>} The item and shipping method changes made on the orders.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await orderModuleService.registerOrderChange({
|
||||||
|
* order_id: "123",
|
||||||
|
* details: Record<string, unknown>
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
registerOrderChange(
|
||||||
|
data: RegisterOrderChangeDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<OrderChangeDTO[]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method soft deletes order changes by their IDs.
|
* This method soft deletes order changes by their IDs.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ export * from "./request-transfer"
|
|||||||
export * from "./accept-transfer"
|
export * from "./accept-transfer"
|
||||||
export * from "./cancel-transfer"
|
export * from "./cancel-transfer"
|
||||||
export * from "./decline-transfer"
|
export * from "./decline-transfer"
|
||||||
|
export * from "./update-order"
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { UpsertOrderAddressDTO } from "../../order"
|
||||||
|
|
||||||
|
export type UpdateOrderWorkflowInput = {
|
||||||
|
id: string
|
||||||
|
shipping_address?: UpsertOrderAddressDTO
|
||||||
|
billing_address?: UpsertOrderAddressDTO
|
||||||
|
email?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateOrderShippingAddressWorkflowInput = {
|
||||||
|
order_id: string
|
||||||
|
shipping_address: UpsertOrderAddressDTO
|
||||||
|
description?: string
|
||||||
|
internal_note?: string
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ export const CustomerWorkflowEvents = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const OrderWorkflowEvents = {
|
export const OrderWorkflowEvents = {
|
||||||
|
UPDATED: "order.updated",
|
||||||
|
|
||||||
PLACED: "order.placed",
|
PLACED: "order.placed",
|
||||||
CANCELED: "order.canceled",
|
CANCELED: "order.canceled",
|
||||||
COMPLETED: "order.completed",
|
COMPLETED: "order.completed",
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ export enum ChangeActionType {
|
|||||||
WRITE_OFF_ITEM = "WRITE_OFF_ITEM",
|
WRITE_OFF_ITEM = "WRITE_OFF_ITEM",
|
||||||
REINSTATE_ITEM = "REINSTATE_ITEM",
|
REINSTATE_ITEM = "REINSTATE_ITEM",
|
||||||
TRANSFER_CUSTOMER = "TRANSFER_CUSTOMER",
|
TRANSFER_CUSTOMER = "TRANSFER_CUSTOMER",
|
||||||
|
UPDATE_ORDER_PROPERTIES = "UPDATE_ORDER_PROPERTIES",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import { getOrderDetailWorkflow } from "@medusajs/core-flows"
|
import {
|
||||||
|
getOrderDetailWorkflow,
|
||||||
|
updateOrderWorkflow,
|
||||||
|
} from "@medusajs/core-flows"
|
||||||
import {
|
import {
|
||||||
AuthenticatedMedusaRequest,
|
AuthenticatedMedusaRequest,
|
||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "@medusajs/framework/http"
|
} from "@medusajs/framework/http"
|
||||||
import { HttpTypes } from "@medusajs/framework/types"
|
import { AdminOrder, HttpTypes } from "@medusajs/framework/types"
|
||||||
import { AdminGetOrdersOrderParamsType } from "../validators"
|
import {
|
||||||
|
AdminGetOrdersOrderParamsType,
|
||||||
|
AdminUpdateOrderType,
|
||||||
|
} from "../validators"
|
||||||
|
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
req: AuthenticatedMedusaRequest<AdminGetOrdersOrderParamsType>,
|
req: AuthenticatedMedusaRequest<AdminGetOrdersOrderParamsType>,
|
||||||
@@ -21,3 +28,25 @@ export const GET = async (
|
|||||||
|
|
||||||
res.status(200).json({ order: result as HttpTypes.AdminOrder })
|
res.status(200).json({ order: result as HttpTypes.AdminOrder })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminUpdateOrderType>,
|
||||||
|
res: MedusaResponse<HttpTypes.AdminOrderResponse>
|
||||||
|
) => {
|
||||||
|
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||||
|
|
||||||
|
await updateOrderWorkflow(req.scope).run({
|
||||||
|
input: {
|
||||||
|
...req.validatedBody,
|
||||||
|
id: req.params.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await query.graph({
|
||||||
|
entity: "order",
|
||||||
|
filters: { id: req.params.id },
|
||||||
|
fields: req.remoteQueryConfig.fields,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.status(200).json({ order: result.data[0] as AdminOrder })
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
AdminOrderCreateFulfillment,
|
AdminOrderCreateFulfillment,
|
||||||
AdminOrderCreateShipment,
|
AdminOrderCreateShipment,
|
||||||
AdminTransferOrder,
|
AdminTransferOrder,
|
||||||
|
AdminUpdateOrder,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [
|
export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
@@ -39,6 +40,17 @@ export const adminOrderRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/orders/:id",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformBody(AdminUpdateOrder),
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetOrdersOrderParams,
|
||||||
|
QueryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/admin/orders/:id/line-items",
|
matcher: "/admin/orders/:id/line-items",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
createSelectParams,
|
createSelectParams,
|
||||||
WithAdditionalData,
|
WithAdditionalData,
|
||||||
} from "../../utils/validators"
|
} from "../../utils/validators"
|
||||||
|
import { AddressPayload } from "../../utils/common-validators"
|
||||||
|
|
||||||
export const AdminGetOrdersOrderParams = createSelectParams().merge(
|
export const AdminGetOrdersOrderParams = createSelectParams().merge(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -136,3 +137,10 @@ export type AdminCancelOrderTransferRequestType = z.infer<
|
|||||||
typeof AdminCancelOrderTransferRequest
|
typeof AdminCancelOrderTransferRequest
|
||||||
>
|
>
|
||||||
export const AdminCancelOrderTransferRequest = z.object({})
|
export const AdminCancelOrderTransferRequest = z.object({})
|
||||||
|
|
||||||
|
export type AdminUpdateOrderType = z.infer<typeof AdminUpdateOrder>
|
||||||
|
export const AdminUpdateOrder = z.object({
|
||||||
|
email: z.string().optional(),
|
||||||
|
shipping_address: AddressPayload.optional(),
|
||||||
|
billing_address: AddressPayload.optional(),
|
||||||
|
})
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { z } from "zod"
|
|||||||
|
|
||||||
export const AddressPayload = z
|
export const AddressPayload = z
|
||||||
.object({
|
.object({
|
||||||
first_name: z.string().nullish(),
|
first_name: z.string().optional(),
|
||||||
last_name: z.string().nullish(),
|
last_name: z.string().optional(),
|
||||||
phone: z.string().nullish(),
|
phone: z.string().optional(),
|
||||||
company: z.string().nullish(),
|
company: z.string().optional(),
|
||||||
address_1: z.string().nullish(),
|
address_1: z.string().optional(),
|
||||||
address_2: z.string().nullish(),
|
address_2: z.string().optional(),
|
||||||
city: z.string().nullish(),
|
city: z.string().optional(),
|
||||||
country_code: z.string().nullish(),
|
country_code: z.string().optional(),
|
||||||
province: z.string().nullish(),
|
province: z.string().optional(),
|
||||||
postal_code: z.string().nullish(),
|
postal_code: z.string().optional(),
|
||||||
metadata: z.record(z.unknown()).nullish(),
|
metadata: z.record(z.unknown()).optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
BigNumber,
|
BigNumber,
|
||||||
|
ChangeActionType,
|
||||||
createRawPropertiesFromBigNumber,
|
createRawPropertiesFromBigNumber,
|
||||||
DecorateCartLikeInputDTO,
|
DecorateCartLikeInputDTO,
|
||||||
decorateCartTotals,
|
decorateCartTotals,
|
||||||
@@ -2233,6 +2234,59 @@ export default class OrderModuleService<
|
|||||||
await this.orderChangeService_.update(updates as any, sharedContext)
|
await this.orderChangeService_.update(updates as any, sharedContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registerOrderChange(
|
||||||
|
data: OrderTypes.RegisterOrderChangeDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<OrderTypes.OrderChangeDTO>
|
||||||
|
async registerOrderChange(
|
||||||
|
data: OrderTypes.RegisterOrderChangeDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<OrderTypes.OrderChangeDTO[]>
|
||||||
|
|
||||||
|
@InjectManager()
|
||||||
|
async registerOrderChange(
|
||||||
|
data:
|
||||||
|
| OrderTypes.RegisterOrderChangeDTO
|
||||||
|
| OrderTypes.RegisterOrderChangeDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<OrderTypes.OrderChangeDTO | OrderTypes.OrderChangeDTO[]> {
|
||||||
|
const inputData = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
|
const orders = await this.orderService_.list(
|
||||||
|
{ id: inputData.map((d) => d.order_id) },
|
||||||
|
{ select: ["id", "version"] },
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderVersionsMap = new Map(orders.map((o) => [o.id, o.version]))
|
||||||
|
|
||||||
|
const changes = (await this.orderChangeService_.create(
|
||||||
|
inputData.map((d) => ({
|
||||||
|
order_id: d.order_id,
|
||||||
|
change_type: d.change_type,
|
||||||
|
internal_note: d.internal_note,
|
||||||
|
description: d.description,
|
||||||
|
metadata: d.metadata,
|
||||||
|
confirmed_at: new Date(),
|
||||||
|
status: OrderChangeStatus.CONFIRMED,
|
||||||
|
version: orderVersionsMap.get(d.order_id)!,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: ChangeActionType.UPDATE_ORDER_PROPERTIES,
|
||||||
|
details: d.details,
|
||||||
|
reference: d.reference,
|
||||||
|
reference_id: d.reference_id,
|
||||||
|
version: orderVersionsMap.get(d.order_id)!,
|
||||||
|
applied: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})) as CreateOrderChangeDTO[],
|
||||||
|
sharedContext
|
||||||
|
)) as OrderTypes.OrderChangeDTO[]
|
||||||
|
|
||||||
|
return Array.isArray(data) ? changes : changes[0]
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager()
|
@InjectManager()
|
||||||
async applyPendingOrderActions(
|
async applyPendingOrderActions(
|
||||||
orderId: string | string[],
|
orderId: string | string[],
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { ChangeActionType } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
|
import { OrderChangeProcessing } from "../calculate-order-change"
|
||||||
|
import { setActionReference } from "../set-action-reference"
|
||||||
|
|
||||||
|
OrderChangeProcessing.registerActionType(
|
||||||
|
ChangeActionType.UPDATE_ORDER_PROPERTIES,
|
||||||
|
{
|
||||||
|
operation({ action, currentOrder, options }) {
|
||||||
|
/**
|
||||||
|
* NOOP: used as a reference for the change
|
||||||
|
*/
|
||||||
|
|
||||||
|
setActionReference(currentOrder, action, options)
|
||||||
|
},
|
||||||
|
validate({ action }) {
|
||||||
|
/* noop */
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -14,3 +14,4 @@ export * from "./shipping-add"
|
|||||||
export * from "./shipping-remove"
|
export * from "./shipping-remove"
|
||||||
export * from "./write-off-item"
|
export * from "./write-off-item"
|
||||||
export * from "./transfer-customer"
|
export * from "./transfer-customer"
|
||||||
|
export * from "./change-shipping-address"
|
||||||
|
|||||||
Reference in New Issue
Block a user