import { apiClient, ApiResponse } from "./client"; // 테이블 간 데이터 관계 설정 관련 타입 정의 // 조건부 연결 관련 타입들 export interface ConditionControl { triggerType: "insert" | "update" | "delete" | "insert_update"; conditionTree: ConditionNode | ConditionNode[] | null; } export interface ConditionNode { id: string; // 고유 ID type: "condition" | "group-start" | "group-end"; field?: string; operator?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE"; value?: string | number | boolean; dataType?: string; tableType?: "from" | "to"; // 어느 테이블의 필드인지 구분 logicalOperator?: "AND" | "OR"; // 다음 조건과의 논리 연산자 groupId?: string; // 그룹 ID (group-start와 group-end가 같은 groupId를 가짐) groupLevel?: number; // 중첩 레벨 (0, 1, 2, ...) } export interface ConnectionCategory { type: "simple-key" | "data-save" | "external-call" | "conditional-link"; rollbackOnError?: boolean; enableLogging?: boolean; maxRetryCount?: number; } export interface ExecutionPlan { sourceTable: string; targetActions: TargetAction[]; } export interface TargetAction { id: string; actionType: "insert" | "update" | "delete" | "upsert"; targetTable: string; enabled: boolean; fieldMappings: FieldMapping[]; conditions?: Array<{ field: string; operator: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE"; value: string; logicalOperator?: "AND" | "OR"; // 다음 조건과의 논리 연산자 }>; splitConfig?: { sourceField: string; delimiter: string; targetField: string; }; description?: string; } export interface FieldMapping { sourceField: string; targetField: string; transformFunction?: string; defaultValue?: string; } export interface ColumnInfo { columnName: string; columnLabel?: string; displayName?: string; dataType?: string; dbType?: string; webType?: string; isNullable?: string; columnDefault?: string; characterMaximumLength?: number; numericPrecision?: number; numericScale?: number; detailSettings?: string; codeCategory?: string; referenceTable?: string; referenceColumn?: string; isVisible?: string; displayOrder?: number; description?: string; } export interface TableDefinition { tableName: string; displayName?: string; description?: string; columns: ColumnInfo[]; } export interface TableInfo { tableName: string; displayName: string; description: string; columnCount: number; } export interface TableRelationship { relationship_id?: number; diagram_id?: number; // 새 관계도 생성 시에는 optional relationship_name: string; from_table_name: string; from_column_name: string; to_table_name: string; to_column_name: string; connection_type: "simple-key" | "data-save" | "external-call" | "conditional-link"; settings?: Record; company_code: string; is_active?: string; } // 데이터 연결 중계 테이블 타입 export interface DataBridge { bridgeId: number; relationshipId: number; fromTableName: string; fromColumnName: string; toTableName: string; toColumnName: string; connectionType: string; companyCode: string; createdAt: string; createdBy?: string; updatedAt: string; updatedBy?: string; isActive: string; bridgeData?: Record; relationship?: { relationshipName: string; relationshipType: string; connectionType: string; }; } // 테이블 데이터 조회 응답 타입 export interface TableDataResponse { data: Record[]; pagination: { page: number; limit: number; total: number; totalPages: number; hasNext: boolean; hasPrev: boolean; }; } // 관계도 정보 인터페이스 export interface DataFlowDiagram { diagramId: number; diagramName: string; connectionType: string; relationshipType: string; tableCount: number; relationshipCount: number; tables: string[]; companyCode: string; // 회사 코드 추가 // 조건부 연결 관련 필드 control?: ConditionControl; // 조건 설정 category?: ConnectionCategory; // 연결 종류 plan?: ExecutionPlan; // 실행 계획 createdAt: Date; createdBy: string; updatedAt: Date; updatedBy: string; } // 관계도 목록 응답 인터페이스 export interface DataFlowDiagramsResponse { diagrams: DataFlowDiagram[]; total: number; page: number; size: number; totalPages: number; hasNext: boolean; hasPrev: boolean; } // 노드 위치 정보 타입 export interface NodePosition { x: number; y: number; } export interface NodePositions { [tableName: string]: NodePosition; } // 새로운 JSON 기반 타입들 export interface JsonDataFlowDiagram { diagram_id: number; diagram_name: string; relationships: { relationships: JsonRelationship[]; tables: string[]; }; node_positions?: NodePositions; category?: Array<{ id: string; category: string; }>; control?: Array<{ id: string; triggerType: "insert" | "update" | "delete"; conditions: Array<{ id?: string; type?: string; field?: string; operator?: string; value?: unknown; logicalOperator?: string; groupId?: string; groupLevel?: number; }>; }>; plan?: Array<{ id: string; sourceTable: string; actions: Array<{ id: string; name: string; actionType: "insert" | "update" | "delete" | "upsert"; conditions?: Array<{ id: string; type: string; field?: string; operator?: string; value?: unknown; logicalOperator?: string; groupId?: string; groupLevel?: number; }>; fieldMappings: Array<{ sourceTable?: string; sourceField: string; targetTable?: string; targetField: string; defaultValue?: string; transformFunction?: string; }>; splitConfig?: { sourceField: string; delimiter: string; targetField: string; }; }>; }>; company_code: string; created_at?: string; updated_at?: string; created_by?: string; updated_by?: string; } export interface JsonRelationship { id: string; relationshipName: string; // 연결 이름 추가 fromTable: string; toTable: string; fromColumns: string[]; toColumns: string[]; connectionType: "simple-key" | "data-save" | "external-call"; note?: string; // 데이터 연결에 대한 설명 } export interface CreateDiagramRequest { diagram_name: string; relationships: { relationships: JsonRelationship[]; tables: string[]; }; node_positions?: NodePositions; // 🔥 수정: 각 관계별 정보를 배열로 저장 category?: Array<{ id: string; category: "simple-key" | "data-save" | "external-call"; }>; // 🔥 전체 실행 조건 - relationships의 id와 동일한 id 사용 control?: Array<{ id: string; // relationships의 id와 동일 triggerType: "insert" | "update" | "delete"; conditions: Array<{ id?: string; type?: string; field?: string; operator?: string; value?: unknown; dataType?: string; logicalOperator?: "AND" | "OR"; groupId?: string; groupLevel?: number; }>; }>; // 🔥 저장 액션 - relationships의 id와 동일한 id 사용 plan?: Array<{ id: string; // relationships의 id와 동일 sourceTable: string; actions: Array<{ id: string; name: string; actionType: "insert" | "update" | "delete" | "upsert"; fieldMappings: Array<{ sourceTable?: string; sourceField: string; targetTable?: string; targetField: string; defaultValue?: string; }>; splitConfig?: { sourceField: string; delimiter: string; targetField: string; }; conditions?: Array<{ id: string; type: string; field?: string; operator?: string; value?: unknown; dataType?: string; logicalOperator?: string; groupId?: string; groupLevel?: number; }>; }>; }>; } export interface JsonDataFlowDiagramsResponse { diagrams: JsonDataFlowDiagram[]; pagination: { page: number; size: number; total: number; totalPages: number; }; } // 테이블 간 데이터 관계 설정 API 클래스 export class DataFlowAPI { /** * 테이블 목록 조회 */ static async getTables(): Promise { try { const response = await apiClient.get>("/table-management/tables"); if (!response.data.success) { throw new Error(response.data.message || "테이블 목록 조회에 실패했습니다."); } return response.data.data || []; } catch (error) { console.error("테이블 목록 조회 오류:", error); throw error; } } /** * 테이블 컬럼 정보 조회 (모든 컬럼) */ static async getTableColumns(tableName: string): Promise { try { const response = await apiClient.get< ApiResponse<{ columns: ColumnInfo[]; page: number; total: number; totalPages: number; }> >(`/table-management/tables/${tableName}/columns?size=1000`); if (!response.data.success) { throw new Error(response.data.message || "컬럼 정보 조회에 실패했습니다."); } // 페이지네이션된 응답에서 columns 배열만 추출 const columns = response.data.data?.columns || []; // 이미 displayName에 라벨이 포함되어 있으므로 추가 처리 불필요 return columns.map((column) => ({ ...column, columnLabel: column.displayName || column.columnName, // displayName을 columnLabel로도 설정 })); } catch (error) { console.error("컬럼 정보 조회 오류:", error); throw error; } } /** * 테이블 정보와 컬럼 정보를 함께 조회 */ static async getTableWithColumns(tableName: string): Promise { try { const columns = await this.getTableColumns(tableName); // 테이블 라벨 정보 조회 let tableLabel = tableName; let tableDescription = `${tableName} 테이블`; try { const response = await apiClient.get(`/table-management/tables/${tableName}/labels`); if (response.data.success && response.data.data) { tableLabel = response.data.data.tableLabel || tableName; tableDescription = response.data.data.description || `${tableName} 테이블`; } } catch (error) { // 라벨 정보가 없으면 기본값 사용 (404 등의 에러는 무시) const axiosError = error as { response?: { status?: number } }; if (axiosError?.response?.status !== 404) { console.warn(`테이블 라벨 조회 중 예상치 못한 오류: ${tableName}`, error); } } // TableNode가 기대하는 컬럼 구조로 변환 const formattedColumns = columns.map((column) => ({ columnName: column.columnName, name: column.columnName, // TableNode에서 사용하는 필드 displayName: column.displayName, // 한국어 라벨 columnLabel: column.displayName, // 동일한 값으로 설정 type: column.dataType, // TableNode에서 사용하는 필드 dataType: column.dataType, description: column.description || "", })); return { tableName, displayName: tableLabel, description: tableDescription, columns: formattedColumns, }; } catch (error) { console.error("테이블 및 컬럼 정보 조회 오류:", error); throw error; } } /** * 테이블 관계 생성 */ static async createRelationship( relationship: Omit, // 백엔드 API 형식 (camelCase) ): Promise { try { const response = await apiClient.post>( "/dataflow/table-relationships", relationship, ); if (!response.data.success) { throw new Error(response.data.message || "관계 생성에 실패했습니다."); } return response.data.data!; } catch (error) { console.error("관계 생성 오류:", error); throw error; } } /** * 회사별 테이블 관계 목록 조회 */ static async getRelationshipsByCompany(companyCode: string): Promise { try { const response = await apiClient.get>("/dataflow/table-relationships", { params: { companyCode }, }); if (!response.data.success) { throw new Error(response.data.message || "관계 목록 조회에 실패했습니다."); } return response.data.data || []; } catch (error) { console.error("관계 목록 조회 오류:", error); throw error; } } /** * 테이블 관계 수정 */ static async updateRelationship( relationshipId: number, relationship: Partial, ): Promise { try { const response = await apiClient.put>( `/dataflow/table-relationships/${relationshipId}`, relationship, ); if (!response.data.success) { throw new Error(response.data.message || "관계 수정에 실패했습니다."); } return response.data.data!; } catch (error) { console.error("관계 수정 오류:", error); throw error; } } /** * 테이블 관계 삭제 */ static async deleteRelationship(relationshipId: number): Promise { try { const response = await apiClient.delete>(`/dataflow/table-relationships/${relationshipId}`); if (!response.data.success) { throw new Error(response.data.message || "관계 삭제에 실패했습니다."); } } catch (error) { console.error("관계 삭제 오류:", error); throw error; } } // ==================== 데이터 연결 관리 API ==================== /** * 데이터 연결 생성 */ static async createDataLink(linkData: { relationshipId: number; fromTableName: string; fromColumnName: string; toTableName: string; toColumnName: string; connectionType: string; bridgeData?: Record; }): Promise { try { const response = await apiClient.post>("/dataflow/data-links", linkData); if (!response.data.success) { throw new Error(response.data.message || "데이터 연결 생성에 실패했습니다."); } return response.data.data as DataBridge; } catch (error) { console.error("데이터 연결 생성 오류:", error); throw error; } } /** * 관계별 연결된 데이터 조회 */ static async getLinkedDataByRelationship(relationshipId: number): Promise { try { const response = await apiClient.get>( `/dataflow/data-links/relationship/${relationshipId}`, ); if (!response.data.success) { throw new Error(response.data.message || "연결된 데이터 조회에 실패했습니다."); } return response.data.data as DataBridge[]; } catch (error) { console.error("연결된 데이터 조회 오류:", error); throw error; } } /** * 데이터 연결 삭제 */ static async deleteDataLink(bridgeId: number): Promise { try { const response = await apiClient.delete>(`/dataflow/data-links/${bridgeId}`); if (!response.data.success) { throw new Error(response.data.message || "데이터 연결 삭제에 실패했습니다."); } } catch (error) { console.error("데이터 연결 삭제 오류:", error); throw error; } } // ==================== 테이블 데이터 조회 API ==================== /** * 테이블 실제 데이터 조회 (페이징) */ static async getTableData( tableName: string, page: number = 1, limit: number = 10, search: string = "", searchColumn: string = "", ): Promise { try { const params = new URLSearchParams({ page: page.toString(), limit: limit.toString(), ...(search && { search }), ...(searchColumn && { searchColumn }), }); const response = await apiClient.get>( `/dataflow/table-data/${tableName}?${params}`, ); if (!response.data.success) { throw new Error(response.data.message || "테이블 데이터 조회에 실패했습니다."); } return response.data.data as TableDataResponse; } catch (error) { console.error("테이블 데이터 조회 오류:", error); throw error; } } // ==================== 관계도 관리 ==================== // 관계도 목록 조회 static async getDataFlowDiagrams( page: number = 1, size: number = 20, searchTerm: string = "", ): Promise { try { const params = new URLSearchParams({ page: page.toString(), size: size.toString(), ...(searchTerm && { searchTerm }), }); const response = await apiClient.get>(`/dataflow/diagrams?${params}`); if (!response.data.success) { throw new Error(response.data.message || "관계도 목록 조회에 실패했습니다."); } return response.data.data as DataFlowDiagramsResponse; } catch (error) { console.error("관계도 목록 조회 오류:", error); throw error; } } // 특정 관계도의 모든 관계 조회 (관계도명으로) static async getDiagramRelationships(diagramName: string): Promise { try { const encodedDiagramName = encodeURIComponent(diagramName); const response = await apiClient.get>( `/dataflow/diagrams/${encodedDiagramName}/relationships`, ); if (!response.data.success) { throw new Error(response.data.message || "관계도 관계 조회에 실패했습니다."); } return response.data.data as TableRelationship[]; } catch (error) { console.error("관계도 관계 조회 오류:", error); throw error; } } // 관계도 복사 static async copyDiagram(diagramName: string): Promise { try { const encodedDiagramName = encodeURIComponent(diagramName); const response = await apiClient.post>( `/dataflow/diagrams/${encodedDiagramName}/copy`, ); if (!response.data.success) { throw new Error(response.data.message || "관계도 복사에 실패했습니다."); } return response.data.data?.newDiagramName || ""; } catch (error) { console.error("관계도 복사 오류:", error); throw error; } } // 관계도 삭제 static async deleteDiagram(diagramName: string): Promise { try { const encodedDiagramName = encodeURIComponent(diagramName); const response = await apiClient.delete>( `/dataflow/diagrams/${encodedDiagramName}`, ); if (!response.data.success) { throw new Error(response.data.message || "관계도 삭제에 실패했습니다."); } return response.data.data?.deletedCount || 0; } catch (error) { console.error("관계도 삭제 오류:", error); throw error; } } // 특정 관계도의 모든 관계 조회 (diagram_id로) - JSON 기반 시스템 static async getDiagramRelationshipsByDiagramId( diagramId: number, companyCode: string = "*", ): Promise { try { // 새로운 JSON 기반 시스템에서 관계도 조회 const jsonDiagram = await this.getJsonDataFlowDiagramById(diagramId, companyCode); if (!jsonDiagram || !jsonDiagram.relationships) { return []; } // JSON 관계를 TableRelationship 형식으로 변환 const relationshipsData = jsonDiagram.relationships as { relationships: JsonRelationship[]; tables: string[] }; const relationships: TableRelationship[] = relationshipsData.relationships.map((rel: JsonRelationship) => ({ relationship_id: 0, // JSON 기반에서는 개별 relationship_id가 없음 relationship_name: rel.relationshipName || rel.id || "관계", // relationshipName 우선 사용 from_table_name: rel.fromTable, to_table_name: rel.toTable, from_column_name: rel.fromColumns.join(","), to_column_name: rel.toColumns.join(","), connection_type: rel.connectionType || "simple-key", // 각 관계의 connectionType 사용 company_code: companyCode, // 실제 사용자 회사 코드 사용 created_at: jsonDiagram.created_at, updated_at: jsonDiagram.updated_at, created_by: jsonDiagram.created_by, updated_by: jsonDiagram.updated_by, })); return relationships; } catch (error) { console.error("관계도 관계 조회 오류:", error); throw error; } } // ==================== 새로운 JSON 기반 관계도 API ==================== /** * JSON 기반 관계도 목록 조회 */ static async getJsonDataFlowDiagrams( page: number = 1, size: number = 20, searchTerm: string = "", companyCode: string = "*", ): Promise { try { const params = new URLSearchParams({ page: page.toString(), size: size.toString(), companyCode: companyCode, ...(searchTerm && { searchTerm }), }); const response = await apiClient.get>(`/dataflow-diagrams?${params}`); if (!response.data.success) { throw new Error(response.data.message || "관계도 목록 조회에 실패했습니다."); } return response.data.data as JsonDataFlowDiagramsResponse; } catch (error) { console.error("JSON 관계도 목록 조회 오류:", error); throw error; } } /** * JSON 기반 특정 관계도 조회 */ static async getJsonDataFlowDiagramById(diagramId: number, companyCode: string = "*"): Promise { try { const params = new URLSearchParams({ companyCode: companyCode, }); const response = await apiClient.get>( `/dataflow-diagrams/${diagramId}?${params}`, ); if (!response.data.success) { throw new Error(response.data.message || "관계도 조회에 실패했습니다."); } return response.data.data as JsonDataFlowDiagram; } catch (error) { console.error("JSON 관계도 조회 오류:", error); throw error; } } /** * JSON 기반 관계도 생성 */ static async createJsonDataFlowDiagram( request: CreateDiagramRequest, companyCode: string = "*", userId: string = "SYSTEM", ): Promise { try { const requestWithUserInfo = { ...request, company_code: companyCode, created_by: userId, updated_by: userId, }; const response = await apiClient.post>( "/dataflow-diagrams", requestWithUserInfo, ); if (!response.data.success) { throw new Error(response.data.message || "관계도 생성에 실패했습니다."); } return response.data.data as JsonDataFlowDiagram; } catch (error) { console.error("JSON 관계도 생성 오류:", error); throw error; } } /** * JSON 기반 관계도 수정 */ static async updateJsonDataFlowDiagram( diagramId: number, request: Partial, companyCode: string = "*", userId: string = "SYSTEM", ): Promise { try { const params = new URLSearchParams({ companyCode: companyCode, }); const requestWithUserInfo = { ...request, updated_by: userId, }; const response = await apiClient.put>( `/dataflow-diagrams/${diagramId}?${params}`, requestWithUserInfo, ); if (!response.data.success) { throw new Error(response.data.message || "관계도 수정에 실패했습니다."); } return response.data.data as JsonDataFlowDiagram; } catch (error) { console.error("JSON 관계도 수정 오류:", error); throw error; } } /** * JSON 기반 관계도 삭제 */ static async deleteJsonDataFlowDiagram(diagramId: number, companyCode: string = "*"): Promise { try { const params = new URLSearchParams({ companyCode: companyCode, }); const response = await apiClient.delete>(`/dataflow-diagrams/${diagramId}?${params}`); if (!response.data.success) { throw new Error(response.data.message || "관계도 삭제에 실패했습니다."); } } catch (error) { console.error("JSON 관계도 삭제 오류:", error); throw error; } } /** * JSON 기반 관계도 복제 */ static async copyJsonDataFlowDiagram( diagramId: number, companyCode: string = "*", newName?: string, userId: string = "SYSTEM", ): Promise { try { const requestData = { companyCode: companyCode, userId: userId, ...(newName && { new_name: newName }), }; const response = await apiClient.post>( `/dataflow-diagrams/${diagramId}/copy`, requestData, ); if (!response.data.success) { throw new Error(response.data.message || "관계도 복제에 실패했습니다."); } return response.data.data as JsonDataFlowDiagram; } catch (error) { console.error("JSON 관계도 복제 오류:", error); throw error; } } }