feat(translation,core-flows): translate remaining core entities and sync shipping option <> method translations (#14358)

* Get translated shipping options step

* Apply translations on shipping options list methods.

* Pass shipping option naem when refreshing cart shipping methods, so if locale changed, we update the name

* Update translatable fields config

* Cart shipping method update translation tests

* Shipping options translations tests

* Add changeset

* Update order shipping method translations on update

* Remove unnecessary workflow and use step instead

* Translate shipping method on order edit

* Use new update shipping methods tranlsations step

* Draft order shipping method translation sync

* Translate shipping method on order exchange

* Translate returns shipping methods

* Translate claims shipping methods

* Remove unnecessary check

* Early return

* Fix import

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Nicolas Gorga
2026-01-02 07:26:06 -03:00
committed by GitHub
parent 5f807ee657
commit 11de7e3e34
25 changed files with 2831 additions and 27 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/translation": patch
"@medusajs/core-flows": patch
---
feat(translation,core-flows): translate remaining core entities and sync shipping option <> method translations

View File

@@ -515,6 +515,229 @@ medusaIntegrationTestRunner({
expect(allItemsTranslated).toBe(true) expect(allItemsTranslated).toBe(true)
}) })
}) })
describe("POST /store/carts/:id/shipping-methods (shipping method translation)", () => {
let shippingOption
beforeEach(async () => {
const stockLocation = (
await api.post(
`/admin/stock-locations`,
{ name: "translation test location" },
adminHeaders
)
).data.stock_location
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{
name: `Translation-Test-${shippingProfile.id}`,
type: "test-type",
},
adminHeaders
)
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: `Translation-Test-${shippingProfile.id}`,
geo_zones: [{ type: "country", country_code: "US" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Standard Shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Standard",
description: "Standard shipping option",
code: "standard",
},
prices: [{ currency_code: "usd", amount: 1000 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
// Create translations for shipping option
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Expédition Standard",
},
},
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Standardversand",
},
},
],
},
adminHeaders
)
})
it("should add shipping method with translated name when cart has locale", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "fr-FR",
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
const cart = cartResponse.data.cart
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
const updatedCart = await api
.get(
`/store/carts/${cart.id}?fields=+shipping_methods.name`,
storeHeaders
)
.then((res) => res.data.cart)
expect(updatedCart.shipping_methods).toHaveLength(1)
expect(updatedCart.shipping_methods[0]).toEqual(
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Expédition Standard",
})
)
})
it("should update shipping method name when cart locale changes", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "fr-FR",
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
const cart = cartResponse.data.cart
// Add shipping method with French locale
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
// Verify French translation
let cartResponseAfter = await api.get(
`/store/carts/${cart.id}?fields=+shipping_methods.name`,
storeHeaders
)
expect(cartResponseAfter.data.cart.shipping_methods[0].name).toEqual(
"Expédition Standard"
)
// Update cart locale to German
await api.post(
`/store/carts/${cart.id}`,
{
locale: "de-DE",
},
storeHeaders
)
// Verify German translation
cartResponseAfter = await api.get(
`/store/carts/${cart.id}?fields=+shipping_methods.name`,
storeHeaders
)
expect(cartResponseAfter.data.cart.shipping_methods[0].name).toEqual(
"Standardversand"
)
})
it("should use original shipping option name when no translation exists", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "ja-JP",
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
const cart = cartResponse.data.cart
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
const updatedCart = await api
.get(
`/store/carts/${cart.id}?fields=+shipping_methods.name`,
storeHeaders
)
.then((res) => res.data.cart)
expect(updatedCart.shipping_methods[0]).toEqual(
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Standard Shipping",
})
)
})
})
}) })
}, },
}) })

View File

@@ -0,0 +1,739 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import {
ClaimReason,
ClaimType,
Modules,
ProductStatus,
RuleOperator,
} from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
} from "../../../helpers/create-admin-user"
jest.setTimeout(60000)
process.env.MEDUSA_FF_TRANSLATION = "true"
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let order
let customer
let returnShippingOption
let outboundShippingOption
let shippingProfile
let fulfillmentSet
let inventoryItem
let location
let salesChannel
let region
let product
let productExtra
beforeEach(async () => {
const container = getContainer()
await createAdminUser(dbConnection, adminHeaders, container)
customer = (
await api.post(
"/admin/customers",
{
first_name: "joe",
email: "joe@admin.com",
},
adminHeaders
)
).data.customer
// Set up supported locales in the store
const storeModule = container.resolve(Modules.STORE)
const [defaultStore] = await storeModule.listStores(
{},
{ select: ["id"], take: 1 }
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],
})
salesChannel = (
await api.post(
"/admin/sales-channels",
{ name: "Webshop", description: "channel" },
adminHeaders
)
).data.sales_channel
shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "Test", type: "default" },
adminHeaders
)
).data.shipping_profile
region = (
await api.post(
"/admin/regions",
{
name: "Test Region",
currency_code: "usd",
},
adminHeaders
)
).data.region
location = (
await api.post(
`/admin/stock-locations`,
{ name: "Test location" },
adminHeaders
)
).data.stock_location
inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "test-variant" },
adminHeaders
)
).data.inventory_item
await api.post(
`/admin/inventory-items/${inventoryItem.id}/location-levels`,
{ location_id: location.id, stocked_quantity: 10 },
adminHeaders
)
await api.post(
`/admin/stock-locations/${location.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
product = (
await api.post(
"/admin/products",
{
title: "Test product",
status: ProductStatus.PUBLISHED,
options: [{ title: "size", values: ["large", "small"] }],
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant",
sku: "test-variant",
options: { size: "large" },
inventory_items: [
{
inventory_item_id: inventoryItem.id,
required_quantity: 1,
},
],
prices: [{ currency_code: "usd", amount: 25 }],
},
],
},
adminHeaders
)
).data.product
productExtra = (
await api.post(
"/admin/products",
{
title: "Extra product",
status: ProductStatus.PUBLISHED,
options: [{ title: "size", values: ["large", "small"] }],
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Extra variant",
sku: "extra-variant",
options: { size: "large" },
manage_inventory: false,
prices: [{ currency_code: "usd", amount: 50 }],
},
],
},
adminHeaders
)
).data.product
location = (
await api.post(
`/admin/stock-locations/${location.id}/fulfillment-sets?fields=*fulfillment_sets`,
{ name: "Test", type: "test-type" },
adminHeaders
)
).data.stock_location
fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${location.fulfillment_sets[0].id}/service-zones`,
{
name: "Test",
geo_zones: [{ type: "country", country_code: "us" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${location.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
returnShippingOption = (
await api.post(
"/admin/shipping-options",
{
name: "Return shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 1500 }],
rules: [
{
operator: RuleOperator.EQ,
attribute: "is_return",
value: "true",
},
],
},
adminHeaders
)
).data.shipping_option
outboundShippingOption = (
await api.post(
"/admin/shipping-options",
{
name: "Outbound shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 0 }],
rules: [
{
operator: RuleOperator.EQ,
attribute: "is_return",
value: "false",
},
],
},
adminHeaders
)
).data.shipping_option
// Create translations for shipping options
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: returnShippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: { name: "Expédition de retour" },
},
{
reference_id: returnShippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: { name: "Rückversand" },
},
{
reference_id: outboundShippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: { name: "Expédition sortante" },
},
{
reference_id: outboundShippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: { name: "Ausgehende Versand" },
},
],
},
adminHeaders
)
})
const createOrderWithLocale = async (locale?: string) => {
const container = getContainer()
const orderModule = container.resolve(Modules.ORDER)
const inventoryModule = container.resolve(Modules.INVENTORY)
const createdOrder = await orderModule.createOrders({
region_id: region.id,
email: "foo@bar.com",
locale,
items: [
{
title: "Custom Item",
variant_id: product.variants[0].id,
quantity: 2,
unit_price: 25,
},
],
sales_channel_id: salesChannel.id,
shipping_address: {
customer_id: customer.id,
first_name: "Test",
last_name: "Test",
address_1: "Test",
city: "Test",
country_code: "US",
postal_code: "12345",
phone: "12345",
},
billing_address: {
customer_id: customer.id,
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: {},
},
],
currency_code: "usd",
customer_id: customer.id,
transactions: [
{
amount: 60,
currency_code: "usd",
},
],
})
await inventoryModule.createReservationItems([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
quantity: 2,
line_item_id: createdOrder.items![0].id,
},
])
// Fulfill the order
await api.post(
`/admin/orders/${createdOrder.id}/fulfillments`,
{
items: [
{
id: createdOrder.items![0].id,
quantity: 2,
},
],
},
adminHeaders
)
return createdOrder
}
describe("Claim shipping method translation", () => {
describe("Inbound (return) shipping method", () => {
it("should translate inbound shipping method name using French locale", async () => {
order = await createOrderWithLocale("fr-FR")
const claim = (
await api.post(
"/admin/claims",
{
order_id: order.id,
type: ClaimType.REPLACE,
description: "Test claim",
},
adminHeaders
)
).data.claim
await api.post(
`/admin/claims/${claim.id}/inbound/items`,
{
items: [
{
id: order.items![0].id,
quantity: 1,
reason: ClaimReason.PRODUCTION_FAILURE,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/claims/${claim.id}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
const inboundShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(inboundShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition de retour",
shipping_option_id: returnShippingOption.id,
})
)
})
it("should use original inbound shipping method name when order has no locale", async () => {
order = await createOrderWithLocale()
const claim = (
await api.post(
"/admin/claims",
{
order_id: order.id,
type: ClaimType.REPLACE,
description: "Test claim",
},
adminHeaders
)
).data.claim
await api.post(
`/admin/claims/${claim.id}/inbound/items`,
{
items: [
{
id: order.items![0].id,
quantity: 1,
reason: ClaimReason.PRODUCTION_FAILURE,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/claims/${claim.id}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
const inboundShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(inboundShippingMethod).toEqual(
expect.objectContaining({
name: "Return shipping",
shipping_option_id: returnShippingOption.id,
})
)
})
})
describe("Outbound shipping method", () => {
it("should translate outbound shipping method", async () => {
order = await createOrderWithLocale("fr-FR")
const claim = (
await api.post(
"/admin/claims",
{
order_id: order.id,
type: ClaimType.REPLACE,
description: "Test claim",
},
adminHeaders
)
).data.claim
await api.post(
`/admin/claims/${claim.id}/inbound/items`,
{
items: [
{
id: order.items![0].id,
quantity: 1,
reason: ClaimReason.PRODUCTION_FAILURE,
},
],
},
adminHeaders
)
await api.post(
`/admin/claims/${claim.id}/outbound/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/claims/${claim.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
const outboundShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === outboundShippingOption.id
)
expect(outboundShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition sortante",
shipping_option_id: outboundShippingOption.id,
})
)
})
it("should use original outbound shipping method name when order has no locale", async () => {
order = await createOrderWithLocale()
const claim = (
await api.post(
"/admin/claims",
{
order_id: order.id,
type: ClaimType.REPLACE,
description: "Test claim",
},
adminHeaders
)
).data.claim
await api.post(
`/admin/claims/${claim.id}/inbound/items`,
{
items: [
{
id: order.items![0].id,
quantity: 1,
reason: ClaimReason.PRODUCTION_FAILURE,
},
],
},
adminHeaders
)
await api.post(
`/admin/claims/${claim.id}/outbound/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/claims/${claim.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
const outboundShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === outboundShippingOption.id
)
expect(outboundShippingMethod).toEqual(
expect.objectContaining({
name: "Outbound shipping",
shipping_option_id: outboundShippingOption.id,
})
)
})
})
describe("Both inbound and outbound shipping methods", () => {
it("should translate both shipping methods", async () => {
order = await createOrderWithLocale("fr-FR")
const claim = (
await api.post(
"/admin/claims",
{
order_id: order.id,
type: ClaimType.REPLACE,
description: "Test claim",
},
adminHeaders
)
).data.claim
await api.post(
`/admin/claims/${claim.id}/inbound/items`,
{
items: [
{
id: order.items![0].id,
quantity: 1,
reason: ClaimReason.PRODUCTION_FAILURE,
},
],
},
adminHeaders
)
await api.post(
`/admin/claims/${claim.id}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
await api.post(
`/admin/claims/${claim.id}/outbound/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
const result = await api.post(
`/admin/claims/${claim.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
const shippingMethods = result.data.order_preview.shipping_methods
const inboundShippingMethod = shippingMethods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
const outboundShippingMethod = shippingMethods.find(
(sm: any) => sm.shipping_option_id === outboundShippingOption.id
)
expect(inboundShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition de retour",
shipping_option_id: returnShippingOption.id,
})
)
expect(outboundShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition sortante",
shipping_option_id: outboundShippingOption.id,
})
)
})
it("should keep translations after confirming claim request", async () => {
order = await createOrderWithLocale("fr-FR")
const claim = (
await api.post(
"/admin/claims",
{
order_id: order.id,
type: ClaimType.REPLACE,
description: "Test claim",
},
adminHeaders
)
).data.claim
await api.post(
`/admin/claims/${claim.id}/inbound/items`,
{
items: [
{
id: order.items![0].id,
quantity: 1,
reason: ClaimReason.PRODUCTION_FAILURE,
},
],
},
adminHeaders
)
await api.post(
`/admin/claims/${claim.id}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
await api.post(
`/admin/claims/${claim.id}/outbound/items`,
{
items: [
{
variant_id: productExtra.variants[0].id,
quantity: 1,
},
],
},
adminHeaders
)
await api.post(
`/admin/claims/${claim.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
await api.post(`/admin/claims/${claim.id}/request`, {}, adminHeaders)
const orderResult = await api.get(
`/admin/orders/${order.id}`,
adminHeaders
)
const shippingMethods = orderResult.data.order.shipping_methods
const outboundShippingMethod = shippingMethods.find(
(sm: any) => sm.shipping_option_id === outboundShippingOption.id
)
expect(outboundShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition sortante",
shipping_option_id: outboundShippingOption.id,
})
)
})
})
})
},
})

View File

@@ -208,6 +208,22 @@ medusaIntegrationTestRunner({
locale_code: "de-DE", locale_code: "de-DE",
translations: { title: "Mittel" }, translations: { title: "Mittel" },
}, },
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Option d'expédition de test",
},
},
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Test-Versandoption",
},
},
], ],
}, },
adminHeaders adminHeaders
@@ -480,6 +496,202 @@ medusaIntegrationTestRunner({
) )
}) })
}) })
describe("POST /admin/draft-orders/:id/edit/shipping-methods (add shipping method to draft order)", () => {
it("should translate shipping method added to draft order using draft order locale", async () => {
const draftOrder = (
await api.post(
"/admin/draft-orders",
{
email: "test@test.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale: "fr-FR",
shipping_address: {
address_1: "123 Main St",
city: "Anytown",
country_code: "us",
postal_code: "12345",
first_name: "John",
},
},
adminHeaders
)
).data.draft_order
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit`,
{},
adminHeaders
)
const previewResponse = await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/shipping-methods`,
{ shipping_option_id: shippingOption.id },
adminHeaders
)
expect(
previewResponse.data.draft_order_preview.shipping_methods
).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Option d'expédition de test",
}),
])
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/confirm`,
{},
adminHeaders
)
const updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
expect(updatedDraftOrder.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Option d'expédition de test",
}),
])
)
})
it("should have original shipping method name when draft order has no locale", async () => {
const draftOrder = (
await api.post(
"/admin/draft-orders",
{
email: "test@test.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
shipping_address: {
address_1: "123 Main St",
city: "Anytown",
country_code: "us",
postal_code: "12345",
first_name: "John",
},
},
adminHeaders
)
).data.draft_order
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit`,
{},
adminHeaders
)
const previewResponse = await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/shipping-methods`,
{ shipping_option_id: shippingOption.id },
adminHeaders
)
expect(
previewResponse.data.draft_order_preview.shipping_methods
).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Test shipping option",
}),
])
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/confirm`,
{},
adminHeaders
)
const updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
expect(updatedDraftOrder.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Test shipping option",
}),
])
)
})
})
describe("POST /admin/draft-orders/:id (update draft order locale)", () => {
it("should re-translate shipping methods when locale is updated", async () => {
const draftOrder = (
await api.post(
"/admin/draft-orders",
{
email: "test@test.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale: "fr-FR",
shipping_address: {
address_1: "123 Main St",
city: "Anytown",
country_code: "us",
postal_code: "12345",
first_name: "John",
},
},
adminHeaders
)
).data.draft_order
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit`,
{},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/shipping-methods`,
{ shipping_option_id: shippingOption.id },
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/confirm`,
{},
adminHeaders
)
let updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
expect(updatedDraftOrder.shipping_methods[0].name).toEqual(
"Option d'expédition de test"
)
await api.post(
`/admin/draft-orders/${draftOrder.id}`,
{ locale: "de-DE" },
adminHeaders
)
updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
expect(updatedDraftOrder.shipping_methods[0]).toEqual(
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Test-Versandoption",
})
)
})
})
}) })
}, },
}) })

View File

@@ -34,6 +34,7 @@ medusaIntegrationTestRunner({
let stockLocation: { id: string } let stockLocation: { id: string }
let shippingOption: { id: string } let shippingOption: { id: string }
let outboundShippingOption: { id: string } let outboundShippingOption: { id: string }
let returnShippingOption: { id: string }
let inventoryItem: { id: string } let inventoryItem: { id: string }
beforeAll(async () => { beforeAll(async () => {
@@ -224,6 +225,33 @@ medusaIntegrationTestRunner({
) )
).data.shipping_option ).data.shipping_option
returnShippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Return shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 500 }],
rules: [
{
operator: RuleOperator.EQ,
attribute: "is_return",
value: "true",
},
],
},
adminHeaders
)
).data.shipping_option
await api.post( await api.post(
"/admin/translations/batch", "/admin/translations/batch",
{ {
@@ -270,6 +298,38 @@ medusaIntegrationTestRunner({
locale_code: "de-DE", locale_code: "de-DE",
translations: { title: "Mittel" }, translations: { title: "Mittel" },
}, },
{
reference_id: outboundShippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Expédition sortante",
},
},
{
reference_id: outboundShippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Ausgehende Versand",
},
},
{
reference_id: returnShippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Expédition de retour",
},
},
{
reference_id: returnShippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Rückversand",
},
},
], ],
}, },
adminHeaders adminHeaders
@@ -522,6 +582,278 @@ medusaIntegrationTestRunner({
) )
}) })
}) })
describe("Exchange shipping method translation", () => {
it("should translate outbound and inbound shipping methods added during exchange using order locale", async () => {
const order = await createOrderFromCart("fr-FR")
await api.post(
`/admin/orders/${order.id}/fulfillments`,
{
location_id: stockLocation.id,
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
const exchange = (
await api.post(
"/admin/exchanges",
{ order_id: order.id, description: "Test exchange" },
adminHeaders
)
).data.exchange
await api.post(
`/admin/exchanges/${exchange.id}/inbound/items`,
{
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
const exchangeResult = (
await api.post(
`/admin/exchanges/${exchange.id}/request`,
{},
adminHeaders
)
).data.exchange
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const outboundShippingMethod = updatedOrder.shipping_methods.find(
(sm: any) => sm.shipping_option_id === outboundShippingOption.id
)
expect(outboundShippingMethod).toEqual(
expect.objectContaining({
shipping_option_id: outboundShippingOption.id,
name: "Expédition sortante",
})
)
const orderReturn = (
await api.get(
`/admin/returns/${exchangeResult.return_id}?fields=*shipping_methods`,
adminHeaders
)
).data.return
const inboundShippingMethod = orderReturn.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(inboundShippingMethod).toEqual(
expect.objectContaining({
shipping_option_id: returnShippingOption.id,
name: "Expédition de retour",
})
)
})
it("should translate outbound and inbound shipping methods using German locale", async () => {
const order = await createOrderFromCart("de-DE")
await api.post(
`/admin/orders/${order.id}/fulfillments`,
{
location_id: stockLocation.id,
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
const exchange = (
await api.post(
"/admin/exchanges",
{ order_id: order.id, description: "Test exchange" },
adminHeaders
)
).data.exchange
await api.post(
`/admin/exchanges/${exchange.id}/inbound/items`,
{
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
const exchangeResult = (
await api.post(
`/admin/exchanges/${exchange.id}/request`,
{},
adminHeaders
)
).data.exchange
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const outboundShippingMethod = updatedOrder.shipping_methods.find(
(sm: any) => sm.shipping_option_id === outboundShippingOption.id
)
expect(outboundShippingMethod).toEqual(
expect.objectContaining({
shipping_option_id: outboundShippingOption.id,
name: "Ausgehende Versand",
})
)
const orderReturn = (
await api.get(
`/admin/returns/${exchangeResult.return_id}?fields=*shipping_methods`,
adminHeaders
)
).data.return
const inboundShippingMethod = orderReturn.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(inboundShippingMethod).toEqual(
expect.objectContaining({
shipping_option_id: returnShippingOption.id,
name: "Rückversand",
})
)
})
it("should have original shipping method names when order has no locale", async () => {
const order = await createOrderFromCart()
await api.post(
`/admin/orders/${order.id}/fulfillments`,
{
location_id: stockLocation.id,
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
const exchange = (
await api.post(
"/admin/exchanges",
{ order_id: order.id, description: "Test exchange" },
adminHeaders
)
).data.exchange
await api.post(
`/admin/exchanges/${exchange.id}/inbound/items`,
{
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/inbound/shipping-method`,
{ shipping_option_id: returnShippingOption.id },
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
const exchangeResult = (
await api.post(
`/admin/exchanges/${exchange.id}/request`,
{},
adminHeaders
)
).data.exchange
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const outboundShippingMethod = updatedOrder.shipping_methods.find(
(sm: any) => sm.shipping_option_id === outboundShippingOption.id
)
expect(outboundShippingMethod).toEqual(
expect.objectContaining({
shipping_option_id: outboundShippingOption.id,
name: "Outbound shipping",
})
)
const orderReturn = (
await api.get(
`/admin/returns/${exchangeResult.return_id}?fields=*shipping_methods`,
adminHeaders
)
).data.return
const inboundShippingMethod = orderReturn.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(inboundShippingMethod).toEqual(
expect.objectContaining({
shipping_option_id: returnShippingOption.id,
name: "Return shipping",
})
)
})
})
}) })
}, },
}) })

View File

@@ -33,6 +33,7 @@ medusaIntegrationTestRunner({
let shippingProfile: { id: string } let shippingProfile: { id: string }
let stockLocation: { id: string } let stockLocation: { id: string }
let shippingOption: { id: string } let shippingOption: { id: string }
let additionalShippingOption: { id: string }
let inventoryItem: { id: string } let inventoryItem: { id: string }
beforeAll(async () => { beforeAll(async () => {
@@ -196,6 +197,27 @@ medusaIntegrationTestRunner({
) )
).data.shipping_option ).data.shipping_option
additionalShippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Additional shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 500 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
await api.post( await api.post(
"/admin/translations/batch", "/admin/translations/batch",
{ {
@@ -242,6 +264,38 @@ medusaIntegrationTestRunner({
locale_code: "de-DE", locale_code: "de-DE",
translations: { title: "Mittel" }, translations: { title: "Mittel" },
}, },
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Option d'expédition de test",
},
},
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Test-Versandoption",
},
},
{
reference_id: additionalShippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Option d'expédition supplémentaire",
},
},
{
reference_id: additionalShippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Zusätzliche Versandoption",
},
},
], ],
}, },
adminHeaders adminHeaders
@@ -414,6 +468,96 @@ medusaIntegrationTestRunner({
) )
}) })
}) })
describe("POST /admin/order-edits/:id/shipping-method (add shipping method during order edit)", () => {
it("should translate shipping method added during order edit using order locale", async () => {
const order = await createOrderFromCart("fr-FR")
await api.post(
"/admin/order-edits",
{ order_id: order.id },
adminHeaders
)
const previewResponse = await api.post(
`/admin/order-edits/${order.id}/shipping-method`,
{ shipping_option_id: additionalShippingOption.id },
adminHeaders
)
expect(previewResponse.data.order_preview.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: additionalShippingOption.id,
name: "Option d'expédition supplémentaire",
}),
])
)
await api.post(
`/admin/order-edits/${order.id}/confirm`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
expect(updatedOrder.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: additionalShippingOption.id,
name: "Option d'expédition supplémentaire",
}),
])
)
})
it("should have original shipping method name when order has no locale", async () => {
const order = await createOrderFromCart()
await api.post(
"/admin/order-edits",
{ order_id: order.id },
adminHeaders
)
const previewResponse = await api.post(
`/admin/order-edits/${order.id}/shipping-method`,
{ shipping_option_id: additionalShippingOption.id },
adminHeaders
)
expect(previewResponse.data.order_preview.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: additionalShippingOption.id,
name: "Additional shipping option",
}),
])
)
await api.post(
`/admin/order-edits/${order.id}/confirm`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
expect(updatedOrder.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: additionalShippingOption.id,
name: "Additional shipping option",
}),
])
)
})
})
}) })
}, },
}) })

View File

@@ -255,7 +255,7 @@ medusaIntegrationTestRunner({
) )
).data.shipping_option ).data.shipping_option
// Create translations for product and variants // Create translations for product, variants, and shipping options
await api.post( await api.post(
"/admin/translations/batch", "/admin/translations/batch",
{ {
@@ -302,6 +302,22 @@ medusaIntegrationTestRunner({
locale_code: "de-DE", locale_code: "de-DE",
translations: { title: "Mittel" }, translations: { title: "Mittel" },
}, },
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Option d'expédition de test",
},
},
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Test-Versandoption",
},
},
], ],
}, },
adminHeaders adminHeaders
@@ -378,6 +394,30 @@ medusaIntegrationTestRunner({
}) })
) )
}) })
it("should preserve translated shipping methods when order is created from cart with locale", async () => {
const order = await createOrderFromCart("fr-FR")
expect(order.shipping_methods).toHaveLength(1)
expect(order.shipping_methods[0]).toEqual(
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Option d'expédition de test",
})
)
})
it("should have original shipping method name when order is created without locale", async () => {
const order = await createOrderFromCart()
expect(order.shipping_methods).toHaveLength(1)
expect(order.shipping_methods[0]).toEqual(
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Test shipping option",
})
)
})
}) })
describe("POST /admin/orders/:id (update order locale)", () => { describe("POST /admin/orders/:id (update order locale)", () => {
@@ -405,6 +445,32 @@ medusaIntegrationTestRunner({
) )
}) })
it("should re-translate shipping methods when locale is updated", async () => {
const order = await createOrderFromCart("fr-FR")
expect(order.shipping_methods[0].name).toEqual(
"Option d'expédition de test"
)
await api.post(
`/admin/orders/${order.id}`,
{ locale: "de-DE" },
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
expect(updatedOrder.shipping_methods).toHaveLength(1)
expect(updatedOrder.shipping_methods[0]).toEqual(
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Test-Versandoption",
})
)
})
it("should not re-translate items when updating other fields", async () => { it("should not re-translate items when updating other fields", async () => {
const order = await createOrderFromCart("fr-FR") const order = await createOrderFromCart("fr-FR")
@@ -429,6 +495,31 @@ medusaIntegrationTestRunner({
}) })
) )
}) })
it("should not re-translate shipping methods when updating other fields", async () => {
const order = await createOrderFromCart("fr-FR")
await api.post(
`/admin/orders/${order.id}`,
{ email: "updated@example.com" },
adminHeaders
)
const updatedOrder = (
await api.get(
`/admin/orders/${order.id}?fields=+email,+shipping_methods.name`,
adminHeaders
)
).data.order
expect(updatedOrder.email).toEqual("updated@example.com")
expect(updatedOrder.shipping_methods[0]).toEqual(
expect.objectContaining({
shipping_option_id: shippingOption.id,
name: "Option d'expédition de test",
})
)
})
}) })
}) })
}, },

View File

@@ -0,0 +1,539 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { Modules, RuleOperator } from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
} from "../../../helpers/create-admin-user"
jest.setTimeout(60000)
process.env.MEDUSA_FF_TRANSLATION = "true"
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let order
let returnShippingOption
let shippingProfile
let fulfillmentSet
let inventoryItem
let location
let salesChannel
let product
beforeEach(async () => {
const container = getContainer()
await createAdminUser(dbConnection, adminHeaders, container)
// Set up supported locales in the store
const storeModule = container.resolve(Modules.STORE)
const [defaultStore] = await storeModule.listStores(
{},
{ select: ["id"], take: 1 }
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],
})
salesChannel = (
await api.post(
"/admin/sales-channels",
{ name: "Webshop", description: "channel" },
adminHeaders
)
).data.sales_channel
shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{
name: "Test",
type: "default",
},
adminHeaders
)
).data.shipping_profile
location = (
await api.post(
`/admin/stock-locations`,
{
name: "Test location",
},
adminHeaders
)
).data.stock_location
inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "inv-1234" },
adminHeaders
)
).data.inventory_item
await api.post(
`/admin/inventory-items/${inventoryItem.id}/location-levels`,
{
location_id: location.id,
stocked_quantity: 2,
},
adminHeaders
)
await api.post(
`/admin/stock-locations/${location.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
product = (
await api.post(
"/admin/products",
{
title: "Test product",
options: [{ title: "size", values: ["x", "l"] }],
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant",
sku: "test-variant",
options: { size: "l" },
inventory_items: [
{
inventory_item_id: inventoryItem.id,
required_quantity: 1,
},
],
prices: [
{
currency_code: "usd",
amount: 10,
},
],
},
],
},
adminHeaders
)
).data.product
location = (
await api.post(
`/admin/stock-locations/${location.id}/fulfillment-sets?fields=*fulfillment_sets`,
{
name: "Test",
type: "test-type",
},
adminHeaders
)
).data.stock_location
fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${location.fulfillment_sets[0].id}/service-zones`,
{
name: "Test",
geo_zones: [{ type: "country", country_code: "us" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${location.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
returnShippingOption = (
await api.post(
"/admin/shipping-options",
{
name: "Return shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [
{
currency_code: "usd",
amount: 1000,
},
],
rules: [
{
operator: RuleOperator.EQ,
attribute: "is_return",
value: "true",
},
],
},
adminHeaders
)
).data.shipping_option
// Create translations for the return shipping option
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: returnShippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Expédition de retour",
},
},
{
reference_id: returnShippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Rückversand",
},
},
],
},
adminHeaders
)
})
const createOrderWithLocale = async (locale?: string) => {
const container = getContainer()
const orderModule = container.resolve(Modules.ORDER)
const inventoryModule = container.resolve(Modules.INVENTORY)
const createdOrder = await orderModule.createOrders({
region_id: "test_region_id",
email: "foo@bar.com",
locale,
items: [
{
title: "Custom Item",
variant_id: product.variants[0].id,
quantity: 2,
unit_price: 25,
},
],
sales_channel_id: salesChannel.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",
},
shipping_methods: [
{
name: "Test shipping method",
amount: 10,
data: {},
},
],
currency_code: "usd",
customer_id: "joe",
})
await inventoryModule.createReservationItems([
{
inventory_item_id: inventoryItem.id,
location_id: location.id,
quantity: 2,
line_item_id: createdOrder.items![0].id,
},
])
// Fulfill the order
await api.post(
`/admin/orders/${createdOrder.id}/fulfillments`,
{
items: [
{
id: createdOrder.items![0].id,
quantity: 2,
},
],
},
adminHeaders
)
return createdOrder
}
describe("Return shipping method translation", () => {
it("should translate return shipping method name using French locale", async () => {
order = await createOrderWithLocale("fr-FR")
const returnResult = await api.post(
"/admin/returns",
{
order_id: order.id,
location_id: location.id,
},
adminHeaders
)
const returnId = returnResult.data.return.id
const item = order.items[0]
await api.post(
`/admin/returns/${returnId}/request-items`,
{
items: [
{
id: item.id,
quantity: 1,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/returns/${returnId}/shipping-method`,
{
shipping_option_id: returnShippingOption.id,
},
adminHeaders
)
const returnShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(returnShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition de retour",
shipping_option_id: returnShippingOption.id,
})
)
})
it("should use original shipping method name when order has no locale", async () => {
order = await createOrderWithLocale()
const returnResult = await api.post(
"/admin/returns",
{
order_id: order.id,
location_id: location.id,
},
adminHeaders
)
const returnId = returnResult.data.return.id
const item = order.items[0]
await api.post(
`/admin/returns/${returnId}/request-items`,
{
items: [
{
id: item.id,
quantity: 1,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/returns/${returnId}/shipping-method`,
{
shipping_option_id: returnShippingOption.id,
},
adminHeaders
)
const returnShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(returnShippingMethod).toEqual(
expect.objectContaining({
name: "Return shipping",
shipping_option_id: returnShippingOption.id,
})
)
})
it("should use original name when locale has no translation", async () => {
order = await createOrderWithLocale("en-US")
const returnResult = await api.post(
"/admin/returns",
{
order_id: order.id,
location_id: location.id,
},
adminHeaders
)
const returnId = returnResult.data.return.id
const item = order.items[0]
await api.post(
`/admin/returns/${returnId}/request-items`,
{
items: [
{
id: item.id,
quantity: 1,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/returns/${returnId}/shipping-method`,
{
shipping_option_id: returnShippingOption.id,
},
adminHeaders
)
const returnShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(returnShippingMethod).toEqual(
expect.objectContaining({
name: "Return shipping",
shipping_option_id: returnShippingOption.id,
})
)
})
it("should translate return shipping method with custom price", async () => {
order = await createOrderWithLocale("fr-FR")
const returnResult = await api.post(
"/admin/returns",
{
order_id: order.id,
location_id: location.id,
},
adminHeaders
)
const returnId = returnResult.data.return.id
const item = order.items[0]
await api.post(
`/admin/returns/${returnId}/request-items`,
{
items: [
{
id: item.id,
quantity: 1,
},
],
},
adminHeaders
)
const shippingMethodResult = await api.post(
`/admin/returns/${returnId}/shipping-method`,
{
shipping_option_id: returnShippingOption.id,
custom_amount: 500,
},
adminHeaders
)
const returnShippingMethod =
shippingMethodResult.data.order_preview.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(returnShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition de retour",
shipping_option_id: returnShippingOption.id,
amount: 500,
})
)
})
it("should keep translation after confirming return request", async () => {
order = await createOrderWithLocale("fr-FR")
const returnResult = await api.post(
"/admin/returns",
{
order_id: order.id,
location_id: location.id,
},
adminHeaders
)
const returnId = returnResult.data.return.id
const item = order.items[0]
await api.post(
`/admin/returns/${returnId}/request-items`,
{
items: [
{
id: item.id,
quantity: 1,
},
],
},
adminHeaders
)
await api.post(
`/admin/returns/${returnId}/shipping-method`,
{
shipping_option_id: returnShippingOption.id,
},
adminHeaders
)
await api.post(`/admin/returns/${returnId}/request`, {}, adminHeaders)
const orderResult = await api.get(
`/admin/orders/${order.id}`,
adminHeaders
)
const returnShippingMethod =
orderResult.data.order.shipping_methods.find(
(sm: any) => sm.shipping_option_id === returnShippingOption.id
)
expect(returnShippingMethod).toEqual(
expect.objectContaining({
name: "Expédition de retour",
shipping_option_id: returnShippingOption.id,
})
)
})
})
},
})

View File

@@ -0,0 +1,344 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import {
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../../helpers/create-admin-user"
import { Modules, ProductStatus } from "@medusajs/utils"
jest.setTimeout(50000)
process.env.MEDUSA_FF_TRANSLATION = "true"
const env = {}
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe("Store: Shipping Option API (with translations)", () => {
let appContainer
let salesChannel
let region
let product
let stockLocation
let shippingProfile
let fulfillmentSet
let shippingOption
let storeHeaders
beforeAll(async () => {
appContainer = getContainer()
})
afterAll(async () => {
delete process.env.MEDUSA_FF_TRANSLATION
})
beforeEach(async () => {
const publishableKey = await generatePublishableKey(appContainer)
storeHeaders = generateStoreHeaders({ publishableKey })
await createAdminUser(dbConnection, adminHeaders, appContainer)
// Set up store locales
const storeModule = appContainer.resolve(Modules.STORE)
const [defaultStore] = await storeModule.listStores(
{},
{
select: ["id"],
take: 1,
}
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],
})
region = (
await api.post(
"/admin/regions",
{ name: "US", currency_code: "usd", countries: ["US"] },
adminHeaders
)
).data.region
shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "Test", type: "default" },
adminHeaders
)
).data.shipping_profile
salesChannel = (
await api.post(
"/admin/sales-channels",
{ name: "first channel", description: "channel" },
adminHeaders
)
).data.sales_channel
product = (
await api.post(
"/admin/products",
{
title: "Test fixture",
status: ProductStatus.PUBLISHED,
options: [
{ title: "size", values: ["large", "small"] },
{ title: "color", values: ["green"] },
],
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant",
manage_inventory: false,
prices: [
{
currency_code: "usd",
amount: 100,
},
],
options: {
size: "large",
color: "green",
},
},
],
},
adminHeaders
)
).data.product
stockLocation = (
await api.post(
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{
name: "Test",
type: "test-type",
},
adminHeaders
)
).data.stock_location.fulfillment_sets
fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: "Test",
geo_zones: [
{ type: "country", country_code: "us" },
{ type: "country", country_code: "dk" },
],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [
{
currency_code: "usd",
amount: 1000,
},
{
region_id: region.id,
amount: 1100,
},
],
rules: [],
},
adminHeaders
)
).data.shipping_option
// Create translations for shipping option
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "fr-FR",
translations: {
name: "Option d'expédition test",
},
},
{
reference_id: shippingOption.id,
reference: "shipping_option",
locale_code: "de-DE",
translations: {
name: "Test-Versandoption",
},
},
],
},
adminHeaders
)
})
describe("GET /store/shipping-options?cart_id=", () => {
it("should return translated shipping options when cart has locale", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
region_id: region.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
locale: "fr-FR",
email: "test@admin.com",
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
},
storeHeaders
)
const cart = cartResponse.data.cart
const shippingOptionsResponse = await api.get(
`/store/shipping-options?cart_id=${cart.id}`,
storeHeaders
)
expect(shippingOptionsResponse.status).toEqual(200)
expect(shippingOptionsResponse.data.shipping_options).toHaveLength(1)
expect(shippingOptionsResponse.data.shipping_options[0]).toEqual(
expect.objectContaining({
id: shippingOption.id,
name: "Option d'expédition test",
})
)
})
it("should return translated shipping options when locale is changed", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
region_id: region.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
locale: "fr-FR",
email: "test@admin.com",
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
},
storeHeaders
)
const cart = cartResponse.data.cart
// Verify French translation
let shippingOptionsResponse = await api.get(
`/store/shipping-options?cart_id=${cart.id}`,
storeHeaders
)
expect(shippingOptionsResponse.data.shipping_options[0].name).toEqual(
"Option d'expédition test"
)
// Update cart locale to German
await api.post(
`/store/carts/${cart.id}`,
{
locale: "de-DE",
},
storeHeaders
)
// Verify German translation
shippingOptionsResponse = await api.get(
`/store/shipping-options?cart_id=${cart.id}`,
storeHeaders
)
expect(shippingOptionsResponse.data.shipping_options[0].name).toEqual(
"Test-Versandoption"
)
})
it("should return original shipping option name when no translation exists", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
region_id: region.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
locale: "ja-JP",
email: "test@admin.com",
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
},
storeHeaders
)
const cart = cartResponse.data.cart
const shippingOptionsResponse = await api.get(
`/store/shipping-options?cart_id=${cart.id}`,
storeHeaders
)
expect(shippingOptionsResponse.status).toEqual(200)
expect(shippingOptionsResponse.data.shipping_options[0]).toEqual(
expect.objectContaining({
id: shippingOption.id,
name: "Test shipping option",
})
)
})
})
})
},
})

View File

@@ -142,6 +142,7 @@ export const cartFieldsForPricingContext = [
"shipping_address.postal_code", "shipping_address.postal_code",
"item_total", "item_total",
"total", "total",
"locale",
"customer.id", "customer.id",
"email", "email",
"customer.groups.id", "customer.groups.id",
@@ -188,6 +189,7 @@ export const productVariantsFields = [
// ensure that at least these fields are present when fetching cart for caluclating shipping options prices // ensure that at least these fields are present when fetching cart for caluclating shipping options prices
export const cartFieldsForCalculateShippingOptionsPrices = [ export const cartFieldsForCalculateShippingOptionsPrices = [
"id", "id",
"locale",
"items.*", "items.*",
"items.variant.id", "items.variant.id",
"items.variant.product.id", "items.variant.product.id",

View File

@@ -23,6 +23,7 @@ import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
import { calculateShippingOptionsPricesStep } from "../../fulfillment" import { calculateShippingOptionsPricesStep } from "../../fulfillment"
import { cartFieldsForCalculateShippingOptionsPrices } from "../utils/fields" import { cartFieldsForCalculateShippingOptionsPrices } from "../utils/fields"
import { shippingOptionsContextResult } from "../utils/schemas" import { shippingOptionsContextResult } from "../utils/schemas"
import { getTranslatedShippingOptionsStep } from "../../common/steps/get-translated-shipping-option"
const COMMON_OPTIONS_FIELDS = [ const COMMON_OPTIONS_FIELDS = [
"id", "id",
@@ -397,7 +398,12 @@ export const listShippingOptionsForCartWithPricingWorkflow = createWorkflow(
} }
) )
return new WorkflowResponse(shippingOptionsWithPrice, { const translatedShippingOptions = getTranslatedShippingOptionsStep({
shippingOptions: shippingOptionsWithPrice,
locale: cart.locale,
})
return new WorkflowResponse(translatedShippingOptions as any[], {
hooks: [setShippingOptionsContext] as const, hooks: [setShippingOptionsContext] as const,
}) })
} }

View File

@@ -24,6 +24,7 @@ import {
pricingContextResult, pricingContextResult,
shippingOptionsContextResult, shippingOptionsContextResult,
} from "../utils/schemas" } from "../utils/schemas"
import { getTranslatedShippingOptionsStep } from "../../common/steps/get-translated-shipping-option"
export const listShippingOptionsForCartWorkflowId = export const listShippingOptionsForCartWorkflowId =
"list-shipping-options-for-cart" "list-shipping-options-for-cart"
@@ -359,7 +360,12 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
}) })
) )
return new WorkflowResponse(shippingOptionsWithPrice, { const translatedShippingOptions = getTranslatedShippingOptionsStep({
shippingOptions: shippingOptionsWithPrice,
locale: cart.locale,
})
return new WorkflowResponse(translatedShippingOptions as any[], {
hooks: [setPricingContext, setShippingOptionsContext] as const, hooks: [setPricingContext, setShippingOptionsContext] as const,
}) })
} }

View File

@@ -192,6 +192,7 @@ export const refreshCartShippingMethodsWorkflow = createWorkflow(
return { return {
id: shippingMethod.id, id: shippingMethod.id,
shipping_option_id: shippingOption.id, shipping_option_id: shippingOption.id,
name: shippingOption.name,
amount: shippingOption.calculated_price.calculated_amount, amount: shippingOption.calculated_price.calculated_amount,
is_tax_inclusive: is_tax_inclusive:
shippingOption.calculated_price shippingOption.calculated_price

View File

@@ -0,0 +1,24 @@
import { applyTranslations } from "@medusajs/framework/utils"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { ShippingOptionDTO } from "@medusajs/types"
export const getTranslatedShippingOptionsStepId =
"get-translated-shipping-options"
export interface GetTranslatedShippingOptionsStepInput {
shippingOptions: ShippingOptionDTO[]
locale: string
}
export const getTranslatedShippingOptionsStep = createStep(
getTranslatedShippingOptionsStepId,
async (data: GetTranslatedShippingOptionsStepInput, { container }) => {
await applyTranslations({
localeCode: data.locale,
objects: data.shippingOptions,
container,
})
return new StepResponse(data.shippingOptions)
}
)

View File

@@ -6,6 +6,7 @@ export const draftOrderFieldsForRefreshSteps = [
"metadata", "metadata",
"sales_channel_id", "sales_channel_id",
"region_id", "region_id",
"locale",
"region.*", "region.*",
"items.*", "items.*",
"items.product.id", "items.product.id",

View File

@@ -31,6 +31,7 @@ import { validateDraftOrderChangeStep } from "../steps/validate-draft-order-chan
import { draftOrderFieldsForRefreshSteps } from "../utils/fields" import { draftOrderFieldsForRefreshSteps } from "../utils/fields"
import { acquireLockStep, releaseLockStep } from "../../locking" import { acquireLockStep, releaseLockStep } from "../../locking"
import { computeDraftOrderAdjustmentsWorkflow } from "./compute-draft-order-adjustments" import { computeDraftOrderAdjustmentsWorkflow } from "./compute-draft-order-adjustments"
import { getTranslatedShippingOptionsStep } from "../../common/steps/get-translated-shipping-option"
const validateShippingOptionStep = createStep( const validateShippingOptionStep = createStep(
"validate-shipping-option", "validate-shipping-option",
@@ -148,12 +149,17 @@ export const addDraftOrderShippingMethodsWorkflow = createWorkflow(
}, },
}).config({ name: "fetch-shipping-option" }) }).config({ name: "fetch-shipping-option" })
const translatedShippingOptions = getTranslatedShippingOptionsStep({
shippingOptions: shippingOptions,
locale: order.locale!,
})
validateShippingOptionStep({ shippingOptions, input }) validateShippingOptionStep({ shippingOptions, input })
const shippingMethodInput = transform( const shippingMethodInput = transform(
{ {
relatedEntity: { order_id: order.id }, relatedEntity: { order_id: order.id },
shippingOptions, shippingOptions: translatedShippingOptions,
customPrice: input.custom_amount as any, // Need to cast this to any otherwise the type becomes to complex. customPrice: input.custom_amount as any, // Need to cast this to any otherwise the type becomes to complex.
orderChange, orderChange,
input, input,

View File

@@ -9,6 +9,7 @@ import { Modules, OrderWorkflowEvents } from "@medusajs/framework/utils"
import { import {
createStep, createStep,
createWorkflow, createWorkflow,
parallelize,
StepResponse, StepResponse,
transform, transform,
when, when,
@@ -21,6 +22,7 @@ import {
previewOrderChangeStep, previewOrderChangeStep,
registerOrderChangesStep, registerOrderChangesStep,
updateOrderItemsTranslationsStep, updateOrderItemsTranslationsStep,
updateOrderShippingMethodsTranslationsStep,
} from "../../order" } from "../../order"
import { validateDraftOrderStep } from "../steps/validate-draft-order" import { validateDraftOrderStep } from "../steps/validate-draft-order"
@@ -179,6 +181,9 @@ export const updateDraftOrderWorkflow = createWorkflow(
"locale", "locale",
"shipping_address.*", "shipping_address.*",
"billing_address.*", "billing_address.*",
"shipping_methods.id",
"shipping_methods.name",
"shipping_methods.shipping_option_id",
"metadata", "metadata",
], ],
variables: { variables: {
@@ -340,10 +345,16 @@ export const updateDraftOrderWorkflow = createWorkflow(
when({ input, order }, ({ input, order }) => { when({ input, order }, ({ input, order }) => {
return !!input.locale && input.locale !== order.locale return !!input.locale && input.locale !== order.locale
}).then(() => { }).then(() => {
updateOrderItemsTranslationsStep({ parallelize(
order_id: input.id, updateOrderShippingMethodsTranslationsStep({
locale: input.locale!, locale: input.locale!,
}) shippingMethods: order.shipping_methods,
}),
updateOrderItemsTranslationsStep({
order_id: input.id,
locale: input.locale!,
})
)
}) })
emitEventStep({ emitEventStep({

View File

@@ -37,5 +37,6 @@ export * from "./set-tax-lines-for-items"
export * from "./update-order-change-actions" export * from "./update-order-change-actions"
export * from "./update-order-changes" export * from "./update-order-changes"
export * from "./update-order-items-translations" export * from "./update-order-items-translations"
export * from "./update-order-shipping-methods-translations"
export * from "./update-orders" export * from "./update-orders"
export * from "./update-shipping-methods" export * from "./update-shipping-methods"

View File

@@ -0,0 +1,71 @@
import {
applyTranslations,
ContainerRegistrationKeys,
FeatureFlag,
Modules,
} from "@medusajs/framework/utils"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { OrderShippingMethodDTO } from "@medusajs/types"
export const updateOrderShippingMethodsTranslationsStepId =
"update-order-shipping-methods-translations"
export interface UpdateOrderShippingMethodsTranslationsStepInput {
shippingMethods: OrderShippingMethodDTO[]
locale: string
}
export const updateOrderShippingMethodsTranslationsStep = createStep(
updateOrderShippingMethodsTranslationsStepId,
async (
data: UpdateOrderShippingMethodsTranslationsStepInput,
{ container }
) => {
const isTranslationEnabled = FeatureFlag.isFeatureEnabled("translation")
if (!isTranslationEnabled || !data.locale || !data.shippingMethods.length) {
return new StepResponse(data.shippingMethods)
}
const query = container.resolve(ContainerRegistrationKeys.QUERY)
const orderModuleService = container.resolve(Modules.ORDER)
const { data: translatedShippingOptions } = await query.graph({
entity: "shipping_option",
fields: ["id", "name"],
filters: {
id: data.shippingMethods.map((sm) => sm.shipping_option_id),
},
})
await applyTranslations({
localeCode: data.locale,
objects: translatedShippingOptions,
container,
})
const shippingOptionTranslationMap = new Map<string, string>(
translatedShippingOptions.map((tos) => [tos.id, tos.name])
)
const updatedShippingMethods =
await orderModuleService.updateOrderShippingMethods(
data.shippingMethods.map((sm) => ({
...sm,
name: sm.shipping_option_id
? shippingOptionTranslationMap.get(sm.shipping_option_id)
: sm.name,
}))
)
return new StepResponse(updatedShippingMethods, data.shippingMethods)
},
async (dataBeforeUpdate, { container }) => {
if (!dataBeforeUpdate?.length) {
return
}
const orderModuleService = container.resolve(Modules.ORDER)
await orderModuleService.updateOrderShippingMethods(dataBeforeUpdate)
}
)

View File

@@ -24,6 +24,7 @@ import { prepareShippingMethod } from "../../utils/prepare-shipping-method"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines" import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { fetchShippingOptionForOrderWorkflow } from "../fetch-shipping-option" import { fetchShippingOptionForOrderWorkflow } from "../fetch-shipping-option"
import { getTranslatedShippingOptionsStep } from "../../../common/steps/get-translated-shipping-option"
/** /**
* The data to validate that a shipping method can be created for a claim. * The data to validate that a shipping method can be created for a claim.
@@ -162,7 +163,14 @@ export const createClaimShippingMethodWorkflow = createWorkflow(
const order: OrderDTO = useRemoteQueryStep({ const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders", entry_point: "orders",
fields: ["id", "status", "region_id", "currency_code", "canceled_at"], fields: [
"id",
"status",
"region_id",
"currency_code",
"canceled_at",
"locale",
],
variables: { id: orderClaim.order_id }, variables: { id: orderClaim.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -227,12 +235,17 @@ export const createClaimShippingMethodWorkflow = createWorkflow(
return [shippingOption] return [shippingOption]
}) })
const translatedShippingOptions = getTranslatedShippingOptionsStep({
shippingOptions: shippingOptions,
locale: order.locale!,
})
createClaimShippingMethodValidationStep({ order, orderClaim, orderChange }) createClaimShippingMethodValidationStep({ order, orderClaim, orderChange })
const shippingMethodInput = transform( const shippingMethodInput = transform(
{ {
relatedEntity: orderClaim, relatedEntity: orderClaim,
shippingOptions, shippingOptions: translatedShippingOptions,
customPrice: input.custom_amount, customPrice: input.custom_amount,
orderChange, orderChange,
input, input,

View File

@@ -24,6 +24,7 @@ import { prepareShippingMethod } from "../../utils/prepare-shipping-method"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines" import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { fetchShippingOptionForOrderWorkflow } from "../fetch-shipping-option" import { fetchShippingOptionForOrderWorkflow } from "../fetch-shipping-option"
import { getTranslatedShippingOptionsStep } from "../../../common/steps/get-translated-shipping-option"
/** /**
* The data to validate that a shipping method can be created for an exchange. * The data to validate that a shipping method can be created for an exchange.
@@ -163,7 +164,7 @@ export const createExchangeShippingMethodWorkflow = createWorkflow(
const order: OrderDTO = useRemoteQueryStep({ const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders", entry_point: "orders",
fields: ["id", "status", "currency_code", "canceled_at"], fields: ["id", "status", "currency_code", "canceled_at", "locale"],
variables: { id: orderExchange.order_id }, variables: { id: orderExchange.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -228,6 +229,11 @@ export const createExchangeShippingMethodWorkflow = createWorkflow(
return [shippingOption] return [shippingOption]
}) })
const translatedShippingOptions = getTranslatedShippingOptionsStep({
shippingOptions: shippingOptions,
locale: order.locale!,
})
createExchangeShippingMethodValidationStep({ createExchangeShippingMethodValidationStep({
order, order,
orderExchange, orderExchange,
@@ -237,7 +243,7 @@ export const createExchangeShippingMethodWorkflow = createWorkflow(
const shippingMethodInput = transform( const shippingMethodInput = transform(
{ {
relatedEntity: orderExchange, relatedEntity: orderExchange,
shippingOptions, shippingOptions: translatedShippingOptions,
customPrice: input.custom_amount, customPrice: input.custom_amount,
orderChange, orderChange,
input, input,

View File

@@ -25,6 +25,7 @@ import {
import { prepareShippingMethod } from "../../utils/prepare-shipping-method" import { prepareShippingMethod } from "../../utils/prepare-shipping-method"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines" import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { getTranslatedShippingOptionsStep } from "../../../common/steps/get-translated-shipping-option"
/** /**
* The data to validate that a shipping method can be created for an order edit. * The data to validate that a shipping method can be created for an order edit.
@@ -163,7 +164,7 @@ export const createOrderEditShippingMethodWorkflow = createWorkflow(
const order: OrderDTO = useRemoteQueryStep({ const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders", entry_point: "orders",
fields: ["id", "status", "currency_code", "canceled_at"], fields: ["id", "status", "currency_code", "canceled_at", "locale"],
variables: { id: input.order_id }, variables: { id: input.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -220,10 +221,15 @@ export const createOrderEditShippingMethodWorkflow = createWorkflow(
list: false, list: false,
}).config({ name: "order-change-query" }) }).config({ name: "order-change-query" })
const translatedShippingOptions = getTranslatedShippingOptionsStep({
shippingOptions: shippingOptions,
locale: order.locale!,
})
const shippingMethodInput = transform( const shippingMethodInput = transform(
{ {
relatedEntity: { order_id: order.id }, relatedEntity: { order_id: order.id },
shippingOptions, shippingOptions: translatedShippingOptions,
customPrice: input.custom_amount, customPrice: input.custom_amount,
orderChange, orderChange,
input, input,

View File

@@ -23,6 +23,7 @@ import { prepareShippingMethod } from "../../utils/prepare-shipping-method"
import { createOrderChangeActionsWorkflow } from "../create-order-change-actions" import { createOrderChangeActionsWorkflow } from "../create-order-change-actions"
import { updateOrderTaxLinesWorkflow } from "../update-tax-lines" import { updateOrderTaxLinesWorkflow } from "../update-tax-lines"
import { fetchShippingOptionForOrderWorkflow } from "../fetch-shipping-option" import { fetchShippingOptionForOrderWorkflow } from "../fetch-shipping-option"
import { getTranslatedShippingOptionsStep } from "../../../common/steps/get-translated-shipping-option"
/** /**
* The data to validate that a shipping method can be created for a return. * The data to validate that a shipping method can be created for a return.
@@ -153,7 +154,7 @@ export const createReturnShippingMethodWorkflow = createWorkflow(
const order: OrderDTO = useRemoteQueryStep({ const order: OrderDTO = useRemoteQueryStep({
entry_point: "orders", entry_point: "orders",
fields: ["id", "status", "currency_code", "canceled_at"], fields: ["id", "status", "currency_code", "canceled_at", "locale"],
variables: { id: orderReturn.order_id }, variables: { id: orderReturn.order_id },
list: false, list: false,
throw_if_key_not_found: true, throw_if_key_not_found: true,
@@ -202,6 +203,11 @@ export const createReturnShippingMethodWorkflow = createWorkflow(
return [shippingOption] return [shippingOption]
}) })
const translatedShippingOptions = getTranslatedShippingOptionsStep({
shippingOptions: shippingOptions,
locale: order.locale!,
})
createReturnShippingMethodValidationStep({ createReturnShippingMethodValidationStep({
order, order,
orderReturn, orderReturn,
@@ -211,7 +217,7 @@ export const createReturnShippingMethodWorkflow = createWorkflow(
const shippingMethodInput = transform( const shippingMethodInput = transform(
{ {
relatedEntity: orderReturn, relatedEntity: orderReturn,
shippingOptions, shippingOptions: translatedShippingOptions,
customPrice: input.custom_amount, customPrice: input.custom_amount,
orderChange, orderChange,
input, input,

View File

@@ -14,6 +14,7 @@ import {
WorkflowResponse, WorkflowResponse,
createStep, createStep,
createWorkflow, createWorkflow,
parallelize,
transform, transform,
when, when,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
@@ -23,6 +24,7 @@ import {
previewOrderChangeStep, previewOrderChangeStep,
registerOrderChangesStep, registerOrderChangesStep,
updateOrderItemsTranslationsStep, updateOrderItemsTranslationsStep,
updateOrderShippingMethodsTranslationsStep,
updateOrdersStep, updateOrdersStep,
} from "../steps" } from "../steps"
import { throwIfOrderIsCancelled } from "../utils/order-validation" import { throwIfOrderIsCancelled } from "../utils/order-validation"
@@ -134,6 +136,9 @@ export const updateOrderWorkflow = createWorkflow(
"shipping_address.*", "shipping_address.*",
"billing_address.*", "billing_address.*",
"metadata", "metadata",
"shipping_methods.id",
"shipping_methods.name",
"shipping_methods.shipping_option_id",
], ],
filters: { id: input.id }, filters: { id: input.id },
options: { throwIfKeyNotFound: true }, options: { throwIfKeyNotFound: true },
@@ -261,10 +266,16 @@ export const updateOrderWorkflow = createWorkflow(
when("locale-changed", { input, order }, ({ input, order }) => { when("locale-changed", { input, order }, ({ input, order }) => {
return !!input.locale && input.locale !== order.locale return !!input.locale && input.locale !== order.locale
}).then(() => { }).then(() => {
updateOrderItemsTranslationsStep({ parallelize(
order_id: input.id, updateOrderItemsTranslationsStep({
locale: input.locale!, order_id: input.id,
}) locale: input.locale!,
}),
updateOrderShippingMethodsTranslationsStep({
locale: input.locale!,
shippingMethods: order.shipping_methods,
})
)
}) })
emitEventStep({ emitEventStep({

View File

@@ -11,9 +11,11 @@ export const PRODUCT_CATEGORY_TRANSLATABLE_FIELDS = ["name", "description"]
export const PRODUCT_TAG_TRANSLATABLE_FIELDS = ["value"] export const PRODUCT_TAG_TRANSLATABLE_FIELDS = ["value"]
export const PRODUCT_OPTION_TRANSLATABLE_FIELDS = ["title"] export const PRODUCT_OPTION_TRANSLATABLE_FIELDS = ["title"]
export const PRODUCT_OPTION_VALUE_TRANSLATABLE_FIELDS = ["value"] export const PRODUCT_OPTION_VALUE_TRANSLATABLE_FIELDS = ["value"]
export const REGION_TRANSLATABLE_FIELDS = ["name"]
// export const SHIPPING_OPTION_TRANSLATABLE_FIELDS = ["name"] export const CUSTOMER_GROUP_TRANSLATABLE_FIELDS = ["name"]
// export const SHIPPING_OPTION_TYPE_TRANSLATABLE_FIELDS = ["label", "description"] export const SHIPPING_OPTION_TRANSLATABLE_FIELDS = ["name"]
export const SHIPPING_OPTION_TYPE_TRANSLATABLE_FIELDS = ["label", "description"]
export const TAX_RATE_TRANSLATABLE_FIELDS = ["name"]
// export const RETURN_REASON_TRANSLATABLE_FIELDS = [ // export const RETURN_REASON_TRANSLATABLE_FIELDS = [
// "value", // "value",
@@ -30,9 +32,10 @@ export const translatableFieldsConfig = {
product_tag: PRODUCT_TAG_TRANSLATABLE_FIELDS, product_tag: PRODUCT_TAG_TRANSLATABLE_FIELDS,
product_option: PRODUCT_OPTION_TRANSLATABLE_FIELDS, product_option: PRODUCT_OPTION_TRANSLATABLE_FIELDS,
product_option_value: PRODUCT_OPTION_VALUE_TRANSLATABLE_FIELDS, product_option_value: PRODUCT_OPTION_VALUE_TRANSLATABLE_FIELDS,
region: REGION_TRANSLATABLE_FIELDS,
// shipping_option: SHIPPING_OPTION_TRANSLATABLE_FIELDS, customer_group: CUSTOMER_GROUP_TRANSLATABLE_FIELDS,
// shipping_option_type: SHIPPING_OPTION_TYPE_TRANSLATABLE_FIELDS, shipping_option: SHIPPING_OPTION_TRANSLATABLE_FIELDS,
shipping_option_type: SHIPPING_OPTION_TYPE_TRANSLATABLE_FIELDS,
tax_rate: TAX_RATE_TRANSLATABLE_FIELDS,
// return_reason: RETURN_REASON_TRANSLATABLE_FIELDS, // return_reason: RETURN_REASON_TRANSLATABLE_FIELDS,
} }