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

390 lines
11 KiB
TypeScript

// 배치 실행 로그 서비스
// 작성일: 2024-12-24
import { query, queryOne } from "../database/db";
import {
BatchExecutionLog,
CreateBatchExecutionLogRequest,
UpdateBatchExecutionLogRequest,
BatchExecutionLogFilter,
BatchExecutionLogWithConfig,
} from "../types/batchExecutionLogTypes";
import { ApiResponse } from "../types/batchTypes";
export class BatchExecutionLogService {
/**
* 배치 실행 로그 목록 조회 (회사별)
*/
static async getExecutionLogs(
filter: BatchExecutionLogFilter = {},
userCompanyCode?: string
): Promise<ApiResponse<BatchExecutionLogWithConfig[]>> {
try {
const {
batch_config_id,
execution_status,
start_date,
end_date,
page = 1,
limit = 50,
} = filter;
const skip = (page - 1) * limit;
const take = limit;
// WHERE 조건 구성
const whereConditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
whereConditions.push(`bc.company_code = $${paramIndex++}`);
params.push(userCompanyCode);
}
if (batch_config_id) {
whereConditions.push(`bel.batch_config_id = $${paramIndex++}`);
params.push(batch_config_id);
}
if (execution_status) {
whereConditions.push(`bel.execution_status = $${paramIndex++}`);
params.push(execution_status);
}
if (start_date) {
whereConditions.push(`bel.start_time >= $${paramIndex++}`);
params.push(start_date);
}
if (end_date) {
whereConditions.push(`bel.start_time <= $${paramIndex++}`);
params.push(end_date);
}
const whereClause =
whereConditions.length > 0
? `WHERE ${whereConditions.join(" AND ")}`
: "";
// 로그 조회 (batch_config 정보 포함)
const sql = `
SELECT
bel.*,
json_build_object(
'id', bc.id,
'batch_name', bc.batch_name,
'description', bc.description,
'cron_schedule', bc.cron_schedule,
'is_active', bc.is_active
) as batch_config
FROM batch_execution_logs bel
LEFT JOIN batch_configs bc ON bel.batch_config_id = bc.id
${whereClause}
ORDER BY bel.start_time DESC
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
`;
const countSql = `
SELECT COUNT(*) as count
FROM batch_execution_logs bel
${whereClause}
`;
params.push(take, skip);
const [logs, countResult] = await Promise.all([
query<any>(sql, params),
query<{ count: number }>(countSql, params.slice(0, -2)),
]);
const total = parseInt(countResult[0]?.count?.toString() || "0", 10);
return {
success: true,
data: logs as BatchExecutionLogWithConfig[],
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
};
} catch (error) {
console.error("배치 실행 로그 조회 실패:", error);
return {
success: false,
message: "배치 실행 로그 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* 배치 실행 로그 생성
*/
static async createExecutionLog(
data: CreateBatchExecutionLogRequest
): Promise<ApiResponse<BatchExecutionLog>> {
try {
const log = await queryOne<BatchExecutionLog>(
`INSERT INTO batch_execution_logs (
batch_config_id, company_code, execution_status, start_time, end_time,
duration_ms, total_records, success_records, failed_records,
error_message, error_details, server_name, process_id
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
RETURNING *`,
[
data.batch_config_id,
data.company_code,
data.execution_status,
data.start_time || new Date(),
data.end_time,
data.duration_ms,
data.total_records || 0,
data.success_records || 0,
data.failed_records || 0,
data.error_message,
data.error_details,
data.server_name || process.env.HOSTNAME || "unknown",
data.process_id || process.pid?.toString(),
]
);
return {
success: true,
data: log as BatchExecutionLog,
message: "배치 실행 로그가 생성되었습니다.",
};
} catch (error) {
console.error("배치 실행 로그 생성 실패:", error);
return {
success: false,
message: "배치 실행 로그 생성 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* 배치 실행 로그 업데이트
*/
static async updateExecutionLog(
id: number,
data: UpdateBatchExecutionLogRequest
): Promise<ApiResponse<BatchExecutionLog>> {
try {
// 동적 UPDATE 쿼리 생성
const updates: string[] = [];
const params: any[] = [];
let paramIndex = 1;
if (data.execution_status !== undefined) {
updates.push(`execution_status = $${paramIndex++}`);
params.push(data.execution_status);
}
if (data.end_time !== undefined) {
updates.push(`end_time = $${paramIndex++}`);
params.push(data.end_time);
}
if (data.duration_ms !== undefined) {
updates.push(`duration_ms = $${paramIndex++}`);
params.push(data.duration_ms);
}
if (data.total_records !== undefined) {
updates.push(`total_records = $${paramIndex++}`);
params.push(data.total_records);
}
if (data.success_records !== undefined) {
updates.push(`success_records = $${paramIndex++}`);
params.push(data.success_records);
}
if (data.failed_records !== undefined) {
updates.push(`failed_records = $${paramIndex++}`);
params.push(data.failed_records);
}
if (data.error_message !== undefined) {
updates.push(`error_message = $${paramIndex++}`);
params.push(data.error_message);
}
if (data.error_details !== undefined) {
updates.push(`error_details = $${paramIndex++}`);
params.push(data.error_details);
}
params.push(id);
const log = await queryOne<BatchExecutionLog>(
`UPDATE batch_execution_logs
SET ${updates.join(", ")}
WHERE id = $${paramIndex}
RETURNING *`,
params
);
return {
success: true,
data: log as BatchExecutionLog,
message: "배치 실행 로그가 업데이트되었습니다.",
};
} catch (error) {
console.error("배치 실행 로그 업데이트 실패:", error);
return {
success: false,
message: "배치 실행 로그 업데이트 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* 배치 실행 로그 삭제
*/
static async deleteExecutionLog(id: number): Promise<ApiResponse<void>> {
try {
await query(`DELETE FROM batch_execution_logs WHERE id = $1`, [id]);
return {
success: true,
message: "배치 실행 로그가 삭제되었습니다.",
};
} catch (error) {
console.error("배치 실행 로그 삭제 실패:", error);
return {
success: false,
message: "배치 실행 로그 삭제 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* 특정 배치의 최신 실행 로그 조회
*/
static async getLatestExecutionLog(
batchConfigId: number
): Promise<ApiResponse<BatchExecutionLog | null>> {
try {
const log = await queryOne<BatchExecutionLog>(
`SELECT * FROM batch_execution_logs
WHERE batch_config_id = $1
ORDER BY start_time DESC
LIMIT 1`,
[batchConfigId]
);
return {
success: true,
data: log || null,
};
} catch (error) {
console.error("최신 배치 실행 로그 조회 실패:", error);
return {
success: false,
message: "최신 배치 실행 로그 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* 배치 실행 통계 조회
*/
static async getExecutionStats(
batchConfigId?: number,
startDate?: Date,
endDate?: Date
): Promise<
ApiResponse<{
total_executions: number;
success_count: number;
failed_count: number;
success_rate: number;
average_duration_ms: number;
total_records_processed: number;
}>
> {
try {
const whereConditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;
if (batchConfigId) {
whereConditions.push(`batch_config_id = $${paramIndex++}`);
params.push(batchConfigId);
}
if (startDate) {
whereConditions.push(`start_time >= $${paramIndex++}`);
params.push(startDate);
}
if (endDate) {
whereConditions.push(`start_time <= $${paramIndex++}`);
params.push(endDate);
}
const whereClause =
whereConditions.length > 0
? `WHERE ${whereConditions.join(" AND ")}`
: "";
const logs = await query<{
execution_status: string;
duration_ms: number;
total_records: number;
}>(
`SELECT execution_status, duration_ms, total_records
FROM batch_execution_logs
${whereClause}`,
params
);
const total_executions = logs.length;
const success_count = logs.filter(
(log: any) => log.execution_status === "SUCCESS"
).length;
const failed_count = logs.filter(
(log: any) => log.execution_status === "FAILED"
).length;
const success_rate =
total_executions > 0 ? (success_count / total_executions) * 100 : 0;
const validDurations = logs
.filter((log: any) => log.duration_ms !== null)
.map((log: any) => log.duration_ms!);
const average_duration_ms =
validDurations.length > 0
? validDurations.reduce(
(sum: number, duration: number) => sum + duration,
0
) / validDurations.length
: 0;
const total_records_processed = logs
.filter((log: any) => log.total_records !== null)
.reduce((sum: number, log: any) => sum + (log.total_records || 0), 0);
return {
success: true,
data: {
total_executions,
success_count,
failed_count,
success_rate,
average_duration_ms,
total_records_processed,
},
};
} catch (error) {
console.error("배치 실행 통계 조회 실패:", error);
return {
success: false,
message: "배치 실행 통계 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
}