import { query, queryOne, transaction } from "../database/db"; import { logger } from "../utils/logger"; // 타입 정의 interface CreateDataflowDiagramData { diagram_name: string; relationships: Record; // JSON 데이터 node_positions?: Record | null; // JSON 데이터 (노드 위치 정보) // 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드 control?: Array> | null; // JSON 배열 (각 관계별 조건 설정) category?: Array> | null; // JSON 배열 (각 관계별 연결 종류) plan?: Array> | null; // JSON 배열 (각 관계별 실행 계획) company_code: string; created_by: string; updated_by: string; } interface UpdateDataflowDiagramData { diagram_name?: string; relationships?: Record; // JSON 데이터 node_positions?: Record | null; // JSON 데이터 (노드 위치 정보) // 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드 control?: Array> | null; // JSON 배열 (각 관계별 조건 설정) category?: Array> | null; // JSON 배열 (각 관계별 연결 종류) plan?: Array> | null; // JSON 배열 (각 관계별 실행 계획) updated_by: string; } /** * 관계도 목록 조회 (페이지네이션) */ export const getDataflowDiagrams = async ( companyCode: string, page: number = 1, size: number = 20, searchTerm?: string ) => { try { const offset = (page - 1) * size; const whereConditions: string[] = []; const values: any[] = []; let paramIndex = 1; // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { whereConditions.push(`company_code = $${paramIndex++}`); values.push(companyCode); } if (searchTerm) { whereConditions.push(`diagram_name ILIKE $${paramIndex++}`); values.push(`%${searchTerm}%`); } const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : ""; // 총 개수 조회 const countResult = await queryOne<{ count: string }>( `SELECT COUNT(*) as count FROM dataflow_diagrams ${whereClause}`, values ); const total = parseInt(countResult?.count || "0"); // 데이터 조회 const diagrams = await query( `SELECT * FROM dataflow_diagrams ${whereClause} ORDER BY updated_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`, [...values, size, offset] ); const totalPages = Math.ceil(total / size); return { diagrams, pagination: { page, size, total, totalPages, }, }; } catch (error) { logger.error("관계도 목록 조회 서비스 오류:", error); throw error; } }; /** * 특정 관계도 조회 */ export const getDataflowDiagramById = async ( diagramId: number, companyCode: string ) => { try { const whereConditions: string[] = ["diagram_id = $1"]; const values: any[] = [diagramId]; // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { whereConditions.push("company_code = $2"); values.push(companyCode); } const whereClause = `WHERE ${whereConditions.join(" AND ")}`; const diagram = await queryOne( `SELECT * FROM dataflow_diagrams ${whereClause} LIMIT 1`, values ); return diagram; } catch (error) { logger.error("관계도 조회 서비스 오류:", error); throw error; } }; /** * 새로운 관계도 생성 */ export const createDataflowDiagram = async ( data: CreateDataflowDiagramData ) => { try { const newDiagram = await queryOne( `INSERT INTO dataflow_diagrams (diagram_name, relationships, node_positions, category, control, plan, company_code, created_by, updated_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW()) RETURNING *`, [ data.diagram_name, JSON.stringify(data.relationships), data.node_positions ? JSON.stringify(data.node_positions) : null, data.category ? JSON.stringify(data.category) : null, data.control ? JSON.stringify(data.control) : null, data.plan ? JSON.stringify(data.plan) : null, data.company_code, data.created_by, data.updated_by, ] ); return newDiagram; } catch (error) { logger.error("관계도 생성 서비스 오류:", error); throw error; } }; /** * 관계도 수정 */ export const updateDataflowDiagram = async ( diagramId: number, data: UpdateDataflowDiagramData, companyCode: string ) => { try { logger.info( `관계도 수정 서비스 시작 - ID: ${diagramId}, Company: ${companyCode}` ); // 먼저 해당 관계도가 존재하는지 확인 const whereConditions: string[] = ["diagram_id = $1"]; const checkValues: any[] = [diagramId]; if (companyCode !== "*") { whereConditions.push("company_code = $2"); checkValues.push(companyCode); } const existingDiagram = await queryOne( `SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`, checkValues ); logger.info( `기존 관계도 조회 결과:`, existingDiagram ? `ID ${existingDiagram.diagram_id} 발견` : "관계도 없음" ); if (!existingDiagram) { logger.warn( `관계도 ID ${diagramId}를 찾을 수 없음 - Company: ${companyCode}` ); return null; } // 동적 UPDATE 쿼리 생성 const updateFields: string[] = ["updated_by = $1", "updated_at = NOW()"]; const values: any[] = [data.updated_by]; let paramIndex = 2; if (data.diagram_name) { updateFields.push(`diagram_name = $${paramIndex++}`); values.push(data.diagram_name); } if (data.relationships) { updateFields.push(`relationships = $${paramIndex++}`); values.push(JSON.stringify(data.relationships)); } if (data.node_positions !== undefined) { updateFields.push(`node_positions = $${paramIndex++}`); values.push( data.node_positions ? JSON.stringify(data.node_positions) : null ); } if (data.category !== undefined) { updateFields.push(`category = $${paramIndex++}`); values.push(data.category ? JSON.stringify(data.category) : null); } if (data.control !== undefined) { updateFields.push(`control = $${paramIndex++}`); values.push(data.control ? JSON.stringify(data.control) : null); } if (data.plan !== undefined) { updateFields.push(`plan = $${paramIndex++}`); values.push(data.plan ? JSON.stringify(data.plan) : null); } const updatedDiagram = await queryOne( `UPDATE dataflow_diagrams SET ${updateFields.join(", ")} WHERE diagram_id = $${paramIndex} RETURNING *`, [...values, diagramId] ); return updatedDiagram; } catch (error) { logger.error("관계도 수정 서비스 오류:", error); throw error; } }; /** * 관계도 삭제 */ export const deleteDataflowDiagram = async ( diagramId: number, companyCode: string ) => { try { // 먼저 해당 관계도가 존재하는지 확인 const whereConditions: string[] = ["diagram_id = $1"]; const values: any[] = [diagramId]; if (companyCode !== "*") { whereConditions.push("company_code = $2"); values.push(companyCode); } const existingDiagram = await queryOne( `SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`, values ); if (!existingDiagram) { return false; } // 삭제 실행 await query(`DELETE FROM dataflow_diagrams WHERE diagram_id = $1`, [ diagramId, ]); return true; } catch (error) { logger.error("관계도 삭제 서비스 오류:", error); throw error; } }; /** * 관계도 복제 */ export const copyDataflowDiagram = async ( diagramId: number, companyCode: string, newName?: string, userId: string = "SYSTEM" ) => { try { // 원본 관계도 조회 const whereConditions: string[] = ["diagram_id = $1"]; const values: any[] = [diagramId]; if (companyCode !== "*") { whereConditions.push("company_code = $2"); values.push(companyCode); } const originalDiagram = await queryOne( `SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`, values ); if (!originalDiagram) { return null; } // 새로운 이름 생성 (제공되지 않은 경우) let copyName = newName; if (!copyName) { // 기존 이름에서 (n) 패턴을 찾아서 증가 const baseNameMatch = originalDiagram.diagram_name.match( /^(.+?)(\s*\((\d+)\))?$/ ); const baseName = baseNameMatch ? baseNameMatch[1] : originalDiagram.diagram_name; // 같은 패턴의 이름들을 찾아서 가장 큰 번호 찾기 const copyWhereConditions: string[] = ["diagram_name LIKE $1"]; const copyValues: any[] = [`${baseName}%`]; if (companyCode !== "*") { copyWhereConditions.push("company_code = $2"); copyValues.push(companyCode); } const existingCopies = await query<{ diagram_name: string }>( `SELECT diagram_name FROM dataflow_diagrams WHERE ${copyWhereConditions.join(" AND ")}`, copyValues ); let maxNumber = 0; existingCopies.forEach((copy) => { const match = copy.diagram_name.match(/\((\d+)\)$/); if (match) { const num = parseInt(match[1]); if (num > maxNumber) { maxNumber = num; } } }); copyName = `${baseName} (${maxNumber + 1})`; } // 새로운 관계도 생성 const copiedDiagram = await queryOne( `INSERT INTO dataflow_diagrams (diagram_name, relationships, node_positions, category, company_code, created_by, updated_by, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW()) RETURNING *`, [ copyName, JSON.stringify(originalDiagram.relationships), originalDiagram.node_positions ? JSON.stringify(originalDiagram.node_positions) : null, originalDiagram.category || null, companyCode, userId, userId, ] ); return copiedDiagram; } catch (error) { logger.error("관계도 복제 서비스 오류:", error); throw error; } }; /** * 🔥 전체 관계 목록 조회 (버튼 제어용) * dataflow_diagrams 테이블에서 관계도 데이터를 조회 (데이터 흐름 관계 화면과 동일) */ export const getAllRelationshipsForButtonControl = async ( companyCode: string ): Promise< Array<{ id: string; name: string; sourceTable: string; targetTable: string; category: string; }> > => { try { logger.info(`전체 관계 목록 조회 시작 - companyCode: ${companyCode}`); // dataflow_diagrams 테이블에서 관계도들을 조회 const diagrams = await query<{ diagram_id: number; diagram_name: string; relationships: any; }>( `SELECT diagram_id, diagram_name, relationships FROM dataflow_diagrams WHERE company_code = $1 ORDER BY updated_at DESC`, [companyCode] ); const allRelationships = diagrams.map((diagram) => { // relationships 구조에서 테이블 정보 추출 const relationships = (diagram.relationships as any) || {}; // 테이블 정보 추출 let sourceTable = ""; let targetTable = ""; if (relationships.fromTable?.tableName) { sourceTable = relationships.fromTable.tableName; } if (relationships.toTable?.tableName) { targetTable = relationships.toTable.tableName; } return { id: diagram.diagram_id.toString(), name: diagram.diagram_name || `관계 ${diagram.diagram_id}`, sourceTable: sourceTable, targetTable: targetTable, category: "데이터 흐름", }; }); logger.info(`전체 관계 ${allRelationships.length}개 조회 완료`); return allRelationships; } catch (error) { logger.error("전체 관계 목록 조회 서비스 오류:", error); throw error; } };