- Users page: paginated, searchable, sortable with inline roles (no N+1) - Roles page: paginated, searchable, sortable + /roles/all for dropdowns - User is_active field with migration, PATCH toggle, auth check (disabled=401) - Frontend permission checks: auth store loads permissions, sidebar/buttons conditional - Profile pictures via file storage for users and members, avatar component - Identifier images use file storage API instead of base64 - Fix TypeScript errors across admin UI - 64 API tests passing (10 new)
93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
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())
|
|
}
|
|
|
|
export const CategoryCreateSchema = z.object({
|
|
name: z.string().min(1).max(255),
|
|
description: opt(z.string()),
|
|
parentId: opt(z.string().uuid()),
|
|
sortOrder: z.number().int().default(0),
|
|
})
|
|
export type CategoryCreateInput = z.infer<typeof CategoryCreateSchema>
|
|
|
|
export const CategoryUpdateSchema = CategoryCreateSchema.partial()
|
|
export type CategoryUpdateInput = z.infer<typeof CategoryUpdateSchema>
|
|
|
|
export const SupplierCreateSchema = z.object({
|
|
name: z.string().min(1).max(255),
|
|
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>
|
|
|
|
export const SupplierUpdateSchema = SupplierCreateSchema.partial()
|
|
export type SupplierUpdateInput = z.infer<typeof SupplierUpdateSchema>
|
|
|
|
// System slugs — used for code-level business logic references.
|
|
// Actual valid values are stored in lookup tables and are company-configurable.
|
|
export const SystemItemCondition = z.enum(['new', 'excellent', 'good', 'fair', 'poor'])
|
|
export const SystemUnitStatus = z.enum([
|
|
'available',
|
|
'sold',
|
|
'rented',
|
|
'on_trial',
|
|
'in_repair',
|
|
'layaway',
|
|
'lost',
|
|
'retired',
|
|
])
|
|
|
|
// API validation accepts any string slug (validated against lookup table at service layer)
|
|
export const ItemCondition = z.string().min(1).max(100)
|
|
export const UnitStatus = z.string().min(1).max(100)
|
|
|
|
export const ProductCreateSchema = z.object({
|
|
sku: opt(z.string().max(100)),
|
|
upc: opt(z.string().max(100)),
|
|
name: z.string().min(1).max(255),
|
|
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),
|
|
price: z.number().min(0).optional(),
|
|
minPrice: z.number().min(0).optional(),
|
|
rentalRateMonthly: z.number().min(0).optional(),
|
|
qtyOnHand: z.number().int().min(0).default(0),
|
|
qtyReorderPoint: z.number().int().min(0).optional(),
|
|
})
|
|
export type ProductCreateInput = z.infer<typeof ProductCreateSchema>
|
|
|
|
export const ProductUpdateSchema = ProductCreateSchema.partial()
|
|
export type ProductUpdateInput = z.infer<typeof ProductUpdateSchema>
|
|
|
|
export const ProductSearchSchema = z.object({
|
|
q: z.string().min(1).max(255),
|
|
})
|
|
|
|
export const InventoryUnitCreateSchema = z.object({
|
|
productId: z.string().uuid(),
|
|
locationId: opt(z.string().uuid()),
|
|
serialNumber: opt(z.string().max(255)),
|
|
condition: ItemCondition.default('new'),
|
|
status: UnitStatus.default('available'),
|
|
purchaseDate: opt(z.string().date()),
|
|
purchaseCost: z.number().min(0).optional(),
|
|
notes: opt(z.string()),
|
|
})
|
|
export type InventoryUnitCreateInput = z.infer<typeof InventoryUnitCreateSchema>
|
|
|
|
export const InventoryUnitUpdateSchema = InventoryUnitCreateSchema.omit({ productId: true }).partial()
|
|
export type InventoryUnitUpdateInput = z.infer<typeof InventoryUnitUpdateSchema>
|