import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export interface ControlCondition { id: string; type: "condition" | "group-start" | "group-end"; field?: string; value?: any; operator?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN"; dataType?: "string" | "number" | "date" | "boolean"; logicalOperator?: "AND" | "OR"; groupId?: string; groupLevel?: number; } export interface ControlAction { id: string; name: string; actionType: "insert" | "update" | "delete"; conditions: ControlCondition[]; fieldMappings: { sourceField?: string; sourceTable?: string; targetField: string; targetTable: string; defaultValue?: any; }[]; splitConfig?: { delimiter?: string; sourceField?: string; targetField?: string; }; } export interface ControlPlan { id: string; sourceTable: string; actions: ControlAction[]; } export interface ControlRule { id: string; triggerType: "insert" | "update" | "delete"; conditions: ControlCondition[]; } export class DataflowControlService { /** * 제어관리 실행 메인 함수 */ async executeDataflowControl( diagramId: number, relationshipId: string, triggerType: "insert" | "update" | "delete", sourceData: Record, tableName: string ): Promise<{ success: boolean; message: string; executedActions?: any[]; errors?: string[]; }> { try { console.log(`🎯 제어관리 실행 시작:`, { diagramId, relationshipId, triggerType, sourceData, tableName, }); // 관계도 정보 조회 const diagram = await prisma.dataflow_diagrams.findUnique({ where: { diagram_id: diagramId }, }); if (!diagram) { return { success: false, message: `관계도를 찾을 수 없습니다. (ID: ${diagramId})`, }; } // 제어 규칙과 실행 계획 추출 const controlRules = (diagram.control as unknown as ControlRule[]) || []; const executionPlans = (diagram.plan as unknown as ControlPlan[]) || []; console.log(`📋 제어 규칙:`, controlRules); console.log(`📋 실행 계획:`, executionPlans); // 해당 관계의 제어 규칙 찾기 const targetRule = controlRules.find( (rule) => rule.id === relationshipId && rule.triggerType === triggerType ); if (!targetRule) { console.log( `⚠️ 해당 관계의 제어 규칙을 찾을 수 없습니다: ${relationshipId}` ); return { success: true, message: "해당 관계의 제어 규칙이 없습니다.", }; } // 제어 조건 검증 const conditionResult = await this.evaluateConditions( targetRule.conditions, sourceData ); console.log(`🔍 조건 검증 결과:`, conditionResult); if (!conditionResult.satisfied) { return { success: true, message: `제어 조건을 만족하지 않습니다: ${conditionResult.reason}`, }; } // 실행 계획 찾기 const targetPlan = executionPlans.find( (plan) => plan.id === relationshipId ); if (!targetPlan) { return { success: true, message: "실행할 계획이 없습니다.", }; } // 액션 실행 const executedActions = []; const errors = []; for (const action of targetPlan.actions) { try { console.log(`⚡ 액션 실행: ${action.name} (${action.actionType})`); // 액션 조건 검증 (있는 경우) if (action.conditions && action.conditions.length > 0) { const actionConditionResult = await this.evaluateConditions( action.conditions, sourceData ); if (!actionConditionResult.satisfied) { console.log( `⚠️ 액션 조건 미충족: ${actionConditionResult.reason}` ); continue; } } const actionResult = await this.executeAction(action, sourceData); executedActions.push({ actionId: action.id, actionName: action.name, result: actionResult, }); } catch (error) { console.error(`❌ 액션 실행 오류: ${action.name}`, error); errors.push( `액션 '${action.name}' 실행 오류: ${error instanceof Error ? error.message : String(error)}` ); } } return { success: true, message: `제어관리 실행 완료. ${executedActions.length}개 액션 실행됨.`, executedActions, errors: errors.length > 0 ? errors : undefined, }; } catch (error) { console.error("❌ 제어관리 실행 오류:", error); return { success: false, message: `제어관리 실행 중 오류 발생: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * 조건 평가 */ private async evaluateConditions( conditions: ControlCondition[], data: Record ): Promise<{ satisfied: boolean; reason?: string }> { if (!conditions || conditions.length === 0) { return { satisfied: true }; } try { // 조건을 SQL WHERE 절로 변환 const whereClause = this.buildWhereClause(conditions, data); console.log(`🔍 생성된 WHERE 절:`, whereClause); // 간단한 조건 평가 (실제로는 더 복잡한 로직 필요) for (const condition of conditions) { if (condition.type === "condition" && condition.field) { const fieldValue = data[condition.field]; const conditionValue = condition.value; console.log( `🔍 조건 평가: ${condition.field} ${condition.operator} ${conditionValue} (실제값: ${fieldValue})` ); const result = this.evaluateSingleCondition( fieldValue, condition.operator || "=", conditionValue, condition.dataType || "string" ); if (!result) { return { satisfied: false, reason: `조건 미충족: ${condition.field} ${condition.operator} ${conditionValue}`, }; } } } return { satisfied: true }; } catch (error) { console.error("조건 평가 오류:", error); return { satisfied: false, reason: `조건 평가 오류: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * 단일 조건 평가 */ private evaluateSingleCondition( fieldValue: any, operator: string, conditionValue: any, dataType: string ): boolean { // 타입 변환 let actualValue = fieldValue; let expectedValue = conditionValue; if (dataType === "number") { actualValue = parseFloat(fieldValue) || 0; expectedValue = parseFloat(conditionValue) || 0; } else if (dataType === "string") { actualValue = String(fieldValue || ""); expectedValue = String(conditionValue || ""); } // 연산자별 평가 switch (operator) { case "=": return actualValue === expectedValue; case "!=": return actualValue !== expectedValue; case ">": return actualValue > expectedValue; case "<": return actualValue < expectedValue; case ">=": return actualValue >= expectedValue; case "<=": return actualValue <= expectedValue; case "LIKE": return String(actualValue).includes(String(expectedValue)); default: console.warn(`지원되지 않는 연산자: ${operator}`); return false; } } /** * WHERE 절 생성 (복잡한 그룹 조건 처리) */ private buildWhereClause( conditions: ControlCondition[], data: Record ): string { // 실제로는 더 복잡한 그룹 처리 로직이 필요 // 현재는 간단한 AND/OR 처리만 구현 const clauses = []; for (const condition of conditions) { if (condition.type === "condition") { const clause = `${condition.field} ${condition.operator} '${condition.value}'`; clauses.push(clause); } } return clauses.join(" AND "); } /** * 액션 실행 */ private async executeAction( action: ControlAction, sourceData: Record ): Promise { console.log(`🚀 액션 실행: ${action.actionType}`, action); switch (action.actionType) { case "insert": return await this.executeInsertAction(action, sourceData); case "update": return await this.executeUpdateAction(action, sourceData); case "delete": return await this.executeDeleteAction(action, sourceData); default: throw new Error(`지원되지 않는 액션 타입: ${action.actionType}`); } } /** * INSERT 액션 실행 */ private async executeInsertAction( action: ControlAction, sourceData: Record ): Promise { const results = []; for (const mapping of action.fieldMappings) { const { targetTable, targetField, defaultValue, sourceField } = mapping; // 삽입할 데이터 준비 const insertData: Record = {}; if (sourceField && sourceData[sourceField]) { insertData[targetField] = sourceData[sourceField]; } else if (defaultValue !== undefined) { insertData[targetField] = defaultValue; } // 동적으로 테이블 컬럼 정보 조회하여 기본 필드 추가 await this.addDefaultFieldsForTable(targetTable, insertData); console.log(`📝 INSERT 실행: ${targetTable}.${targetField}`, insertData); try { // 동적 테이블 INSERT 실행 const result = await prisma.$executeRawUnsafe( ` INSERT INTO ${targetTable} (${Object.keys(insertData).join(", ")}) VALUES (${Object.keys(insertData) .map((_, index) => `$${index + 1}`) .join(", ")}) `, ...Object.values(insertData) ); results.push({ table: targetTable, field: targetField, data: insertData, result, }); console.log(`✅ INSERT 성공: ${targetTable}.${targetField}`); } catch (error) { console.error(`❌ INSERT 실패: ${targetTable}.${targetField}`, error); throw error; } } return results; } /** * UPDATE 액션 실행 */ private async executeUpdateAction( action: ControlAction, sourceData: Record ): Promise { // UPDATE 로직 구현 console.log("UPDATE 액션 실행 (미구현)"); return { message: "UPDATE 액션은 아직 구현되지 않았습니다." }; } /** * DELETE 액션 실행 */ private async executeDeleteAction( action: ControlAction, sourceData: Record ): Promise { // DELETE 로직 구현 console.log("DELETE 액션 실행 (미구현)"); return { message: "DELETE 액션은 아직 구현되지 않았습니다." }; } /** * 테이블의 컬럼 정보를 동적으로 조회하여 기본 필드 추가 */ private async addDefaultFieldsForTable( tableName: string, insertData: Record ): Promise { try { // 테이블의 컬럼 정보 조회 const columns = await prisma.$queryRawUnsafe< Array<{ column_name: string; data_type: string; is_nullable: string }> >( ` SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = $1 ORDER BY ordinal_position `, tableName ); console.log(`📋 ${tableName} 테이블 컬럼 정보:`, columns); const currentDate = new Date(); // 일반적인 타임스탬프 필드들 확인 및 추가 const timestampFields = [ { names: ["created_at", "create_date", "reg_date", "regdate"], value: currentDate, }, { names: ["updated_at", "update_date", "mod_date", "moddate"], value: currentDate, }, ]; for (const fieldGroup of timestampFields) { for (const fieldName of fieldGroup.names) { const column = columns.find( (col) => col.column_name.toLowerCase() === fieldName.toLowerCase() ); if (column && !insertData[column.column_name]) { // 해당 컬럼이 존재하고 아직 값이 설정되지 않은 경우 if ( column.data_type.includes("timestamp") || column.data_type.includes("date") ) { insertData[column.column_name] = fieldGroup.value; console.log( `📅 기본 타임스탬프 필드 추가: ${column.column_name} = ${fieldGroup.value}` ); } } } } // 필수 필드 중 값이 없는 경우 기본값 설정 for (const column of columns) { if (column.is_nullable === "NO" && !insertData[column.column_name]) { // NOT NULL 필드인데 값이 없는 경우 기본값 설정 const defaultValue = this.getDefaultValueForColumn(column); if (defaultValue !== null) { insertData[column.column_name] = defaultValue; console.log( `🔧 필수 필드 기본값 설정: ${column.column_name} = ${defaultValue}` ); } } } } catch (error) { console.error(`❌ ${tableName} 테이블 컬럼 정보 조회 실패:`, error); // 에러가 발생해도 INSERT는 계속 진행 (기본 필드 없이) } } /** * 컬럼 타입에 따른 기본값 반환 */ private getDefaultValueForColumn(column: { column_name: string; data_type: string; }): any { const dataType = column.data_type.toLowerCase(); const columnName = column.column_name.toLowerCase(); // 컬럼명 기반 기본값 if (columnName.includes("status")) { return "Y"; // 상태 필드는 보통 'Y' } if (columnName.includes("type")) { return "default"; // 타입 필드는 'default' } // 데이터 타입 기반 기본값 if ( dataType.includes("varchar") || dataType.includes("text") || dataType.includes("char") ) { return ""; // 문자열은 빈 문자열 } if ( dataType.includes("int") || dataType.includes("numeric") || dataType.includes("decimal") ) { return 0; // 숫자는 0 } if (dataType.includes("bool")) { return false; // 불린은 false } if (dataType.includes("timestamp") || dataType.includes("date")) { return new Date(); // 날짜는 현재 시간 } return null; // 기본값을 설정할 수 없는 경우 } }