feat(core-flows,types): cancel order changes workflow (#8035)

what:

- adds cancel order changes workflow
This commit is contained in:
Riqwan Thamir
2024-07-09 16:06:33 +02:00
committed by GitHub
parent 101ebbe0f0
commit 566bbd5074
12 changed files with 608 additions and 9 deletions

View File

@@ -0,0 +1,319 @@
import {
cancelOrderChangeWorkflow,
cancelOrderChangeWorkflowId,
createOrderChangeWorkflow,
declineOrderChangeWorkflow,
declineOrderChangeWorkflowId,
deleteOrderChangeWorkflow,
deleteOrderChangeWorkflowId,
} from "@medusajs/core-flows"
import { IOrderModuleService, OrderChangeDTO, OrderDTO } from "@medusajs/types"
import { ModuleRegistrationName } 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 workflows", () => {
let order: OrderDTO
let service: IOrderModuleService
describe("createOrderChangeWorkflow", () => {
beforeEach(async () => {
const fixtures = await prepareDataFixtures({
container,
})
order = await createOrderFixture({
container,
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
})
})
it("should successfully create an order change", async () => {
const { result } = await createOrderChangeWorkflow(container).run({
input: {
order_id: order.id,
},
})
expect(result).toEqual(
expect.objectContaining({
id: expect.any(String),
order_id: order.id,
})
)
})
it("should throw an error when creating an order change when an active one already exists", async () => {
await createOrderChangeWorkflow(container).run({
input: {
order_id: order.id,
},
})
const {
errors: [error],
} = await createOrderChangeWorkflow(container).run({
input: {
order_id: order.id,
},
throwOnError: false,
})
expect(error.error).toEqual(
expect.objectContaining({
message: `Order (${order.id}) already has an existing active order change`,
})
)
})
})
describe("cancelOrderChangeWorkflow", () => {
let orderChange: OrderChangeDTO
beforeEach(async () => {
const fixtures = await prepareDataFixtures({
container,
})
order = await createOrderFixture({
container,
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
})
const { result } = await createOrderChangeWorkflow(container).run({
input: { order_id: order.id },
})
orderChange = result
service = container.resolve(ModuleRegistrationName.ORDER)
})
it("should successfully cancel an order change", async () => {
await cancelOrderChangeWorkflow(container).run({
input: {
id: orderChange.id,
canceled_by: "test",
},
})
const orderChange2 = await service.retrieveOrderChange(orderChange.id)
expect(orderChange2).toEqual(
expect.objectContaining({
id: expect.any(String),
canceled_by: "test",
canceled_at: expect.any(Date),
})
)
})
it("should rollback to its original state when step throws error", async () => {
const workflow = cancelOrderChangeWorkflow(container)
workflow.appendAction("throw", cancelOrderChangeWorkflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const {
errors: [error],
} = await workflow.run({
input: {
id: orderChange.id,
canceled_by: "test",
},
throwOnError: false,
})
expect(error.error).toEqual(
expect.objectContaining({
message: `Fail`,
})
)
const orderChange2 = await service.retrieveOrderChange(orderChange.id)
expect(orderChange2).toEqual(
expect.objectContaining({
id: expect.any(String),
canceled_by: null,
canceled_at: null,
})
)
})
})
describe("deleteOrderChangeWorkflow", () => {
let orderChange: OrderChangeDTO
beforeEach(async () => {
const fixtures = await prepareDataFixtures({
container,
})
order = await createOrderFixture({
container,
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
})
const { result } = await createOrderChangeWorkflow(container).run({
input: { order_id: order.id },
})
orderChange = result
service = container.resolve(ModuleRegistrationName.ORDER)
})
it("should successfully delete an order change", async () => {
await deleteOrderChangeWorkflow(container).run({
input: { id: orderChange.id },
})
const orderChange2 = await service.retrieveOrderChange(
orderChange.id,
{ withDeleted: true }
)
expect(orderChange2).toEqual(
expect.objectContaining({
id: orderChange.id,
deleted_at: expect.any(Date),
})
)
})
it("should rollback to its original state when step throws error", async () => {
const workflow = deleteOrderChangeWorkflow(container)
workflow.appendAction("throw", deleteOrderChangeWorkflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const {
errors: [error],
} = await workflow.run({
input: { id: orderChange.id },
throwOnError: false,
})
expect(error.error).toEqual(
expect.objectContaining({
message: `Fail`,
})
)
const orderChange2 = await service.retrieveOrderChange(
orderChange.id,
{ withDeleted: true }
)
expect(orderChange2).toEqual(
expect.objectContaining({
id: orderChange.id,
deleted_at: null,
})
)
})
})
describe("declineOrderChangeWorkflow", () => {
let orderChange: OrderChangeDTO
beforeEach(async () => {
const fixtures = await prepareDataFixtures({
container,
})
order = await createOrderFixture({
container,
product: fixtures.product,
location: fixtures.location,
inventoryItem: fixtures.inventoryItem,
})
const { result } = await createOrderChangeWorkflow(container).run({
input: { order_id: order.id },
})
orderChange = result
service = container.resolve(ModuleRegistrationName.ORDER)
})
it("should successfully decline an order change", async () => {
await declineOrderChangeWorkflow(container).run({
input: {
id: orderChange.id,
declined_by: "test",
},
})
const orderChange2 = await service.retrieveOrderChange(orderChange.id)
expect(orderChange2).toEqual(
expect.objectContaining({
id: expect.any(String),
declined_by: "test",
declined_at: expect.any(Date),
})
)
})
it("should rollback to its original state when step throws error", async () => {
const workflow = declineOrderChangeWorkflow(container)
workflow.appendAction("throw", declineOrderChangeWorkflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const {
errors: [error],
} = await workflow.run({
input: {
id: orderChange.id,
declined_by: "test",
},
throwOnError: false,
})
expect(error.error).toEqual(
expect.objectContaining({
message: `Fail`,
})
)
const orderChange2 = await service.retrieveOrderChange(orderChange.id)
expect(orderChange2).toEqual(
expect.objectContaining({
id: expect.any(String),
declined_by: null,
declined_at: null,
})
)
})
})
})
},
})

View File

@@ -0,0 +1,45 @@
import {
CancelOrderChangeDTO,
IOrderModuleService,
UpdateOrderChangeDTO,
} from "@medusajs/types"
import {
getSelectsAndRelationsFromObjectArray,
ModuleRegistrationName,
} from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export const cancelOrderChangeStepId = "cancel-order-change"
export const cancelOrderChangeStep = createStep(
cancelOrderChangeStepId,
async (data: CancelOrderChangeDTO, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray(
[data],
{ objectFields: ["metadata"] }
)
const dataBeforeUpdate = await service.retrieveOrderChange(data.id, {
select: [...selects, "canceled_at"],
relations,
})
await service.cancelOrderChange(data)
return new StepResponse(void 0, dataBeforeUpdate)
},
async (rollbackData, { container }) => {
if (!rollbackData) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.updateOrderChanges(rollbackData as UpdateOrderChangeDTO)
}
)

View File

@@ -0,0 +1,45 @@
import {
DeclineOrderChangeDTO,
IOrderModuleService,
UpdateOrderChangeDTO,
} from "@medusajs/types"
import {
getSelectsAndRelationsFromObjectArray,
ModuleRegistrationName,
} from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
export const declineOrderChangeStepId = "decline-order-change"
export const declineOrderChangeStep = createStep(
declineOrderChangeStepId,
async (data: DeclineOrderChangeDTO, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray(
[data],
{ objectFields: ["metadata"] }
)
const dataBeforeUpdate = await service.retrieveOrderChange(data.id, {
select: [...selects, "declined_at"],
relations,
})
await service.declineOrderChange(data)
return new StepResponse(void 0, dataBeforeUpdate)
},
async (rollbackData, { container }) => {
if (!rollbackData) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.updateOrderChanges(rollbackData as UpdateOrderChangeDTO)
}
)

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 deleteOrderChangeStepId = "delete-order-change"
export const deleteOrderChangeStep = createStep(
deleteOrderChangeStepId,
async (data: { id: string }, { container }) => {
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
const deleted = await service.softDeleteOrderChanges(data.id)
return new StepResponse(deleted, data.id)
},
async (id, { container }) => {
if (!id) {
return
}
const service = container.resolve<IOrderModuleService>(
ModuleRegistrationName.ORDER
)
await service.restoreOrderChanges(id)
}
)

View File

@@ -1,11 +1,14 @@
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 "./complete-orders"
export * from "./create-order-change"
export * from "./create-orders"
export * from "./decline-order-change"
export * from "./delete-order-change"
export * from "./get-item-tax-lines"
export * from "./register-fulfillment"
export * from "./register-shipment"

View File

@@ -0,0 +1,11 @@
import { CancelOrderChangeDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { cancelOrderChangeStep } from "../steps"
export const cancelOrderChangeWorkflowId = "cancel-order-change"
export const cancelOrderChangeWorkflow = createWorkflow(
cancelOrderChangeWorkflowId,
(input: WorkflowData<CancelOrderChangeDTO>): WorkflowData<void> => {
cancelOrderChangeStep(input)
}
)

View File

@@ -0,0 +1,11 @@
import { DeclineOrderChangeDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { declineOrderChangeStep } from "../steps"
export const declineOrderChangeWorkflowId = "decline-order-change"
export const declineOrderChangeWorkflow = createWorkflow(
declineOrderChangeWorkflowId,
(input: WorkflowData<DeclineOrderChangeDTO>): WorkflowData<void> => {
declineOrderChangeStep(input)
}
)

View File

@@ -0,0 +1,10 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteOrderChangeStep } from "../steps"
export const deleteOrderChangeWorkflowId = "delete-order-change"
export const deleteOrderChangeWorkflow = createWorkflow(
deleteOrderChangeWorkflowId,
(input: WorkflowData<{ id: string }>): WorkflowData<void> => {
deleteOrderChangeStep(input)
}
)

View File

@@ -1,5 +1,6 @@
export * from "./archive-orders"
export * from "./cancel-order"
export * from "./cancel-order-change"
export * from "./cancel-order-fulfillment"
export * from "./cancel-return"
export * from "./complete-orders"
@@ -8,6 +9,8 @@ export * from "./create-order-change"
export * from "./create-orders"
export * from "./create-return"
export * from "./create-shipment"
export * from "./decline-order-change"
export * from "./delete-order-change"
export * from "./get-order-detail"
export * from "./get-orders-list"
export * from "./receive-return"

View File

@@ -270,14 +270,15 @@ export interface UpdateOrderChangeDTO {
status?: string
description?: string
internal_note?: string | null
requested_by?: string
requested_at?: Date
confirmed_by?: string
confirmed_at?: Date
declined_by?: string
declined_reason?: string
declined_at?: Date
canceled_by?: string
requested_by?: string | null
requested_at?: Date | null
confirmed_by?: string | null
confirmed_at?: Date | null
declined_by?: string | null
declined_reason?: string | null
declined_at?: Date | null
canceled_by?: string | null
canceled_at?: Date | null
metadata?: Record<string, unknown> | null
}

View File

@@ -59,6 +59,7 @@ import {
RegisterOrderFulfillmentDTO,
RegisterOrderShipmentDTO,
UpdateOrderAddressDTO,
UpdateOrderChangeDTO,
UpdateOrderDTO,
UpdateOrderItemDTO,
UpdateOrderItemWithSelectorDTO,
@@ -1075,7 +1076,30 @@ export interface IOrderModuleService extends IModuleService {
selector: FilterableOrderShippingMethodTaxLineProps,
sharedContext?: Context
): Promise<void>
// Order Change
/**
* This method retrieves a {return type} by its ID.
*
* @param {string} orderChangeId - The order change ID.
* @param {FindConfig<OrderChangeDTO>} config - The configurations determining how the order is retrieved. Its properties, such as `select` or `relations`, accept the
* attributes or relations associated with a order.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<OrderChangeDTO>} The retrieved {return type}(s).
*
* @example
* ```typescript
* const result = await orderModuleService.retrieveOrder("orderId123");
* ```
*
*/
retrieveOrderChange(
orderChangeId: string,
config?: FindConfig<OrderChangeDTO>,
sharedContext?: Context
): Promise<OrderChangeDTO>
createOrderChange(
data: CreateOrderChangeDTO,
sharedContext?: Context
@@ -1127,6 +1151,93 @@ export interface IOrderModuleService extends IModuleService {
sharedContext?: Context
): Promise<OrderChangeDTO | OrderChangeDTO[]>
updateOrderChanges(
data: UpdateOrderChangeDTO,
sharedContext?: Context
): Promise<OrderChangeDTO>
/**
* This method updates {return type}(s)
*
* @param {UpdateOrderChangeDTO[]} data - The order change to be updated.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<OrderChangeDTO[]>} The updated {return type}(s).
*
* @example
* ```typescript
* // Example call to updateOrderChanges
*
* const updateOrderChangesData: UpdateOrderChangeDTO[] = [{
* id: "orderchange123",
* description: "Change due to customer request"
* }];
*
* const result = await orderModuleService.updateOrderChanges(updateOrderChangesData);
* ```
*
*/
updateOrderChanges(
data: UpdateOrderChangeDTO[],
sharedContext?: Context
): Promise<OrderChangeDTO[]>
/**
* This method updates {return type}(s)
*
* @param {UpdateOrderChangeDTO | UpdateOrderChangeDTO[]} data - The order change d t o | order change to be updated.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<OrderChangeDTO | OrderChangeDTO[]>} The updated {return type}(s).
*
* @example
* ```typescript
* const result = await orderModuleService.createOrderChange({
* order_id: "order123",
* description: "Adding new item to the order"
* });
* ```
*
*/
updateOrderChanges(
data: UpdateOrderChangeDTO | UpdateOrderChangeDTO[],
sharedContext?: Context
): Promise<OrderChangeDTO | OrderChangeDTO[]>
/**
* This method deletes order change by its ID.
*
* @param {string[]} orderChangeId - The list of {summary}
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<void>} Resolves when {summary}
*
* @example
* ```typescript
* await orderModuleService.deleteOrderChanges(["orderChangeId1", "orderChangeId2"]);
* ```
*
*/
deleteOrderChanges(
orderChangeId: string[],
sharedContext?: Context
): Promise<void>
/**
* This method deletes order change by its ID.
*
* @param {string} orderChangeId - The order's ID.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<void>} Resolves when {summary}
*
* @example
* ```typescript
* await orderModuleService.deleteOrderChanges("orderChangeId");
* ```
*
*/
deleteOrderChanges(
orderChangeId: string,
sharedContext?: Context
): Promise<void>
/**
* This method deletes order change by its ID.
*
@@ -1389,6 +1500,18 @@ export interface IOrderModuleService extends IModuleService {
sharedContext?: Context
): Promise<void>
softDeleteOrderChanges<TReturnableLinkableKeys extends string = string>(
orderChangeId: string | string[],
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<string, string[]> | void>
restoreOrderChanges<TReturnableLinkableKeys extends string = string>(
orderChangeId: string | string[],
config?: RestoreReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<string, string[]> | void>
/**
* This method {summary}
*

View File

@@ -211,7 +211,7 @@ export default class OrderChange {
columnType: "timestamptz",
nullable: true,
})
canceled_at?: Date
canceled_at?: Date | null = null
@Property({
onCreate: () => new Date(),