/** * 플로우 데이터 이동 서비스 * 데이터의 플로우 단계 이동 및 오딧 로그 관리 */ import db from "../database/db"; import { FlowAuditLog } from "../types/flow"; import { FlowDefinitionService } from "./flowDefinitionService"; export class FlowDataMoveService { private flowDefinitionService: FlowDefinitionService; constructor() { this.flowDefinitionService = new FlowDefinitionService(); } /** * 데이터를 다음 플로우 단계로 이동 */ async moveDataToStep( flowId: number, recordId: string, toStepId: number, userId: string, note?: string ): Promise { await db.transaction(async (client) => { // 1. 플로우 정의 조회 const flowDef = await this.flowDefinitionService.findById(flowId); if (!flowDef) { throw new Error(`Flow definition not found: ${flowId}`); } // 2. 현재 상태 조회 const currentStatusQuery = ` SELECT current_step_id, table_name FROM flow_data_status WHERE flow_definition_id = $1 AND record_id = $2 `; const currentStatusResult = await client.query(currentStatusQuery, [ flowId, recordId, ]); const currentStatus = currentStatusResult.rows.length > 0 ? { currentStepId: currentStatusResult.rows[0].current_step_id, tableName: currentStatusResult.rows[0].table_name, } : null; const fromStepId = currentStatus?.currentStepId || null; // 3. flow_data_status 업데이트 또는 삽입 if (currentStatus) { await client.query( ` UPDATE flow_data_status SET current_step_id = $1, updated_by = $2, updated_at = NOW() WHERE flow_definition_id = $3 AND record_id = $4 `, [toStepId, userId, flowId, recordId] ); } else { await client.query( ` INSERT INTO flow_data_status (flow_definition_id, table_name, record_id, current_step_id, updated_by) VALUES ($1, $2, $3, $4, $5) `, [flowId, flowDef.tableName, recordId, toStepId, userId] ); } // 4. 오딧 로그 기록 await client.query( ` INSERT INTO flow_audit_log (flow_definition_id, table_name, record_id, from_step_id, to_step_id, changed_by, note) VALUES ($1, $2, $3, $4, $5, $6, $7) `, [ flowId, flowDef.tableName, recordId, fromStepId, toStepId, userId, note || null, ] ); }); } /** * 여러 데이터를 동시에 다음 단계로 이동 */ async moveBatchData( flowId: number, recordIds: string[], toStepId: number, userId: string, note?: string ): Promise { for (const recordId of recordIds) { await this.moveDataToStep(flowId, recordId, toStepId, userId, note); } } /** * 데이터의 플로우 이력 조회 */ async getAuditLogs( flowId: number, recordId: string ): Promise { const query = ` SELECT fal.*, fs_from.step_name as from_step_name, fs_to.step_name as to_step_name FROM flow_audit_log fal LEFT JOIN flow_step fs_from ON fal.from_step_id = fs_from.id LEFT JOIN flow_step fs_to ON fal.to_step_id = fs_to.id WHERE fal.flow_definition_id = $1 AND fal.record_id = $2 ORDER BY fal.changed_at DESC `; const result = await db.query(query, [flowId, recordId]); return result.map((row) => ({ id: row.id, flowDefinitionId: row.flow_definition_id, tableName: row.table_name, recordId: row.record_id, fromStepId: row.from_step_id, toStepId: row.to_step_id, changedBy: row.changed_by, changedAt: row.changed_at, note: row.note, fromStepName: row.from_step_name, toStepName: row.to_step_name, })); } /** * 특정 플로우의 모든 이력 조회 */ async getFlowAuditLogs( flowId: number, limit: number = 100 ): Promise { const query = ` SELECT fal.*, fs_from.step_name as from_step_name, fs_to.step_name as to_step_name FROM flow_audit_log fal LEFT JOIN flow_step fs_from ON fal.from_step_id = fs_from.id LEFT JOIN flow_step fs_to ON fal.to_step_id = fs_to.id WHERE fal.flow_definition_id = $1 ORDER BY fal.changed_at DESC LIMIT $2 `; const result = await db.query(query, [flowId, limit]); return result.map((row) => ({ id: row.id, flowDefinitionId: row.flow_definition_id, tableName: row.table_name, recordId: row.record_id, fromStepId: row.from_step_id, toStepId: row.to_step_id, changedBy: row.changed_by, changedAt: row.changed_at, note: row.note, fromStepName: row.from_step_name, toStepName: row.to_step_name, })); } }