Feat(order): order changes (#6614)
This is a PR to keep them relatively small. Very likely changes, validations and other features will be added. What: Basic methods to cancel, confirm or decline order changes Apply order changes to modify and create a new version of an order Things related to calculation, Order and Item totals are not covered in this PR. Properties won't match with definition, etc. Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
e4acde1aa2
commit
43399c8d0d
@@ -107,6 +107,14 @@ moduleIntegrationTestRunner({
|
||||
],
|
||||
},
|
||||
],
|
||||
transactions: [
|
||||
{
|
||||
amount: 58,
|
||||
currency_code: "USD",
|
||||
reference: "payment",
|
||||
reference_id: "pay_123",
|
||||
},
|
||||
],
|
||||
currency_code: "usd",
|
||||
customer_id: "joe",
|
||||
} as CreateOrderDTO
|
||||
@@ -114,6 +122,9 @@ moduleIntegrationTestRunner({
|
||||
const expectation = expect.objectContaining({
|
||||
id: expect.stringContaining("order_"),
|
||||
version: 1,
|
||||
summary: expect.objectContaining({
|
||||
total: expect.any(Number),
|
||||
}),
|
||||
shipping_address: expect.objectContaining({
|
||||
id: expect.stringContaining("ordaddr_"),
|
||||
}),
|
||||
@@ -193,6 +204,7 @@ moduleIntegrationTestRunner({
|
||||
"id",
|
||||
"version",
|
||||
"items.id",
|
||||
"summary",
|
||||
"items.quantity",
|
||||
"items.detail.id",
|
||||
"items.detail.version",
|
||||
@@ -223,20 +235,93 @@ moduleIntegrationTestRunner({
|
||||
expect(getOrder).toEqual(expectation)
|
||||
})
|
||||
|
||||
it.skip("should transform where clause to match the db schema and return the order", async function () {
|
||||
it("should return order transactions", async function () {
|
||||
const createdOrder = await service.create(input)
|
||||
const getOrder = await service.retrieve(createdOrder.id, {
|
||||
select: [
|
||||
"id",
|
||||
"version",
|
||||
"items.id",
|
||||
"items.detail.version",
|
||||
"items.quantity",
|
||||
"transactions.amount",
|
||||
"transactions.reference",
|
||||
"transactions.reference_id",
|
||||
],
|
||||
relations: ["items"],
|
||||
relations: ["transactions"],
|
||||
})
|
||||
|
||||
expect(getOrder).toEqual(expectation)
|
||||
expect(getOrder).toEqual(
|
||||
expect.objectContaining({
|
||||
id: createdOrder.id,
|
||||
transactions: [
|
||||
expect.objectContaining({
|
||||
amount: 58,
|
||||
reference: "payment",
|
||||
reference_id: "pay_123",
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should transform where clause to match the db schema and return the order", async function () {
|
||||
await service.create(input)
|
||||
const orders = await service.list(
|
||||
{
|
||||
items: {
|
||||
quantity: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
select: ["id"],
|
||||
relations: ["items"],
|
||||
take: null,
|
||||
}
|
||||
)
|
||||
expect(orders.length).toEqual(1)
|
||||
|
||||
const orders2 = await service.list(
|
||||
{
|
||||
items: {
|
||||
quantity: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
select: ["items.quantity"],
|
||||
relations: ["items"],
|
||||
take: null,
|
||||
}
|
||||
)
|
||||
expect(orders2.length).toEqual(0)
|
||||
|
||||
const orders3 = await service.list(
|
||||
{
|
||||
items: {
|
||||
detail: {
|
||||
shipped_quantity: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
select: ["id"],
|
||||
relations: ["items.detail"],
|
||||
take: null,
|
||||
}
|
||||
)
|
||||
expect(orders3.length).toEqual(1)
|
||||
|
||||
const orders4 = await service.list(
|
||||
{
|
||||
items: {
|
||||
detail: {
|
||||
shipped_quantity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
select: ["id"],
|
||||
relations: ["items.detail"],
|
||||
take: null,
|
||||
}
|
||||
)
|
||||
expect(orders4.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
557
packages/order/integration-tests/__tests__/order-edit.ts
Normal file
557
packages/order/integration-tests/__tests__/order-edit.ts
Normal file
@@ -0,0 +1,557 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { CreateOrderDTO, IOrderModuleService } from "@medusajs/types"
|
||||
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
|
||||
import { ChangeActionType } from "../../src/utils"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
debug: 0,
|
||||
moduleName: Modules.ORDER,
|
||||
testSuite: ({ service }: SuiteOptions<IOrderModuleService>) => {
|
||||
describe("Order Module Service - Order Edits", () => {
|
||||
const input = {
|
||||
email: "foo@bar.com",
|
||||
items: [
|
||||
{
|
||||
title: "Item 1",
|
||||
subtitle: "Subtitle 1",
|
||||
thumbnail: "thumbnail1.jpg",
|
||||
quantity: 1,
|
||||
product_id: "product1",
|
||||
product_title: "Product 1",
|
||||
product_description: "Description 1",
|
||||
product_subtitle: "Product Subtitle 1",
|
||||
product_type: "Type 1",
|
||||
product_collection: "Collection 1",
|
||||
product_handle: "handle1",
|
||||
variant_id: "variant1",
|
||||
variant_sku: "SKU1",
|
||||
variant_barcode: "Barcode1",
|
||||
variant_title: "Variant 1",
|
||||
variant_option_values: {
|
||||
color: "Red",
|
||||
size: "Large",
|
||||
},
|
||||
requires_shipping: true,
|
||||
is_discountable: true,
|
||||
is_tax_inclusive: true,
|
||||
compare_at_unit_price: 10,
|
||||
unit_price: 8,
|
||||
tax_lines: [
|
||||
{
|
||||
description: "Tax 1",
|
||||
tax_rate_id: "tax_usa",
|
||||
code: "code",
|
||||
rate: 0.1,
|
||||
provider_id: "taxify_master",
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
code: "VIP_10",
|
||||
amount: 10,
|
||||
description: "VIP discount",
|
||||
promotion_id: "prom_123",
|
||||
provider_id: "coupon_kings",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Item 2",
|
||||
quantity: 2,
|
||||
unit_price: 5,
|
||||
},
|
||||
{
|
||||
title: "Item 3",
|
||||
quantity: 1,
|
||||
unit_price: 30,
|
||||
},
|
||||
],
|
||||
sales_channel_id: "test",
|
||||
shipping_address: {
|
||||
first_name: "Test",
|
||||
last_name: "Test",
|
||||
address_1: "Test",
|
||||
city: "Test",
|
||||
country_code: "US",
|
||||
postal_code: "12345",
|
||||
phone: "12345",
|
||||
},
|
||||
billing_address: {
|
||||
first_name: "Test",
|
||||
last_name: "Test",
|
||||
address_1: "Test",
|
||||
city: "Test",
|
||||
country_code: "US",
|
||||
postal_code: "12345",
|
||||
},
|
||||
shipping_methods: [
|
||||
{
|
||||
name: "Test shipping method",
|
||||
amount: 10,
|
||||
data: {},
|
||||
tax_lines: [
|
||||
{
|
||||
description: "shipping Tax 1",
|
||||
tax_rate_id: "tax_usa_shipping",
|
||||
code: "code",
|
||||
rate: 10,
|
||||
},
|
||||
],
|
||||
adjustments: [
|
||||
{
|
||||
code: "VIP_10",
|
||||
amount: 1,
|
||||
description: "VIP discount",
|
||||
promotion_id: "prom_123",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
transactions: [
|
||||
{
|
||||
amount: 58,
|
||||
currency_code: "USD",
|
||||
reference: "payment",
|
||||
reference_id: "pay_123",
|
||||
},
|
||||
],
|
||||
currency_code: "usd",
|
||||
customer_id: "joe",
|
||||
} as CreateOrderDTO
|
||||
|
||||
it("should change an order by adding actions to it", async function () {
|
||||
const createdOrder = await service.create(input)
|
||||
|
||||
await service.addOrderAction([
|
||||
{
|
||||
action: ChangeActionType.ITEM_ADD,
|
||||
order_id: createdOrder.id,
|
||||
version: createdOrder.version,
|
||||
internal_note: "adding an item",
|
||||
reference: "order_line_item",
|
||||
reference_id: createdOrder.items[0].id,
|
||||
amount:
|
||||
createdOrder.items[0].unit_price * createdOrder.items[0].quantity,
|
||||
details: {
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.ITEM_ADD,
|
||||
order_id: createdOrder.id,
|
||||
version: createdOrder.version,
|
||||
reference: "order_line_item",
|
||||
reference_id: createdOrder.items[1].id,
|
||||
amount:
|
||||
createdOrder.items[1].unit_price * createdOrder.items[1].quantity,
|
||||
details: {
|
||||
quantity: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.FULFILL_ITEM,
|
||||
order_id: createdOrder.id,
|
||||
version: createdOrder.version,
|
||||
reference: "fullfilment",
|
||||
reference_id: "fulfill_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.SHIP_ITEM,
|
||||
order_id: createdOrder.id,
|
||||
version: createdOrder.version,
|
||||
reference: "fullfilment",
|
||||
reference_id: "shipping_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.RETURN_ITEM,
|
||||
order_id: createdOrder.id,
|
||||
version: createdOrder.version,
|
||||
internal_note: "client has called and wants to return an item",
|
||||
reference: "return",
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM,
|
||||
order_id: createdOrder.id,
|
||||
version: createdOrder.version,
|
||||
internal_note: "Item broken",
|
||||
reference: "return",
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
await service.applyPendingOrderActions(createdOrder.id)
|
||||
|
||||
const finalOrder = await service.retrieve(createdOrder.id, {
|
||||
select: [
|
||||
"id",
|
||||
"version",
|
||||
"items.detail",
|
||||
"summary",
|
||||
"shipping_methods",
|
||||
"transactions",
|
||||
],
|
||||
relations: ["items", "shipping_methods", "transactions"],
|
||||
})
|
||||
|
||||
expect(createdOrder.items).toEqual([
|
||||
expect.objectContaining({
|
||||
title: "Item 1",
|
||||
unit_price: 8,
|
||||
quantity: 1,
|
||||
detail: expect.objectContaining({
|
||||
version: 1,
|
||||
quantity: 1,
|
||||
fulfilled_quantity: 0,
|
||||
shipped_quantity: 0,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "Item 2",
|
||||
compare_at_unit_price: null,
|
||||
unit_price: 5,
|
||||
quantity: 2,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "Item 3",
|
||||
unit_price: 30,
|
||||
quantity: 1,
|
||||
detail: expect.objectContaining({
|
||||
version: 1,
|
||||
quantity: 1,
|
||||
fulfilled_quantity: 0,
|
||||
shipped_quantity: 0,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
}),
|
||||
}),
|
||||
])
|
||||
|
||||
expect(finalOrder).toEqual(
|
||||
expect.objectContaining({
|
||||
version: 1,
|
||||
})
|
||||
)
|
||||
expect(finalOrder.items).toEqual([
|
||||
expect.objectContaining({
|
||||
title: "Item 1",
|
||||
subtitle: "Subtitle 1",
|
||||
thumbnail: "thumbnail1.jpg",
|
||||
variant_id: "variant1",
|
||||
product_id: "product1",
|
||||
product_title: "Product 1",
|
||||
product_description: "Description 1",
|
||||
product_subtitle: "Product Subtitle 1",
|
||||
product_type: "Type 1",
|
||||
product_collection: "Collection 1",
|
||||
product_handle: "handle1",
|
||||
variant_sku: "SKU1",
|
||||
variant_barcode: "Barcode1",
|
||||
variant_title: "Variant 1",
|
||||
variant_option_values: { size: "Large", color: "Red" },
|
||||
requires_shipping: true,
|
||||
is_discountable: true,
|
||||
is_tax_inclusive: true,
|
||||
compare_at_unit_price: 10,
|
||||
unit_price: 8,
|
||||
quantity: 2,
|
||||
detail: expect.objectContaining({
|
||||
version: 1,
|
||||
quantity: 2,
|
||||
fulfilled_quantity: 0,
|
||||
shipped_quantity: 0,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "Item 2",
|
||||
compare_at_unit_price: null,
|
||||
unit_price: 5,
|
||||
quantity: 5,
|
||||
detail: expect.objectContaining({
|
||||
version: 1,
|
||||
quantity: 5,
|
||||
fulfilled_quantity: 0,
|
||||
shipped_quantity: 0,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "Item 3",
|
||||
unit_price: 30,
|
||||
quantity: 1,
|
||||
detail: expect.objectContaining({
|
||||
version: 1,
|
||||
quantity: 1,
|
||||
fulfilled_quantity: 1,
|
||||
shipped_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 1,
|
||||
written_off_quantity: 0,
|
||||
}),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should create an order change, add actions to it and confirm the changes.", async function () {
|
||||
const createdOrder = await service.create(input)
|
||||
|
||||
const orderChange = await service.createOrderChange({
|
||||
order_id: createdOrder.id,
|
||||
description: "changing the order",
|
||||
internal_note: "changing the order to version 2",
|
||||
created_by: "user_123",
|
||||
actions: [
|
||||
{
|
||||
action: ChangeActionType.ITEM_ADD,
|
||||
reference: "order_line_item",
|
||||
reference_id: createdOrder.items[0].id,
|
||||
amount:
|
||||
createdOrder.items[0].unit_price *
|
||||
createdOrder.items[0].quantity,
|
||||
details: {
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.ITEM_ADD,
|
||||
reference: "order_line_item",
|
||||
reference_id: createdOrder.items[1].id,
|
||||
amount:
|
||||
createdOrder.items[1].unit_price *
|
||||
createdOrder.items[1].quantity,
|
||||
details: {
|
||||
quantity: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.FULFILL_ITEM,
|
||||
reference: "fullfilment",
|
||||
reference_id: "fulfill_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.SHIP_ITEM,
|
||||
reference: "fullfilment",
|
||||
reference_id: "shipping_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.RETURN_ITEM,
|
||||
reference: "return",
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
action: ChangeActionType.RECEIVE_DAMAGED_RETURN_ITEM,
|
||||
internal_note: "Item broken",
|
||||
reference: "return",
|
||||
reference_id: "return_123",
|
||||
details: {
|
||||
reference_id: createdOrder.items[2].id,
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await service.confirmOrderChange(orderChange.id, {
|
||||
confirmed_by: "cx_agent_123",
|
||||
})
|
||||
|
||||
expect(service.confirmOrderChange(orderChange.id)).rejects.toThrowError(
|
||||
`Order Change cannot be modified: ${orderChange.id}`
|
||||
)
|
||||
|
||||
const modified = await service.retrieve(createdOrder.id, {
|
||||
select: [
|
||||
"id",
|
||||
"version",
|
||||
"items.detail",
|
||||
"summary",
|
||||
"shipping_methods",
|
||||
"transactions",
|
||||
],
|
||||
relations: ["items", "shipping_methods", "transactions"],
|
||||
})
|
||||
|
||||
expect(modified).toEqual(
|
||||
expect.objectContaining({
|
||||
version: 2,
|
||||
})
|
||||
)
|
||||
|
||||
expect(modified.items).toEqual([
|
||||
expect.objectContaining({
|
||||
quantity: 2,
|
||||
detail: expect.objectContaining({
|
||||
version: 2,
|
||||
quantity: 2,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "Item 2",
|
||||
unit_price: 5,
|
||||
quantity: 5,
|
||||
detail: expect.objectContaining({
|
||||
version: 2,
|
||||
quantity: 5,
|
||||
fulfilled_quantity: 0,
|
||||
shipped_quantity: 0,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 0,
|
||||
written_off_quantity: 0,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "Item 3",
|
||||
unit_price: 30,
|
||||
quantity: 1,
|
||||
detail: expect.objectContaining({
|
||||
version: 2,
|
||||
quantity: 1,
|
||||
fulfilled_quantity: 1,
|
||||
shipped_quantity: 1,
|
||||
return_requested_quantity: 0,
|
||||
return_received_quantity: 0,
|
||||
return_dismissed_quantity: 1,
|
||||
written_off_quantity: 0,
|
||||
}),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should create order changes, cancel and reject them.", async function () {
|
||||
const createdOrder = await service.create(input)
|
||||
|
||||
const orderChange = await service.createOrderChange({
|
||||
order_id: createdOrder.id,
|
||||
description: "changing the order",
|
||||
internal_note: "changing the order to version 2",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
const orderChange2 = await service.createOrderChange({
|
||||
order_id: createdOrder.id,
|
||||
description: "changing the order again",
|
||||
internal_note: "trying again...",
|
||||
created_by: "user_123",
|
||||
actions: [
|
||||
{
|
||||
action: ChangeActionType.ITEM_ADD,
|
||||
reference: "order_line_item",
|
||||
reference_id: createdOrder.items[0].id,
|
||||
amount:
|
||||
createdOrder.items[0].unit_price *
|
||||
createdOrder.items[0].quantity,
|
||||
details: {
|
||||
quantity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await service.cancelOrderChange({
|
||||
id: orderChange.id,
|
||||
canceled_by: "cx_agent_123",
|
||||
})
|
||||
|
||||
expect(service.cancelOrderChange(orderChange.id)).rejects.toThrowError(
|
||||
"Order Change cannot be modified"
|
||||
)
|
||||
|
||||
await service.declineOrderChange({
|
||||
id: orderChange2.id,
|
||||
declined_by: "user_123",
|
||||
declined_reason: "changed my mind",
|
||||
})
|
||||
|
||||
expect(
|
||||
service.declineOrderChange(orderChange2.id)
|
||||
).rejects.toThrowError("Order Change cannot be modified")
|
||||
|
||||
const [change1, change2] = await service.listOrderChanges(
|
||||
{
|
||||
id: [orderChange.id, orderChange2.id],
|
||||
},
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"status",
|
||||
"canceled_by",
|
||||
"canceled_at",
|
||||
"declined_by",
|
||||
"declined_at",
|
||||
"declined_reason",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
expect(change1).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
status: "canceled",
|
||||
declined_by: null,
|
||||
declined_reason: null,
|
||||
declined_at: null,
|
||||
canceled_by: "cx_agent_123",
|
||||
canceled_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
|
||||
expect(change2).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
status: "declined",
|
||||
declined_by: "user_123",
|
||||
declined_reason: "changed my mind",
|
||||
declined_at: expect.any(Date),
|
||||
canceled_by: null,
|
||||
canceled_at: null,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user