Fix empty string validation on all optional form fields across all schemas
Add opt() preprocessor that coerces empty strings to undefined before Zod validation. Applied to every optional string field in account, member, identifier, supplier, product, inventory unit, tax exemption, payment method, and lookup schemas. Fixes forms rejecting blank optional fields.
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
/** Coerce empty strings to undefined — solves HTML form inputs sending '' for blank optional fields */
|
||||
function opt<T extends z.ZodTypeAny>(schema: T) {
|
||||
return z.preprocess((v) => (v === '' ? undefined : v), schema.optional()) as z.ZodEffects<z.ZodOptional<T>>
|
||||
}
|
||||
|
||||
export const CategoryCreateSchema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
description: z.string().optional(),
|
||||
parentId: z.string().uuid().optional(),
|
||||
description: opt(z.string()),
|
||||
parentId: opt(z.string().uuid()),
|
||||
sortOrder: z.number().int().default(0),
|
||||
})
|
||||
export type CategoryCreateInput = z.infer<typeof CategoryCreateSchema>
|
||||
@@ -13,13 +18,13 @@ export type CategoryUpdateInput = z.infer<typeof CategoryUpdateSchema>
|
||||
|
||||
export const SupplierCreateSchema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
contactName: z.string().max(255).optional(),
|
||||
email: z.string().email().optional(),
|
||||
phone: z.string().max(50).optional(),
|
||||
website: z.string().max(255).optional(),
|
||||
accountNumber: z.string().max(100).optional(),
|
||||
paymentTerms: z.string().max(100).optional(),
|
||||
notes: z.string().optional(),
|
||||
contactName: opt(z.string().max(255)),
|
||||
email: opt(z.string().email()),
|
||||
phone: opt(z.string().max(50)),
|
||||
website: opt(z.string().max(255)),
|
||||
accountNumber: opt(z.string().max(100)),
|
||||
paymentTerms: opt(z.string().max(100)),
|
||||
notes: opt(z.string()),
|
||||
})
|
||||
export type SupplierCreateInput = z.infer<typeof SupplierCreateSchema>
|
||||
|
||||
@@ -45,14 +50,14 @@ export const ItemCondition = z.string().min(1).max(100)
|
||||
export const UnitStatus = z.string().min(1).max(100)
|
||||
|
||||
export const ProductCreateSchema = z.object({
|
||||
sku: z.string().max(100).optional(),
|
||||
upc: z.string().max(100).optional(),
|
||||
sku: opt(z.string().max(100)),
|
||||
upc: opt(z.string().max(100)),
|
||||
name: z.string().min(1).max(255),
|
||||
description: z.string().optional(),
|
||||
brand: z.string().max(255).optional(),
|
||||
model: z.string().max(255).optional(),
|
||||
categoryId: z.string().uuid().optional(),
|
||||
locationId: z.string().uuid().optional(),
|
||||
description: opt(z.string()),
|
||||
brand: opt(z.string().max(255)),
|
||||
model: opt(z.string().max(255)),
|
||||
categoryId: opt(z.string().uuid()),
|
||||
locationId: opt(z.string().uuid()),
|
||||
isSerialized: z.boolean().default(false),
|
||||
isRental: z.boolean().default(false),
|
||||
isDualUseRepair: z.boolean().default(false),
|
||||
@@ -73,13 +78,13 @@ export const ProductSearchSchema = z.object({
|
||||
|
||||
export const InventoryUnitCreateSchema = z.object({
|
||||
productId: z.string().uuid(),
|
||||
locationId: z.string().uuid().optional(),
|
||||
serialNumber: z.string().max(255).optional(),
|
||||
locationId: opt(z.string().uuid()),
|
||||
serialNumber: opt(z.string().max(255)),
|
||||
condition: ItemCondition.default('new'),
|
||||
status: UnitStatus.default('available'),
|
||||
purchaseDate: z.string().date().optional(),
|
||||
purchaseDate: opt(z.string().date()),
|
||||
purchaseCost: z.number().min(0).optional(),
|
||||
notes: z.string().optional(),
|
||||
notes: opt(z.string()),
|
||||
})
|
||||
export type InventoryUnitCreateInput = z.infer<typeof InventoryUnitCreateSchema>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user