feat: Scheduled Maintenance feature complete

This commit is contained in:
Eric Gullickson
2025-12-22 14:12:33 -06:00
parent c017b8816f
commit 91b4534e76
44 changed files with 2740 additions and 117 deletions

View File

@@ -41,6 +41,11 @@ export class MaintenanceRepository {
nextDueMileage: row.next_due_mileage,
isActive: row.is_active,
emailNotifications: row.email_notifications,
scheduleType: row.schedule_type,
fixedDueDate: row.fixed_due_date,
reminderDays1: row.reminder_days_1,
reminderDays2: row.reminder_days_2,
reminderDays3: row.reminder_days_3,
createdAt: row.created_at,
updatedAt: row.updated_at
};
@@ -192,12 +197,18 @@ export class MaintenanceRepository {
nextDueMileage?: number | null;
isActive: boolean;
emailNotifications?: boolean;
scheduleType?: string;
fixedDueDate?: string | null;
reminderDays1?: number | null;
reminderDays2?: number | null;
reminderDays3?: number | null;
}): 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, email_notifications
) VALUES ($1, $2, $3, $4, $5::text[], $6, $7, $8, $9, $10, $11, $12, $13)
last_service_date, last_service_mileage, next_due_date, next_due_mileage, is_active, email_notifications,
schedule_type, fixed_due_date, reminder_days_1, reminder_days_2, reminder_days_3
) VALUES ($1, $2, $3, $4, $5::text[], $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
RETURNING *`,
[
schedule.id,
@@ -213,6 +224,11 @@ export class MaintenanceRepository {
schedule.nextDueMileage ?? null,
schedule.isActive,
schedule.emailNotifications ?? false,
schedule.scheduleType ?? 'interval',
schedule.fixedDueDate ?? null,
schedule.reminderDays1 ?? null,
schedule.reminderDays2 ?? null,
schedule.reminderDays3 ?? null,
]
);
return this.mapMaintenanceSchedule(res.rows[0]);
@@ -245,7 +261,7 @@ export class MaintenanceRepository {
async updateSchedule(
id: string,
userId: string,
patch: Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'intervalMonths' | 'intervalMiles' | 'lastServiceDate' | 'lastServiceMileage' | 'nextDueDate' | 'nextDueMileage' | 'isActive' | 'emailNotifications'>>
patch: Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'intervalMonths' | 'intervalMiles' | 'lastServiceDate' | 'lastServiceMileage' | 'nextDueDate' | 'nextDueMileage' | 'isActive' | 'emailNotifications' | 'scheduleType' | 'fixedDueDate' | 'reminderDays1' | 'reminderDays2' | 'reminderDays3'>>
): Promise<MaintenanceSchedule | null> {
const fields: string[] = [];
const params: any[] = [];
@@ -291,6 +307,26 @@ export class MaintenanceRepository {
fields.push(`email_notifications = $${i++}`);
params.push(patch.emailNotifications);
}
if (patch.scheduleType !== undefined) {
fields.push(`schedule_type = $${i++}`);
params.push(patch.scheduleType);
}
if (patch.fixedDueDate !== undefined) {
fields.push(`fixed_due_date = $${i++}`);
params.push(patch.fixedDueDate);
}
if (patch.reminderDays1 !== undefined) {
fields.push(`reminder_days_1 = $${i++}`);
params.push(patch.reminderDays1);
}
if (patch.reminderDays2 !== undefined) {
fields.push(`reminder_days_2 = $${i++}`);
params.push(patch.reminderDays2);
}
if (patch.reminderDays3 !== undefined) {
fields.push(`reminder_days_3 = $${i++}`);
params.push(patch.reminderDays3);
}
if (!fields.length) return this.findScheduleById(id, userId);
@@ -306,4 +342,46 @@ export class MaintenanceRepository {
[id, userId]
);
}
async findMatchingSchedules(
userId: string,
vehicleId: string,
category: MaintenanceCategory,
subtypes: string[]
): Promise<MaintenanceSchedule[]> {
const result = await this.db.query(
`SELECT * FROM maintenance_schedules
WHERE user_id = $1
AND vehicle_id = $2
AND category = $3
AND is_active = true
AND schedule_type = 'time_since_last'
AND subtypes && $4::text[]
ORDER BY created_at DESC`,
[userId, vehicleId, category, subtypes]
);
return result.rows.map(row => this.mapMaintenanceSchedule(row));
}
async updateScheduleLastService(
id: string,
userId: string,
lastServiceDate: string,
lastServiceMileage?: number | null,
nextDueDate?: string | null,
nextDueMileage?: number | null
): Promise<MaintenanceSchedule | null> {
const result = await this.db.query(
`UPDATE maintenance_schedules SET
last_service_date = $1,
last_service_mileage = $2,
next_due_date = $3,
next_due_mileage = $4,
updated_at = CURRENT_TIMESTAMP
WHERE id = $5 AND user_id = $6
RETURNING *`,
[lastServiceDate, lastServiceMileage, nextDueDate, nextDueMileage, id, userId]
);
return result.rows[0] ? this.mapMaintenanceSchedule(result.rows[0]) : null;
}
}