feat(): sync cart translation synced (#14226)
ref: https://github.com/medusajs/medusa/pull/14189 **Summary** This PR extends the translation module to support automatic translation syncing for cart line items based on the cart's locale. Key changes: - Added locale field to the Cart model to store the cart's locale preference - Created new workflow steps: - getTranslatedLineItemsStep - Translates line items when adding to cart or creating a cart - updateCartItemsTranslationsStep - Re-translates all cart items when the cart's locale changes - Integrated translation logic into cart workflows: - createCartWorkflow - Applies translations to initial line items - addToCartWorkflow - Applies translations when adding new items - updateCartWorkflow - Re-translates all items when locale_code is updated - refreshCartItemsWorkflow - Maintains translations during cart refresh - Added applyTranslationsToItems utility to map variant/product/type/collection translations to line item fields (title, subtitle, description, etc.)
This commit is contained in:
1 parent
356283c359
commit
e4877616c3
31 files changed
+2599
-1438
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
"@medusajs/test-utils": patch
|
||||||
|
"@medusajs/cart": patch
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(): sync cart translation synced
|
||||||
@@ -0,0 +1,520 @@
|
|||||||
|
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
|
import { Modules, ProductStatus } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
createAdminUser,
|
||||||
|
generatePublishableKey,
|
||||||
|
generateStoreHeaders,
|
||||||
|
} from "../../../../helpers/create-admin-user"
|
||||||
|
import { MedusaContainer } from "@medusajs/types"
|
||||||
|
|
||||||
|
jest.setTimeout(100000)
|
||||||
|
|
||||||
|
process.env.MEDUSA_FF_TRANSLATION = "true"
|
||||||
|
|
||||||
|
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||||
|
|
||||||
|
const shippingAddressData = {
|
||||||
|
address_1: "test address 1",
|
||||||
|
address_2: "test address 2",
|
||||||
|
city: "SF",
|
||||||
|
country_code: "US",
|
||||||
|
province: "CA",
|
||||||
|
postal_code: "94016",
|
||||||
|
}
|
||||||
|
|
||||||
|
medusaIntegrationTestRunner({
|
||||||
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
|
describe("Store Cart Translation API", () => {
|
||||||
|
let appContainer: MedusaContainer
|
||||||
|
let storeHeaders: { headers: { [key: string]: string } }
|
||||||
|
let region: { id: string }
|
||||||
|
let product: { id: string; variants: { id: string; title: string }[] }
|
||||||
|
let salesChannel: { id: string }
|
||||||
|
let shippingProfile: { id: string }
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
appContainer = getContainer()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
delete process.env.MEDUSA_FF_TRANSLATION
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
|
const publishableKey = await generatePublishableKey(appContainer)
|
||||||
|
storeHeaders = generateStoreHeaders({ publishableKey })
|
||||||
|
|
||||||
|
salesChannel = (
|
||||||
|
await api.post(
|
||||||
|
"/admin/sales-channels",
|
||||||
|
{ name: "Webshop", description: "channel" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.sales_channel
|
||||||
|
|
||||||
|
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", is_default: true },
|
||||||
|
{ locale_code: "fr-FR" },
|
||||||
|
{ locale_code: "de-DE" },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
shippingProfile = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/shipping-profiles`,
|
||||||
|
{ name: "default", type: "default" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.shipping_profile
|
||||||
|
|
||||||
|
region = (
|
||||||
|
await api.post(
|
||||||
|
"/admin/regions",
|
||||||
|
{ name: "US", currency_code: "usd", countries: ["us"] },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.region
|
||||||
|
|
||||||
|
// Create product with description for translation
|
||||||
|
product = (
|
||||||
|
await api.post(
|
||||||
|
"/admin/products",
|
||||||
|
{
|
||||||
|
title: "Medusa T-Shirt",
|
||||||
|
description: "A comfortable cotton t-shirt",
|
||||||
|
handle: "t-shirt",
|
||||||
|
status: ProductStatus.PUBLISHED,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
title: "Size",
|
||||||
|
values: ["S", "M"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Small",
|
||||||
|
sku: "SHIRT-S",
|
||||||
|
options: { Size: "S" },
|
||||||
|
manage_inventory: false,
|
||||||
|
prices: [{ amount: 1500, currency_code: "usd" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Medium",
|
||||||
|
sku: "SHIRT-M",
|
||||||
|
options: { Size: "M" },
|
||||||
|
manage_inventory: false,
|
||||||
|
prices: [{ amount: 1500, currency_code: "usd" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.product
|
||||||
|
|
||||||
|
// Maintain predictable variants order
|
||||||
|
const variantSmall = product.variants.find((v) => v.title === "Small")
|
||||||
|
const variantMedium = product.variants.find((v) => v.title === "Medium")
|
||||||
|
product.variants = [variantSmall!, variantMedium!]
|
||||||
|
|
||||||
|
// Create translations for product and variants
|
||||||
|
await api.post(
|
||||||
|
"/admin/translations/batch",
|
||||||
|
{
|
||||||
|
create: [
|
||||||
|
{
|
||||||
|
reference_id: product.id,
|
||||||
|
reference: "product",
|
||||||
|
locale_code: "fr-FR",
|
||||||
|
translations: {
|
||||||
|
title: "T-Shirt Medusa",
|
||||||
|
description: "Un t-shirt en coton confortable",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_id: product.id,
|
||||||
|
reference: "product",
|
||||||
|
locale_code: "de-DE",
|
||||||
|
translations: {
|
||||||
|
title: "Medusa T-Shirt DE",
|
||||||
|
description: "Ein bequemes Baumwoll-T-Shirt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_id: product.variants[0].id,
|
||||||
|
reference: "product_variant",
|
||||||
|
locale_code: "fr-FR",
|
||||||
|
translations: {
|
||||||
|
title: "Petit",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_id: product.variants[0].id,
|
||||||
|
reference: "product_variant",
|
||||||
|
locale_code: "de-DE",
|
||||||
|
translations: {
|
||||||
|
title: "Klein",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_id: product.variants[1].id,
|
||||||
|
reference: "product_variant",
|
||||||
|
locale_code: "fr-FR",
|
||||||
|
translations: {
|
||||||
|
title: "Moyen",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_id: product.variants[1].id,
|
||||||
|
reference: "product_variant",
|
||||||
|
locale_code: "de-DE",
|
||||||
|
translations: {
|
||||||
|
title: "Mittel",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /store/carts (create cart with locale)", () => {
|
||||||
|
it("should create a cart with translated items when locale is provided", async () => {
|
||||||
|
const response = 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
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "T-Shirt Medusa",
|
||||||
|
product_description: "Un t-shirt en coton confortable",
|
||||||
|
variant_title: "Petit",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a cart with original values when no locale is provided", async () => {
|
||||||
|
const response = await api.post(
|
||||||
|
`/store/carts`,
|
||||||
|
{
|
||||||
|
currency_code: "usd",
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
region_id: region.id,
|
||||||
|
shipping_address: shippingAddressData,
|
||||||
|
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart.items[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "Medusa T-Shirt",
|
||||||
|
product_description: "A comfortable cotton t-shirt",
|
||||||
|
variant_title: "Small",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id/line-items (add items to cart)", () => {
|
||||||
|
it("should translate new items using the cart's 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,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = cartResponse.data.cart
|
||||||
|
|
||||||
|
const addItemResponse = await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(addItemResponse.status).toEqual(200)
|
||||||
|
expect(addItemResponse.data.cart.items[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "T-Shirt Medusa",
|
||||||
|
product_description: "Un t-shirt en coton confortable",
|
||||||
|
variant_title: "Petit",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should translate multiple items added to cart", 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,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = cartResponse.data.cart
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[1].id,
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart.items).toHaveLength(2)
|
||||||
|
|
||||||
|
const smallItem = response.data.cart.items.find(
|
||||||
|
(item) => item.variant_id === product.variants[0].id
|
||||||
|
)
|
||||||
|
const mediumItem = response.data.cart.items.find(
|
||||||
|
(item) => item.variant_id === product.variants[1].id
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(smallItem).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "T-Shirt Medusa",
|
||||||
|
variant_title: "Petit",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(mediumItem).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "T-Shirt Medusa",
|
||||||
|
variant_title: "Moyen",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /store/carts/:id (update cart locale)", () => {
|
||||||
|
it("should re-translate all items when locale is updated", 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 },
|
||||||
|
{ variant_id: product.variants[1].id, quantity: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = cartResponse.data.cart
|
||||||
|
|
||||||
|
const frenchSmallItem = cart.items.find(
|
||||||
|
(item) => item.variant_id === product.variants[0].id
|
||||||
|
)
|
||||||
|
expect(frenchSmallItem.variant_title).toEqual("Petit")
|
||||||
|
|
||||||
|
const updateResponse = await api.post(
|
||||||
|
`/store/carts/${cart.id}`,
|
||||||
|
{
|
||||||
|
locale: "de-DE",
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updateResponse.status).toEqual(200)
|
||||||
|
|
||||||
|
const updatedCartResponse = await api.get(
|
||||||
|
`/store/carts/${cart.id}`,
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedCart = updatedCartResponse.data.cart
|
||||||
|
|
||||||
|
const germanSmallItem = updatedCart.items.find(
|
||||||
|
(item) => item.variant_id === product.variants[0].id
|
||||||
|
)
|
||||||
|
const germanMediumItem = updatedCart.items.find(
|
||||||
|
(item) => item.variant_id === product.variants[1].id
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(germanSmallItem).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "Medusa T-Shirt DE",
|
||||||
|
product_description: "Ein bequemes Baumwoll-T-Shirt",
|
||||||
|
variant_title: "Klein",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(germanMediumItem).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "Medusa T-Shirt DE",
|
||||||
|
variant_title: "Mittel",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not re-translate items when locale is not changed", 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
|
||||||
|
|
||||||
|
const updateResponse = await api.post(
|
||||||
|
`/store/carts/${cart.id}`,
|
||||||
|
{
|
||||||
|
email: "test@example.com",
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updateResponse.status).toEqual(200)
|
||||||
|
|
||||||
|
const updatedCartResponse = await api.get(
|
||||||
|
`/store/carts/${cart.id}`,
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedCart = updatedCartResponse.data.cart
|
||||||
|
expect(updatedCart.items[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "T-Shirt Medusa",
|
||||||
|
variant_title: "Petit",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle updating to a locale with no translations", 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
|
||||||
|
|
||||||
|
const updateResponse = await api.post(
|
||||||
|
`/store/carts/${cart.id}`,
|
||||||
|
{
|
||||||
|
locale: "ja-JP",
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updateResponse.status).toEqual(200)
|
||||||
|
|
||||||
|
// Fetch updated cart - should have original values since no Japanese translation exists
|
||||||
|
const updatedCartResponse = await api.get(
|
||||||
|
`/store/carts/${cart.id}`,
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
// no translation means it will revert to default values
|
||||||
|
const updatedCart = updatedCartResponse.data.cart
|
||||||
|
expect(updatedCart.items[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
product_title: "Medusa T-Shirt",
|
||||||
|
variant_title: "Small",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Cart with items and locale changes", () => {
|
||||||
|
it("should maintain translations when adding items to a cart with existing 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
|
||||||
|
|
||||||
|
const addResponse = await api.post(
|
||||||
|
`/store/carts/${cart.id}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: product.variants[1].id,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(addResponse.data.cart.items).toHaveLength(2)
|
||||||
|
|
||||||
|
const allItemsTranslated = addResponse.data.cart.items.every(
|
||||||
|
(item) => item.product_title === "T-Shirt Medusa"
|
||||||
|
)
|
||||||
|
expect(allItemsTranslated).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -21,19 +21,17 @@ const customFulfillmentProviderCalculated = {
|
|||||||
id: "test-provider-calculated",
|
id: "test-provider-calculated",
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = defineConfig({
|
const translationModuleResolutions =
|
||||||
admin: {
|
process.env.MEDUSA_FF_TRANSLATION === "true"
|
||||||
disable: true,
|
? {
|
||||||
|
[Modules.TRANSLATION]: {
|
||||||
|
resolve: "@medusajs/translation",
|
||||||
},
|
},
|
||||||
projectConfig: {
|
}
|
||||||
http: {
|
: {}
|
||||||
jwtSecret: "test",
|
|
||||||
},
|
const modules = {
|
||||||
},
|
...translationModuleResolutions,
|
||||||
featureFlags: {
|
|
||||||
index_engine: process.env.ENABLE_INDEX_MODULE === "true",
|
|
||||||
},
|
|
||||||
modules: {
|
|
||||||
[Modules.FULFILLMENT]: {
|
[Modules.FULFILLMENT]: {
|
||||||
/** @type {import('@medusajs/fulfillment').FulfillmentModuleOptions} */
|
/** @type {import('@medusajs/fulfillment').FulfillmentModuleOptions} */
|
||||||
options: {
|
options: {
|
||||||
@@ -78,5 +76,25 @@ module.exports = defineConfig({
|
|||||||
resolve: "@medusajs/index",
|
resolve: "@medusajs/index",
|
||||||
disable: process.env.ENABLE_INDEX_MODULE !== "true",
|
disable: process.env.ENABLE_INDEX_MODULE !== "true",
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.MEDUSA_FF_TRANSLATION === "true") {
|
||||||
|
modules[Modules.TRANSLATION] = {
|
||||||
|
resolve: "@medusajs/translation",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
admin: {
|
||||||
|
disable: true,
|
||||||
},
|
},
|
||||||
|
projectConfig: {
|
||||||
|
http: {
|
||||||
|
jwtSecret: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featureFlags: {
|
||||||
|
index_engine: process.env.ENABLE_INDEX_MODULE === "true",
|
||||||
|
},
|
||||||
|
modules,
|
||||||
})
|
})
|
||||||
@@ -1360,6 +1360,7 @@ medusaIntegrationTestRunner({
|
|||||||
id: expect.stringContaining("cart_"),
|
id: expect.stringContaining("cart_"),
|
||||||
sales_channel_id: expect.stringContaining("sc_"),
|
sales_channel_id: expect.stringContaining("sc_"),
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
|
locale: null,
|
||||||
region_id: expect.stringContaining("reg_"),
|
region_id: expect.stringContaining("reg_"),
|
||||||
shipping_address: null,
|
shipping_address: null,
|
||||||
item_total: 0,
|
item_total: 0,
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { ProductVariantDTO } from "@medusajs/framework/types"
|
||||||
|
import { applyTranslations, FeatureFlag } from "@medusajs/framework/utils"
|
||||||
|
import {
|
||||||
|
createStep,
|
||||||
|
StepFunction,
|
||||||
|
StepResponse,
|
||||||
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { applyTranslationsToItems } from "../utils/apply-translations-to-items"
|
||||||
|
|
||||||
|
export interface GetTranslatedLineItemsStepInput<T> {
|
||||||
|
items: T[] | undefined
|
||||||
|
variants: Partial<ProductVariantDTO>[]
|
||||||
|
locale: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTranslatedLineItemsStepId = "get-translated-line-items"
|
||||||
|
|
||||||
|
const step = createStep(
|
||||||
|
getTranslatedLineItemsStepId,
|
||||||
|
async (data: GetTranslatedLineItemsStepInput<any>, { container }) => {
|
||||||
|
const isTranslationEnabled = FeatureFlag.isFeatureEnabled("translation")
|
||||||
|
|
||||||
|
if (!isTranslationEnabled || !data.locale || !data.items?.length) {
|
||||||
|
return new StepResponse(data.items ?? [])
|
||||||
|
}
|
||||||
|
|
||||||
|
await applyTranslations({
|
||||||
|
localeCode: data.locale,
|
||||||
|
objects: data.variants,
|
||||||
|
container,
|
||||||
|
})
|
||||||
|
|
||||||
|
const translatedItems = applyTranslationsToItems(data.items, data.variants)
|
||||||
|
|
||||||
|
return new StepResponse(translatedItems)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step translates cart line items based on their associated variant and product IDs.
|
||||||
|
* It fetches translations for the product (title, description, subtitle) and variant (title),
|
||||||
|
* then applies them to the corresponding line item fields.
|
||||||
|
*/
|
||||||
|
export const getTranslatedLineItemsStep = <T>(
|
||||||
|
data: GetTranslatedLineItemsStepInput<T>
|
||||||
|
): ReturnType<StepFunction<any, T[]>> => step(data)
|
||||||
@@ -10,6 +10,8 @@ export * from "./find-or-create-customer"
|
|||||||
export * from "./find-sales-channel"
|
export * from "./find-sales-channel"
|
||||||
export * from "./get-actions-to-compute-from-promotions"
|
export * from "./get-actions-to-compute-from-promotions"
|
||||||
export * from "./get-line-item-actions"
|
export * from "./get-line-item-actions"
|
||||||
|
export * from "./get-translated-line-items"
|
||||||
|
export * from "./update-cart-items-translations"
|
||||||
export * from "./get-promotion-codes-to-apply"
|
export * from "./get-promotion-codes-to-apply"
|
||||||
export * from "./get-variant-price-sets"
|
export * from "./get-variant-price-sets"
|
||||||
export * from "./get-variants"
|
export * from "./get-variants"
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { MedusaContainer } from "@medusajs/framework"
|
||||||
|
import {
|
||||||
|
ICartModuleService,
|
||||||
|
ProductVariantDTO,
|
||||||
|
RemoteQueryFunction,
|
||||||
|
} from "@medusajs/framework/types"
|
||||||
|
import {
|
||||||
|
applyTranslations,
|
||||||
|
ContainerRegistrationKeys,
|
||||||
|
deduplicate,
|
||||||
|
FeatureFlag,
|
||||||
|
Modules,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { applyTranslationsToItems } from "../utils/apply-translations-to-items"
|
||||||
|
import { productVariantsFields } from "../utils/fields"
|
||||||
|
|
||||||
|
export interface UpdateCartItemsTranslationsStepInput {
|
||||||
|
cart_id: string
|
||||||
|
locale: string
|
||||||
|
/**
|
||||||
|
* Pre-loaded items to avoid re-fetching.
|
||||||
|
*/
|
||||||
|
items?: { id: string; variant_id?: string; [key: string]: any }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const BATCH_SIZE = 100
|
||||||
|
|
||||||
|
const lineItemFields = [
|
||||||
|
"id",
|
||||||
|
"variant_id",
|
||||||
|
"product_id",
|
||||||
|
"title",
|
||||||
|
"subtitle",
|
||||||
|
"product_title",
|
||||||
|
"product_description",
|
||||||
|
"product_subtitle",
|
||||||
|
"product_type",
|
||||||
|
"product_collection",
|
||||||
|
"product_handle",
|
||||||
|
"variant_title",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const updateCartItemsTranslationsStepId =
|
||||||
|
"update-cart-items-translations"
|
||||||
|
|
||||||
|
type ItemTranslationSnapshot = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
product_title: string
|
||||||
|
product_description: string
|
||||||
|
product_subtitle: string
|
||||||
|
product_type: string
|
||||||
|
product_collection: string
|
||||||
|
product_handle: string
|
||||||
|
variant_title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compensation(
|
||||||
|
originalItems,
|
||||||
|
{ container }: { container: MedusaContainer }
|
||||||
|
) {
|
||||||
|
if (!originalItems?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cartModule = container.resolve<ICartModuleService>(Modules.CART)
|
||||||
|
|
||||||
|
for (let i = 0; i < originalItems.length; i += BATCH_SIZE) {
|
||||||
|
const batch = originalItems.slice(i, i + BATCH_SIZE)
|
||||||
|
await cartModule.updateLineItems(batch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step re-translates all cart line items when the cart's locale changes.
|
||||||
|
* It fetches items and their variants in batches to handle large carts gracefully.
|
||||||
|
*/
|
||||||
|
export const updateCartItemsTranslationsStep = createStep(
|
||||||
|
updateCartItemsTranslationsStepId,
|
||||||
|
async (data: UpdateCartItemsTranslationsStepInput, { container }) => {
|
||||||
|
const originalItems: ItemTranslationSnapshot[] = []
|
||||||
|
try {
|
||||||
|
const isTranslationEnabled = FeatureFlag.isFeatureEnabled("translation")
|
||||||
|
|
||||||
|
if (!isTranslationEnabled || !data.locale) {
|
||||||
|
return new StepResponse(void 0, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const cartModule = container.resolve<ICartModuleService>(Modules.CART)
|
||||||
|
const query = container.resolve<RemoteQueryFunction>(
|
||||||
|
ContainerRegistrationKeys.QUERY
|
||||||
|
)
|
||||||
|
|
||||||
|
const processBatch = async (
|
||||||
|
items: { id: string; variant_id?: string; [key: string]: any }[]
|
||||||
|
) => {
|
||||||
|
const variantIds = deduplicate(
|
||||||
|
items
|
||||||
|
.map((item) => item.variant_id)
|
||||||
|
.filter((id): id is string => !!id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (variantIds.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store original values before updating
|
||||||
|
for (const item of items) {
|
||||||
|
originalItems.push({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
subtitle: item.subtitle,
|
||||||
|
product_title: item.product_title,
|
||||||
|
product_description: item.product_description,
|
||||||
|
product_subtitle: item.product_subtitle,
|
||||||
|
product_type: item.product_type,
|
||||||
|
product_collection: item.product_collection,
|
||||||
|
product_handle: item.product_handle,
|
||||||
|
variant_title: item.variant_title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: variants } = await query.graph({
|
||||||
|
entity: "variants",
|
||||||
|
filters: { id: variantIds },
|
||||||
|
fields: productVariantsFields,
|
||||||
|
})
|
||||||
|
|
||||||
|
await applyTranslations({
|
||||||
|
localeCode: data.locale,
|
||||||
|
objects: variants as Record<string, any>[],
|
||||||
|
container,
|
||||||
|
})
|
||||||
|
|
||||||
|
const translatedItems = applyTranslationsToItems(
|
||||||
|
items as { variant_id?: string; [key: string]: any }[],
|
||||||
|
variants as Partial<ProductVariantDTO>[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const itemsToUpdate = translatedItems
|
||||||
|
.filter((item) => item.id)
|
||||||
|
.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
subtitle: item.subtitle,
|
||||||
|
product_title: item.product_title,
|
||||||
|
product_description: item.product_description,
|
||||||
|
product_subtitle: item.product_subtitle,
|
||||||
|
product_type: item.product_type,
|
||||||
|
product_collection: item.product_collection,
|
||||||
|
product_handle: item.product_handle,
|
||||||
|
variant_title: item.variant_title,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (itemsToUpdate.length > 0) {
|
||||||
|
await cartModule.updateLineItems(itemsToUpdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.items?.length) {
|
||||||
|
await processBatch(data.items)
|
||||||
|
return new StepResponse(void 0, originalItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
|
let hasMore = true
|
||||||
|
|
||||||
|
while (hasMore) {
|
||||||
|
const { data: items } = await query.graph({
|
||||||
|
entity: "line_items",
|
||||||
|
filters: { cart_id: data.cart_id },
|
||||||
|
fields: lineItemFields,
|
||||||
|
pagination: {
|
||||||
|
take: BATCH_SIZE,
|
||||||
|
skip: offset,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
hasMore = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await processBatch(items as { id: string; variant_id?: string }[])
|
||||||
|
|
||||||
|
offset += items.length
|
||||||
|
hasMore = items.length === BATCH_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(void 0, originalItems)
|
||||||
|
} catch (error) {
|
||||||
|
await compensation(originalItems, { container })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compensation
|
||||||
|
)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { ProductVariantDTO } from "@medusajs/framework/types"
|
||||||
|
|
||||||
|
const VARIANT_PREFIX = "variant_"
|
||||||
|
const PRODUCT_PREFIX = "product_"
|
||||||
|
const PRODUCT_TYPE_PREFIX = "type_"
|
||||||
|
const PRODUCT_COLLECTION_PREFIX = "collection_"
|
||||||
|
|
||||||
|
const TRANSLATABLE_ITEM_PROP_PREFIXES = [
|
||||||
|
VARIANT_PREFIX,
|
||||||
|
PRODUCT_PREFIX,
|
||||||
|
PRODUCT_TYPE_PREFIX,
|
||||||
|
PRODUCT_COLLECTION_PREFIX,
|
||||||
|
]
|
||||||
|
|
||||||
|
const entityGetterPerPrefix = {
|
||||||
|
[VARIANT_PREFIX]: (variant: ProductVariantDTO) => variant,
|
||||||
|
[PRODUCT_PREFIX]: (variant: ProductVariantDTO) => variant.product!,
|
||||||
|
[PRODUCT_TYPE_PREFIX]: (variant: ProductVariantDTO) => variant.product?.type!,
|
||||||
|
[PRODUCT_COLLECTION_PREFIX]: (variant: ProductVariantDTO) =>
|
||||||
|
variant.product?.collection!,
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTranslation(
|
||||||
|
itemAny: Record<string, any>,
|
||||||
|
translatedInput: Record<string, any>,
|
||||||
|
key: string,
|
||||||
|
translationKey: string
|
||||||
|
) {
|
||||||
|
if (typeof itemAny[key] === typeof translatedInput?.[translationKey]) {
|
||||||
|
itemAny[key] = translatedInput?.[translationKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies translated variant/product fields to line items.
|
||||||
|
*/
|
||||||
|
export function applyTranslationsToItems<
|
||||||
|
T extends { variant_id?: string; [key: string]: any }
|
||||||
|
>(items: T[], variants: Partial<ProductVariantDTO>[]): T[] {
|
||||||
|
const variantMap = new Map(variants.map((variant) => [variant.id, variant]))
|
||||||
|
|
||||||
|
return items.map((item) => {
|
||||||
|
if (!item.variant_id) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
const variant = variantMap.get(item.variant_id)
|
||||||
|
if (!variant) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemAny = item as Record<string, any>
|
||||||
|
|
||||||
|
Object.entries(itemAny).forEach(([key, value]) => {
|
||||||
|
for (const prefix of TRANSLATABLE_ITEM_PROP_PREFIXES) {
|
||||||
|
if (key.startsWith(prefix)) {
|
||||||
|
const translationKey = key.replace(prefix, "")
|
||||||
|
const entity = entityGetterPerPrefix[prefix](variant)
|
||||||
|
if (!entity) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
applyTranslation(itemAny, entity, key, translationKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ export const cartFieldsForRefreshSteps = [
|
|||||||
"quantity",
|
"quantity",
|
||||||
"subtotal",
|
"subtotal",
|
||||||
"item_total",
|
"item_total",
|
||||||
|
"locale",
|
||||||
"total",
|
"total",
|
||||||
"item_subtotal",
|
"item_subtotal",
|
||||||
"shipping_subtotal",
|
"shipping_subtotal",
|
||||||
|
|||||||
@@ -139,30 +139,30 @@ export function prepareLineItemData(data: PrepareLineItemDataInput) {
|
|||||||
|
|
||||||
let lineItem: any = {
|
let lineItem: any = {
|
||||||
quantity: item?.quantity,
|
quantity: item?.quantity,
|
||||||
title: variant?.product?.title ?? item?.title,
|
title: item?.title ?? variant?.product?.title,
|
||||||
subtitle: variant?.title ?? item?.subtitle,
|
subtitle: item?.subtitle ?? variant?.title,
|
||||||
thumbnail:
|
thumbnail:
|
||||||
variant?.thumbnail ?? variant?.product?.thumbnail ?? item?.thumbnail,
|
item?.thumbnail ?? variant?.thumbnail ?? variant?.product?.thumbnail,
|
||||||
|
|
||||||
product_id: variant?.product?.id ?? item?.product_id,
|
product_id: variant?.product?.id ?? item?.product_id,
|
||||||
product_title: variant?.product?.title ?? item?.product_title,
|
product_title: item?.product_title ?? variant?.product?.title,
|
||||||
product_description:
|
product_description:
|
||||||
variant?.product?.description ?? item?.product_description,
|
item?.product_description ?? variant?.product?.description,
|
||||||
product_subtitle: variant?.product?.subtitle ?? item?.product_subtitle,
|
product_subtitle: item?.product_subtitle ?? variant?.product?.subtitle,
|
||||||
product_type: variant?.product?.type?.value ?? item?.product_type ?? null,
|
product_type: item?.product_type ?? variant?.product?.type?.value ?? null,
|
||||||
product_type_id:
|
product_type_id:
|
||||||
variant?.product?.type?.id ?? item?.product_type_id ?? null,
|
item?.product_type_id ?? variant?.product?.type?.id ?? null,
|
||||||
product_collection:
|
product_collection:
|
||||||
variant?.product?.collection?.title ?? item?.product_collection ?? null,
|
item?.product_collection ?? variant?.product?.collection?.title ?? null,
|
||||||
product_handle: variant?.product?.handle ?? item?.product_handle,
|
product_handle: item?.product_handle ?? variant?.product?.handle,
|
||||||
|
|
||||||
variant_id: variant?.id,
|
variant_id: variant?.id,
|
||||||
variant_sku: variant?.sku ?? item?.variant_sku,
|
variant_sku: item?.variant_sku ?? variant?.sku,
|
||||||
variant_barcode: variant?.barcode ?? item?.variant_barcode,
|
variant_barcode: item?.variant_barcode ?? variant?.barcode,
|
||||||
variant_title: variant?.title ?? item?.variant_title,
|
variant_title: item?.variant_title ?? variant?.title,
|
||||||
variant_option_values: item?.variant_option_values,
|
variant_option_values: item?.variant_option_values,
|
||||||
|
|
||||||
is_discountable: variant?.product?.discountable ?? item?.is_discountable,
|
is_discountable: item?.is_discountable ?? variant?.product?.discountable,
|
||||||
is_giftcard: variant?.product?.is_giftcard ?? false,
|
is_giftcard: variant?.product?.is_giftcard ?? false,
|
||||||
requires_shipping: requiresShipping,
|
requires_shipping: requiresShipping,
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { acquireLockStep, releaseLockStep } from "../../locking"
|
|||||||
import {
|
import {
|
||||||
createLineItemsStep,
|
createLineItemsStep,
|
||||||
getLineItemActionsStep,
|
getLineItemActionsStep,
|
||||||
|
getTranslatedLineItemsStep,
|
||||||
updateLineItemsStep,
|
updateLineItemsStep,
|
||||||
} from "../steps"
|
} from "../steps"
|
||||||
import { validateCartStep } from "../steps/validate-cart"
|
import { validateCartStep } from "../steps/validate-cart"
|
||||||
@@ -42,7 +43,9 @@ import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
|
|||||||
import { getVariantsAndItemsWithPrices } from "./get-variants-and-items-with-prices"
|
import { getVariantsAndItemsWithPrices } from "./get-variants-and-items-with-prices"
|
||||||
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
|
import { refreshCartItemsWorkflow } from "./refresh-cart-items"
|
||||||
|
|
||||||
const cartFields = ["completed_at"].concat(cartFieldsForPricingContext)
|
const cartFields = ["completed_at", "locale"].concat(
|
||||||
|
cartFieldsForPricingContext
|
||||||
|
)
|
||||||
|
|
||||||
export const addToCartWorkflowId = "add-to-cart"
|
export const addToCartWorkflowId = "add-to-cart"
|
||||||
/**
|
/**
|
||||||
@@ -292,10 +295,33 @@ export const addToCartWorkflow = createWorkflow(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const itemsToCreateVariants = transform(
|
||||||
|
{ itemsToCreate, variants } as {
|
||||||
|
itemsToCreate: CreateLineItemForCartDTO[]
|
||||||
|
variants: PrepareVariantLineItemInput[]
|
||||||
|
},
|
||||||
|
(data) => {
|
||||||
|
if (!data.itemsToCreate?.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantsMap = new Map(data.variants?.map((v) => [v.id, v]))
|
||||||
|
return data.itemsToCreate
|
||||||
|
.map((item) => item.variant_id && variantsMap.get(item.variant_id))
|
||||||
|
.filter(Boolean) as PrepareVariantLineItemInput[]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const translatedItemsToCreate = getTranslatedLineItemsStep({
|
||||||
|
items: itemsToCreate,
|
||||||
|
variants: itemsToCreateVariants,
|
||||||
|
locale: cart.locale,
|
||||||
|
})
|
||||||
|
|
||||||
const [createdLineItems, updatedLineItems] = parallelize(
|
const [createdLineItems, updatedLineItems] = parallelize(
|
||||||
createLineItemsStep({
|
createLineItemsStep({
|
||||||
id: cart.id,
|
id: cart.id,
|
||||||
items: itemsToCreate,
|
items: translatedItemsToCreate,
|
||||||
}),
|
}),
|
||||||
updateLineItemsStep({
|
updateLineItemsStep({
|
||||||
id: cart.id,
|
id: cart.id,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
ConfirmVariantInventoryWorkflowInputDTO,
|
ConfirmVariantInventoryWorkflowInputDTO,
|
||||||
CreateCartDTO,
|
CreateCartDTO,
|
||||||
CreateCartWorkflowInputDTO,
|
CreateCartWorkflowInputDTO,
|
||||||
|
CreateLineItemDTO,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
CartWorkflowEvents,
|
CartWorkflowEvents,
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
findOneOrAnyRegionStep,
|
findOneOrAnyRegionStep,
|
||||||
findOrCreateCustomerStep,
|
findOrCreateCustomerStep,
|
||||||
findSalesChannelStep,
|
findSalesChannelStep,
|
||||||
|
getTranslatedLineItemsStep,
|
||||||
} from "../steps"
|
} from "../steps"
|
||||||
import { validateSalesChannelStep } from "../steps/validate-sales-channel"
|
import { validateSalesChannelStep } from "../steps/validate-sales-channel"
|
||||||
import { productVariantsFields } from "../utils/fields"
|
import { productVariantsFields } from "../utils/fields"
|
||||||
@@ -205,17 +207,31 @@ export const createCartWorkflow = createWorkflow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data_
|
return data_ as CreateCartDTO
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const cartToCreate = transform({ lineItems, cartInput }, (data) => {
|
const itemsToCreate = transform({ lineItems }, (data) => {
|
||||||
return {
|
return data.lineItems.map((i) => i.data as CreateLineItemDTO)
|
||||||
...data.cartInput,
|
|
||||||
items: data.lineItems.map((i) => i.data),
|
|
||||||
} as unknown as CreateCartDTO
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const translatedItems = getTranslatedLineItemsStep({
|
||||||
|
items: itemsToCreate,
|
||||||
|
variants,
|
||||||
|
locale: input.locale,
|
||||||
|
})
|
||||||
|
|
||||||
|
const cartToCreate = transform(
|
||||||
|
{ cartInput, translatedItems } as unknown as {
|
||||||
|
cartInput: CreateCartDTO
|
||||||
|
translatedItems: CreateLineItemDTO[]
|
||||||
|
},
|
||||||
|
(data) => {
|
||||||
|
data.cartInput.items = data.translatedItems
|
||||||
|
return data.cartInput as unknown as CreateCartDTO
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const validate = createHook("validate", {
|
const validate = createHook("validate", {
|
||||||
input: cartInput,
|
input: cartInput,
|
||||||
cart: cartToCreate,
|
cart: cartToCreate,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
CreateCartCreateLineItemDTO,
|
CreateCartCreateLineItemDTO,
|
||||||
CustomerDTO,
|
CustomerDTO,
|
||||||
OrderWorkflow,
|
OrderWorkflow,
|
||||||
|
ProductVariantDTO,
|
||||||
RegionDTO,
|
RegionDTO,
|
||||||
UpdateLineItemDTO,
|
UpdateLineItemDTO,
|
||||||
UpdateLineItemWithSelectorDTO,
|
UpdateLineItemWithSelectorDTO,
|
||||||
@@ -54,7 +55,7 @@ interface GetVariantsAndItemsWithPricesWorkflowInput {
|
|||||||
|
|
||||||
type GetVariantsAndItemsWithPricesWorkflowOutput = {
|
type GetVariantsAndItemsWithPricesWorkflowOutput = {
|
||||||
// The variant can depend on the requested fields and therefore the caller will know better
|
// The variant can depend on the requested fields and therefore the caller will know better
|
||||||
variants: (object & {
|
variants: (Partial<ProductVariantDTO> & {
|
||||||
calculated_price: {
|
calculated_price: {
|
||||||
calculated_price: {
|
calculated_price: {
|
||||||
price_list_type: string
|
price_list_type: string
|
||||||
@@ -184,8 +185,11 @@ export const getVariantsAndItemsWithPrices = createWorkflow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const variant = variantsData.find((v) => v.id === item.variant_id)
|
const variant = variantsData.find((v) => v.id === item.variant_id)
|
||||||
if ((item.variant_id && !variant) || // variant specified but doesn't exist
|
if (
|
||||||
(variant && (!variant?.product?.status || variant.product.status !== ProductStatus.PUBLISHED)) // variant exists but product is not published
|
(item.variant_id && !variant) || // variant specified but doesn't exist
|
||||||
|
(variant &&
|
||||||
|
(!variant?.product?.status ||
|
||||||
|
variant.product.status !== ProductStatus.PUBLISHED)) // variant exists but product is not published
|
||||||
) {
|
) {
|
||||||
variantNotFoundOrPublished.push(item_.variant_id)
|
variantNotFoundOrPublished.push(item_.variant_id)
|
||||||
}
|
}
|
||||||
@@ -225,7 +229,9 @@ export const getVariantsAndItemsWithPrices = createWorkflow(
|
|||||||
if (variantNotFoundOrPublished.length > 0) {
|
if (variantNotFoundOrPublished.length > 0) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
`Variants ${variantNotFoundOrPublished.join(", ")} do not exist or belong to a product that is not published`
|
`Variants ${variantNotFoundOrPublished.join(
|
||||||
|
", "
|
||||||
|
)} do not exist or belong to a product that is not published`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (priceNotFound.length > 0) {
|
if (priceNotFound.length > 0) {
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import {
|
|||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { useQueryGraphStep } from "../../common"
|
import { useQueryGraphStep } from "../../common"
|
||||||
import { acquireLockStep, releaseLockStep } from "../../locking"
|
import { acquireLockStep, releaseLockStep } from "../../locking"
|
||||||
import { updateLineItemsStep, validateCartStep } from "../steps"
|
import {
|
||||||
|
updateCartItemsTranslationsStep,
|
||||||
|
updateLineItemsStep,
|
||||||
|
validateCartStep,
|
||||||
|
} from "../steps"
|
||||||
import { cartFieldsForRefreshSteps } from "../utils/fields"
|
import { cartFieldsForRefreshSteps } from "../utils/fields"
|
||||||
import { pricingContextResult } from "../utils/schemas"
|
import { pricingContextResult } from "../utils/schemas"
|
||||||
import { getVariantsAndItemsWithPrices } from "./get-variants-and-items-with-prices"
|
import { getVariantsAndItemsWithPrices } from "./get-variants-and-items-with-prices"
|
||||||
@@ -54,6 +58,12 @@ export type RefreshCartItemsWorkflowInput = {
|
|||||||
* on the configurations of the cart's tax region.
|
* on the configurations of the cart's tax region.
|
||||||
*/
|
*/
|
||||||
force_tax_calculation?: boolean
|
force_tax_calculation?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new locale code to update cart items translations.
|
||||||
|
* When provided, all cart items will be re-translated using this locale.
|
||||||
|
*/
|
||||||
|
locale?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const refreshCartItemsWorkflowId = "refresh-cart-items"
|
export const refreshCartItemsWorkflowId = "refresh-cart-items"
|
||||||
@@ -234,6 +244,16 @@ export const refreshCartItemsWorkflow = createWorkflow(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
when("should-update-item-translations", { input }, ({ input }) => {
|
||||||
|
return !!input.locale
|
||||||
|
}).then(() => {
|
||||||
|
updateCartItemsTranslationsStep({
|
||||||
|
cart_id: input.cart_id,
|
||||||
|
locale: input.locale!,
|
||||||
|
items: refetchedCart.items,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const beforeRefreshingPaymentCollection = createHook(
|
const beforeRefreshingPaymentCollection = createHook(
|
||||||
"beforeRefreshingPaymentCollection",
|
"beforeRefreshingPaymentCollection",
|
||||||
{ input }
|
{ input }
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export const updateCartWorkflow = createWorkflow(
|
|||||||
"email",
|
"email",
|
||||||
"customer_id",
|
"customer_id",
|
||||||
"sales_channel_id",
|
"sales_channel_id",
|
||||||
|
"locale",
|
||||||
"shipping_address.*",
|
"shipping_address.*",
|
||||||
"region.*",
|
"region.*",
|
||||||
"region.countries.*",
|
"region.countries.*",
|
||||||
@@ -280,6 +281,17 @@ export const updateCartWorkflow = createWorkflow(
|
|||||||
}).config({ name: "emit-region-updated" })
|
}).config({ name: "emit-region-updated" })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get the new locale code if it's being updated
|
||||||
|
const newLocaleCode = transform(
|
||||||
|
{ input, cartToUpdate },
|
||||||
|
({ input, cartToUpdate }) => {
|
||||||
|
if (isDefined(input.locale) && input.locale !== cartToUpdate?.locale) {
|
||||||
|
return input.locale
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
parallelize(
|
parallelize(
|
||||||
updateCartsStep([cartInput]),
|
updateCartsStep([cartInput]),
|
||||||
emitEventStep({
|
emitEventStep({
|
||||||
@@ -314,6 +326,7 @@ export const updateCartWorkflow = createWorkflow(
|
|||||||
cart_id: cartInput.id,
|
cart_id: cartInput.id,
|
||||||
promo_codes: input.promo_codes,
|
promo_codes: input.promo_codes,
|
||||||
force_refresh: !!newRegion,
|
force_refresh: !!newRegion,
|
||||||
|
locale: newLocaleCode,
|
||||||
additional_data: input.additional_data,
|
additional_data: input.additional_data,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const createTranslationsStepId = "create-translations"
|
|||||||
* {
|
* {
|
||||||
* reference_id: "prod_123",
|
* reference_id: "prod_123",
|
||||||
* reference: "product",
|
* reference: "product",
|
||||||
* locale_code: "fr-FR",
|
* locale: "fr-FR",
|
||||||
* translations: { title: "Produit", description: "Description du produit" }
|
* translations: { title: "Produit", description: "Description du produit" }
|
||||||
* }
|
* }
|
||||||
* ])
|
* ])
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const updateTranslationsStepId = "update-translations"
|
|||||||
* const data = updateTranslationsStep({
|
* const data = updateTranslationsStep({
|
||||||
* selector: {
|
* selector: {
|
||||||
* reference_id: "prod_123",
|
* reference_id: "prod_123",
|
||||||
* locale_code: "fr-FR"
|
* locale: "fr-FR"
|
||||||
* },
|
* },
|
||||||
* update: {
|
* update: {
|
||||||
* translations: { title: "Nouveau titre" }
|
* translations: { title: "Nouveau titre" }
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const validateTranslationsStep = createStep(
|
|||||||
} = await query.graph(
|
} = await query.graph(
|
||||||
{
|
{
|
||||||
entity: "store",
|
entity: "store",
|
||||||
fields: ["supported_locales.*"],
|
fields: ["id", "supported_locales.*"],
|
||||||
pagination: {
|
pagination: {
|
||||||
take: 1,
|
take: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const createTranslationsWorkflowId = "create-translations"
|
|||||||
* {
|
* {
|
||||||
* reference_id: "prod_123",
|
* reference_id: "prod_123",
|
||||||
* reference: "product",
|
* reference: "product",
|
||||||
* locale_code: "fr-FR",
|
* locale: "fr-FR",
|
||||||
* translations: { title: "Produit", description: "Description du produit" }
|
* translations: { title: "Produit", description: "Description du produit" }
|
||||||
* }
|
* }
|
||||||
* ]
|
* ]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const updateTranslationsWorkflowId = "update-translations"
|
|||||||
* input: {
|
* input: {
|
||||||
* selector: {
|
* selector: {
|
||||||
* reference_id: "prod_123",
|
* reference_id: "prod_123",
|
||||||
* locale_code: "fr-FR"
|
* locale: "fr-FR"
|
||||||
* },
|
* },
|
||||||
* update: {
|
* update: {
|
||||||
* translations: { title: "Nouveau titre" }
|
* translations: { title: "Nouveau titre" }
|
||||||
|
|||||||
@@ -769,6 +769,11 @@ export interface CartDTO {
|
|||||||
*/
|
*/
|
||||||
currency_code: string
|
currency_code: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locale code of the cart.
|
||||||
|
*/
|
||||||
|
locale?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The associated shipping address.
|
* The associated shipping address.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ export interface CreateCartDTO {
|
|||||||
*/
|
*/
|
||||||
currency_code: string
|
currency_code: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locale code of the cart.
|
||||||
|
*/
|
||||||
|
locale?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The associated shipping address's ID.
|
* The associated shipping address's ID.
|
||||||
*/
|
*/
|
||||||
@@ -176,6 +181,11 @@ export interface UpdateCartDataDTO {
|
|||||||
*/
|
*/
|
||||||
currency_code?: string
|
currency_code?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locale code of the cart.
|
||||||
|
*/
|
||||||
|
locale?: string | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The associated shipping address's ID.
|
* The associated shipping address's ID.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -279,6 +279,11 @@ export interface CreateCartWorkflowInputDTO {
|
|||||||
* The promotional codes applied on the cart.
|
* The promotional codes applied on the cart.
|
||||||
*/
|
*/
|
||||||
promo_codes?: string[]
|
promo_codes?: string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locale code of the cart.
|
||||||
|
*/
|
||||||
|
locale?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -336,6 +341,11 @@ export interface UpdateCartWorkflowInputDTO {
|
|||||||
*/
|
*/
|
||||||
currency_code?: string
|
currency_code?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locale code for the cart.
|
||||||
|
*/
|
||||||
|
locale?: string | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom key-value pairs of data related to the cart.
|
* Custom key-value pairs of data related to the cart.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ export interface StoreCreateCart {
|
|||||||
* Key-value pairs of custom data.
|
* Key-value pairs of custom data.
|
||||||
*/
|
*/
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The locale code of the cart.
|
||||||
|
*/
|
||||||
|
locale?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoreUpdateCart {
|
export interface StoreUpdateCart {
|
||||||
@@ -71,6 +76,10 @@ export interface StoreUpdateCart {
|
|||||||
* The promotion codes to apply on the cart.
|
* The promotion codes to apply on the cart.
|
||||||
*/
|
*/
|
||||||
promo_codes?: string[]
|
promo_codes?: string[]
|
||||||
|
/**
|
||||||
|
* The locale code of the cart.
|
||||||
|
*/
|
||||||
|
locale?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StoreUpdateCartCustomer {}
|
export interface StoreUpdateCartCustomer {}
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ class MedusaTestRunner {
|
|||||||
cwd: this.cwd,
|
cwd: this.cwd,
|
||||||
})
|
})
|
||||||
await medusaAppLoader.runModulesLoader()
|
await medusaAppLoader.runModulesLoader()
|
||||||
|
|
||||||
await createDefaultsWorkflow(copiedContainer).run()
|
await createDefaultsWorkflow(copiedContainer).run()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await copiedContainer.dispose?.()
|
await copiedContainer.dispose?.()
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { BatchMethodRequest, HttpTypes } from "@medusajs/types"
|
import { BatchMethodRequest, HttpTypes } from "@medusajs/types"
|
||||||
|
import TranslationFeatureFlag from "../../../../feature-flags/translation"
|
||||||
import { defaultAdminTranslationFields } from "../query-config"
|
import { defaultAdminTranslationFields } from "../query-config"
|
||||||
import {
|
import {
|
||||||
AdminCreateTranslationType,
|
AdminCreateTranslationType,
|
||||||
AdminUpdateTranslationType,
|
AdminUpdateTranslationType,
|
||||||
} from "../validators"
|
} from "../validators"
|
||||||
import TranslationFeatureFlag from "../../../../feature-flags/translation"
|
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
req: AuthenticatedMedusaRequest<
|
req: AuthenticatedMedusaRequest<
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const CreateCart = z
|
|||||||
sales_channel_id: z.string().nullish(),
|
sales_channel_id: z.string().nullish(),
|
||||||
promo_codes: z.array(z.string()).optional(),
|
promo_codes: z.array(z.string()).optional(),
|
||||||
metadata: z.record(z.unknown()).nullish(),
|
metadata: z.record(z.unknown()).nullish(),
|
||||||
|
locale: z.string().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
export const StoreCreateCart = WithAdditionalData(CreateCart)
|
export const StoreCreateCart = WithAdditionalData(CreateCart)
|
||||||
@@ -57,6 +58,7 @@ export const UpdateCart = z
|
|||||||
sales_channel_id: z.string().nullish(),
|
sales_channel_id: z.string().nullish(),
|
||||||
metadata: z.record(z.unknown()).nullish(),
|
metadata: z.record(z.unknown()).nullish(),
|
||||||
promo_codes: z.array(z.string()).optional(),
|
promo_codes: z.array(z.string()).optional(),
|
||||||
|
locale: z.string().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
export const StoreUpdateCart = WithAdditionalData(UpdateCart)
|
export const StoreUpdateCart = WithAdditionalData(UpdateCart)
|
||||||
|
|||||||
@@ -166,7 +166,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_address_deleted_at\" ON \"cart_address\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_address_deleted_at\" ON \"cart_address\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_address_pkey",
|
"keyName": "cart_address_pkey",
|
||||||
@@ -239,6 +239,15 @@
|
|||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
|
"locale": {
|
||||||
|
"name": "locale",
|
||||||
|
"type": "text",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "text"
|
||||||
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "metadata",
|
"name": "metadata",
|
||||||
"type": "jsonb",
|
"type": "jsonb",
|
||||||
@@ -319,7 +328,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_deleted_at\" ON \"cart\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_deleted_at\" ON \"cart\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_region_id",
|
"keyName": "IDX_cart_region_id",
|
||||||
@@ -328,7 +337,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_region_id\" ON \"cart\" (region_id) WHERE deleted_at IS NULL AND region_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_region_id\" ON \"cart\" (\"region_id\") WHERE deleted_at IS NULL AND region_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_customer_id",
|
"keyName": "IDX_cart_customer_id",
|
||||||
@@ -337,7 +346,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_customer_id\" ON \"cart\" (customer_id) WHERE deleted_at IS NULL AND customer_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_customer_id\" ON \"cart\" (\"customer_id\") WHERE deleted_at IS NULL AND customer_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_sales_channel_id",
|
"keyName": "IDX_cart_sales_channel_id",
|
||||||
@@ -346,7 +355,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_sales_channel_id\" ON \"cart\" (sales_channel_id) WHERE deleted_at IS NULL AND sales_channel_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_sales_channel_id\" ON \"cart\" (\"sales_channel_id\") WHERE deleted_at IS NULL AND sales_channel_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_curency_code",
|
"keyName": "IDX_cart_curency_code",
|
||||||
@@ -355,7 +364,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_curency_code\" ON \"cart\" (currency_code) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_curency_code\" ON \"cart\" (\"currency_code\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_shipping_address_id",
|
"keyName": "IDX_cart_shipping_address_id",
|
||||||
@@ -364,7 +373,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_address_id\" ON \"cart\" (shipping_address_id) WHERE deleted_at IS NULL AND shipping_address_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_address_id\" ON \"cart\" (\"shipping_address_id\") WHERE deleted_at IS NULL AND shipping_address_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_billing_address_id",
|
"keyName": "IDX_cart_billing_address_id",
|
||||||
@@ -373,7 +382,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_billing_address_id\" ON \"cart\" (billing_address_id) WHERE deleted_at IS NULL AND billing_address_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_billing_address_id\" ON \"cart\" (\"billing_address_id\") WHERE deleted_at IS NULL AND billing_address_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_pkey",
|
"keyName": "cart_pkey",
|
||||||
@@ -525,7 +534,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_credit_line_cart_id\" ON \"credit_line\" (cart_id) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_credit_line_cart_id\" ON \"credit_line\" (\"cart_id\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_credit_line_deleted_at",
|
"keyName": "IDX_credit_line_deleted_at",
|
||||||
@@ -534,7 +543,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_credit_line_deleted_at\" ON \"credit_line\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_credit_line_deleted_at\" ON \"credit_line\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_credit_line_reference_reference_id",
|
"keyName": "IDX_cart_credit_line_reference_reference_id",
|
||||||
@@ -543,7 +552,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_credit_line_reference_reference_id\" ON \"credit_line\" (reference, reference_id) WHERE deleted_at IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_credit_line_reference_reference_id\" ON \"credit_line\" (\"reference\", \"reference_id\") WHERE deleted_at IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "credit_line_pkey",
|
"keyName": "credit_line_pkey",
|
||||||
@@ -884,7 +893,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_cart_id\" ON \"cart_line_item\" (cart_id) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_cart_id\" ON \"cart_line_item\" (\"cart_id\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_line_item_deleted_at",
|
"keyName": "IDX_cart_line_item_deleted_at",
|
||||||
@@ -893,7 +902,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_deleted_at\" ON \"cart_line_item\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_deleted_at\" ON \"cart_line_item\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_line_item_variant_id",
|
"keyName": "IDX_line_item_variant_id",
|
||||||
@@ -902,7 +911,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_variant_id\" ON \"cart_line_item\" (variant_id) WHERE deleted_at IS NULL AND variant_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_variant_id\" ON \"cart_line_item\" (\"variant_id\") WHERE deleted_at IS NULL AND variant_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_line_item_product_id",
|
"keyName": "IDX_line_item_product_id",
|
||||||
@@ -911,7 +920,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_product_id\" ON \"cart_line_item\" (product_id) WHERE deleted_at IS NULL AND product_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_product_id\" ON \"cart_line_item\" (\"product_id\") WHERE deleted_at IS NULL AND product_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_line_item_product_type_id",
|
"keyName": "IDX_line_item_product_type_id",
|
||||||
@@ -920,7 +929,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_product_type_id\" ON \"cart_line_item\" (product_type_id) WHERE deleted_at IS NULL AND product_type_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_product_type_id\" ON \"cart_line_item\" (\"product_type_id\") WHERE deleted_at IS NULL AND product_type_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_line_item_pkey",
|
"keyName": "cart_line_item_pkey",
|
||||||
@@ -1087,7 +1096,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_adjustment_item_id\" ON \"cart_line_item_adjustment\" (item_id) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_adjustment_item_id\" ON \"cart_line_item_adjustment\" (\"item_id\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_line_item_adjustment_deleted_at",
|
"keyName": "IDX_cart_line_item_adjustment_deleted_at",
|
||||||
@@ -1096,7 +1105,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_adjustment_deleted_at\" ON \"cart_line_item_adjustment\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_adjustment_deleted_at\" ON \"cart_line_item_adjustment\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_line_item_adjustment_promotion_id",
|
"keyName": "IDX_line_item_adjustment_promotion_id",
|
||||||
@@ -1105,7 +1114,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_adjustment_promotion_id\" ON \"cart_line_item_adjustment\" (promotion_id) WHERE deleted_at IS NULL AND promotion_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_adjustment_promotion_id\" ON \"cart_line_item_adjustment\" (\"promotion_id\") WHERE deleted_at IS NULL AND promotion_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_line_item_adjustment_pkey",
|
"keyName": "cart_line_item_adjustment_pkey",
|
||||||
@@ -1259,7 +1268,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_tax_line_item_id\" ON \"cart_line_item_tax_line\" (item_id) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_tax_line_item_id\" ON \"cart_line_item_tax_line\" (\"item_id\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_line_item_tax_line_deleted_at",
|
"keyName": "IDX_cart_line_item_tax_line_deleted_at",
|
||||||
@@ -1268,7 +1277,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_tax_line_deleted_at\" ON \"cart_line_item_tax_line\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_line_item_tax_line_deleted_at\" ON \"cart_line_item_tax_line\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_line_item_tax_line_tax_rate_id",
|
"keyName": "IDX_line_item_tax_line_tax_rate_id",
|
||||||
@@ -1277,7 +1286,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_tax_line_tax_rate_id\" ON \"cart_line_item_tax_line\" (tax_rate_id) WHERE deleted_at IS NULL AND tax_rate_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_line_item_tax_line_tax_rate_id\" ON \"cart_line_item_tax_line\" (\"tax_rate_id\") WHERE deleted_at IS NULL AND tax_rate_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_line_item_tax_line_pkey",
|
"keyName": "cart_line_item_tax_line_pkey",
|
||||||
@@ -1444,7 +1453,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_cart_id\" ON \"cart_shipping_method\" (cart_id) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_cart_id\" ON \"cart_shipping_method\" (\"cart_id\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_shipping_method_deleted_at",
|
"keyName": "IDX_cart_shipping_method_deleted_at",
|
||||||
@@ -1453,7 +1462,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_deleted_at\" ON \"cart_shipping_method\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_deleted_at\" ON \"cart_shipping_method\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_shipping_method_option_id",
|
"keyName": "IDX_shipping_method_option_id",
|
||||||
@@ -1462,7 +1471,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_method_option_id\" ON \"cart_shipping_method\" (shipping_option_id) WHERE deleted_at IS NULL AND shipping_option_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_method_option_id\" ON \"cart_shipping_method\" (\"shipping_option_id\") WHERE deleted_at IS NULL AND shipping_option_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_shipping_method_pkey",
|
"keyName": "cart_shipping_method_pkey",
|
||||||
@@ -1625,7 +1634,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_adjustment_shipping_method_id\" ON \"cart_shipping_method_adjustment\" (shipping_method_id) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_adjustment_shipping_method_id\" ON \"cart_shipping_method_adjustment\" (\"shipping_method_id\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_shipping_method_adjustment_deleted_at",
|
"keyName": "IDX_cart_shipping_method_adjustment_deleted_at",
|
||||||
@@ -1634,7 +1643,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_adjustment_deleted_at\" ON \"cart_shipping_method_adjustment\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_adjustment_deleted_at\" ON \"cart_shipping_method_adjustment\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_shipping_method_adjustment_promotion_id",
|
"keyName": "IDX_shipping_method_adjustment_promotion_id",
|
||||||
@@ -1643,7 +1652,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_method_adjustment_promotion_id\" ON \"cart_shipping_method_adjustment\" (promotion_id) WHERE deleted_at IS NULL AND promotion_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_method_adjustment_promotion_id\" ON \"cart_shipping_method_adjustment\" (\"promotion_id\") WHERE deleted_at IS NULL AND promotion_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_shipping_method_adjustment_pkey",
|
"keyName": "cart_shipping_method_adjustment_pkey",
|
||||||
@@ -1790,7 +1799,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_tax_line_shipping_method_id\" ON \"cart_shipping_method_tax_line\" (shipping_method_id) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_tax_line_shipping_method_id\" ON \"cart_shipping_method_tax_line\" (\"shipping_method_id\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_cart_shipping_method_tax_line_deleted_at",
|
"keyName": "IDX_cart_shipping_method_tax_line_deleted_at",
|
||||||
@@ -1799,7 +1808,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_tax_line_deleted_at\" ON \"cart_shipping_method_tax_line\" (deleted_at) WHERE deleted_at IS NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_cart_shipping_method_tax_line_deleted_at\" ON \"cart_shipping_method_tax_line\" (\"deleted_at\") WHERE deleted_at IS NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "IDX_shipping_method_tax_line_tax_rate_id",
|
"keyName": "IDX_shipping_method_tax_line_tax_rate_id",
|
||||||
@@ -1808,7 +1817,7 @@
|
|||||||
"constraint": false,
|
"constraint": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_method_tax_line_tax_rate_id\" ON \"cart_shipping_method_tax_line\" (tax_rate_id) WHERE deleted_at IS NULL AND tax_rate_id IS NOT NULL"
|
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_method_tax_line_tax_rate_id\" ON \"cart_shipping_method_tax_line\" (\"tax_rate_id\") WHERE deleted_at IS NULL AND tax_rate_id IS NOT NULL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keyName": "cart_shipping_method_tax_line_pkey",
|
"keyName": "cart_shipping_method_tax_line_pkey",
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Migration } from "@mikro-orm/migrations"
|
||||||
|
|
||||||
|
export class Migration20251208130704 extends Migration {
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
this.addSql(
|
||||||
|
`alter table if exists "cart" add column if not exists "locale" text null;`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override async down(): Promise<void> {
|
||||||
|
this.addSql(`alter table if exists "cart" drop column if exists "locale";`)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ const Cart = model
|
|||||||
sales_channel_id: model.text().nullable(),
|
sales_channel_id: model.text().nullable(),
|
||||||
email: model.text().nullable(),
|
email: model.text().nullable(),
|
||||||
currency_code: model.text(),
|
currency_code: model.text(),
|
||||||
|
locale: model.text().nullable(),
|
||||||
metadata: model.json().nullable(),
|
metadata: model.json().nullable(),
|
||||||
completed_at: model.dateTime().nullable(),
|
completed_at: model.dateTime().nullable(),
|
||||||
shipping_address: model
|
shipping_address: model
|
||||||
|
|||||||
Reference in new issue
Block a user