Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1c18e02
Pass shipping_methods to computeActions context
NicolasGorga Jan 7, 2026
581573f
Tests
NicolasGorga Jan 7, 2026
b1dab6f
Add changeset
NicolasGorga Jan 7, 2026
03d0a75
Implementation plan
NicolasGorga Jan 6, 2026
a24b8ac
Update db model and add missing indexes
NicolasGorga Jan 6, 2026
083b5ab
Migration
NicolasGorga Jan 7, 2026
7e8a90e
Update todos status
NicolasGorga Jan 7, 2026
dbbc932
Data migration script
NicolasGorga Jan 7, 2026
0e1ef49
Update plan
NicolasGorga Jan 7, 2026
63b8fe9
Create versioned shipping methods upon order changes
NicolasGorga Jan 7, 2026
314420e
Delete adjustments on version reversion
NicolasGorga Jan 7, 2026
2abf746
Update plan
NicolasGorga Jan 7, 2026
b732970
Fix
NicolasGorga Jan 7, 2026
26bdf7b
Module tests
NicolasGorga Jan 7, 2026
d4479d1
Add changeset
NicolasGorga Jan 7, 2026
ae6522b
New order change action type to include shipping methods adjustments …
NicolasGorga Jan 8, 2026
ef4b875
Inlcude backfill inside migration file since it is not cross module
NicolasGorga Jan 8, 2026
5e72cb1
Fix tests
NicolasGorga Jan 8, 2026
b99bb22
Add versioned find of shipping_method adjustments to order repo
NicolasGorga Jan 8, 2026
23df8e6
Pass adjustments to populate when related entity
NicolasGorga Jan 10, 2026
d58d91c
Fix tests
NicolasGorga Jan 10, 2026
1155381
Remove new indes on order line item adjustment and version
NicolasGorga Jan 12, 2026
8f21655
Merge branch 'develop' into feat/version-order-shipping-method-adjust…
NicolasGorga Jan 12, 2026
a0f877c
Test
NicolasGorga Jan 18, 2026
a9d4111
Merge branch 'develop' into feat/version-order-shipping-method-adjust…
NicolasGorga Jan 18, 2026
47cde33
Merge branch 'develop' into feat/version-order-shipping-method-adjust…
NicolasGorga Jan 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sixty-eagles-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/core-flows": patch
---

fix(core-flows): Pass shipping_methods to computeActions context
8 changes: 8 additions & 0 deletions .changeset/strong-ants-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@medusajs/core-flows": patch
"@medusajs/order": patch
"@medusajs/types": patch
"@medusajs/medusa": patch
---

feat(core-flows,order,medusa,types): Version shipping method adjustments & implement missing creation flow for versioned adjustments
180 changes: 180 additions & 0 deletions integration-tests/http/__tests__/order-edits/order-edits.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2492,6 +2492,186 @@ medusaIntegrationTestRunner({
expect(orderResult2.total).toEqual(20)
expect(orderResult2.original_total).toEqual(20)
})

it("should maintain shipping method adjustments when adding a new item with promotion targeting shipping methods", async () => {
// Create a promotion that targets shipping methods
const shippingPromotion = await promotionModule.createPromotions({
code: "SHIPPING_PROMO",
type: PromotionType.STANDARD,
status: PromotionStatus.ACTIVE,
application_method: {
type: "fixed",
target_type: "shipping_methods",
allocation: "each",
value: 2,
max_quantity: 1,
currency_code: "usd",
target_rules: [
{
attribute: "name",
operator: "in",
values: ["Test shipping method"],
},
],
},
})

// Create an order with shipping method
// @ts-ignore
const orderWithShipping = await orderModule.createOrders({
email: "foo@bar.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
items: [
{
// @ts-ignore
id: "item-shipping-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",
},
shipping_methods: [
{
name: "Test shipping method",
amount: 5,
},
],
currency_code: "usd",
})

// Create shipping method adjustment for the promotion
if (orderWithShipping.shipping_methods?.[0]) {
await orderModule.createOrderShippingMethodAdjustments(
orderWithShipping.id,
[
{
shipping_method_id: orderWithShipping.shipping_methods[0].id,
code: shippingPromotion.code!,
amount: 2,
},
]
)
}

// Link promotion to order
await remoteLink.create({
[Modules.ORDER]: { order_id: orderWithShipping.id },
[Modules.PROMOTION]: { promotion_id: shippingPromotion.id },
})

// Create order edit
let result = await api.post(
"/admin/order-edits",
{
order_id: orderWithShipping.id,
description: "Test shipping promotion",
},
adminHeaders
)

const orderChangeId = result.data.order_change.id
const orderId = result.data.order_change.order_id

// Enable carry over promotions
await api.post(
`/admin/order-changes/${orderChangeId}`,
{
carry_over_promotions: true,
},
adminHeaders
)

result = (await api.get(`/admin/orders/${orderId}`, adminHeaders)).data
.order

// Original order: $10 item + $5 shipping - $2 shipping discount = $13
expect(result.total).toEqual(13)
expect(result.shipping_methods[0].adjustments).toEqual([
expect.objectContaining({
code: shippingPromotion.code!,
amount: 2,
}),
])

// Add item with price $12
result = (
await api.post(
`/admin/order-edits/${orderId}/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
).data.order_preview

// After adding item: $22 items + $5 shipping - $2 shipping discount + $1.2 tax = $26.2
expect(result.total).toEqual(26.2)
expect(result.original_total).toEqual(28.2) // +$2 shipping discount

// Shipping method adjustment should still be present
expect(result.shipping_methods[0].adjustments).toEqual([
expect.objectContaining({
code: shippingPromotion.code!,
amount: 2,
}),
])

// Confirm original order is not updated
const orderResult = (
await api.get(`/admin/orders/${orderId}`, adminHeaders)
).data.order

expect(orderResult.total).toEqual(13)
expect(orderResult.shipping_methods[0].adjustments).toEqual([
expect.objectContaining({
code: shippingPromotion.code!,
amount: 2,
}),
])

// Confirm the order edit
await api.post(
`/admin/order-edits/${orderId}/confirm`,
{},
adminHeaders
)

const orderResult2 = (
await api.get(`/admin/orders/${orderId}`, adminHeaders)
).data.order

expect(orderResult2.total).toEqual(26.2)
expect(orderResult2.original_total).toEqual(28.2)
expect(orderResult2.shipping_methods[0].adjustments).toEqual([
expect.objectContaining({
code: shippingPromotion.code!,
amount: 2,
}),
])
})
})
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from "@medusajs/framework/workflows-sdk"
import type {
ComputeActionContext,
ComputeActionShippingLine,
OrderChangeDTO,
OrderDTO,
PromotionDTO,
Expand Down Expand Up @@ -145,7 +146,7 @@ export const computeDraftOrderAdjustmentsWorkflow = createWorkflow(
.filter((p) => p !== undefined)
})

const actionsToComputeItemsInput = transform(
const actionsToComputeContext = transform(
{ previewedOrder, order },
({ previewedOrder, order }) => {
return {
Expand All @@ -155,12 +156,14 @@ export const computeDraftOrderAdjustmentsWorkflow = createWorkflow(
// Buy-Get promotions rely on the product ID, so we need to manually set it before refreshing adjustments
product: { id: item.product_id },
})),
shipping_methods:
previewedOrder.shipping_methods as unknown as ComputeActionShippingLine[],
} as ComputeActionContext
}
)

const actions = getActionsToComputeFromPromotionsStep({
computeActionContext: actionsToComputeItemsInput,
computeActionContext: actionsToComputeContext,
promotionCodesToApply: orderPromotions,
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ComputeActionContext,
ComputeActionShippingLine,
OrderChangeDTO,
OrderDTO,
PromotionDTO,
Expand Down Expand Up @@ -87,7 +88,7 @@ export const computeAdjustmentsForPreviewWorkflow = createWorkflow(
*/
!!order.promotions.length && !!input.orderChange.carry_over_promotions
).then(() => {
const actionsToComputeItemsInput = transform(
const actionsToComputeContext = transform(
Copy link
Contributor

@fPolic fPolic Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: belive the part that is missing is that in this file, prepareAdjustmentsFromPromotionActionsStep returns shipping adjustments which we need to store on the change action (or create a new action dedicated to shipping adjustments).
Then this adjustments need to be applied when processing a change, see: packages/modules/order/src/utils/actions/item-adjustments-replace.ts

edit: from the plan, I see that some of this was explicitley skipped but without the full flow, new adjustments wont't be created and won't be filtered for that order version when fetching order

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point, will look into it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed lmkwyt @fPolic

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, think we are almost there...next thing to consider is when we fetch an order at a specific version that only shipping adjustments for that version are fetched: see packages/modules/order/src/utils/base-repository-find.ts and loadItemAdjustments method for referrence

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed

{ previewedOrder, order: input.order },
({ previewedOrder, order }) => {
return {
Expand All @@ -97,6 +98,8 @@ export const computeAdjustmentsForPreviewWorkflow = createWorkflow(
// Buy-Get promotions rely on the product ID, so we need to manually set it before refreshing adjustments
product: { id: item.product_id },
})),
shipping_methods:
previewedOrder.shipping_methods as unknown as ComputeActionShippingLine[],
} as ComputeActionContext
}
)
Expand All @@ -108,7 +111,7 @@ export const computeAdjustmentsForPreviewWorkflow = createWorkflow(
})

const actions = getActionsToComputeFromPromotionsStep({
computeActionContext: actionsToComputeItemsInput,
computeActionContext: actionsToComputeContext,
promotionCodesToApply: orderPromotions,
options: {
skip_usage_limit_checks: true,
Expand Down
10 changes: 10 additions & 0 deletions packages/core/types/src/order/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ export interface OrderShippingMethodAdjustmentDTO
* The ID of the associated shipping method.
*/
shipping_method_id: string

/**
* The version of the adjustment.
*/
version: number
}

/**
Expand All @@ -147,6 +152,11 @@ export interface OrderLineItemAdjustmentDTO extends OrderAdjustmentLineDTO {
* The ID of the associated line item.
*/
item_id: string

/**
* The version of the adjustment.
*/
version: number
}

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/core/types/src/order/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,11 @@ export interface CreateOrderShippingMethodAdjustmentDTO {
* The associated provider's ID.
*/
provider_id?: string

/**
* The version of the adjustment.
*/
version?: number
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ExecArgs } from "@medusajs/framework/types"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"

/**
* Data migration to backfill version field for existing order_shipping_method_adjustment records.
* Sets adjustment versions based on the latest order_shipping version for their associated shipping method.
*/
export default async function backfillShippingAdjustmentVersions({
container,
}: ExecArgs) {
const knex = container.resolve(ContainerRegistrationKeys.PG_CONNECTION)
const logger = container.resolve(ContainerRegistrationKeys.LOGGER)

logger.info("Backfilling shipping method adjustment versions")

try {
await knex.transaction(async (trx) => {
const result = await trx.raw(`
WITH latest_order_shipping_version AS (
SELECT
os.shipping_method_id AS shipping_method_id,
MAX(os.version) AS version
FROM "order_shipping" os
WHERE os.deleted_at IS NULL
GROUP BY os.shipping_method_id
)
UPDATE "order_shipping_method_adjustment" osma
SET version = losv.version
FROM latest_order_shipping_version losv
WHERE osma.shipping_method_id = losv.shipping_method_id
AND osma.version <> losv.version
`)

logger.info(
`Successfully backfilled shipping method adjustment versions (${result.rowCount} rows updated)`
)
})
} catch (e) {
logger.error("Failed to backfill shipping method adjustment versions", e)
throw e
}
}
Loading
Loading