Files
motovaultpro/backend/src/features/vehicles/api/vehicles.validation.ts
Eric Gullickson 8517b1ded2 feat: add TCO types and repository updates (refs #15)
- Add CostInterval type and PAYMENTS_PER_YEAR constant
- Add 7 TCO fields to Vehicle, CreateVehicleRequest, UpdateVehicleRequest
- Update VehicleResponse and Body types
- Update mapRow() with snake_case to camelCase mapping
- Update create(), update(), batchInsert() for new fields
- Add Zod validation for TCO fields with interval enum

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:58:59 -06:00

60 lines
2.6 KiB
TypeScript

/**
* @ai-summary Request validation schemas for vehicles API
* @ai-context Uses Zod for runtime validation and type safety
*/
import { z } from 'zod';
import { isValidVIN } from '../../../shared-minimal/utils/validators';
// Cost interval enum for TCO recurring costs
const costIntervalSchema = z.enum(['monthly', 'semi_annual', 'annual']);
export const createVehicleSchema = z.object({
vin: z.string()
.length(17, 'VIN must be exactly 17 characters')
.refine(isValidVIN, 'Invalid VIN format'),
nickname: z.string().min(1).max(100).optional(),
color: z.string().min(1).max(50).optional(),
licensePlate: z.string().min(1).max(20).optional(),
odometerReading: z.number().min(0).max(9999999).optional(),
// TCO fields
purchasePrice: z.number().min(0).max(99999999.99).optional(),
purchaseDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be YYYY-MM-DD').optional(),
insuranceCost: z.number().min(0).max(9999999.99).optional(),
insuranceInterval: costIntervalSchema.optional(),
registrationCost: z.number().min(0).max(9999999.99).optional(),
registrationInterval: costIntervalSchema.optional(),
tcoEnabled: z.boolean().optional(),
});
export const updateVehicleSchema = z.object({
vin: z.string().max(17).optional(),
year: z.number().min(1900).max(new Date().getFullYear() + 1).optional(),
make: z.string().max(100).optional(),
model: z.string().max(100).optional(),
engine: z.string().max(100).optional(),
transmission: z.string().max(100).optional(),
trimLevel: z.string().max(100).optional(),
driveType: z.string().max(50).optional(),
fuelType: z.string().max(50).optional(),
nickname: z.string().min(1).max(100).optional(),
color: z.string().min(1).max(50).optional(),
licensePlate: z.string().min(1).max(20).optional(),
odometerReading: z.number().min(0).max(9999999).optional(),
// TCO fields
purchasePrice: z.number().min(0).max(99999999.99).optional().nullable(),
purchaseDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be YYYY-MM-DD').optional().nullable(),
insuranceCost: z.number().min(0).max(9999999.99).optional().nullable(),
insuranceInterval: costIntervalSchema.optional().nullable(),
registrationCost: z.number().min(0).max(9999999.99).optional().nullable(),
registrationInterval: costIntervalSchema.optional().nullable(),
tcoEnabled: z.boolean().optional(),
});
export const vehicleIdSchema = z.object({
id: z.string().uuid('Invalid vehicle ID format'),
});
export type CreateVehicleInput = z.infer<typeof createVehicleSchema>;
export type UpdateVehicleInput = z.infer<typeof updateVehicleSchema>;
export type VehicleIdInput = z.infer<typeof vehicleIdSchema>;