diff --git a/backend-node/prisma/schema.prisma b/backend-node/prisma/schema.prisma index bb214604..d91d5603 100644 --- a/backend-node/prisma/schema.prisma +++ b/backend-node/prisma/schema.prisma @@ -5331,7 +5331,7 @@ model dataflow_diagrams { // 조건부 연결 관련 컬럼들 control Json? // 조건 설정 (트리거 타입, 조건 트리) - category Json? // 연결 종류 (타입, 기본 옵션) + category String? @db.VarChar(50) // 연결 종류 ("simple-key", "data-save", "external-call") plan Json? // 실행 계획 (대상 액션들) company_code String @db.VarChar(50) diff --git a/backend-node/src/controllers/dataflowDiagramController.ts b/backend-node/src/controllers/dataflowDiagramController.ts index 20634d64..56f24164 100644 --- a/backend-node/src/controllers/dataflowDiagramController.ts +++ b/backend-node/src/controllers/dataflowDiagramController.ts @@ -97,6 +97,10 @@ export const createDataflowDiagram = async (req: Request, res: Response) => { created_by, updated_by, } = req.body; + + logger.info(`새 관계도 생성 요청:`, { diagram_name, company_code }); + logger.info(`node_positions:`, node_positions); + logger.info(`전체 요청 Body:`, JSON.stringify(req.body, null, 2)); const companyCode = company_code || (req.query.companyCode as string) || @@ -162,6 +166,14 @@ export const updateDataflowDiagram = async (req: Request, res: Response) => { const userId = updated_by || (req.headers["x-user-id"] as string) || "SYSTEM"; + logger.info(`관계도 수정 요청 - ID: ${diagramId}, Company: ${companyCode}`); + logger.info(`요청 Body:`, JSON.stringify(req.body, null, 2)); + logger.info(`node_positions:`, req.body.node_positions); + logger.info(`요청 Body 키들:`, Object.keys(req.body)); + logger.info(`요청 Body 타입:`, typeof req.body); + logger.info(`node_positions 타입:`, typeof req.body.node_positions); + logger.info(`node_positions 값:`, req.body.node_positions); + if (isNaN(diagramId)) { return res.status(400).json({ success: false, diff --git a/backend-node/src/services/dataflowDiagramService.ts b/backend-node/src/services/dataflowDiagramService.ts index c477df93..d84de82c 100644 --- a/backend-node/src/services/dataflowDiagramService.ts +++ b/backend-node/src/services/dataflowDiagramService.ts @@ -1,4 +1,4 @@ -import { PrismaClient } from "@prisma/client"; +import { PrismaClient, Prisma } from "@prisma/client"; import { logger } from "../utils/logger"; const prisma = new PrismaClient(); @@ -6,13 +6,13 @@ const prisma = new PrismaClient(); // 타입 정의 interface CreateDataflowDiagramData { diagram_name: string; - relationships: any; // JSON 데이터 - node_positions?: any; // JSON 데이터 (노드 위치 정보) + relationships: Record; // JSON 데이터 + node_positions?: Record | null; // JSON 데이터 (노드 위치 정보) // 조건부 연결 관련 필드 - control?: any; // JSON 데이터 (조건 설정) - category?: any; // JSON 데이터 (연결 종류) - plan?: any; // JSON 데이터 (실행 계획) + control?: Record | null; // JSON 데이터 (조건 설정) + category?: string; // 연결 종류 ("simple-key", "data-save", "external-call") + plan?: Record | null; // JSON 데이터 (실행 계획) company_code: string; created_by: string; @@ -21,13 +21,13 @@ interface CreateDataflowDiagramData { interface UpdateDataflowDiagramData { diagram_name?: string; - relationships?: any; // JSON 데이터 - node_positions?: any; // JSON 데이터 (노드 위치 정보) + relationships?: Record; // JSON 데이터 + node_positions?: Record | null; // JSON 데이터 (노드 위치 정보) // 조건부 연결 관련 필드 - control?: any; // JSON 데이터 (조건 설정) - category?: any; // JSON 데이터 (연결 종류) - plan?: any; // JSON 데이터 (실행 계획) + control?: Record | null; // JSON 데이터 (조건 설정) + category?: string; // 연결 종류 ("simple-key", "data-save", "external-call") + plan?: Record | null; // JSON 데이터 (실행 계획) updated_by: string; } @@ -45,7 +45,13 @@ export const getDataflowDiagrams = async ( const offset = (page - 1) * size; // 검색 조건 구성 - const whereClause: any = {}; + const whereClause: { + company_code?: string; + diagram_name?: { + contains: string; + mode: "insensitive"; + }; + } = {}; // company_code가 '*'가 아닌 경우에만 필터링 if (companyCode !== "*") { @@ -99,7 +105,10 @@ export const getDataflowDiagramById = async ( companyCode: string ) => { try { - const whereClause: any = { + const whereClause: { + diagram_id: number; + company_code?: string; + } = { diagram_id: diagramId, }; @@ -129,8 +138,11 @@ export const createDataflowDiagram = async ( const newDiagram = await prisma.dataflow_diagrams.create({ data: { diagram_name: data.diagram_name, - relationships: data.relationships, - node_positions: data.node_positions || null, + relationships: data.relationships as Prisma.InputJsonValue, + node_positions: data.node_positions as + | Prisma.InputJsonValue + | undefined, + category: data.category || undefined, company_code: data.company_code, created_by: data.created_by, updated_by: data.updated_by, @@ -153,8 +165,15 @@ export const updateDataflowDiagram = async ( companyCode: string ) => { try { + logger.info( + `관계도 수정 서비스 시작 - ID: ${diagramId}, Company: ${companyCode}` + ); + // 먼저 해당 관계도가 존재하는지 확인 - const whereClause: any = { + const whereClause: { + diagram_id: number; + company_code?: string; + } = { diagram_id: diagramId, }; @@ -167,7 +186,15 @@ export const updateDataflowDiagram = async ( where: whereClause, }); + logger.info( + `기존 관계도 조회 결과:`, + existingDiagram ? `ID ${existingDiagram.diagram_id} 발견` : "관계도 없음" + ); + if (!existingDiagram) { + logger.warn( + `관계도 ID ${diagramId}를 찾을 수 없음 - Company: ${companyCode}` + ); return null; } @@ -178,10 +205,15 @@ export const updateDataflowDiagram = async ( }, data: { ...(data.diagram_name && { diagram_name: data.diagram_name }), - ...(data.relationships && { relationships: data.relationships }), - ...(data.node_positions !== undefined && { - node_positions: data.node_positions, + ...(data.relationships && { + relationships: data.relationships as Prisma.InputJsonValue, }), + ...(data.node_positions !== undefined && { + node_positions: data.node_positions + ? (data.node_positions as Prisma.InputJsonValue) + : Prisma.JsonNull, + }), + ...(data.category !== undefined && { category: data.category }), updated_by: data.updated_by, updated_at: new Date(), }, @@ -203,7 +235,10 @@ export const deleteDataflowDiagram = async ( ) => { try { // 먼저 해당 관계도가 존재하는지 확인 - const whereClause: any = { + const whereClause: { + diagram_id: number; + company_code?: string; + } = { diagram_id: diagramId, }; @@ -245,7 +280,10 @@ export const copyDataflowDiagram = async ( ) => { try { // 원본 관계도 조회 - const whereClause: any = { + const whereClause: { + diagram_id: number; + company_code?: string; + } = { diagram_id: diagramId, }; @@ -274,7 +312,12 @@ export const copyDataflowDiagram = async ( : originalDiagram.diagram_name; // 같은 패턴의 이름들을 찾아서 가장 큰 번호 찾기 - const copyWhereClause: any = { + const copyWhereClause: { + diagram_name: { + startsWith: string; + }; + company_code?: string; + } = { diagram_name: { startsWith: baseName, }, @@ -310,7 +353,11 @@ export const copyDataflowDiagram = async ( const copiedDiagram = await prisma.dataflow_diagrams.create({ data: { diagram_name: copyName, - relationships: originalDiagram.relationships as any, + relationships: originalDiagram.relationships as Prisma.InputJsonValue, + node_positions: originalDiagram.node_positions + ? (originalDiagram.node_positions as Prisma.InputJsonValue) + : Prisma.JsonNull, + category: originalDiagram.category || undefined, company_code: companyCode, created_by: userId, updated_by: userId, diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx index bcd1721a..f525c046 100644 --- a/frontend/components/dataflow/ConnectionSetupModal.tsx +++ b/frontend/components/dataflow/ConnectionSetupModal.tsx @@ -34,7 +34,6 @@ interface ConnectionInfo { }; existingRelationship?: { relationshipName: string; - relationshipType: string; connectionType: string; settings?: Record; }; @@ -43,12 +42,10 @@ interface ConnectionInfo { // 연결 설정 타입 interface ConnectionConfig { relationshipName: string; - relationshipType: "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many"; connectionType: "simple-key" | "data-save" | "external-call"; fromColumnName: string; toColumnName: string; settings?: Record; - description?: string; } // 단순 키값 연결 설정 @@ -105,11 +102,9 @@ export const ConnectionSetupModal: React.FC = ({ }) => { const [config, setConfig] = useState({ relationshipName: "", - relationshipType: "one-to-one", connectionType: "simple-key", fromColumnName: "", toColumnName: "", - description: "", settings: {}, }); @@ -177,14 +172,9 @@ export const ConnectionSetupModal: React.FC = ({ const existingRel = connection.existingRelationship; setConfig({ relationshipName: existingRel?.relationshipName || `${fromDisplayName} → ${toDisplayName}`, - relationshipType: - (existingRel?.relationshipType as "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many") || - "one-to-one", connectionType: (existingRel?.connectionType as "simple-key" | "data-save" | "external-call") || "simple-key", fromColumnName: "", toColumnName: "", - description: - (existingRel?.settings?.description as string) || `${fromDisplayName}과 ${toDisplayName} 간의 데이터 관계`, settings: existingRel?.settings || {}, }); @@ -333,10 +323,12 @@ export const ConnectionSetupModal: React.FC = ({ break; } - // 선택된 컬럼들 검증 - if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) { - toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요."); - return; + // 단순 키값 연결일 때만 컬럼 선택 검증 + if (config.connectionType === "simple-key") { + if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) { + toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요."); + return; + } } // 선택된 테이블과 컬럼 정보 사용 @@ -377,31 +369,24 @@ export const ConnectionSetupModal: React.FC = ({ } : {}; + // 컬럼 정보는 단순 키값 연결일 때만 사용 + const finalFromColumns = config.connectionType === "simple-key" ? selectedFromColumns : []; + const finalToColumns = config.connectionType === "simple-key" ? selectedToColumns : []; + // 메모리 기반 시스템: 관계 데이터만 생성하여 부모로 전달 const relationshipData: TableRelationship = { relationship_name: config.relationshipName, from_table_name: fromTableName, to_table_name: toTableName, - from_column_name: selectedFromColumns.join(","), // 여러 컬럼을 콤마로 구분 - to_column_name: selectedToColumns.join(","), // 여러 컬럼을 콤마로 구분 - relationship_type: config.relationshipType, + from_column_name: finalFromColumns.join(","), // 여러 컬럼을 콤마로 구분 + to_column_name: finalToColumns.join(","), // 여러 컬럼을 콤마로 구분 connection_type: config.connectionType, company_code: companyCode, settings: { ...settings, ...conditionalSettings, // 조건부 연결 설정 추가 - description: config.description, - multiColumnMapping: { - fromColumns: selectedFromColumns, - toColumns: selectedToColumns, - fromTable: fromTableName, - toTable: toTableName, - }, - isMultiColumn: selectedFromColumns.length > 1 || selectedToColumns.length > 1, - columnCount: { - from: selectedFromColumns.length, - to: selectedToColumns.length, - }, + // 중복 제거: multiColumnMapping, isMultiColumn, columnCount, description 제거 + // 필요시 from_column_name, to_column_name에서 split으로 추출 가능 }, }; @@ -415,11 +400,9 @@ export const ConnectionSetupModal: React.FC = ({ const handleCancel = () => { setConfig({ relationshipName: "", - relationshipType: "one-to-one", connectionType: "simple-key", fromColumnName: "", toColumnName: "", - description: "", }); onCancel(); }; @@ -791,7 +774,7 @@ export const ConnectionSetupModal: React.FC = ({ return ( { const newActions = [...dataSaveSettings.actions]; newActions[actionIndex].conditions![condIndex].value = e.target.value; @@ -804,7 +787,7 @@ export const ConnectionSetupModal: React.FC = ({ return ( { const newActions = [...dataSaveSettings.actions]; newActions[actionIndex].conditions![condIndex].value = e.target.value; @@ -817,7 +800,7 @@ export const ConnectionSetupModal: React.FC = ({ return ( { const newActions = [...dataSaveSettings.actions]; newActions[actionIndex].conditions![condIndex].value = e.target.value; @@ -837,7 +820,7 @@ export const ConnectionSetupModal: React.FC = ({ { const newActions = [...dataSaveSettings.actions]; newActions[actionIndex].conditions![condIndex].value = e.target.value; @@ -849,7 +832,7 @@ export const ConnectionSetupModal: React.FC = ({ } else if (dataType.includes("bool")) { return ( { const newActions = [...dataSaveSettings.actions]; newActions[actionIndex].conditions![condIndex].value = e.target.value; @@ -1071,7 +1054,7 @@ export const ConnectionSetupModal: React.FC = ({ return ( updateCondition(index, "value", e.target.value)} className="h-8 flex-1 text-xs" /> @@ -1080,7 +1063,7 @@ export const ConnectionSetupModal: React.FC = ({ return ( updateCondition(index, "value", e.target.value)} className="h-8 flex-1 text-xs" /> @@ -1089,7 +1072,7 @@ export const ConnectionSetupModal: React.FC = ({ return ( updateCondition(index, "value", e.target.value)} className="h-8 flex-1 text-xs" /> @@ -1105,7 +1088,7 @@ export const ConnectionSetupModal: React.FC = ({ updateCondition(index, "value", e.target.value)} className="h-8 flex-1 text-xs" /> @@ -1113,7 +1096,7 @@ export const ConnectionSetupModal: React.FC = ({ } else if (dataType.includes("bool")) { return ( updateCondition(index, "value", e.target.value)} className="h-8 flex-1 text-xs" /> @@ -1157,24 +1140,151 @@ export const ConnectionSetupModal: React.FC = ({ switch (config.connectionType) { case "simple-key": return ( -
-
- - 단순 키값 연결 설정 +
+ {/* 테이블 및 컬럼 선택 */} +
+
테이블 및 컬럼 선택
+ + {/* 현재 선택된 테이블 표시 */} +
+
+ +
+ + {availableTables.find((t) => t.tableName === selectedFromTable)?.displayName || selectedFromTable} + + ({selectedFromTable}) +
+
+ +
+ +
+ + {availableTables.find((t) => t.tableName === selectedToTable)?.displayName || selectedToTable} + + ({selectedToTable}) +
+
+
+ + {/* 컬럼 선택 */} +
+
+ +
+ {fromTableColumns.map((column) => ( + + ))} + {fromTableColumns.length === 0 && ( +
+ {selectedFromTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} +
+ )} +
+
+ +
+ +
+ {toTableColumns.map((column) => ( + + ))} + {toTableColumns.length === 0 && ( +
+ {selectedToTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} +
+ )} +
+
+
+ + {/* 선택된 컬럼 미리보기 */} + {(selectedFromColumns.length > 0 || selectedToColumns.length > 0) && ( +
+
+ +
+ {selectedFromColumns.length > 0 ? ( + selectedFromColumns.map((column) => ( + + {column} + + )) + ) : ( + 선택된 컬럼 없음 + )} +
+
+ +
+ +
+ {selectedToColumns.length > 0 ? ( + selectedToColumns.map((column) => ( + + {column} + + )) + ) : ( + 선택된 컬럼 없음 + )} +
+
+
+ )}
-
-
- -