fix: Compute "virtual" adjustments for order previews (#13306)

* wip

* add wip

* wip

* reuse action

* finish first draft

* fix tests

* cleanup

* Only compute adjustments when necessary

* Create hot-carrots-look.md

* address comments

* minor tweaks

* fix pay col

* fix test

* wip

* Dwip

* wip

* fix: adjustment typo

* fix: import

* fix: workflow imports

* wip: update test

* feat: upsert versioned  adjustments when previewing order

* fix: revert unique codes change

* fix: order spec test with versioning

* wip: save

* feat: make adjustments work for preview and confirm flow, wip base repo filtering of older version adjustments

* fix: missing populate where

* wip: populate where loading versioned adjustments

* fix: filter out older adjustment versions

* temp: comment adjustments in repo

* test: add adjustment if no version

* wip: configure populate where in order base repository

* fix: rm manual filtering

* fix: revert base repo changes

* fix: revert

* fix: use order item version instead of order version

* fix: rm only in test

* fix: update case spec

* fix: remove sceanrio, wip test with draft promotion

* feat: test correct adjustments when disabling promotion

* feat: complex test case

* feat: test consecutive order edits

* feat: 2 promotions test case with a fixed promo

* feat: migrate existing order line item adjustments to order items latest version

* feat: update dep after merge

* wip: load adjustments separatley

* feat: adjustments collections

* fix: spread result, handle related entity case

* fix: update lock

* feat: make sure version is loaded, refactor, handle related entity case

* fix: check fields

* feat: loading adjustments for list and count

* fix: correct items version field

* fix: rm empty array

* fix: wip order modules spec

* fix: order module specs

* feat: preinit items adjustments

* fix: rm only

* fix: rm only

* chore: cleanup

* fix: migration files

* fix: dont change formatting

* fix: core package build

* chore: more cleanup

* fix: item update util

* fix: duplicate import

* fix: refresh adjustments for exchanges (#13992)

* wip: exchange adjustments

* feat: test - receive items

* feat: finish test case

* fix: casing

* fix(draft-orders, core-flows, orders) refresh adjustments for draft orders (#14025)

* wip: draft orders adjustments refresh

* feat: rewrite to use REPLACE action + test

* fix: rm only

* feat: cleanup old REPLACE actions

* feat: cleanup adjustemnts when 0 promotions

* wip: canceling draft order

* fix: make version arg optional

* fix: restore promotion links

* feat: test reverting on cancelation

* fix: address comments in tests

* wip: fix summary on preview

* fix: get pending diff on preview summary from total

* fix: revert pending diff change

---------

Co-authored-by: fPolic <mainacc.polic@gmail.com>
Co-authored-by: Frane Polić <16856471+fPolic@users.noreply.github.com>
This commit is contained in:
Oli Juhl
2025-11-25 10:41:14 +01:00
committed by GitHub
parent 0ddd9e36b5
commit 78842af1c3
60 changed files with 3271 additions and 233 deletions

View File

@@ -1,8 +1,11 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { IOrderModuleService, IPromotionModuleService } from "@medusajs/types"
import {
ContainerRegistrationKeys,
Modules,
ProductStatus,
PromotionStatus,
PromotionType,
RuleOperator,
} from "@medusajs/utils"
import {
@@ -913,6 +916,305 @@ medusaIntegrationTestRunner({
expect(updatedClaimShippingMethods).toHaveLength(0)
})
})
describe("Exchange adjustments", () => {
let appliedPromotion
let promotionModule: IPromotionModuleService
let orderModule: IOrderModuleService
let remoteLink
let orderWithPromotion
let productForAdjustmentTest
beforeEach(async () => {
const container = getContainer()
promotionModule = container.resolve(Modules.PROMOTION)
orderModule = container.resolve(Modules.ORDER)
remoteLink = container.resolve(ContainerRegistrationKeys.LINK)
productForAdjustmentTest = (
await api.post(
"/admin/products",
{
title: "Product for adjustment test",
status: ProductStatus.PUBLISHED,
shipping_profile_id: shippingProfile.id,
options: [{ title: "size", values: ["large", "small"] }],
variants: [
{
title: "Test variant",
sku: "test-variant-adjustment",
manage_inventory: false,
options: { size: "large" },
prices: [
{
currency_code: "usd",
amount: 12,
},
],
},
],
},
adminHeaders
)
).data.product
appliedPromotion = await promotionModule.createPromotions({
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
application_method: {
type: "percentage",
target_type: "order",
allocation: "each",
value: 10,
max_quantity: 5,
currency_code: "usd",
target_rules: [],
},
})
await remoteLink.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: (
await api.get("/admin/sales-channels", adminHeaders)
).data.sales_channels[0].id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
])
// @ts-ignore
orderWithPromotion = await orderModule.createOrders({
email: "foo@bar.com",
region_id: (
await api.get("/admin/regions", adminHeaders)
).data.regions[0].id,
sales_channel_id: (
await api.get("/admin/sales-channels", adminHeaders)
).data.sales_channels[0].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.createOrderLineItemTaxLines(orderWithPromotion.id, [
{
// @ts-ignore
item_id: "item-1",
code: "standard",
rate: 10,
description: "tax-1",
provider_id: "system",
total: 1.2,
subtotal: 1.2,
},
])
await orderModule.createOrderLineItemAdjustments([
{
version: 1,
code: appliedPromotion.code!,
amount: 1,
item_id: "item-1",
promotion_id: appliedPromotion.id,
},
])
await remoteLink.create({
[Modules.ORDER]: { order_id: orderWithPromotion.id },
[Modules.PROMOTION]: { promotion_id: appliedPromotion.id },
})
})
it("should update adjustments when adding an inbound and outbound item", async () => {
// First item -> 10$ | 10% discount tax excl. | 10% tax
// Second item -> 12$ | 10% discount tax excl. | 2% tax
// fulfill item so it can be returned
await api.post(
`/admin/orders/${orderWithPromotion.id}/fulfillments`,
{
items: [
{
id: orderWithPromotion.items[0].id,
quantity: 1,
},
],
},
adminHeaders
)
let result = await api.post(
"/admin/exchanges",
{
order_id: orderWithPromotion.id,
description: "Test",
},
adminHeaders
)
const exchangeId = result.data.exchange.id
const orderId = result.data.exchange.order_id
result = (await api.get(`/admin/orders/${orderId}`, adminHeaders))
.data.order
expect(result.original_total).toEqual(11) // $10 + 10% tax
expect(result.total).toEqual(10 * 0.9 * 1.1) // ($10 - 10% discount) + 10% tax
// Add outbound item with price $12, 10% discount and 10% tax
result = (
await api
.post(
`/admin/exchanges/${exchangeId}/outbound/items`,
{
items: [
{
variant_id: productForAdjustmentTest.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
.catch((e) => console.log(e))
).data.order_preview
expect(result.total).toEqual(20.916) // 10 * 0.9 * 1.1 + 12 * 0.9 * 1.02
expect(result.original_total).toEqual(23.24) // 10 * 1.1 + 12 * 1.02
// Confirm that the adjustment values are correct
const adjustments = result.items[0].adjustments
const adjustments2 = result.items[1].adjustments
expect(adjustments).toEqual([
expect.objectContaining({
amount: 1,
}),
])
expect(adjustments2).toEqual([
expect.objectContaining({
amount: 1.2,
}),
])
let orderResult = (
await api.get(`/admin/orders/${orderId}`, adminHeaders)
).data.order
// confirm original order is not updated
expect(orderResult.total).toEqual(9.9) // initial item 10$ and 10% discount and 10% tax
expect(orderResult.original_total).toEqual(11) // initial item 10$ + 10% tax
const originalItemId = result.items[0].id
// Request inbound item return
result = (
await api
.post(
`/admin/exchanges/${exchangeId}/inbound/items`,
{
items: [
{
id: originalItemId,
reason_id: returnReason.id,
quantity: 1,
},
],
},
adminHeaders
)
.catch((e) => console.log(e))
).data.order_preview
const returnId = result.order_change.return_id
await api.post(
`/admin/exchanges/${exchangeId}/request`,
{},
adminHeaders
)
orderResult = (
await api.get(`/admin/orders/${orderId}`, adminHeaders)
).data.order
// after exchange request order contains both items and adjustments untill return is received
expect(orderResult.total).toEqual(20.916) // 10 * 0.9 * 1.1 + 12 * 0.9 * 1.02
expect(orderResult.original_total).toEqual(23.24) // 10 * 1.1 + 12 * 1.02
await api.post(`/admin/returns/${returnId}/receive`, {}, adminHeaders)
orderResult = (
await api.get(`/admin/orders/${orderId}`, adminHeaders)
).data.order
// still the same state while return receive process is pending
expect(orderResult.total).toEqual(20.916) // 10 * 0.9 * 1.1 + 12 * 0.9 * 1.02
expect(orderResult.original_total).toEqual(23.24) // 10 * 1.1 + 12 * 1.02
await api.post(
`/admin/returns/${returnId}/receive-items`,
{
items: [
{
id: originalItemId,
quantity: 1,
},
],
},
adminHeaders
)
orderResult = (
await api.get(`/admin/orders/${orderId}`, adminHeaders)
).data.order
// still the same state while return receive process is pending
expect(orderResult.total).toEqual(20.916) // 10 * 0.9 * 1.1 + 12 * 0.9 * 1.02
expect(orderResult.original_total).toEqual(23.24) // 10 * 1.1 + 12 * 1.02
await api.post(
`/admin/returns/${returnId}/receive/confirm`,
{},
adminHeaders
)
const orderResult2 = (
await api.get(`/admin/orders/${orderId}`, adminHeaders)
).data.order
// after confirmation only first added item is active
expect(orderResult2.total).toEqual(11.016)
expect(orderResult2.original_total).toEqual(12.24)
})
})
})
},
})