ERP-node/backend-node/src/services/maintenanceService.ts

268 lines
8.2 KiB
TypeScript

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<MaintenanceSchedule[]> {
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<MaintenanceSchedule> {
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<MaintenanceSchedule[]> {
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<MaintenanceSchedule> {
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;
}
}