fix(dashboard, product): product attributes update with a relation update (#13019)

* fix(dashboard, product): product attributes update with a relation update

* fix: rm log

* chore: refactor
This commit is contained in:
Frane Polić authored and GitHub committed 2025-07-23 21:01:50 +02:00
1 parent c71ace0be2
commit 439c711845
6 files changed
+139 -18

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/dashboard": patch
"@medusajs/product": patch
---
fix(dashboard, product): update product attributes
@@ -1813,6 +1813,110 @@ medusaIntegrationTestRunner({
) )
}) })
it("updates products relations and attributes", async () => {
const shortsCategory = (
await api.post(
"/admin/product-categories",
{ name: "Shorts", is_internal: false, is_active: true },
adminHeaders
)
).data.product_category
const pantsCategory = (
await api.post(
"/admin/product-categories",
{ name: "Pants", is_internal: false, is_active: true },
adminHeaders
)
).data.product_category
const payload = {
title: "Test an update",
weight: 100,
length: 100,
width: 100,
height: 100,
options: [{ title: "size", values: ["large", "small"] }],
variants: [
{
options: { size: "large" },
title: "New variant",
prices: [
{
currency_code: "usd",
amount: 200,
},
],
},
],
}
const createdProduct = (
await api.post("/admin/products", payload, adminHeaders)
).data.product
let updatedProduct = (
await api.post(
`/admin/products/${createdProduct.id}`,
{ weight: 20, length: null },
adminHeaders
)
).data.product
expect(updatedProduct).toEqual(
expect.objectContaining({
weight: "20",
length: null,
width: "100",
height: "100",
})
)
updatedProduct = (
await api.post(
`/admin/products/${createdProduct.id}?fields=+categories.id`,
{ categories: [{ id: pantsCategory.id }] },
adminHeaders
)
).data.product
expect(updatedProduct).toEqual(
expect.objectContaining({
weight: "20",
length: null,
width: "100",
height: "100",
categories: expect.arrayContaining([
expect.objectContaining({
id: pantsCategory.id,
}),
]),
})
)
updatedProduct = (
await api.post(
`/admin/products/${createdProduct.id}?fields=+categories.id`,
{ weight: null, length: 20, width: 50 },
adminHeaders
)
).data.product
expect(updatedProduct).toEqual(
expect.objectContaining({
weight: null,
length: "20",
width: "50",
height: "100",
categories: expect.arrayContaining([
expect.objectContaining({
id: pantsCategory.id,
}),
]),
})
)
})
it("updates a product (update prices, tags, update status, delete collection, delete type, replaces images)", async () => { it("updates a product (update prices, tags, update status, delete collection, delete type, replaces images)", async () => {
const payload = { const payload = {
collection_id: null, collection_id: null,
@@ -68,10 +68,10 @@ export const ProductAttributesForm = ({
const handleSubmit = form.handleSubmit(async (data) => { const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync( await mutateAsync(
{ {
weight: data.weight ? data.weight : undefined, weight: data.weight ? data.weight : null,
length: data.length ? data.length : undefined, length: data.length ? data.length : null,
width: data.width ? data.width : undefined, width: data.width ? data.width : null,
height: data.height ? data.height : undefined, height: data.height ? data.height : null,
mid_code: data.mid_code, mid_code: data.mid_code,
hs_code: data.hs_code, hs_code: data.hs_code,
origin_country: data.origin_country, origin_country: data.origin_country,
@@ -53,9 +53,7 @@ export type CancelReturnValidateOrderInput = {
*/ */
export const cancelReturnValidateOrder = createStep( export const cancelReturnValidateOrder = createStep(
"validate-return", "validate-return",
({ ({ orderReturn }: CancelReturnValidateOrderInput) => {
orderReturn,
}: CancelReturnValidateOrderInput) => {
const orderReturn_ = orderReturn as ReturnDTO & { const orderReturn_ = orderReturn as ReturnDTO & {
payment_collections: PaymentCollectionDTO[] payment_collections: PaymentCollectionDTO[]
fulfillments: FulfillmentDTO[] fulfillments: FulfillmentDTO[]
@@ -11,9 +11,9 @@ import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
*/ */
export type UpdateProductsStepInput = export type UpdateProductsStepInput =
| { | {
/** /**
* The filters to select the products to update. * The filters to select the products to update.
*/ */
selector: ProductTypes.FilterableProductProps selector: ProductTypes.FilterableProductProps
/** /**
* The data to update the products with. * The data to update the products with.
@@ -21,9 +21,9 @@ export type UpdateProductsStepInput =
update: ProductTypes.UpdateProductDTO update: ProductTypes.UpdateProductDTO
} }
| { | {
/** /**
* The data to create or update products. * The data to create or update products.
*/ */
products: ProductTypes.UpsertProductDTO[] products: ProductTypes.UpsertProductDTO[]
} }
@@ -8,6 +8,7 @@ import {
MedusaError, MedusaError,
isPresent, isPresent,
mergeMetadata, mergeMetadata,
isDefined,
} from "@medusajs/framework/utils" } from "@medusajs/framework/utils"
import { SqlEntityManager, wrap } from "@mikro-orm/postgresql" import { SqlEntityManager, wrap } from "@mikro-orm/postgresql"
@@ -57,10 +58,18 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
height?: string | number height?: string | number
width?: string | number width?: string | number
}) { }) {
productToUpdate.weight = productToUpdate.weight?.toString() if (isDefined(productToUpdate.weight)) {
productToUpdate.length = productToUpdate.length?.toString() productToUpdate.weight = productToUpdate.weight?.toString()
productToUpdate.height = productToUpdate.height?.toString() }
productToUpdate.width = productToUpdate.width?.toString() if (isDefined(productToUpdate.length)) {
productToUpdate.length = productToUpdate.length?.toString()
}
if (isDefined(productToUpdate.height)) {
productToUpdate.height = productToUpdate.height?.toString()
}
if (isDefined(productToUpdate.width)) {
productToUpdate.width = productToUpdate.width?.toString()
}
} }
async deepUpdate( async deepUpdate(
@@ -72,6 +81,7 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
context: Context = {} context: Context = {}
): Promise<InferEntityType<typeof Product>[]> { ): Promise<InferEntityType<typeof Product>[]> {
const productIdsToUpdate: string[] = [] const productIdsToUpdate: string[] = []
productsToUpdate.forEach((productToUpdate) => { productsToUpdate.forEach((productToUpdate) => {
ProductRepository.#correctUpdateDTOTypes(productToUpdate) ProductRepository.#correctUpdateDTOTypes(productToUpdate)
productIdsToUpdate.push(productToUpdate.id) productIdsToUpdate.push(productToUpdate.id)
@@ -151,7 +161,10 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
} }
if (isPresent(productToUpdate.metadata)) { if (isPresent(productToUpdate.metadata)) {
productToUpdate.metadata = mergeMetadata(product.metadata ?? {}, productToUpdate.metadata) productToUpdate.metadata = mergeMetadata(
product.metadata ?? {},
productToUpdate.metadata
)
} }
wrappedProduct.assign(productToUpdate) wrappedProduct.assign(productToUpdate)