feat: Enable filtering admin products by variant EAN, UPC, and barcode (#12815)
* Add filters for variant ean, upc, and barcode in product queries and validators * fix: Omit 'q' field from variants in product list and validation parameters * Add tests for admin products filtering by variants ean, upc, and barcode * Add changeset for filter admin products api by variant ean, upc, and barcode --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
7
.changeset/blue-bags-grin.md
Normal file
7
.changeset/blue-bags-grin.md
Normal file
@@ -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
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -46,5 +58,5 @@ export interface AdminProductListParams
|
||||
/**
|
||||
* Apply filters on the product variants.
|
||||
*/
|
||||
variants?: AdminProductVariantParams
|
||||
variants?: Omit<AdminProductVariantParams, "q">
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user