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:
committed by
GitHub
parent
356283c359
commit
e4877616c3
9
.changeset/dull-trains-shave.md
Normal file
9
.changeset/dull-trains-shave.md
Normal file
@@ -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,6 +21,69 @@ const customFulfillmentProviderCalculated = {
|
||||
id: "test-provider-calculated",
|
||||
}
|
||||
|
||||
const translationModuleResolutions =
|
||||
process.env.MEDUSA_FF_TRANSLATION === "true"
|
||||
? {
|
||||
[Modules.TRANSLATION]: {
|
||||
resolve: "@medusajs/translation",
|
||||
},
|
||||
}
|
||||
: {}
|
||||
|
||||
const modules = {
|
||||
...translationModuleResolutions,
|
||||
[Modules.FULFILLMENT]: {
|
||||
/** @type {import('@medusajs/fulfillment').FulfillmentModuleOptions} */
|
||||
options: {
|
||||
providers: [
|
||||
customFulfillmentProvider,
|
||||
customFulfillmentProviderCalculated,
|
||||
],
|
||||
},
|
||||
},
|
||||
[Modules.NOTIFICATION]: {
|
||||
resolve: "@medusajs/notification",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/notification-local",
|
||||
id: "local",
|
||||
options: {
|
||||
name: "Local Notification Provider",
|
||||
channels: ["feed"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
[Modules.FILE]: {
|
||||
resolve: "@medusajs/file",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/file-local",
|
||||
id: "local",
|
||||
options: {
|
||||
// This is the directory where we can reliably write in CI environments
|
||||
upload_dir: path.join(os.tmpdir(), "uploads"),
|
||||
private_upload_dir: path.join(os.tmpdir(), "static"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
[Modules.INDEX]: {
|
||||
resolve: "@medusajs/index",
|
||||
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,
|
||||
@@ -33,50 +96,5 @@ module.exports = defineConfig({
|
||||
featureFlags: {
|
||||
index_engine: process.env.ENABLE_INDEX_MODULE === "true",
|
||||
},
|
||||
modules: {
|
||||
[Modules.FULFILLMENT]: {
|
||||
/** @type {import('@medusajs/fulfillment').FulfillmentModuleOptions} */
|
||||
options: {
|
||||
providers: [
|
||||
customFulfillmentProvider,
|
||||
customFulfillmentProviderCalculated,
|
||||
],
|
||||
},
|
||||
},
|
||||
[Modules.NOTIFICATION]: {
|
||||
resolve: "@medusajs/notification",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/notification-local",
|
||||
id: "local",
|
||||
options: {
|
||||
name: "Local Notification Provider",
|
||||
channels: ["feed"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
[Modules.FILE]: {
|
||||
resolve: "@medusajs/file",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/file-local",
|
||||
id: "local",
|
||||
options: {
|
||||
// This is the directory where we can reliably write in CI environments
|
||||
upload_dir: path.join(os.tmpdir(), "uploads"),
|
||||
private_upload_dir: path.join(os.tmpdir(), "static"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
[Modules.INDEX]: {
|
||||
resolve: "@medusajs/index",
|
||||
disable: process.env.ENABLE_INDEX_MODULE !== "true",
|
||||
},
|
||||
},
|
||||
modules,
|
||||
})
|
||||
|
||||
@@ -1360,6 +1360,7 @@ medusaIntegrationTestRunner({
|
||||
id: expect.stringContaining("cart_"),
|
||||
sales_channel_id: expect.stringContaining("sc_"),
|
||||
currency_code: "usd",
|
||||
locale: null,
|
||||
region_id: expect.stringContaining("reg_"),
|
||||
shipping_address: null,
|
||||
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 "./get-actions-to-compute-from-promotions"
|
||||
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-variant-price-sets"
|
||||
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",
|
||||
"subtotal",
|
||||
"item_total",
|
||||
"locale",
|
||||
"total",
|
||||
"item_subtotal",
|
||||
"shipping_subtotal",
|
||||
|
||||
@@ -139,30 +139,30 @@ export function prepareLineItemData(data: PrepareLineItemDataInput) {
|
||||
|
||||
let lineItem: any = {
|
||||
quantity: item?.quantity,
|
||||
title: variant?.product?.title ?? item?.title,
|
||||
subtitle: variant?.title ?? item?.subtitle,
|
||||
title: item?.title ?? variant?.product?.title,
|
||||
subtitle: item?.subtitle ?? variant?.title,
|
||||
thumbnail:
|
||||
variant?.thumbnail ?? variant?.product?.thumbnail ?? item?.thumbnail,
|
||||
item?.thumbnail ?? variant?.thumbnail ?? variant?.product?.thumbnail,
|
||||
|
||||
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:
|
||||
variant?.product?.description ?? item?.product_description,
|
||||
product_subtitle: variant?.product?.subtitle ?? item?.product_subtitle,
|
||||
product_type: variant?.product?.type?.value ?? item?.product_type ?? null,
|
||||
item?.product_description ?? variant?.product?.description,
|
||||
product_subtitle: item?.product_subtitle ?? variant?.product?.subtitle,
|
||||
product_type: item?.product_type ?? variant?.product?.type?.value ?? null,
|
||||
product_type_id:
|
||||
variant?.product?.type?.id ?? item?.product_type_id ?? null,
|
||||
item?.product_type_id ?? variant?.product?.type?.id ?? null,
|
||||
product_collection:
|
||||
variant?.product?.collection?.title ?? item?.product_collection ?? null,
|
||||
product_handle: variant?.product?.handle ?? item?.product_handle,
|
||||
item?.product_collection ?? variant?.product?.collection?.title ?? null,
|
||||
product_handle: item?.product_handle ?? variant?.product?.handle,
|
||||
|
||||
variant_id: variant?.id,
|
||||
variant_sku: variant?.sku ?? item?.variant_sku,
|
||||
variant_barcode: variant?.barcode ?? item?.variant_barcode,
|
||||
variant_title: variant?.title ?? item?.variant_title,
|
||||
variant_sku: item?.variant_sku ?? variant?.sku,
|
||||
variant_barcode: item?.variant_barcode ?? variant?.barcode,
|
||||
variant_title: item?.variant_title ?? variant?.title,
|
||||
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,
|
||||
requires_shipping: requiresShipping,
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { acquireLockStep, releaseLockStep } from "../../locking"
|
||||
import {
|
||||
createLineItemsStep,
|
||||
getLineItemActionsStep,
|
||||
getTranslatedLineItemsStep,
|
||||
updateLineItemsStep,
|
||||
} from "../steps"
|
||||
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 { refreshCartItemsWorkflow } from "./refresh-cart-items"
|
||||
|
||||
const cartFields = ["completed_at"].concat(cartFieldsForPricingContext)
|
||||
const cartFields = ["completed_at", "locale"].concat(
|
||||
cartFieldsForPricingContext
|
||||
)
|
||||
|
||||
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(
|
||||
createLineItemsStep({
|
||||
id: cart.id,
|
||||
items: itemsToCreate,
|
||||
items: translatedItemsToCreate,
|
||||
}),
|
||||
updateLineItemsStep({
|
||||
id: cart.id,
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ConfirmVariantInventoryWorkflowInputDTO,
|
||||
CreateCartDTO,
|
||||
CreateCartWorkflowInputDTO,
|
||||
CreateLineItemDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
CartWorkflowEvents,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
findOneOrAnyRegionStep,
|
||||
findOrCreateCustomerStep,
|
||||
findSalesChannelStep,
|
||||
getTranslatedLineItemsStep,
|
||||
} from "../steps"
|
||||
import { validateSalesChannelStep } from "../steps/validate-sales-channel"
|
||||
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) => {
|
||||
return {
|
||||
...data.cartInput,
|
||||
items: data.lineItems.map((i) => i.data),
|
||||
} as unknown as CreateCartDTO
|
||||
const itemsToCreate = transform({ lineItems }, (data) => {
|
||||
return data.lineItems.map((i) => i.data as CreateLineItemDTO)
|
||||
})
|
||||
|
||||
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", {
|
||||
input: cartInput,
|
||||
cart: cartToCreate,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
CreateCartCreateLineItemDTO,
|
||||
CustomerDTO,
|
||||
OrderWorkflow,
|
||||
ProductVariantDTO,
|
||||
RegionDTO,
|
||||
UpdateLineItemDTO,
|
||||
UpdateLineItemWithSelectorDTO,
|
||||
@@ -54,7 +55,7 @@ interface GetVariantsAndItemsWithPricesWorkflowInput {
|
||||
|
||||
type GetVariantsAndItemsWithPricesWorkflowOutput = {
|
||||
// The variant can depend on the requested fields and therefore the caller will know better
|
||||
variants: (object & {
|
||||
variants: (Partial<ProductVariantDTO> & {
|
||||
calculated_price: {
|
||||
calculated_price: {
|
||||
price_list_type: string
|
||||
@@ -184,8 +185,11 @@ export const getVariantsAndItemsWithPrices = createWorkflow(
|
||||
}
|
||||
|
||||
const variant = variantsData.find((v) => v.id === item.variant_id)
|
||||
if ((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
|
||||
if (
|
||||
(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)
|
||||
}
|
||||
@@ -225,7 +229,9 @@ export const getVariantsAndItemsWithPrices = createWorkflow(
|
||||
if (variantNotFoundOrPublished.length > 0) {
|
||||
throw new MedusaError(
|
||||
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) {
|
||||
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useQueryGraphStep } from "../../common"
|
||||
import { acquireLockStep, releaseLockStep } from "../../locking"
|
||||
import { updateLineItemsStep, validateCartStep } from "../steps"
|
||||
import {
|
||||
updateCartItemsTranslationsStep,
|
||||
updateLineItemsStep,
|
||||
validateCartStep,
|
||||
} from "../steps"
|
||||
import { cartFieldsForRefreshSteps } from "../utils/fields"
|
||||
import { pricingContextResult } from "../utils/schemas"
|
||||
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.
|
||||
*/
|
||||
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"
|
||||
@@ -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(
|
||||
"beforeRefreshingPaymentCollection",
|
||||
{ input }
|
||||
|
||||
@@ -99,6 +99,7 @@ export const updateCartWorkflow = createWorkflow(
|
||||
"email",
|
||||
"customer_id",
|
||||
"sales_channel_id",
|
||||
"locale",
|
||||
"shipping_address.*",
|
||||
"region.*",
|
||||
"region.countries.*",
|
||||
@@ -280,6 +281,17 @@ export const updateCartWorkflow = createWorkflow(
|
||||
}).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(
|
||||
updateCartsStep([cartInput]),
|
||||
emitEventStep({
|
||||
@@ -314,6 +326,7 @@ export const updateCartWorkflow = createWorkflow(
|
||||
cart_id: cartInput.id,
|
||||
promo_codes: input.promo_codes,
|
||||
force_refresh: !!newRegion,
|
||||
locale: newLocaleCode,
|
||||
additional_data: input.additional_data,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ export const createTranslationsStepId = "create-translations"
|
||||
* {
|
||||
* reference_id: "prod_123",
|
||||
* reference: "product",
|
||||
* locale_code: "fr-FR",
|
||||
* locale: "fr-FR",
|
||||
* translations: { title: "Produit", description: "Description du produit" }
|
||||
* }
|
||||
* ])
|
||||
|
||||
@@ -36,7 +36,7 @@ export const updateTranslationsStepId = "update-translations"
|
||||
* const data = updateTranslationsStep({
|
||||
* selector: {
|
||||
* reference_id: "prod_123",
|
||||
* locale_code: "fr-FR"
|
||||
* locale: "fr-FR"
|
||||
* },
|
||||
* update: {
|
||||
* translations: { title: "Nouveau titre" }
|
||||
|
||||
@@ -24,7 +24,7 @@ export const validateTranslationsStep = createStep(
|
||||
} = await query.graph(
|
||||
{
|
||||
entity: "store",
|
||||
fields: ["supported_locales.*"],
|
||||
fields: ["id", "supported_locales.*"],
|
||||
pagination: {
|
||||
take: 1,
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ export const createTranslationsWorkflowId = "create-translations"
|
||||
* {
|
||||
* reference_id: "prod_123",
|
||||
* reference: "product",
|
||||
* locale_code: "fr-FR",
|
||||
* locale: "fr-FR",
|
||||
* translations: { title: "Produit", description: "Description du produit" }
|
||||
* }
|
||||
* ]
|
||||
|
||||
@@ -24,7 +24,7 @@ export const updateTranslationsWorkflowId = "update-translations"
|
||||
* input: {
|
||||
* selector: {
|
||||
* reference_id: "prod_123",
|
||||
* locale_code: "fr-FR"
|
||||
* locale: "fr-FR"
|
||||
* },
|
||||
* update: {
|
||||
* translations: { title: "Nouveau titre" }
|
||||
|
||||
@@ -769,6 +769,11 @@ export interface CartDTO {
|
||||
*/
|
||||
currency_code: string
|
||||
|
||||
/**
|
||||
* The locale code of the cart.
|
||||
*/
|
||||
locale?: string
|
||||
|
||||
/**
|
||||
* The associated shipping address.
|
||||
*
|
||||
|
||||
@@ -116,6 +116,11 @@ export interface CreateCartDTO {
|
||||
*/
|
||||
currency_code: string
|
||||
|
||||
/**
|
||||
* The locale code of the cart.
|
||||
*/
|
||||
locale?: string
|
||||
|
||||
/**
|
||||
* The associated shipping address's ID.
|
||||
*/
|
||||
@@ -176,6 +181,11 @@ export interface UpdateCartDataDTO {
|
||||
*/
|
||||
currency_code?: string
|
||||
|
||||
/**
|
||||
* The locale code of the cart.
|
||||
*/
|
||||
locale?: string | null
|
||||
|
||||
/**
|
||||
* The associated shipping address's ID.
|
||||
*/
|
||||
|
||||
@@ -279,6 +279,11 @@ export interface CreateCartWorkflowInputDTO {
|
||||
* The promotional codes applied on the cart.
|
||||
*/
|
||||
promo_codes?: string[]
|
||||
|
||||
/**
|
||||
* The locale code of the cart.
|
||||
*/
|
||||
locale?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,6 +341,11 @@ export interface UpdateCartWorkflowInputDTO {
|
||||
*/
|
||||
currency_code?: string
|
||||
|
||||
/**
|
||||
* The locale code for the cart.
|
||||
*/
|
||||
locale?: string | null
|
||||
|
||||
/**
|
||||
* Custom key-value pairs of data related to the cart.
|
||||
*/
|
||||
@@ -553,4 +563,4 @@ export type CreateCartCreditLinesWorkflowInput = {
|
||||
* The metadata of the cart detail
|
||||
*/
|
||||
metadata: Record<string, unknown> | null
|
||||
}[]
|
||||
}[]
|
||||
|
||||
@@ -39,6 +39,11 @@ export interface StoreCreateCart {
|
||||
* Key-value pairs of custom data.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* The locale code of the cart.
|
||||
*/
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export interface StoreUpdateCart {
|
||||
@@ -71,6 +76,10 @@ export interface StoreUpdateCart {
|
||||
* The promotion codes to apply on the cart.
|
||||
*/
|
||||
promo_codes?: string[]
|
||||
/**
|
||||
* The locale code of the cart.
|
||||
*/
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export interface StoreUpdateCartCustomer {}
|
||||
@@ -187,4 +196,4 @@ export interface StoreCartAddPromotion {
|
||||
* The promotion codes to add to the cart.
|
||||
*/
|
||||
promo_codes: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +288,7 @@ class MedusaTestRunner {
|
||||
cwd: this.cwd,
|
||||
})
|
||||
await medusaAppLoader.runModulesLoader()
|
||||
|
||||
await createDefaultsWorkflow(copiedContainer).run()
|
||||
} catch (error) {
|
||||
await copiedContainer.dispose?.()
|
||||
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
FeatureFlag,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { BatchMethodRequest, HttpTypes } from "@medusajs/types"
|
||||
import TranslationFeatureFlag from "../../../../feature-flags/translation"
|
||||
import { defaultAdminTranslationFields } from "../query-config"
|
||||
import {
|
||||
AdminCreateTranslationType,
|
||||
AdminUpdateTranslationType,
|
||||
} from "../validators"
|
||||
import TranslationFeatureFlag from "../../../../feature-flags/translation"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<
|
||||
|
||||
@@ -23,6 +23,7 @@ export const CreateCart = z
|
||||
sales_channel_id: z.string().nullish(),
|
||||
promo_codes: z.array(z.string()).optional(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
locale: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
export const StoreCreateCart = WithAdditionalData(CreateCart)
|
||||
@@ -57,6 +58,7 @@ export const UpdateCart = z
|
||||
sales_channel_id: z.string().nullish(),
|
||||
metadata: z.record(z.unknown()).nullish(),
|
||||
promo_codes: z.array(z.string()).optional(),
|
||||
locale: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
export const StoreUpdateCart = WithAdditionalData(UpdateCart)
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -239,6 +239,15 @@
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"locale": {
|
||||
"name": "locale",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
@@ -319,7 +328,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -328,7 +337,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -337,7 +346,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -346,7 +355,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -355,7 +364,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -364,7 +373,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -373,7 +382,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -525,7 +534,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -534,7 +543,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -543,7 +552,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -884,7 +893,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -893,7 +902,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -902,7 +911,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -911,7 +920,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -920,7 +929,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1087,7 +1096,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1096,7 +1105,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1105,7 +1114,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1259,7 +1268,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1268,7 +1277,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1277,7 +1286,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1444,7 +1453,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1453,7 +1462,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1462,7 +1471,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1625,7 +1634,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1634,7 +1643,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1643,7 +1652,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1790,7 +1799,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1799,7 +1808,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
@@ -1808,7 +1817,7 @@
|
||||
"constraint": false,
|
||||
"primary": 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",
|
||||
|
||||
@@ -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(),
|
||||
email: model.text().nullable(),
|
||||
currency_code: model.text(),
|
||||
locale: model.text().nullable(),
|
||||
metadata: model.json().nullable(),
|
||||
completed_at: model.dateTime().nullable(),
|
||||
shipping_address: model
|
||||
|
||||
Reference in New Issue
Block a user