diff --git a/integration-tests/http/__tests__/product/admin/product.spec.ts b/integration-tests/http/__tests__/product/admin/product.spec.ts index 1894de34a8..d557030239 100644 --- a/integration-tests/http/__tests__/product/admin/product.spec.ts +++ b/integration-tests/http/__tests__/product/admin/product.spec.ts @@ -1092,6 +1092,58 @@ medusaIntegrationTestRunner({ }), ]) }) + + it("should get product variants filtered by manage_inventory", async () => { + const payload = { + title: "Test product - 1", + handle: "test-1", + variants: [ + { + title: "Custom inventory 1", + prices: [{ currency_code: "usd", amount: 100 }], + manage_inventory: true, + inventory_items: [], + }, + { + title: "Custom inventory 2", + prices: [{ currency_code: "usd", amount: 100 }], + manage_inventory: false, + }, + ], + } + + const product = ( + await api.post(`/admin/products`, payload, adminHeaders) + ).data.product + + let variants = ( + await api.get( + `/admin/products/${product.id}/variants?manage_inventory=true`, + adminHeaders + ) + ).data.variants + + expect(variants).toEqual([ + expect.objectContaining({ + title: "Custom inventory 1", + product_id: product.id, + }), + ]) + + variants = ( + await api.get( + `/admin/products/${product.id}/variants?manage_inventory=false`, + adminHeaders + ) + ).data.variants + + expect(variants).toEqual([ + expect.objectContaining({ + title: "Custom inventory 2", + product_id: product.id, + }), + ]) + }) }) describe("POST /admin/products", () => { diff --git a/packages/medusa/src/api/admin/fulfillment-providers/validators.ts b/packages/medusa/src/api/admin/fulfillment-providers/validators.ts index 10376df43d..78818a3d1b 100644 --- a/packages/medusa/src/api/admin/fulfillment-providers/validators.ts +++ b/packages/medusa/src/api/admin/fulfillment-providers/validators.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { OptionalBooleanValidator } from "../../utils/common-validators" +import { booleanString } from "../../utils/common-validators" import { createFindParams } from "../../utils/validators" export type AdminFulfillmentProvidersParamsType = z.infer< @@ -13,7 +13,7 @@ export const AdminFulfillmentProvidersParams = createFindParams({ z.object({ id: z.union([z.string(), z.array(z.string())]).optional(), stock_location_id: z.union([z.string(), z.array(z.string())]).optional(), - is_enabled: OptionalBooleanValidator, + is_enabled: booleanString().optional(), q: z.string().optional(), }) ) diff --git a/packages/medusa/src/api/admin/inventory-items/validators.ts b/packages/medusa/src/api/admin/inventory-items/validators.ts index 64d070547d..e54c688440 100644 --- a/packages/medusa/src/api/admin/inventory-items/validators.ts +++ b/packages/medusa/src/api/admin/inventory-items/validators.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { OptionalBooleanValidator } from "../../utils/common-validators" +import { booleanString } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -27,7 +27,7 @@ export const AdminGetInventoryItemsParams = createFindParams({ mid_code: z.union([z.string(), z.array(z.string())]).optional(), hs_code: z.union([z.string(), z.array(z.string())]).optional(), material: z.union([z.string(), z.array(z.string())]).optional(), - requires_shipping: OptionalBooleanValidator, + requires_shipping: booleanString().optional(), weight: createOperatorMap(z.number(), parseFloat).optional(), length: createOperatorMap(z.number(), parseFloat).optional(), height: createOperatorMap(z.number(), parseFloat).optional(), diff --git a/packages/medusa/src/api/admin/payments/validators.ts b/packages/medusa/src/api/admin/payments/validators.ts index fead7ea939..affa0a34ed 100644 --- a/packages/medusa/src/api/admin/payments/validators.ts +++ b/packages/medusa/src/api/admin/payments/validators.ts @@ -1,4 +1,5 @@ import { z } from "zod" +import { booleanString } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -34,7 +35,7 @@ export const AdminGetPaymentProvidersParams = createFindParams({ }).merge( z.object({ id: z.union([z.string(), z.array(z.string())]).optional(), - is_enabled: z.boolean().optional(), + is_enabled: booleanString().optional(), $and: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(), $or: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(), }) diff --git a/packages/medusa/src/api/admin/product-categories/validators.ts b/packages/medusa/src/api/admin/product-categories/validators.ts index 8a131905d4..d274197962 100644 --- a/packages/medusa/src/api/admin/product-categories/validators.ts +++ b/packages/medusa/src/api/admin/product-categories/validators.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { OptionalBooleanValidator } from "../../utils/common-validators" +import { booleanString } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -11,8 +11,8 @@ export type AdminProductCategoryParamsType = z.infer< > export const AdminProductCategoryParams = createSelectParams().merge( z.object({ - include_ancestors_tree: OptionalBooleanValidator, - include_descendants_tree: OptionalBooleanValidator, + include_ancestors_tree: booleanString().optional(), + include_descendants_tree: booleanString().optional(), }) ) @@ -29,10 +29,10 @@ export const AdminProductCategoriesParams = createFindParams({ description: z.union([z.string(), z.array(z.string())]).optional(), handle: z.union([z.string(), z.array(z.string())]).optional(), parent_category_id: z.union([z.string(), z.array(z.string())]).optional(), - include_ancestors_tree: OptionalBooleanValidator, - include_descendants_tree: OptionalBooleanValidator, - is_internal: OptionalBooleanValidator, - is_active: OptionalBooleanValidator, + include_ancestors_tree: booleanString().optional(), + include_descendants_tree: booleanString().optional(), + is_internal: booleanString().optional(), + is_active: booleanString().optional(), created_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(), diff --git a/packages/medusa/src/api/admin/product-variants/validators.ts b/packages/medusa/src/api/admin/product-variants/validators.ts index 8e4633eca0..a0e63f62a3 100644 --- a/packages/medusa/src/api/admin/product-variants/validators.ts +++ b/packages/medusa/src/api/admin/product-variants/validators.ts @@ -1,4 +1,5 @@ import { z } from "zod" +import { booleanString } from "../../utils/common-validators" import { createFindParams, createOperatorMap } from "../../utils/validators" export type AdminGetProductVariantsParamsType = z.infer< @@ -11,8 +12,8 @@ export const AdminGetProductVariantsParams = createFindParams({ z.object({ q: z.string().optional(), id: z.union([z.string(), z.array(z.string())]).optional(), - manage_inventory: z.boolean().optional(), - allow_backorder: z.boolean().optional(), + manage_inventory: booleanString().optional(), + allow_backorder: booleanString().optional(), created_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(), diff --git a/packages/medusa/src/api/admin/products/validators.ts b/packages/medusa/src/api/admin/products/validators.ts index ccbbf9973f..df1fa8912b 100644 --- a/packages/medusa/src/api/admin/products/validators.ts +++ b/packages/medusa/src/api/admin/products/validators.ts @@ -1,10 +1,11 @@ import { BatchMethodRequest } from "@medusajs/types" import { ProductStatus } from "@medusajs/utils" +import { z } from "zod" import { + booleanString, GetProductsParams, transformProductParams, } from "../../utils/common-validators" -import { z } from "zod" import { createFindParams, createOperatorMap, @@ -28,8 +29,8 @@ export const AdminGetProductVariantsParams = createFindParams({ z.object({ q: z.string().optional(), id: z.union([z.string(), z.array(z.string())]).optional(), - manage_inventory: z.boolean().optional(), - allow_backorder: z.boolean().optional(), + manage_inventory: booleanString().optional(), + allow_backorder: booleanString().optional(), created_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(), @@ -140,8 +141,8 @@ export const CreateProductVariant = z barcode: z.string().nullish(), hs_code: z.string().nullish(), mid_code: z.string().nullish(), - allow_backorder: z.boolean().optional().default(false), - manage_inventory: z.boolean().optional().default(true), + allow_backorder: booleanString().optional().default(false), + manage_inventory: booleanString().optional().default(true), variant_rank: z.number().optional(), weight: z.number().nullish(), length: z.number().nullish(), @@ -177,8 +178,8 @@ export const UpdateProductVariant = z barcode: z.string().nullish(), hs_code: z.string().nullish(), mid_code: z.string().nullish(), - allow_backorder: z.boolean().optional(), - manage_inventory: z.boolean().optional(), + allow_backorder: booleanString().optional(), + manage_inventory: booleanString().optional(), variant_rank: z.number().optional(), weight: z.number().nullish(), length: z.number().nullish(), @@ -211,8 +212,8 @@ export const CreateProduct = z title: z.string(), subtitle: z.string().nullish(), description: z.string().nullish(), - is_giftcard: z.boolean().optional().default(false), - discountable: z.boolean().optional().default(true), + is_giftcard: booleanString().optional().default(false), + discountable: booleanString().optional().default(true), images: z.array(z.object({ url: z.string() })).optional(), thumbnail: z.string().nullish(), handle: z.string().optional(), @@ -242,8 +243,8 @@ export type AdminUpdateProductType = z.infer export const UpdateProduct = z .object({ title: z.string().optional(), - discountable: z.boolean().optional(), - is_giftcard: z.boolean().optional(), + discountable: booleanString().optional(), + is_giftcard: booleanString().optional(), options: z.array(UpdateProductOption).optional(), variants: z.array(UpdateProductVariant).optional(), status: statusEnum.optional(), diff --git a/packages/medusa/src/api/admin/sales-channels/validators.ts b/packages/medusa/src/api/admin/sales-channels/validators.ts index 4613b83263..021b22012f 100644 --- a/packages/medusa/src/api/admin/sales-channels/validators.ts +++ b/packages/medusa/src/api/admin/sales-channels/validators.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { OptionalBooleanValidator } from "../../utils/common-validators" +import { booleanString } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -23,7 +23,7 @@ export const AdminGetSalesChannelsParams = createFindParams({ id: z.union([z.string(), z.array(z.string())]).optional(), name: z.union([z.string(), z.array(z.string())]).optional(), description: z.string().optional(), - is_disabled: OptionalBooleanValidator, + is_disabled: booleanString().optional(), created_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(), diff --git a/packages/medusa/src/api/store/payment-providers/validators.ts b/packages/medusa/src/api/store/payment-providers/validators.ts index 72b9e395ef..ec17b004d1 100644 --- a/packages/medusa/src/api/store/payment-providers/validators.ts +++ b/packages/medusa/src/api/store/payment-providers/validators.ts @@ -1,4 +1,5 @@ import { z } from "zod" +import { booleanString } from "../../utils/common-validators" import { createFindParams } from "../../utils/validators" export type StoreGetPaymentProvidersParamsType = z.infer< @@ -11,7 +12,7 @@ export const StoreGetPaymentProvidersParams = createFindParams({ z.object({ region_id: z.string(), id: z.union([z.string(), z.array(z.string())]).optional(), - is_enabled: z.boolean().optional(), + is_enabled: booleanString().optional(), $and: z.lazy(() => StoreGetPaymentProvidersParams.array()).optional(), $or: z.lazy(() => StoreGetPaymentProvidersParams.array()).optional(), }) diff --git a/packages/medusa/src/api/store/product-categories/validators.ts b/packages/medusa/src/api/store/product-categories/validators.ts index 5df8ed059c..a797c9fe67 100644 --- a/packages/medusa/src/api/store/product-categories/validators.ts +++ b/packages/medusa/src/api/store/product-categories/validators.ts @@ -1,5 +1,5 @@ import { z } from "zod" -import { OptionalBooleanValidator } from "../../utils/common-validators" +import { booleanString } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -11,8 +11,8 @@ export type StoreProductCategoryParamsType = z.infer< > export const StoreProductCategoryParams = createSelectParams().merge( z.object({ - include_ancestors_tree: OptionalBooleanValidator, - include_descendants_tree: OptionalBooleanValidator, + include_ancestors_tree: booleanString().optional(), + include_descendants_tree: booleanString().optional(), }) ) @@ -29,8 +29,8 @@ export const StoreProductCategoriesParams = createFindParams({ description: z.union([z.string(), z.array(z.string())]).optional(), handle: z.union([z.string(), z.array(z.string())]).optional(), parent_category_id: z.union([z.string(), z.array(z.string())]).optional(), - include_ancestors_tree: OptionalBooleanValidator, - include_descendants_tree: OptionalBooleanValidator, + include_ancestors_tree: booleanString().optional(), + include_descendants_tree: booleanString().optional(), created_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(), diff --git a/packages/medusa/src/api/utils/common-validators/common.ts b/packages/medusa/src/api/utils/common-validators/common.ts index 94b9810ad5..026f288fe3 100644 --- a/packages/medusa/src/api/utils/common-validators/common.ts +++ b/packages/medusa/src/api/utils/common-validators/common.ts @@ -32,7 +32,24 @@ const optionalBooleanMapper = new Map([ ["false", false], ]) +/** + * @deprecated Use `booleanString` instead + * It support the chainable API of zod. Please note it does not come with `.optional()` by default + */ export const OptionalBooleanValidator = z.preprocess( (val: any) => optionalBooleanMapper.get(val?.toLowerCase()), z.boolean().optional() ) + +/** + * Validates that a value is a boolean when it is passed as a string. + */ +export const booleanString = () => + z + .union([z.boolean(), z.string()]) + .refine((value) => { + return ["true", "false"].includes(value.toString().toLowerCase()) + }) + .transform((value) => { + return value.toString().toLowerCase() === "true" + }) diff --git a/packages/medusa/src/api/utils/common-validators/products/index.ts b/packages/medusa/src/api/utils/common-validators/products/index.ts index af14d126f9..a7c7294139 100644 --- a/packages/medusa/src/api/utils/common-validators/products/index.ts +++ b/packages/medusa/src/api/utils/common-validators/products/index.ts @@ -1,8 +1,8 @@ +import { FilterableProductProps } from "@medusajs/types" import { ProductStatus } from "@medusajs/utils" import { z } from "zod" import { createOperatorMap } from "../../validators" -import { OptionalBooleanValidator } from "../common" -import { FilterableProductProps } from "@medusajs/types" +import { booleanString } from "../common" export const ProductStatusEnum = z.nativeEnum(ProductStatus) @@ -11,7 +11,7 @@ export const GetProductsParams = z.object({ id: z.union([z.string(), z.array(z.string())]).optional(), title: z.string().optional(), handle: z.string().optional(), - is_giftcard: OptionalBooleanValidator, + is_giftcard: booleanString().optional(), category_id: z.union([z.string(), z.array(z.string())]).optional(), sales_channel_id: z.union([z.string(), z.array(z.string())]).optional(), collection_id: z.union([z.string(), z.array(z.string())]).optional(),