import { apiClient, ApiResponse } from "./client"; // 테이블 간 데이터 관계 설정 관련 타입 정의 // 조건부 연결 관련 타입들 export interface ConditionControl { triggerType: "insert" | "update" | "delete" | "insert_update"; conditionTree: ConditionNode; } export interface ConditionNode { type: "group" | "condition"; operator?: "AND" | "OR"; children?: ConditionNode[]; field?: string; operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE"; value?: any; dataType?: string; } 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; relationship_type: "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many"; 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; 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[]; relationshipType: string; connectionType: string; settings?: any; } export interface CreateDiagramRequest { diagram_name: string; relationships: { relationships: JsonRelationship[]; tables: string[]; }; node_positions?: NodePositions; } 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`); if (!response.data.success) { throw new Error(response.data.message || "컬럼 정보 조회에 실패했습니다."); } // 페이지네이션된 응답에서 columns 배열만 추출 return response.data.data?.columns || []; } catch (error) { console.error("컬럼 정보 조회 오류:", error); throw error; } } /** * 테이블 정보와 컬럼 정보를 함께 조회 */ static async getTableWithColumns(tableName: string): Promise { try { const columns = await this.getTableColumns(tableName); return { tableName, displayName: tableName, description: `${tableName} 테이블`, columns, }; } catch (error) { console.error("테이블 및 컬럼 정보 조회 오류:", error); throw error; } } /** * 테이블 관계 생성 */ static async createRelationship( relationship: any, // 백엔드 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(","), relationship_type: rel.relationshipType as "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many", connection_type: rel.connectionType as "simple-key" | "data-save" | "external-call", company_code: companyCode, // 실제 사용자 회사 코드 사용 settings: rel.settings || {}, 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; } } }