Notification updates
This commit is contained in:
@@ -22,9 +22,9 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance records list requested', {
|
||||
operation: 'maintenance.records.list',
|
||||
user_id: userId,
|
||||
userId,
|
||||
filters: {
|
||||
vehicle_id: request.query.vehicleId,
|
||||
vehicleId: request.query.vehicleId,
|
||||
category: request.query.category,
|
||||
},
|
||||
});
|
||||
@@ -42,15 +42,15 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance records list retrieved', {
|
||||
operation: 'maintenance.records.list.success',
|
||||
user_id: userId,
|
||||
record_count: records.length,
|
||||
userId,
|
||||
recordCount: records.length,
|
||||
});
|
||||
|
||||
return reply.code(200).send(records);
|
||||
} catch (error) {
|
||||
logger.error('Failed to list maintenance records', {
|
||||
operation: 'maintenance.records.list.error',
|
||||
user_id: userId,
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -63,8 +63,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance record get requested', {
|
||||
operation: 'maintenance.records.get',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -72,17 +72,17 @@ export class MaintenanceController {
|
||||
if (!record) {
|
||||
logger.warn('Maintenance record not found', {
|
||||
operation: 'maintenance.records.get.not_found',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
});
|
||||
return reply.code(404).send({ error: 'Not Found' });
|
||||
}
|
||||
|
||||
logger.info('Maintenance record retrieved', {
|
||||
operation: 'maintenance.records.get.success',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
vehicle_id: record.vehicle_id,
|
||||
userId,
|
||||
recordId,
|
||||
vehicleId: record.vehicleId,
|
||||
category: record.category,
|
||||
});
|
||||
|
||||
@@ -90,8 +90,8 @@ export class MaintenanceController {
|
||||
} catch (error) {
|
||||
logger.error('Failed to get maintenance record', {
|
||||
operation: 'maintenance.records.get.error',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -107,8 +107,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance records by vehicle requested', {
|
||||
operation: 'maintenance.records.by_vehicle',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
userId,
|
||||
vehicleId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -116,17 +116,17 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance records by vehicle retrieved', {
|
||||
operation: 'maintenance.records.by_vehicle.success',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
record_count: records.length,
|
||||
userId,
|
||||
vehicleId,
|
||||
recordCount: records.length,
|
||||
});
|
||||
|
||||
return reply.code(200).send(records);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get maintenance records by vehicle', {
|
||||
operation: 'maintenance.records.by_vehicle.error',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
userId,
|
||||
vehicleId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -138,7 +138,7 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance record create requested', {
|
||||
operation: 'maintenance.records.create',
|
||||
user_id: userId,
|
||||
userId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -148,11 +148,11 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance record created', {
|
||||
operation: 'maintenance.records.create.success',
|
||||
user_id: userId,
|
||||
record_id: record.id,
|
||||
vehicle_id: record.vehicle_id,
|
||||
userId,
|
||||
recordId: record.id,
|
||||
vehicleId: record.vehicleId,
|
||||
category: record.category,
|
||||
subtype_count: record.subtypes.length,
|
||||
subtypeCount: record.subtypes.length,
|
||||
});
|
||||
|
||||
return reply.code(201).send(record);
|
||||
@@ -160,7 +160,7 @@ export class MaintenanceController {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn('Maintenance record validation failed', {
|
||||
operation: 'maintenance.records.create.validation_error',
|
||||
user_id: userId,
|
||||
userId,
|
||||
errors: error.errors,
|
||||
});
|
||||
return reply.code(400).send({ error: 'Bad Request', details: error.errors });
|
||||
@@ -170,8 +170,8 @@ export class MaintenanceController {
|
||||
const statusCode = (error as any).statusCode;
|
||||
logger.warn('Maintenance record creation failed', {
|
||||
operation: 'maintenance.records.create.error',
|
||||
user_id: userId,
|
||||
status_code: statusCode,
|
||||
userId,
|
||||
statusCode,
|
||||
error: error.message,
|
||||
});
|
||||
return reply.code(statusCode).send({ error: error.message });
|
||||
@@ -179,7 +179,7 @@ export class MaintenanceController {
|
||||
|
||||
logger.error('Failed to create maintenance record', {
|
||||
operation: 'maintenance.records.create.error',
|
||||
user_id: userId,
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -195,8 +195,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance record update requested', {
|
||||
operation: 'maintenance.records.update',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -206,17 +206,17 @@ export class MaintenanceController {
|
||||
if (!record) {
|
||||
logger.warn('Maintenance record not found for update', {
|
||||
operation: 'maintenance.records.update.not_found',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
});
|
||||
return reply.code(404).send({ error: 'Not Found' });
|
||||
}
|
||||
|
||||
logger.info('Maintenance record updated', {
|
||||
operation: 'maintenance.records.update.success',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
vehicle_id: record.vehicle_id,
|
||||
userId,
|
||||
recordId,
|
||||
vehicleId: record.vehicleId,
|
||||
category: record.category,
|
||||
});
|
||||
|
||||
@@ -225,8 +225,8 @@ export class MaintenanceController {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn('Maintenance record update validation failed', {
|
||||
operation: 'maintenance.records.update.validation_error',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
errors: error.errors,
|
||||
});
|
||||
return reply.code(400).send({ error: 'Bad Request', details: error.errors });
|
||||
@@ -236,9 +236,9 @@ export class MaintenanceController {
|
||||
const statusCode = (error as any).statusCode;
|
||||
logger.warn('Maintenance record update failed', {
|
||||
operation: 'maintenance.records.update.error',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
status_code: statusCode,
|
||||
userId,
|
||||
recordId,
|
||||
statusCode,
|
||||
error: error.message,
|
||||
});
|
||||
return reply.code(statusCode).send({ error: error.message });
|
||||
@@ -246,8 +246,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.error('Failed to update maintenance record', {
|
||||
operation: 'maintenance.records.update.error',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -260,8 +260,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance record delete requested', {
|
||||
operation: 'maintenance.records.delete',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -269,16 +269,16 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance record deleted', {
|
||||
operation: 'maintenance.records.delete.success',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
});
|
||||
|
||||
return reply.code(204).send();
|
||||
} catch (error) {
|
||||
logger.error('Failed to delete maintenance record', {
|
||||
operation: 'maintenance.records.delete.error',
|
||||
user_id: userId,
|
||||
record_id: recordId,
|
||||
userId,
|
||||
recordId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -294,8 +294,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance schedules by vehicle requested', {
|
||||
operation: 'maintenance.schedules.by_vehicle',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
userId,
|
||||
vehicleId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -303,17 +303,17 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance schedules by vehicle retrieved', {
|
||||
operation: 'maintenance.schedules.by_vehicle.success',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
schedule_count: schedules.length,
|
||||
userId,
|
||||
vehicleId,
|
||||
scheduleCount: schedules.length,
|
||||
});
|
||||
|
||||
return reply.code(200).send(schedules);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get maintenance schedules by vehicle', {
|
||||
operation: 'maintenance.schedules.by_vehicle.error',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
userId,
|
||||
vehicleId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -325,7 +325,7 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance schedule create requested', {
|
||||
operation: 'maintenance.schedules.create',
|
||||
user_id: userId,
|
||||
userId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -335,11 +335,11 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance schedule created', {
|
||||
operation: 'maintenance.schedules.create.success',
|
||||
user_id: userId,
|
||||
schedule_id: schedule.id,
|
||||
vehicle_id: schedule.vehicle_id,
|
||||
userId,
|
||||
scheduleId: schedule.id,
|
||||
vehicleId: schedule.vehicleId,
|
||||
category: schedule.category,
|
||||
subtype_count: schedule.subtypes.length,
|
||||
subtypeCount: schedule.subtypes.length,
|
||||
});
|
||||
|
||||
return reply.code(201).send(schedule);
|
||||
@@ -347,7 +347,7 @@ export class MaintenanceController {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn('Maintenance schedule validation failed', {
|
||||
operation: 'maintenance.schedules.create.validation_error',
|
||||
user_id: userId,
|
||||
userId,
|
||||
errors: error.errors,
|
||||
});
|
||||
return reply.code(400).send({ error: 'Bad Request', details: error.errors });
|
||||
@@ -357,8 +357,8 @@ export class MaintenanceController {
|
||||
const statusCode = (error as any).statusCode;
|
||||
logger.warn('Maintenance schedule creation failed', {
|
||||
operation: 'maintenance.schedules.create.error',
|
||||
user_id: userId,
|
||||
status_code: statusCode,
|
||||
userId,
|
||||
statusCode,
|
||||
error: error.message,
|
||||
});
|
||||
return reply.code(statusCode).send({ error: error.message });
|
||||
@@ -366,7 +366,7 @@ export class MaintenanceController {
|
||||
|
||||
logger.error('Failed to create maintenance schedule', {
|
||||
operation: 'maintenance.schedules.create.error',
|
||||
user_id: userId,
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -382,8 +382,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance schedule update requested', {
|
||||
operation: 'maintenance.schedules.update',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
userId,
|
||||
scheduleId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -393,17 +393,17 @@ export class MaintenanceController {
|
||||
if (!schedule) {
|
||||
logger.warn('Maintenance schedule not found for update', {
|
||||
operation: 'maintenance.schedules.update.not_found',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
userId,
|
||||
scheduleId,
|
||||
});
|
||||
return reply.code(404).send({ error: 'Not Found' });
|
||||
}
|
||||
|
||||
logger.info('Maintenance schedule updated', {
|
||||
operation: 'maintenance.schedules.update.success',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
vehicle_id: schedule.vehicle_id,
|
||||
userId,
|
||||
scheduleId,
|
||||
vehicleId: schedule.vehicleId,
|
||||
category: schedule.category,
|
||||
});
|
||||
|
||||
@@ -412,8 +412,8 @@ export class MaintenanceController {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.warn('Maintenance schedule update validation failed', {
|
||||
operation: 'maintenance.schedules.update.validation_error',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
userId,
|
||||
scheduleId,
|
||||
errors: error.errors,
|
||||
});
|
||||
return reply.code(400).send({ error: 'Bad Request', details: error.errors });
|
||||
@@ -423,9 +423,9 @@ export class MaintenanceController {
|
||||
const statusCode = (error as any).statusCode;
|
||||
logger.warn('Maintenance schedule update failed', {
|
||||
operation: 'maintenance.schedules.update.error',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
status_code: statusCode,
|
||||
userId,
|
||||
scheduleId,
|
||||
statusCode,
|
||||
error: error.message,
|
||||
});
|
||||
return reply.code(statusCode).send({ error: error.message });
|
||||
@@ -433,8 +433,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.error('Failed to update maintenance schedule', {
|
||||
operation: 'maintenance.schedules.update.error',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
userId,
|
||||
scheduleId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -447,8 +447,8 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance schedule delete requested', {
|
||||
operation: 'maintenance.schedules.delete',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
userId,
|
||||
scheduleId,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -456,16 +456,16 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance schedule deleted', {
|
||||
operation: 'maintenance.schedules.delete.success',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
userId,
|
||||
scheduleId,
|
||||
});
|
||||
|
||||
return reply.code(204).send();
|
||||
} catch (error) {
|
||||
logger.error('Failed to delete maintenance schedule', {
|
||||
operation: 'maintenance.schedules.delete.error',
|
||||
user_id: userId,
|
||||
schedule_id: scheduleId,
|
||||
userId,
|
||||
scheduleId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -482,9 +482,9 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Upcoming maintenance requested', {
|
||||
operation: 'maintenance.upcoming',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
current_mileage: currentMileage,
|
||||
userId,
|
||||
vehicleId,
|
||||
currentMileage,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -492,17 +492,17 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Upcoming maintenance retrieved', {
|
||||
operation: 'maintenance.upcoming.success',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
upcoming_count: upcoming.length,
|
||||
userId,
|
||||
vehicleId,
|
||||
upcomingCount: upcoming.length,
|
||||
});
|
||||
|
||||
return reply.code(200).send(upcoming);
|
||||
} catch (error) {
|
||||
logger.error('Failed to get upcoming maintenance', {
|
||||
operation: 'maintenance.upcoming.error',
|
||||
user_id: userId,
|
||||
vehicle_id: vehicleId,
|
||||
userId,
|
||||
vehicleId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
@@ -515,16 +515,16 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance subtypes requested', {
|
||||
operation: 'maintenance.subtypes',
|
||||
user_id: userId,
|
||||
category: category,
|
||||
userId,
|
||||
category,
|
||||
});
|
||||
|
||||
try {
|
||||
if (!['routine_maintenance', 'repair', 'performance_upgrade'].includes(category)) {
|
||||
logger.warn('Invalid maintenance category', {
|
||||
operation: 'maintenance.subtypes.invalid_category',
|
||||
user_id: userId,
|
||||
category: category,
|
||||
userId,
|
||||
category,
|
||||
});
|
||||
return reply.code(400).send({ error: 'Bad Request', message: 'Invalid category' });
|
||||
}
|
||||
@@ -533,17 +533,17 @@ export class MaintenanceController {
|
||||
|
||||
logger.info('Maintenance subtypes retrieved', {
|
||||
operation: 'maintenance.subtypes.success',
|
||||
user_id: userId,
|
||||
category: category,
|
||||
subtype_count: subtypes.length,
|
||||
userId,
|
||||
category,
|
||||
subtypeCount: subtypes.length,
|
||||
});
|
||||
|
||||
return reply.code(200).send({ category, subtypes: Array.from(subtypes) });
|
||||
} catch (error) {
|
||||
logger.error('Failed to get maintenance subtypes', {
|
||||
operation: 'maintenance.subtypes.error',
|
||||
user_id: userId,
|
||||
category: category,
|
||||
userId,
|
||||
category,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
|
||||
@@ -5,20 +5,61 @@ import type { MaintenanceRecord, MaintenanceSchedule, MaintenanceCategory } from
|
||||
export class MaintenanceRepository {
|
||||
constructor(private readonly db: Pool = pool) {}
|
||||
|
||||
// ========================
|
||||
// Row Mappers
|
||||
// ========================
|
||||
|
||||
private mapMaintenanceRecord(row: any): MaintenanceRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
userId: row.user_id,
|
||||
vehicleId: row.vehicle_id,
|
||||
category: row.category,
|
||||
subtypes: row.subtypes,
|
||||
date: row.date,
|
||||
odometerReading: row.odometer_reading,
|
||||
cost: row.cost,
|
||||
shopName: row.shop_name,
|
||||
notes: row.notes,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at
|
||||
};
|
||||
}
|
||||
|
||||
private mapMaintenanceSchedule(row: any): MaintenanceSchedule {
|
||||
return {
|
||||
id: row.id,
|
||||
userId: row.user_id,
|
||||
vehicleId: row.vehicle_id,
|
||||
category: row.category,
|
||||
subtypes: row.subtypes,
|
||||
intervalMonths: row.interval_months,
|
||||
intervalMiles: row.interval_miles,
|
||||
lastServiceDate: row.last_service_date,
|
||||
lastServiceMileage: row.last_service_mileage,
|
||||
nextDueDate: row.next_due_date,
|
||||
nextDueMileage: row.next_due_mileage,
|
||||
isActive: row.is_active,
|
||||
emailNotifications: row.email_notifications,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at
|
||||
};
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Maintenance Records
|
||||
// ========================
|
||||
|
||||
async insertRecord(record: {
|
||||
id: string;
|
||||
user_id: string;
|
||||
vehicle_id: string;
|
||||
userId: string;
|
||||
vehicleId: string;
|
||||
category: MaintenanceCategory;
|
||||
subtypes: string[];
|
||||
date: string;
|
||||
odometer_reading?: number | null;
|
||||
odometerReading?: number | null;
|
||||
cost?: number | null;
|
||||
shop_name?: string | null;
|
||||
shopName?: string | null;
|
||||
notes?: string | null;
|
||||
}): Promise<MaintenanceRecord> {
|
||||
const res = await this.db.query(
|
||||
@@ -28,18 +69,18 @@ export class MaintenanceRepository {
|
||||
RETURNING *`,
|
||||
[
|
||||
record.id,
|
||||
record.user_id,
|
||||
record.vehicle_id,
|
||||
record.userId,
|
||||
record.vehicleId,
|
||||
record.category,
|
||||
record.subtypes,
|
||||
record.date,
|
||||
record.odometer_reading ?? null,
|
||||
record.odometerReading ?? null,
|
||||
record.cost ?? null,
|
||||
record.shop_name ?? null,
|
||||
record.shopName ?? null,
|
||||
record.notes ?? null,
|
||||
]
|
||||
);
|
||||
return res.rows[0] as MaintenanceRecord;
|
||||
return this.mapMaintenanceRecord(res.rows[0]);
|
||||
}
|
||||
|
||||
async findRecordById(id: string, userId: string): Promise<MaintenanceRecord | null> {
|
||||
@@ -47,7 +88,7 @@ export class MaintenanceRepository {
|
||||
`SELECT * FROM maintenance_records WHERE id = $1 AND user_id = $2`,
|
||||
[id, userId]
|
||||
);
|
||||
return res.rows[0] || null;
|
||||
return res.rows[0] ? this.mapMaintenanceRecord(res.rows[0]) : null;
|
||||
}
|
||||
|
||||
async findRecordsByUserId(
|
||||
@@ -69,7 +110,7 @@ export class MaintenanceRepository {
|
||||
|
||||
const sql = `SELECT * FROM maintenance_records WHERE ${conds.join(' AND ')} ORDER BY date DESC`;
|
||||
const res = await this.db.query(sql, params);
|
||||
return res.rows as MaintenanceRecord[];
|
||||
return res.rows.map(row => this.mapMaintenanceRecord(row));
|
||||
}
|
||||
|
||||
async findRecordsByVehicleId(vehicleId: string, userId: string): Promise<MaintenanceRecord[]> {
|
||||
@@ -77,13 +118,13 @@ export class MaintenanceRepository {
|
||||
`SELECT * FROM maintenance_records WHERE vehicle_id = $1 AND user_id = $2 ORDER BY date DESC`,
|
||||
[vehicleId, userId]
|
||||
);
|
||||
return res.rows as MaintenanceRecord[];
|
||||
return res.rows.map(row => this.mapMaintenanceRecord(row));
|
||||
}
|
||||
|
||||
async updateRecord(
|
||||
id: string,
|
||||
userId: string,
|
||||
patch: Partial<Pick<MaintenanceRecord, 'category' | 'subtypes' | 'date' | 'odometer_reading' | 'cost' | 'shop_name' | 'notes'>>
|
||||
patch: Partial<Pick<MaintenanceRecord, 'category' | 'subtypes' | 'date' | 'odometerReading' | 'cost' | 'shopName' | 'notes'>>
|
||||
): Promise<MaintenanceRecord | null> {
|
||||
const fields: string[] = [];
|
||||
const params: any[] = [];
|
||||
@@ -101,17 +142,17 @@ export class MaintenanceRepository {
|
||||
fields.push(`date = $${i++}`);
|
||||
params.push(patch.date);
|
||||
}
|
||||
if (patch.odometer_reading !== undefined) {
|
||||
if (patch.odometerReading !== undefined) {
|
||||
fields.push(`odometer_reading = $${i++}`);
|
||||
params.push(patch.odometer_reading);
|
||||
params.push(patch.odometerReading);
|
||||
}
|
||||
if (patch.cost !== undefined) {
|
||||
fields.push(`cost = $${i++}`);
|
||||
params.push(patch.cost);
|
||||
}
|
||||
if (patch.shop_name !== undefined) {
|
||||
if (patch.shopName !== undefined) {
|
||||
fields.push(`shop_name = $${i++}`);
|
||||
params.push(patch.shop_name);
|
||||
params.push(patch.shopName);
|
||||
}
|
||||
if (patch.notes !== undefined) {
|
||||
fields.push(`notes = $${i++}`);
|
||||
@@ -123,7 +164,7 @@ export class MaintenanceRepository {
|
||||
params.push(id, userId);
|
||||
const sql = `UPDATE maintenance_records SET ${fields.join(', ')} WHERE id = $${i++} AND user_id = $${i++} RETURNING *`;
|
||||
const res = await this.db.query(sql, params);
|
||||
return res.rows[0] || null;
|
||||
return res.rows[0] ? this.mapMaintenanceRecord(res.rows[0]) : null;
|
||||
}
|
||||
|
||||
async deleteRecord(id: string, userId: string): Promise<void> {
|
||||
@@ -139,40 +180,42 @@ export class MaintenanceRepository {
|
||||
|
||||
async insertSchedule(schedule: {
|
||||
id: string;
|
||||
user_id: string;
|
||||
vehicle_id: string;
|
||||
userId: string;
|
||||
vehicleId: string;
|
||||
category: MaintenanceCategory;
|
||||
subtypes: string[];
|
||||
interval_months?: number | null;
|
||||
interval_miles?: number | null;
|
||||
last_service_date?: string | null;
|
||||
last_service_mileage?: number | null;
|
||||
next_due_date?: string | null;
|
||||
next_due_mileage?: number | null;
|
||||
is_active: boolean;
|
||||
intervalMonths?: number | null;
|
||||
intervalMiles?: number | null;
|
||||
lastServiceDate?: string | null;
|
||||
lastServiceMileage?: number | null;
|
||||
nextDueDate?: string | null;
|
||||
nextDueMileage?: number | null;
|
||||
isActive: boolean;
|
||||
emailNotifications?: boolean;
|
||||
}): Promise<MaintenanceSchedule> {
|
||||
const res = await this.db.query(
|
||||
`INSERT INTO maintenance_schedules (
|
||||
id, user_id, vehicle_id, category, subtypes, interval_months, interval_miles,
|
||||
last_service_date, last_service_mileage, next_due_date, next_due_mileage, is_active
|
||||
) VALUES ($1, $2, $3, $4, $5::text[], $6, $7, $8, $9, $10, $11, $12)
|
||||
last_service_date, last_service_mileage, next_due_date, next_due_mileage, is_active, email_notifications
|
||||
) VALUES ($1, $2, $3, $4, $5::text[], $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
RETURNING *`,
|
||||
[
|
||||
schedule.id,
|
||||
schedule.user_id,
|
||||
schedule.vehicle_id,
|
||||
schedule.userId,
|
||||
schedule.vehicleId,
|
||||
schedule.category,
|
||||
schedule.subtypes,
|
||||
schedule.interval_months ?? null,
|
||||
schedule.interval_miles ?? null,
|
||||
schedule.last_service_date ?? null,
|
||||
schedule.last_service_mileage ?? null,
|
||||
schedule.next_due_date ?? null,
|
||||
schedule.next_due_mileage ?? null,
|
||||
schedule.is_active,
|
||||
schedule.intervalMonths ?? null,
|
||||
schedule.intervalMiles ?? null,
|
||||
schedule.lastServiceDate ?? null,
|
||||
schedule.lastServiceMileage ?? null,
|
||||
schedule.nextDueDate ?? null,
|
||||
schedule.nextDueMileage ?? null,
|
||||
schedule.isActive,
|
||||
schedule.emailNotifications ?? false,
|
||||
]
|
||||
);
|
||||
return res.rows[0] as MaintenanceSchedule;
|
||||
return this.mapMaintenanceSchedule(res.rows[0]);
|
||||
}
|
||||
|
||||
async findScheduleById(id: string, userId: string): Promise<MaintenanceSchedule | null> {
|
||||
@@ -180,7 +223,7 @@ export class MaintenanceRepository {
|
||||
`SELECT * FROM maintenance_schedules WHERE id = $1 AND user_id = $2`,
|
||||
[id, userId]
|
||||
);
|
||||
return res.rows[0] || null;
|
||||
return res.rows[0] ? this.mapMaintenanceSchedule(res.rows[0]) : null;
|
||||
}
|
||||
|
||||
async findSchedulesByVehicleId(vehicleId: string, userId: string): Promise<MaintenanceSchedule[]> {
|
||||
@@ -188,7 +231,7 @@ export class MaintenanceRepository {
|
||||
`SELECT * FROM maintenance_schedules WHERE vehicle_id = $1 AND user_id = $2 ORDER BY created_at DESC`,
|
||||
[vehicleId, userId]
|
||||
);
|
||||
return res.rows as MaintenanceSchedule[];
|
||||
return res.rows.map(row => this.mapMaintenanceSchedule(row));
|
||||
}
|
||||
|
||||
async findActiveSchedulesByVehicleId(vehicleId: string, userId: string): Promise<MaintenanceSchedule[]> {
|
||||
@@ -196,13 +239,13 @@ export class MaintenanceRepository {
|
||||
`SELECT * FROM maintenance_schedules WHERE vehicle_id = $1 AND user_id = $2 AND is_active = true ORDER BY created_at DESC`,
|
||||
[vehicleId, userId]
|
||||
);
|
||||
return res.rows as MaintenanceSchedule[];
|
||||
return res.rows.map(row => this.mapMaintenanceSchedule(row));
|
||||
}
|
||||
|
||||
async updateSchedule(
|
||||
id: string,
|
||||
userId: string,
|
||||
patch: Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'interval_months' | 'interval_miles' | 'last_service_date' | 'last_service_mileage' | 'next_due_date' | 'next_due_mileage' | 'is_active'>>
|
||||
patch: Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'intervalMonths' | 'intervalMiles' | 'lastServiceDate' | 'lastServiceMileage' | 'nextDueDate' | 'nextDueMileage' | 'isActive' | 'emailNotifications'>>
|
||||
): Promise<MaintenanceSchedule | null> {
|
||||
const fields: string[] = [];
|
||||
const params: any[] = [];
|
||||
@@ -216,33 +259,37 @@ export class MaintenanceRepository {
|
||||
fields.push(`subtypes = $${i++}::text[]`);
|
||||
params.push(patch.subtypes);
|
||||
}
|
||||
if (patch.interval_months !== undefined) {
|
||||
if (patch.intervalMonths !== undefined) {
|
||||
fields.push(`interval_months = $${i++}`);
|
||||
params.push(patch.interval_months);
|
||||
params.push(patch.intervalMonths);
|
||||
}
|
||||
if (patch.interval_miles !== undefined) {
|
||||
if (patch.intervalMiles !== undefined) {
|
||||
fields.push(`interval_miles = $${i++}`);
|
||||
params.push(patch.interval_miles);
|
||||
params.push(patch.intervalMiles);
|
||||
}
|
||||
if (patch.last_service_date !== undefined) {
|
||||
if (patch.lastServiceDate !== undefined) {
|
||||
fields.push(`last_service_date = $${i++}`);
|
||||
params.push(patch.last_service_date);
|
||||
params.push(patch.lastServiceDate);
|
||||
}
|
||||
if (patch.last_service_mileage !== undefined) {
|
||||
if (patch.lastServiceMileage !== undefined) {
|
||||
fields.push(`last_service_mileage = $${i++}`);
|
||||
params.push(patch.last_service_mileage);
|
||||
params.push(patch.lastServiceMileage);
|
||||
}
|
||||
if (patch.next_due_date !== undefined) {
|
||||
if (patch.nextDueDate !== undefined) {
|
||||
fields.push(`next_due_date = $${i++}`);
|
||||
params.push(patch.next_due_date);
|
||||
params.push(patch.nextDueDate);
|
||||
}
|
||||
if (patch.next_due_mileage !== undefined) {
|
||||
if (patch.nextDueMileage !== undefined) {
|
||||
fields.push(`next_due_mileage = $${i++}`);
|
||||
params.push(patch.next_due_mileage);
|
||||
params.push(patch.nextDueMileage);
|
||||
}
|
||||
if (patch.is_active !== undefined) {
|
||||
if (patch.isActive !== undefined) {
|
||||
fields.push(`is_active = $${i++}`);
|
||||
params.push(patch.is_active);
|
||||
params.push(patch.isActive);
|
||||
}
|
||||
if (patch.emailNotifications !== undefined) {
|
||||
fields.push(`email_notifications = $${i++}`);
|
||||
params.push(patch.emailNotifications);
|
||||
}
|
||||
|
||||
if (!fields.length) return this.findScheduleById(id, userId);
|
||||
@@ -250,7 +297,7 @@ export class MaintenanceRepository {
|
||||
params.push(id, userId);
|
||||
const sql = `UPDATE maintenance_schedules SET ${fields.join(', ')} WHERE id = $${i++} AND user_id = $${i++} RETURNING *`;
|
||||
const res = await this.db.query(sql, params);
|
||||
return res.rows[0] || null;
|
||||
return res.rows[0] ? this.mapMaintenanceSchedule(res.rows[0]) : null;
|
||||
}
|
||||
|
||||
async deleteSchedule(id: string, userId: string): Promise<void> {
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MaintenanceService {
|
||||
private readonly repo = new MaintenanceRepository(pool);
|
||||
|
||||
async createRecord(userId: string, body: CreateMaintenanceRecordRequest): Promise<MaintenanceRecord> {
|
||||
await this.assertVehicleOwnership(userId, body.vehicle_id);
|
||||
await this.assertVehicleOwnership(userId, body.vehicleId);
|
||||
|
||||
if (!validateSubtypes(body.category, body.subtypes)) {
|
||||
const err: any = new Error('Invalid subtypes for selected category');
|
||||
@@ -29,14 +29,14 @@ export class MaintenanceService {
|
||||
const id = randomUUID();
|
||||
return this.repo.insertRecord({
|
||||
id,
|
||||
user_id: userId,
|
||||
vehicle_id: body.vehicle_id,
|
||||
userId,
|
||||
vehicleId: body.vehicleId,
|
||||
category: body.category,
|
||||
subtypes: body.subtypes,
|
||||
date: body.date,
|
||||
odometer_reading: body.odometer_reading,
|
||||
odometerReading: body.odometerReading,
|
||||
cost: body.cost,
|
||||
shop_name: body.shop_name,
|
||||
shopName: body.shopName,
|
||||
notes: body.notes,
|
||||
});
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export class MaintenanceService {
|
||||
// Convert nulls to undefined for repository compatibility
|
||||
const cleanPatch = Object.fromEntries(
|
||||
Object.entries(patch).map(([k, v]) => [k, v === null ? undefined : v])
|
||||
) as Partial<Pick<MaintenanceRecord, 'date' | 'notes' | 'category' | 'subtypes' | 'odometer_reading' | 'cost' | 'shop_name'>>;
|
||||
) as Partial<Pick<MaintenanceRecord, 'date' | 'notes' | 'category' | 'subtypes' | 'odometerReading' | 'cost' | 'shopName'>>;
|
||||
|
||||
const updated = await this.repo.updateRecord(id, userId, cleanPatch);
|
||||
if (!updated) return null;
|
||||
@@ -86,7 +86,7 @@ export class MaintenanceService {
|
||||
}
|
||||
|
||||
async createSchedule(userId: string, body: CreateScheduleRequest): Promise<MaintenanceSchedule> {
|
||||
await this.assertVehicleOwnership(userId, body.vehicle_id);
|
||||
await this.assertVehicleOwnership(userId, body.vehicleId);
|
||||
|
||||
if (!validateSubtypes(body.category, body.subtypes)) {
|
||||
const err: any = new Error('Invalid subtypes for selected category');
|
||||
@@ -94,7 +94,7 @@ export class MaintenanceService {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!body.interval_months && !body.interval_miles) {
|
||||
if (!body.intervalMonths && !body.intervalMiles) {
|
||||
const err: any = new Error('At least one interval (months or miles) is required');
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
@@ -103,13 +103,14 @@ export class MaintenanceService {
|
||||
const id = randomUUID();
|
||||
return this.repo.insertSchedule({
|
||||
id,
|
||||
user_id: userId,
|
||||
vehicle_id: body.vehicle_id,
|
||||
userId,
|
||||
vehicleId: body.vehicleId,
|
||||
category: body.category,
|
||||
subtypes: body.subtypes,
|
||||
interval_months: body.interval_months,
|
||||
interval_miles: body.interval_miles,
|
||||
is_active: true,
|
||||
intervalMonths: body.intervalMonths,
|
||||
intervalMiles: body.intervalMiles,
|
||||
isActive: true,
|
||||
emailNotifications: body.emailNotifications ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,25 +144,25 @@ export class MaintenanceService {
|
||||
}
|
||||
|
||||
const needsRecalculation =
|
||||
patch.interval_months !== undefined ||
|
||||
patch.interval_miles !== undefined;
|
||||
patch.intervalMonths !== undefined ||
|
||||
patch.intervalMiles !== undefined;
|
||||
|
||||
let patchWithRecalc: any = { ...patch };
|
||||
const patchWithRecalc: any = { ...patch };
|
||||
if (needsRecalculation) {
|
||||
const nextDue = this.calculateNextDue({
|
||||
last_service_date: existing.last_service_date,
|
||||
last_service_mileage: existing.last_service_mileage,
|
||||
interval_months: patch.interval_months ?? existing.interval_months,
|
||||
interval_miles: patch.interval_miles ?? existing.interval_miles,
|
||||
lastServiceDate: existing.lastServiceDate,
|
||||
lastServiceMileage: existing.lastServiceMileage,
|
||||
intervalMonths: patch.intervalMonths ?? existing.intervalMonths,
|
||||
intervalMiles: patch.intervalMiles ?? existing.intervalMiles,
|
||||
});
|
||||
patchWithRecalc.next_due_date = nextDue.next_due_date ?? undefined;
|
||||
patchWithRecalc.next_due_mileage = nextDue.next_due_mileage ?? undefined;
|
||||
patchWithRecalc.nextDueDate = nextDue.nextDueDate ?? undefined;
|
||||
patchWithRecalc.nextDueMileage = nextDue.nextDueMileage ?? undefined;
|
||||
}
|
||||
|
||||
// Convert nulls to undefined for repository compatibility
|
||||
const cleanPatch = Object.fromEntries(
|
||||
Object.entries(patchWithRecalc).map(([k, v]) => [k, v === null ? undefined : v])
|
||||
) as Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'interval_months' | 'interval_miles' | 'is_active' | 'last_service_date' | 'last_service_mileage' | 'next_due_date' | 'next_due_mileage'>>;
|
||||
) as Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'intervalMonths' | 'intervalMiles' | 'isActive' | 'emailNotifications' | 'lastServiceDate' | 'lastServiceMileage' | 'nextDueDate' | 'nextDueMileage'>>;
|
||||
|
||||
const updated = await this.repo.updateSchedule(id, userId, cleanPatch);
|
||||
if (!updated) return null;
|
||||
@@ -178,7 +179,7 @@ export class MaintenanceService {
|
||||
|
||||
return schedules
|
||||
.map(s => this.toScheduleResponse(s, today, currentMileage))
|
||||
.filter(s => s.is_due_soon || s.is_overdue);
|
||||
.filter(s => s.isDueSoon || s.isOverdue);
|
||||
}
|
||||
|
||||
private async assertVehicleOwnership(userId: string, vehicleId: string) {
|
||||
@@ -191,66 +192,66 @@ export class MaintenanceService {
|
||||
}
|
||||
|
||||
private calculateNextDue(schedule: {
|
||||
last_service_date?: string | null;
|
||||
last_service_mileage?: number | null;
|
||||
interval_months?: number | null;
|
||||
interval_miles?: number | null;
|
||||
}): { next_due_date: string | null; next_due_mileage: number | null } {
|
||||
let next_due_date: string | null = null;
|
||||
let next_due_mileage: number | null = null;
|
||||
lastServiceDate?: string | null;
|
||||
lastServiceMileage?: number | null;
|
||||
intervalMonths?: number | null;
|
||||
intervalMiles?: number | null;
|
||||
}): { nextDueDate: string | null; nextDueMileage: number | null } {
|
||||
let nextDueDate: string | null = null;
|
||||
let nextDueMileage: number | null = null;
|
||||
|
||||
if (schedule.last_service_date && schedule.interval_months) {
|
||||
const lastDate = new Date(schedule.last_service_date);
|
||||
if (schedule.lastServiceDate && schedule.intervalMonths) {
|
||||
const lastDate = new Date(schedule.lastServiceDate);
|
||||
const nextDate = new Date(lastDate);
|
||||
nextDate.setMonth(nextDate.getMonth() + schedule.interval_months);
|
||||
next_due_date = nextDate.toISOString().split('T')[0];
|
||||
nextDate.setMonth(nextDate.getMonth() + schedule.intervalMonths);
|
||||
nextDueDate = nextDate.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
if (schedule.last_service_mileage !== null && schedule.last_service_mileage !== undefined && schedule.interval_miles) {
|
||||
next_due_mileage = schedule.last_service_mileage + schedule.interval_miles;
|
||||
if (schedule.lastServiceMileage !== null && schedule.lastServiceMileage !== undefined && schedule.intervalMiles) {
|
||||
nextDueMileage = schedule.lastServiceMileage + schedule.intervalMiles;
|
||||
}
|
||||
|
||||
return { next_due_date, next_due_mileage };
|
||||
return { nextDueDate, nextDueMileage };
|
||||
}
|
||||
|
||||
private toRecordResponse(record: MaintenanceRecord): MaintenanceRecordResponse {
|
||||
return {
|
||||
...record,
|
||||
subtype_count: record.subtypes.length,
|
||||
subtypeCount: record.subtypes.length,
|
||||
};
|
||||
}
|
||||
|
||||
private toScheduleResponse(schedule: MaintenanceSchedule, today?: string, currentMileage?: number): MaintenanceScheduleResponse {
|
||||
const todayStr = today || new Date().toISOString().split('T')[0];
|
||||
let is_due_soon = false;
|
||||
let is_overdue = false;
|
||||
let isDueSoon = false;
|
||||
let isOverdue = false;
|
||||
|
||||
if (schedule.next_due_date) {
|
||||
const nextDue = new Date(schedule.next_due_date);
|
||||
if (schedule.nextDueDate) {
|
||||
const nextDue = new Date(schedule.nextDueDate);
|
||||
const todayDate = new Date(todayStr);
|
||||
const daysUntilDue = Math.floor((nextDue.getTime() - todayDate.getTime()) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (daysUntilDue < 0) {
|
||||
is_overdue = true;
|
||||
isOverdue = true;
|
||||
} else if (daysUntilDue <= 30) {
|
||||
is_due_soon = true;
|
||||
isDueSoon = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMileage !== undefined && schedule.next_due_mileage !== null && schedule.next_due_mileage !== undefined) {
|
||||
const milesUntilDue = schedule.next_due_mileage - currentMileage;
|
||||
if (currentMileage !== undefined && schedule.nextDueMileage !== null && schedule.nextDueMileage !== undefined) {
|
||||
const milesUntilDue = schedule.nextDueMileage - currentMileage;
|
||||
if (milesUntilDue < 0) {
|
||||
is_overdue = true;
|
||||
isOverdue = true;
|
||||
} else if (milesUntilDue <= 500) {
|
||||
is_due_soon = true;
|
||||
isDueSoon = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...schedule,
|
||||
subtype_count: schedule.subtypes.length,
|
||||
is_due_soon,
|
||||
is_overdue,
|
||||
subtypeCount: schedule.subtypes.length,
|
||||
isDueSoon,
|
||||
isOverdue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,50 +55,51 @@ export const PERFORMANCE_UPGRADE_SUBTYPES = [
|
||||
'Exterior'
|
||||
] as const;
|
||||
|
||||
// Database record types
|
||||
// Database record types (camelCase for TypeScript)
|
||||
export interface MaintenanceRecord {
|
||||
id: string;
|
||||
user_id: string;
|
||||
vehicle_id: string;
|
||||
userId: string;
|
||||
vehicleId: string;
|
||||
category: MaintenanceCategory;
|
||||
subtypes: string[];
|
||||
date: string;
|
||||
odometer_reading?: number;
|
||||
odometerReading?: number;
|
||||
cost?: number;
|
||||
shop_name?: string;
|
||||
shopName?: string;
|
||||
notes?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface MaintenanceSchedule {
|
||||
id: string;
|
||||
user_id: string;
|
||||
vehicle_id: string;
|
||||
userId: string;
|
||||
vehicleId: string;
|
||||
category: MaintenanceCategory;
|
||||
subtypes: string[];
|
||||
interval_months?: number;
|
||||
interval_miles?: number;
|
||||
last_service_date?: string;
|
||||
last_service_mileage?: number;
|
||||
next_due_date?: string;
|
||||
next_due_mileage?: number;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
intervalMonths?: number;
|
||||
intervalMiles?: number;
|
||||
lastServiceDate?: string;
|
||||
lastServiceMileage?: number;
|
||||
nextDueDate?: string;
|
||||
nextDueMileage?: number;
|
||||
isActive: boolean;
|
||||
emailNotifications: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// Zod schemas for validation
|
||||
// Zod schemas for validation (camelCase for API)
|
||||
export const MaintenanceCategorySchema = z.enum(['routine_maintenance', 'repair', 'performance_upgrade']);
|
||||
|
||||
export const CreateMaintenanceRecordSchema = z.object({
|
||||
vehicle_id: z.string().uuid(),
|
||||
vehicleId: z.string().uuid(),
|
||||
category: MaintenanceCategorySchema,
|
||||
subtypes: z.array(z.string()).min(1),
|
||||
date: z.string(),
|
||||
odometer_reading: z.number().int().positive().optional(),
|
||||
odometerReading: z.number().int().positive().optional(),
|
||||
cost: z.number().positive().optional(),
|
||||
shop_name: z.string().max(200).optional(),
|
||||
shopName: z.string().max(200).optional(),
|
||||
notes: z.string().max(10000).optional(),
|
||||
});
|
||||
export type CreateMaintenanceRecordRequest = z.infer<typeof CreateMaintenanceRecordSchema>;
|
||||
@@ -107,40 +108,42 @@ export const UpdateMaintenanceRecordSchema = z.object({
|
||||
category: MaintenanceCategorySchema.optional(),
|
||||
subtypes: z.array(z.string()).min(1).optional(),
|
||||
date: z.string().optional(),
|
||||
odometer_reading: z.number().int().positive().nullable().optional(),
|
||||
odometerReading: z.number().int().positive().nullable().optional(),
|
||||
cost: z.number().positive().nullable().optional(),
|
||||
shop_name: z.string().max(200).nullable().optional(),
|
||||
shopName: z.string().max(200).nullable().optional(),
|
||||
notes: z.string().max(10000).nullable().optional(),
|
||||
});
|
||||
export type UpdateMaintenanceRecordRequest = z.infer<typeof UpdateMaintenanceRecordSchema>;
|
||||
|
||||
export const CreateScheduleSchema = z.object({
|
||||
vehicle_id: z.string().uuid(),
|
||||
vehicleId: z.string().uuid(),
|
||||
category: MaintenanceCategorySchema,
|
||||
subtypes: z.array(z.string()).min(1),
|
||||
interval_months: z.number().int().positive().optional(),
|
||||
interval_miles: z.number().int().positive().optional(),
|
||||
intervalMonths: z.number().int().positive().optional(),
|
||||
intervalMiles: z.number().int().positive().optional(),
|
||||
emailNotifications: z.boolean().optional(),
|
||||
});
|
||||
export type CreateScheduleRequest = z.infer<typeof CreateScheduleSchema>;
|
||||
|
||||
export const UpdateScheduleSchema = z.object({
|
||||
category: MaintenanceCategorySchema.optional(),
|
||||
subtypes: z.array(z.string()).min(1).optional(),
|
||||
interval_months: z.number().int().positive().nullable().optional(),
|
||||
interval_miles: z.number().int().positive().nullable().optional(),
|
||||
is_active: z.boolean().optional(),
|
||||
intervalMonths: z.number().int().positive().nullable().optional(),
|
||||
intervalMiles: z.number().int().positive().nullable().optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
emailNotifications: z.boolean().optional(),
|
||||
});
|
||||
export type UpdateScheduleRequest = z.infer<typeof UpdateScheduleSchema>;
|
||||
|
||||
// Response types
|
||||
export interface MaintenanceRecordResponse extends MaintenanceRecord {
|
||||
subtype_count: number;
|
||||
subtypeCount: number;
|
||||
}
|
||||
|
||||
export interface MaintenanceScheduleResponse extends MaintenanceSchedule {
|
||||
subtype_count: number;
|
||||
is_due_soon?: boolean;
|
||||
is_overdue?: boolean;
|
||||
subtypeCount: number;
|
||||
isDueSoon?: boolean;
|
||||
isOverdue?: boolean;
|
||||
}
|
||||
|
||||
// Validation helpers
|
||||
|
||||
Reference in New Issue
Block a user