feat(core-flows): Refresh adjustments when editing orders (#13189)

* wip

* add tests

* add more tests

* fixes buy-get promotion

* Create empty-pillows-promise.md

* chore: use query graph

* Update packages/core/core-flows/src/order/workflows/order-edit/refresh-order-edit-adjustments.ts

Co-authored-by: Frane Polić <16856471+fPolic@users.noreply.github.com>

---------

Co-authored-by: Frane Polić <16856471+fPolic@users.noreply.github.com>
This commit is contained in:
Oli Juhl
2025-08-20 15:04:27 +02:00
committed by GitHub
parent d4a9728879
commit b152210554
23 changed files with 1055 additions and 264 deletions

View File

@@ -1,8 +1,11 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { IOrderModuleService, IPromotionModuleService } from "@medusajs/types"
import {
ContainerRegistrationKeys,
Modules,
OrderChangeStatus,
PromotionStatus,
PromotionType,
RuleOperator,
} from "@medusajs/utils"
import {
@@ -26,14 +29,18 @@ medusaIntegrationTestRunner({
let inventoryItemExtra
let location
let locationTwo
let buyRuleProduct
let productExtra
let container
let region
let salesChannel
const shippingProviderId = "manual_test-provider"
beforeEach(async () => {
const container = getContainer()
container = getContainer()
await createAdminUser(dbConnection, adminHeaders, container)
const region = (
region = (
await api.post(
"/admin/regions",
{
@@ -80,7 +87,7 @@ medusaIntegrationTestRunner({
)
).data.tax_rate
const salesChannel = (
salesChannel = (
await api.post(
"/admin/sales-channels",
{
@@ -151,6 +158,31 @@ medusaIntegrationTestRunner({
)
).data.product
buyRuleProduct = (
await api.post(
"/admin/products",
{
title: "Buy rule product",
options: [{ title: "size", values: ["large", "small"] }],
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "buy rule variant",
sku: "buy-rule-variant-sku",
options: { size: "large" },
prices: [
{
currency_code: "usd",
amount: 10,
},
],
},
],
},
adminHeaders
)
).data.product
const orderModule = container.resolve(Modules.ORDER)
order = await orderModule.createOrders({
@@ -1145,5 +1177,493 @@ medusaIntegrationTestRunner({
)
})
})
describe("Order Edits promotions", () => {
let appliedPromotion
let promotionModule: IPromotionModuleService
let orderModule: IOrderModuleService
beforeEach(async () => {
promotionModule = container.resolve(Modules.PROMOTION)
appliedPromotion = await promotionModule.createPromotions({
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
application_method: {
type: "percentage",
target_type: "order",
allocation: "across",
value: 10,
currency_code: "usd",
target_rules: [],
},
})
orderModule = container.resolve(Modules.ORDER)
order = await orderModule.createOrders({
email: "foo@bar.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
items: [
{
// @ts-ignore
id: "item-1",
title: "Custom Item",
quantity: 1,
unit_price: 10,
},
],
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",
},
currency_code: "usd",
})
await orderModule.createOrderLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 1,
item_id: "item-1",
promotion_id: appliedPromotion.id,
},
])
const remoteLink = container.resolve(ContainerRegistrationKeys.LINK)
await remoteLink.create({
[Modules.ORDER]: { order_id: order.id },
[Modules.PROMOTION]: { promotion_id: appliedPromotion.id },
})
})
it("should update adjustments when adding a new item", async () => {
let result = await api.post(
"/admin/order-edits",
{
order_id: order.id,
description: "Test",
},
adminHeaders
)
const orderId = result.data.order_change.order_id
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(10)
expect(result.total).toEqual(9)
// Add item with price $12 + $1.2 in taxes
result = (
await api.post(
`/admin/order-edits/${orderId}/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
).data.order_preview
// 10% discount on two items of $12 and $10 = $2.2
// Aside from this there is a tax rate of 10%, which adds ($1.2 - $0.12 (discount tax)) of taxes on the item of $12.
expect(result.total).toEqual(20.88)
expect(result.original_total).toEqual(23.2)
})
it("should update adjustments when updating an item", async () => {
let result = await api.post(
"/admin/order-edits",
{
order_id: order.id,
description: "Test",
},
adminHeaders
)
const orderId = result.data.order_change.order_id
const item = order.items[0]
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(10)
expect(result.total).toEqual(9)
let adjustments = result.items[0].adjustments
expect(adjustments).toEqual([
expect.objectContaining({
amount: 1,
item_id: item.id,
}),
])
// Update item quantity
result = (
await api.post(
`/admin/order-edits/${orderId}/items/item/${item.id}`,
{
quantity: 2,
},
adminHeaders
)
).data.order_preview
expect(result.total).toEqual(18)
expect(result.original_total).toEqual(20)
adjustments = result.items[0].adjustments
expect(adjustments).toEqual([
expect.objectContaining({
amount: 2,
item_id: item.id,
}),
])
})
it("should update adjustments when removing an item", async () => {
let result = await api.post(
"/admin/order-edits",
{
order_id: order.id,
description: "Test",
},
adminHeaders
)
const orderId = result.data.order_change.order_id
const item = order.items[0]
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(10)
expect(result.total).toEqual(9)
let adjustments = result.items[0].adjustments
expect(adjustments).toEqual([
expect.objectContaining({
amount: 1,
item_id: item.id,
}),
])
result = (
await api.post(
`/admin/order-edits/${orderId}/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
).data.order_preview
const orderItems = result.items
expect(orderItems).toEqual([
expect.objectContaining({
adjustments: [
expect.objectContaining({
amount: 1,
item_id: item.id,
}),
],
}),
expect.objectContaining({
adjustments: [
expect.objectContaining({
amount: 1.2,
}),
],
}),
])
const newItem = result.items.find(
(item) => item.variant_id === productExtra.variants[0].id
)
const actionId = newItem.actions[0].id
result = (
await api.delete(
`/admin/order-edits/${orderId}/items/${actionId}`,
adminHeaders
)
).data.order_preview
adjustments = result.items[0].adjustments
expect(adjustments).toEqual([
expect.objectContaining({
amount: 1,
item_id: item.id,
}),
])
})
it("should not create adjustments when adding a new item if promotion is disabled", async () => {
let result = await api.post(
"/admin/order-edits",
{
order_id: order.id,
description: "Test",
},
adminHeaders
)
const orderId = result.data.order_change.order_id
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(10)
expect(result.total).toEqual(9)
await api.post(
`/admin/promotions/${appliedPromotion.id}`,
{
status: "draft",
},
adminHeaders
)
// Add item with price $12 + $1.2 in taxes
result = (
await api.post(
`/admin/order-edits/${orderId}/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
).data.order_preview
expect(result.total).toEqual(23.2)
expect(result.original_total).toEqual(23.2)
})
it("should not change adjustments if order edit is canceled", async () => {
let result = await api.post(
"/admin/order-edits",
{
order_id: order.id,
description: "Test",
},
adminHeaders
)
const orderId = result.data.order_change.order_id
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(10)
expect(result.total).toEqual(9)
// Add item with price $12 + $1.2 in taxes
result = (
await api.post(
`/admin/order-edits/${orderId}/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
).data.order_preview
expect(result.total).toEqual(20.88)
expect(result.original_total).toEqual(23.2)
await api.delete(`/admin/order-edits/${orderId}`, adminHeaders)
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(10)
expect(result.total).toEqual(9)
})
it("should add, remove, and add buy-get adjustment depending on the quantity of the buy rule product", async () => {
promotionModule = container.resolve(Modules.PROMOTION)
appliedPromotion = await promotionModule.createPromotions({
code: "BUY_GET_PROMO",
type: "buyget",
status: "active",
application_method: {
allocation: "each",
value: 100,
max_quantity: 1,
type: "percentage",
target_type: "items",
apply_to_quantity: 1,
buy_rules_min_quantity: 2,
target_rules: [
{
operator: "eq",
attribute: "items.product.id",
values: [productExtra.id],
},
],
buy_rules: [
{
operator: "eq",
attribute: "items.product.id",
values: [buyRuleProduct.id],
},
],
},
is_tax_inclusive: false,
is_automatic: true,
})
const orderModule: IOrderModuleService = container.resolve(
Modules.ORDER
)
order = await orderModule.createOrders({
email: "foo@bar.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
items: [
{
variant_id: buyRuleProduct.variants[0].id,
quantity: 2,
title: "Buy rule product",
unit_price: 10,
product_id: buyRuleProduct.id,
},
{
variant_id: productExtra.variants[0].id,
quantity: 1,
title: "Extra product",
unit_price: 10,
product_id: productExtra.id,
},
],
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",
},
currency_code: "usd",
})
await orderModule.createOrderLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 1,
item_id: "item-1",
promotion_id: appliedPromotion.id,
},
])
const remoteLink = container.resolve(ContainerRegistrationKeys.LINK)
await remoteLink.create({
[Modules.ORDER]: { order_id: order.id },
[Modules.PROMOTION]: { promotion_id: appliedPromotion.id },
})
// Initially, the buy-get adjustment should be added to the order
let result = await api.post(
"/admin/order-edits",
{
order_id: order.id,
description: "Test",
},
adminHeaders
)
const orderId = result.data.order_change.order_id
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(30)
expect(result.total).toEqual(20)
const buyRuleItem = result.items.find(
(item) => item.product_id === buyRuleProduct.id
)
// Update buy rule product quantity to 1
// This should remove the buy-get adjustment, as it is no longer valid
result = (
await api.post(
`/admin/order-edits/${orderId}/items/item/${buyRuleItem.id}`,
{
quantity: 1,
},
adminHeaders
)
).data.order_preview
expect(result.total).toEqual(20)
expect(result.original_total).toEqual(20)
// Canceling the order edit should bring back the buy-get adjustment
await api.delete(`/admin/order-edits/${orderId}`, adminHeaders)
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order
expect(result.original_total).toEqual(30)
expect(result.total).toEqual(20)
})
})
},
})