// @ts-nocheck /** * 플로우 관리 컨트롤러 */ import { Request, Response } from "express"; import { FlowDefinitionService } from "../services/flowDefinitionService"; import { FlowStepService } from "../services/flowStepService"; import { FlowConnectionService } from "../services/flowConnectionService"; import { FlowExecutionService } from "../services/flowExecutionService"; import { FlowDataMoveService } from "../services/flowDataMoveService"; export class FlowController { private flowDefinitionService: FlowDefinitionService; private flowStepService: FlowStepService; private flowConnectionService: FlowConnectionService; private flowExecutionService: FlowExecutionService; private flowDataMoveService: FlowDataMoveService; constructor() { this.flowDefinitionService = new FlowDefinitionService(); this.flowStepService = new FlowStepService(); this.flowConnectionService = new FlowConnectionService(); this.flowExecutionService = new FlowExecutionService(); this.flowDataMoveService = new FlowDataMoveService(); } // ==================== 플로우 정의 ==================== /** * 플로우 정의 생성 */ createFlowDefinition = async (req: Request, res: Response): Promise => { try { const { name, description, tableName, dbSourceType, dbConnectionId, // REST API 관련 필드 restApiConnectionId, restApiEndpoint, restApiJsonPath, } = req.body; const userId = (req as any).user?.userId || "system"; const userCompanyCode = (req as any).user?.companyCode; console.log("🔍 createFlowDefinition called with:", { name, description, tableName, dbSourceType, dbConnectionId, restApiConnectionId, restApiEndpoint, restApiJsonPath, userCompanyCode, }); if (!name) { res.status(400).json({ success: false, message: "Name is required", }); return; } // REST API 또는 다중 연결인 경우 테이블 존재 확인 스킵 const isRestApi = dbSourceType === "restapi" || dbSourceType === "multi_restapi"; const isMultiConnection = dbSourceType === "multi_restapi" || dbSourceType === "multi_external_db"; // 테이블 이름이 제공된 경우에만 존재 확인 (REST API 및 다중 연결 제외) if (tableName && !isRestApi && !isMultiConnection && !tableName.startsWith("_restapi_") && !tableName.startsWith("_multi_restapi_") && !tableName.startsWith("_multi_external_db_")) { const tableExists = await this.flowDefinitionService.checkTableExists(tableName); if (!tableExists) { res.status(400).json({ success: false, message: `Table '${tableName}' does not exist`, }); return; } } const flowDef = await this.flowDefinitionService.create( { name, description, tableName, dbSourceType, dbConnectionId, restApiConnectionId, restApiEndpoint, restApiJsonPath, restApiConnections: req.body.restApiConnections, // 다중 REST API 설정 }, userId, userCompanyCode ); res.json({ success: true, data: flowDef, }); } catch (error: any) { console.error("Error creating flow definition:", error); res.status(500).json({ success: false, message: error.message || "Failed to create flow definition", }); } }; /** * 플로우 정의 목록 조회 */ getFlowDefinitions = async (req: Request, res: Response): Promise => { try { const { tableName, isActive } = req.query; const user = (req as any).user; const userCompanyCode = user?.companyCode; console.log("🎯 getFlowDefinitions called:", { userId: user?.userId, userCompanyCode: userCompanyCode, userType: user?.userType, tableName, isActive, }); const flows = await this.flowDefinitionService.findAll( tableName as string | undefined, isActive !== undefined ? isActive === "true" : undefined, userCompanyCode ); console.log(`✅ Returning ${flows.length} flows to user ${user?.userId}`); res.json({ success: true, data: flows, }); } catch (error: any) { console.error("Error fetching flow definitions:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow definitions", }); } }; /** * 플로우 정의 상세 조회 (단계 및 연결 포함) */ getFlowDefinitionDetail = async ( req: Request, res: Response ): Promise => { try { const { id } = req.params; const flowId = parseInt(id); const definition = await this.flowDefinitionService.findById(flowId); if (!definition) { res.status(404).json({ success: false, message: "Flow definition not found", }); return; } const steps = await this.flowStepService.findByFlowId(flowId); const connections = await this.flowConnectionService.findByFlowId(flowId); res.json({ success: true, data: { definition, steps, connections, }, }); } catch (error: any) { console.error("Error fetching flow definition detail:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow definition detail", }); } }; /** * 플로우 정의 수정 */ updateFlowDefinition = async (req: Request, res: Response): Promise => { try { const { id } = req.params; const flowId = parseInt(id); const { name, description, isActive } = req.body; const flowDef = await this.flowDefinitionService.update(flowId, { name, description, isActive, }); if (!flowDef) { res.status(404).json({ success: false, message: "Flow definition not found", }); return; } res.json({ success: true, data: flowDef, }); } catch (error: any) { console.error("Error updating flow definition:", error); res.status(500).json({ success: false, message: error.message || "Failed to update flow definition", }); } }; /** * 플로우 정의 삭제 */ deleteFlowDefinition = async (req: Request, res: Response): Promise => { try { const { id } = req.params; const flowId = parseInt(id); const success = await this.flowDefinitionService.delete(flowId); if (!success) { res.status(404).json({ success: false, message: "Flow definition not found", }); return; } res.json({ success: true, message: "Flow definition deleted successfully", }); } catch (error: any) { console.error("Error deleting flow definition:", error); res.status(500).json({ success: false, message: error.message || "Failed to delete flow definition", }); } }; // ==================== 플로우 단계 ==================== /** * 플로우 단계 목록 조회 */ getFlowSteps = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const flowDefinitionId = parseInt(flowId); const steps = await this.flowStepService.findByFlowId(flowDefinitionId); res.json({ success: true, data: steps, }); return; } catch (error: any) { console.error("Error fetching flow steps:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow steps", }); return; } }; /** * 플로우 단계 생성 */ createFlowStep = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const flowDefinitionId = parseInt(flowId); const { stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, } = req.body; if (!stepName || stepOrder === undefined) { res.status(400).json({ success: false, message: "stepName and stepOrder are required", }); return; } const step = await this.flowStepService.create({ flowDefinitionId, stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, }); res.json({ success: true, data: step, }); } catch (error: any) { console.error("Error creating flow step:", error); res.status(500).json({ success: false, message: error.message || "Failed to create flow step", }); } }; /** * 플로우 단계 수정 */ updateFlowStep = async (req: Request, res: Response): Promise => { try { const { stepId } = req.params; const id = parseInt(stepId); const { stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, moveType, statusColumn, statusValue, targetTable, fieldMappings, integrationType, integrationConfig, displayConfig, } = req.body; const step = await this.flowStepService.update(id, { stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, moveType, statusColumn, statusValue, targetTable, fieldMappings, integrationType, integrationConfig, displayConfig, }); if (!step) { res.status(404).json({ success: false, message: "Flow step not found", }); return; } res.json({ success: true, data: step, }); } catch (error: any) { console.error("Error updating flow step:", error); res.status(500).json({ success: false, message: error.message || "Failed to update flow step", }); } }; /** * 플로우 단계 삭제 */ deleteFlowStep = async (req: Request, res: Response): Promise => { try { const { stepId } = req.params; const id = parseInt(stepId); const success = await this.flowStepService.delete(id); if (!success) { res.status(404).json({ success: false, message: "Flow step not found", }); return; } res.json({ success: true, message: "Flow step deleted successfully", }); } catch (error: any) { console.error("Error deleting flow step:", error); res.status(500).json({ success: false, message: error.message || "Failed to delete flow step", }); } }; // ==================== 플로우 연결 ==================== /** * 플로우 연결 목록 조회 */ getFlowConnections = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const flowDefinitionId = parseInt(flowId); const connections = await this.flowConnectionService.findByFlowId(flowDefinitionId); res.json({ success: true, data: connections, }); return; } catch (error: any) { console.error("Error fetching flow connections:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow connections", }); return; } }; /** * 플로우 연결 생성 */ createConnection = async (req: Request, res: Response): Promise => { try { const { flowDefinitionId, fromStepId, toStepId, label } = req.body; if (!flowDefinitionId || !fromStepId || !toStepId) { res.status(400).json({ success: false, message: "flowDefinitionId, fromStepId, and toStepId are required", }); return; } const connection = await this.flowConnectionService.create({ flowDefinitionId, fromStepId, toStepId, label, }); res.json({ success: true, data: connection, }); } catch (error: any) { console.error("Error creating connection:", error); res.status(500).json({ success: false, message: error.message || "Failed to create connection", }); } }; /** * 플로우 연결 삭제 */ deleteConnection = async (req: Request, res: Response): Promise => { try { const { connectionId } = req.params; const id = parseInt(connectionId); const success = await this.flowConnectionService.delete(id); if (!success) { res.status(404).json({ success: false, message: "Connection not found", }); return; } res.json({ success: true, message: "Connection deleted successfully", }); } catch (error: any) { console.error("Error deleting connection:", error); res.status(500).json({ success: false, message: error.message || "Failed to delete connection", }); } }; // ==================== 플로우 실행 ==================== /** * 단계별 데이터 카운트 조회 */ getStepDataCount = async (req: Request, res: Response): Promise => { try { const { flowId, stepId } = req.params; const count = await this.flowExecutionService.getStepDataCount( parseInt(flowId), parseInt(stepId) ); res.json({ success: true, data: { count }, }); } catch (error: any) { console.error("Error getting step data count:", error); res.status(500).json({ success: false, message: error.message || "Failed to get step data count", }); } }; /** * 단계별 데이터 리스트 조회 */ getStepDataList = async (req: Request, res: Response): Promise => { try { const { flowId, stepId } = req.params; const { page = 1, pageSize = 20 } = req.query; const data = await this.flowExecutionService.getStepDataList( parseInt(flowId), parseInt(stepId), parseInt(page as string), parseInt(pageSize as string) ); res.json({ success: true, data, }); } catch (error: any) { console.error("Error getting step data list:", error); res.status(500).json({ success: false, message: error.message || "Failed to get step data list", }); } }; /** * 플로우 스텝의 컬럼 라벨 조회 */ getStepColumnLabels = async (req: Request, res: Response): Promise => { try { const { flowId, stepId } = req.params; console.log("🏷️ [FlowController] 컬럼 라벨 조회 요청:", { flowId, stepId, }); const step = await this.flowStepService.findById(parseInt(stepId)); if (!step) { console.warn("⚠️ [FlowController] 스텝을 찾을 수 없음:", stepId); res.status(404).json({ success: false, message: "Step not found", }); return; } const flowDef = await this.flowDefinitionService.findById( parseInt(flowId) ); if (!flowDef) { console.warn("⚠️ [FlowController] 플로우를 찾을 수 없음:", flowId); res.status(404).json({ success: false, message: "Flow definition not found", }); return; } // 테이블명 결정 (스텝 테이블 우선, 없으면 플로우 테이블) const tableName = step.tableName || flowDef.tableName; console.log("📋 [FlowController] 테이블명 결정:", { stepTableName: step.tableName, flowTableName: flowDef.tableName, selectedTableName: tableName, }); if (!tableName) { console.warn("⚠️ [FlowController] 테이블명이 지정되지 않음"); res.json({ success: true, data: {}, }); return; } // column_labels 테이블에서 라벨 정보 조회 const { query } = await import("../database/db"); const labelRows = await query<{ column_name: string; column_label: string | null; }>( `SELECT column_name, column_label FROM column_labels WHERE table_name = $1 AND column_label IS NOT NULL`, [tableName] ); console.log(`✅ [FlowController] column_labels 조회 완료:`, { tableName, rowCount: labelRows.length, labels: labelRows.map((r) => ({ col: r.column_name, label: r.column_label, })), }); // { columnName: label } 형태의 객체로 변환 const labels: Record = {}; labelRows.forEach((row) => { if (row.column_label) { labels[row.column_name] = row.column_label; } }); console.log("📦 [FlowController] 반환할 라벨 객체:", labels); res.json({ success: true, data: labels, }); } catch (error: any) { console.error("❌ [FlowController] 컬럼 라벨 조회 오류:", error); res.status(500).json({ success: false, message: error.message || "Failed to get step column labels", }); } }; /** * 플로우의 모든 단계별 카운트 조회 */ getAllStepCounts = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const counts = await this.flowExecutionService.getAllStepCounts( parseInt(flowId) ); res.json({ success: true, data: counts, }); } catch (error: any) { console.error("Error getting all step counts:", error); res.status(500).json({ success: false, message: error.message || "Failed to get all step counts", }); } }; /** * 데이터를 다음 단계로 이동 */ moveData = async (req: Request, res: Response): Promise => { try { const { flowId, recordId, toStepId, note } = req.body; const userId = (req as any).user?.userId || "system"; if (!flowId || !recordId || !toStepId) { res.status(400).json({ success: false, message: "flowId, recordId, and toStepId are required", }); return; } await this.flowDataMoveService.moveDataToStep( flowId, recordId, toStepId, userId, note ); res.json({ success: true, message: "Data moved successfully", }); } catch (error: any) { console.error("Error moving data:", error); res.status(500).json({ success: false, message: error.message || "Failed to move data", }); } }; /** * 여러 데이터를 동시에 이동 */ moveBatchData = async (req: Request, res: Response): Promise => { try { const { flowId, fromStepId, toStepId, dataIds } = req.body; const userId = (req as any).user?.userId || "system"; if ( !flowId || !fromStepId || !toStepId || !dataIds || !Array.isArray(dataIds) ) { res.status(400).json({ success: false, message: "flowId, fromStepId, toStepId, and dataIds (array) are required", }); return; } const result = await this.flowDataMoveService.moveBatchData( flowId, fromStepId, toStepId, dataIds, userId ); const successCount = result.results.filter((r) => r.success).length; const failureCount = result.results.filter((r) => !r.success).length; res.json({ success: result.success, message: result.success ? `${successCount}건의 데이터를 성공적으로 이동했습니다` : `${successCount}건 성공, ${failureCount}건 실패`, data: { successCount, failureCount, total: dataIds.length, }, results: result.results, }); } catch (error: any) { console.error("Error moving batch data:", error); res.status(500).json({ success: false, message: error.message || "Failed to move batch data", }); } }; /** * 데이터의 플로우 이력 조회 */ getAuditLogs = async (req: Request, res: Response): Promise => { try { const { flowId, recordId } = req.params; const logs = await this.flowDataMoveService.getAuditLogs( parseInt(flowId), recordId ); res.json({ success: true, data: logs, }); } catch (error: any) { console.error("Error getting audit logs:", error); res.status(500).json({ success: false, message: error.message || "Failed to get audit logs", }); } }; /** * 플로우의 모든 이력 조회 */ getFlowAuditLogs = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const { limit = 100 } = req.query; const logs = await this.flowDataMoveService.getFlowAuditLogs( parseInt(flowId), parseInt(limit as string) ); res.json({ success: true, data: logs, }); } catch (error: any) { console.error("Error getting flow audit logs:", error); res.status(500).json({ success: false, message: error.message || "Failed to get flow audit logs", }); } }; /** * 스텝 데이터 업데이트 (인라인 편집) */ updateStepData = async (req: Request, res: Response): Promise => { try { const { flowId, stepId, recordId } = req.params; const updateData = req.body; const userId = (req as any).user?.userId || "system"; const userCompanyCode = (req as any).user?.companyCode; if (!flowId || !stepId || !recordId) { res.status(400).json({ success: false, message: "flowId, stepId, and recordId are required", }); return; } if (!updateData || Object.keys(updateData).length === 0) { res.status(400).json({ success: false, message: "Update data is required", }); return; } const result = await this.flowExecutionService.updateStepData( parseInt(flowId), parseInt(stepId), recordId, updateData, userId, userCompanyCode ); res.json({ success: true, message: "Data updated successfully", data: result, }); } catch (error: any) { console.error("Error updating step data:", error); res.status(500).json({ success: false, message: error.message || "Failed to update step data", }); } }; }