Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/blue-bags-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"integration-tests-http": patch
"@medusajs/types": patch
"@medusajs/medusa": patch
---

Enable filtering admin products API by variant EAN, UPC, and barcode
225 changes: 225 additions & 0 deletions integration-tests/http/__tests__/product/admin/product.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,231 @@ medusaIntegrationTestRunner({
}),
])
})

it("returns a list of products filtered by variants[ean]", async () => {
const productWithEan = await api.post(
"/admin/products",
getProductFixture({
title: "Product with EAN",
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant",
ean: "1234567890123",
prices: [{ currency_code: "usd", amount: 100 }],
options: {
size: "large",
color: "green",
},
},
],
}),
adminHeaders
)

await api.post(
"/admin/products",
getProductFixture({
title: "Product with different EAN",
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant 2",
ean: "9876543210987",
prices: [{ currency_code: "usd", amount: 150 }],
options: {
size: "large",
color: "green",
},
},
],
}),
adminHeaders
)

const response = await api
.get("/admin/products?variants[ean]=1234567890123", adminHeaders)
.catch((err) => {
console.log(err)
})

expect(response.status).toEqual(200)
expect(response.data.products).toHaveLength(1)
expect(response.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: productWithEan.data.product.id,
title: "Product with EAN",
variants: expect.arrayContaining([
expect.objectContaining({
ean: "1234567890123",
}),
]),
}),
])
)
})

it("returns a list of products filtered by variants[upc]", async () => {
const productWithUpc = await api.post(
"/admin/products",
getProductFixture({
title: "Product with UPC",
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant",
upc: "123456789012",
prices: [{ currency_code: "usd", amount: 200 }],
options: {
size: "large",
color: "green",
},
},
],
}),
adminHeaders
)

await api.post(
"/admin/products",
getProductFixture({
title: "Product with different UPC",
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant 2",
upc: "098765432109",
prices: [{ currency_code: "usd", amount: 250 }],
options: {
size: "large",
color: "green",
},
},
],
}),
adminHeaders
)

const response = await api
.get("/admin/products?variants[upc]=123456789012", adminHeaders)
.catch((err) => {
console.log(err)
})

expect(response.status).toEqual(200)
expect(response.data.products).toHaveLength(1)
expect(response.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: productWithUpc.data.product.id,
title: "Product with UPC",
variants: expect.arrayContaining([
expect.objectContaining({
upc: "123456789012",
}),
]),
}),
])
)
})

it("returns a list of products filtered by variants[barcode]", async () => {
const productWithBarcode = await api.post(
"/admin/products",
getProductFixture({
title: "Product with Barcode",
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant",
barcode: "1234567890",
prices: [{ currency_code: "usd", amount: 300 }],
options: {
size: "large",
color: "green",
},
},
],
}),
adminHeaders
)

await api.post(
"/admin/products",
getProductFixture({
title: "Product with different Barcode",
shipping_profile_id: shippingProfile.id,
variants: [
{
title: "Test variant 2",
barcode: "0987654321",
prices: [{ currency_code: "usd", amount: 350 }],
options: {
size: "large",
color: "green",
},
},
],
}),
adminHeaders
)

const response = await api
.get("/admin/products?variants[barcode]=1234567890", adminHeaders)
.catch((err) => {
console.log(err)
})

expect(response.status).toEqual(200)
expect(response.data.products).toHaveLength(1)
expect(response.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: productWithBarcode.data.product.id,
title: "Product with Barcode",
variants: expect.arrayContaining([
expect.objectContaining({
barcode: "1234567890",
}),
]),
}),
])
)
})

it("returns empty list when filtering by non-existent variants[ean]", async () => {
const response = await api
.get("/admin/products?variants[ean]=5555555555555", adminHeaders)
.catch((err) => {
console.log(err)
})

expect(response.status).toEqual(200)
expect(response.data.products).toHaveLength(0)
})

it("returns empty list when filtering by non-existent variants[upc]", async () => {
const response = await api
.get("/admin/products?variants[upc]=555555555555", adminHeaders)
.catch((err) => {
console.log(err)
})

expect(response.status).toEqual(200)
expect(response.data.products).toHaveLength(0)
})

it("returns empty list when filtering by non-existent variants[barcode]", async () => {
const response = await api
.get("/admin/products?variants[barcode]=5555555555", adminHeaders)
.catch((err) => {
console.log(err)
})

expect(response.status).toEqual(200)
expect(response.data.products).toHaveLength(0)
})
})

describe("GET /admin/products/:id", () => {
Expand Down
14 changes: 13 additions & 1 deletion packages/core/types/src/http/product/admin/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ export interface AdminProductVariantParams
* out of stock.
*/
allow_backorder?: boolean
/**
* Filter by variant ean(s).
*/
ean?: string | string[]
/**
* Filter by variant upc(s).
*/
upc?: string | string[]
/**
* Filter by variant barcode(s).
*/
barcode?: string | string[]
/**
* Apply filters on the variant's creation date.
*/
Expand All @@ -46,5 +58,5 @@ export interface AdminProductListParams
/**
* Apply filters on the product variants.
*/
variants?: AdminProductVariantParams
variants?: Omit<AdminProductVariantParams, "q">
}
13 changes: 10 additions & 3 deletions packages/medusa/src/api/admin/products/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const AdminGetProductVariantsParamsFields = z.object({
id: z.union([z.string(), z.array(z.string())]).optional(),
manage_inventory: booleanString().optional(),
allow_backorder: booleanString().optional(),
ean: z.union([z.string(), z.array(z.string())]).optional(),
upc: z.union([z.string(), z.array(z.string())]).optional(),
barcode: z.union([z.string(), z.array(z.string())]).optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
Expand All @@ -41,9 +44,13 @@ export const AdminGetProductVariantsParams = createFindParams({
.merge(applyAndAndOrOperators(AdminGetProductVariantsParamsFields))

export const AdminGetProductsParamsDirectFields = z.object({
variants: AdminGetProductVariantsParamsFields.merge(
applyAndAndOrOperators(AdminGetProductVariantsParamsFields)
).optional(),
variants: AdminGetProductVariantsParamsFields.omit({ q: true })
.merge(
applyAndAndOrOperators(
AdminGetProductVariantsParamsFields.omit({ q: true })
)
)
.optional(),
status: statusEnum.array().optional(),
})

Expand Down
Loading