Files
lunarfront-app/packages/shared/src/schemas/inventory.schema.ts
Ryan Moon b9f78639e2 Add paginated users/roles, user status, frontend permissions, profile pictures, identifier file storage
- 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)
2026-03-29 08:16:34 -05:00

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>