import { logger } from "../utils/logger"; import { query } from "../database/db"; // 환경 변수로 데이터 소스 선택 const DATA_SOURCE = process.env.MAINTENANCE_DATA_SOURCE || "memory"; export interface MaintenanceSchedule { id: string; vehicleNumber: string; vehicleType: string; maintenanceType: "정기점검" | "수리" | "타이어교체" | "오일교환" | "기타"; scheduledDate: string; status: "scheduled" | "in_progress" | "completed" | "overdue"; notes?: string; estimatedCost?: number; actualCost?: number; createdAt: string; updatedAt: string; startedAt?: string; completedAt?: string; mechanicName?: string; location?: string; } // 메모리 목 데이터 const mockSchedules: MaintenanceSchedule[] = [ { id: "maint-1", vehicleNumber: "서울12가3456", vehicleType: "1톤 트럭", maintenanceType: "정기점검", scheduledDate: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(), status: "scheduled", notes: "6개월 정기점검", estimatedCost: 300000, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), location: "본사 정비소", }, { id: "maint-2", vehicleNumber: "경기34나5678", vehicleType: "2.5톤 트럭", maintenanceType: "오일교환", scheduledDate: new Date(Date.now() + 1 * 24 * 60 * 60 * 1000).toISOString(), status: "scheduled", estimatedCost: 150000, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), location: "본사 정비소", }, { id: "maint-3", vehicleNumber: "인천56다7890", vehicleType: "라보", maintenanceType: "타이어교체", scheduledDate: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), status: "overdue", notes: "긴급", estimatedCost: 400000, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), location: "외부 정비소", }, { id: "maint-4", vehicleNumber: "부산78라1234", vehicleType: "1톤 트럭", maintenanceType: "수리", scheduledDate: new Date().toISOString(), status: "in_progress", notes: "엔진 점검 중", estimatedCost: 800000, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), startedAt: new Date().toISOString(), location: "본사 정비소", }, ]; /** * 정비 일정 관리 서비스 (Memory/DB 하이브리드) */ export class MaintenanceService { private static instance: MaintenanceService; private constructor() { logger.info(`🔧 정비 일정 데이터 소스: ${DATA_SOURCE.toUpperCase()}`); } public static getInstance(): MaintenanceService { if (!MaintenanceService.instance) { MaintenanceService.instance = new MaintenanceService(); } return MaintenanceService.instance; } public async getAllSchedules(filter?: { status?: string; vehicleNumber?: string; }): Promise { try { const schedules = DATA_SOURCE === "database" ? await this.loadSchedulesFromDB(filter) : this.loadSchedulesFromMemory(filter); // 자동으로 overdue 상태 업데이트 const now = new Date(); schedules.forEach((s) => { if (s.status === "scheduled" && new Date(s.scheduledDate) < now) { s.status = "overdue"; } }); // 정렬: 지연 > 진행중 > 예정 > 완료 schedules.sort((a, b) => { const statusOrder = { overdue: 0, in_progress: 1, scheduled: 2, completed: 3 }; if (a.status !== b.status) { return statusOrder[a.status] - statusOrder[b.status]; } return new Date(a.scheduledDate).getTime() - new Date(b.scheduledDate).getTime(); }); return schedules; } catch (error) { logger.error("❌ 정비 일정 조회 오류:", error); throw error; } } public async updateScheduleStatus( id: string, status: MaintenanceSchedule["status"] ): Promise { try { if (DATA_SOURCE === "database") { return await this.updateScheduleStatusDB(id, status); } else { return this.updateScheduleStatusMemory(id, status); } } catch (error) { logger.error("❌ 정비 상태 업데이트 오류:", error); throw error; } } // ==================== DATABASE 메서드 ==================== private async loadSchedulesFromDB(filter?: { status?: string; vehicleNumber?: string; }): Promise { let sql = ` SELECT id, vehicle_number as "vehicleNumber", vehicle_type as "vehicleType", maintenance_type as "maintenanceType", scheduled_date as "scheduledDate", status, notes, estimated_cost as "estimatedCost", actual_cost as "actualCost", created_at as "createdAt", updated_at as "updatedAt", started_at as "startedAt", completed_at as "completedAt", mechanic_name as "mechanicName", location FROM maintenance_schedules WHERE 1=1 `; const params: any[] = []; let paramIndex = 1; if (filter?.status) { sql += ` AND status = $${paramIndex++}`; params.push(filter.status); } if (filter?.vehicleNumber) { sql += ` AND vehicle_number = $${paramIndex++}`; params.push(filter.vehicleNumber); } const rows = await query(sql, params); return rows.map((row: any) => ({ ...row, scheduledDate: new Date(row.scheduledDate).toISOString(), createdAt: new Date(row.createdAt).toISOString(), updatedAt: new Date(row.updatedAt).toISOString(), startedAt: row.startedAt ? new Date(row.startedAt).toISOString() : undefined, completedAt: row.completedAt ? new Date(row.completedAt).toISOString() : undefined, })); } private async updateScheduleStatusDB( id: string, status: MaintenanceSchedule["status"] ): Promise { let additionalSet = ""; if (status === "in_progress") { additionalSet = ", started_at = NOW()"; } else if (status === "completed") { additionalSet = ", completed_at = NOW()"; } const rows = await query( `UPDATE maintenance_schedules SET status = $1, updated_at = NOW() ${additionalSet} WHERE id = $2 RETURNING id, vehicle_number as "vehicleNumber", vehicle_type as "vehicleType", maintenance_type as "maintenanceType", scheduled_date as "scheduledDate", status, notes, estimated_cost as "estimatedCost", created_at as "createdAt", updated_at as "updatedAt", started_at as "startedAt", completed_at as "completedAt", mechanic_name as "mechanicName", location`, [status, id] ); if (rows.length === 0) { throw new Error(`정비 일정을 찾을 수 없습니다: ${id}`); } const row = rows[0]; return { ...row, scheduledDate: new Date(row.scheduledDate).toISOString(), createdAt: new Date(row.createdAt).toISOString(), updatedAt: new Date(row.updatedAt).toISOString(), startedAt: row.startedAt ? new Date(row.startedAt).toISOString() : undefined, completedAt: row.completedAt ? new Date(row.completedAt).toISOString() : undefined, }; } // ==================== MEMORY 메서드 ==================== private loadSchedulesFromMemory(filter?: { status?: string; vehicleNumber?: string; }): MaintenanceSchedule[] { let schedules = [...mockSchedules]; if (filter?.status) { schedules = schedules.filter((s) => s.status === filter.status); } if (filter?.vehicleNumber) { schedules = schedules.filter((s) => s.vehicleNumber === filter.vehicleNumber); } return schedules; } private updateScheduleStatusMemory( id: string, status: MaintenanceSchedule["status"] ): MaintenanceSchedule { const schedule = mockSchedules.find((s) => s.id === id); if (!schedule) { throw new Error(`정비 일정을 찾을 수 없습니다: ${id}`); } schedule.status = status; schedule.updatedAt = new Date().toISOString(); if (status === "in_progress") { schedule.startedAt = new Date().toISOString(); } else if (status === "completed") { schedule.completedAt = new Date().toISOString(); } return schedule; } }