- 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>
60 lines
2.6 KiB
TypeScript
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>; |