diff --git a/backend-node/prisma/schema.prisma b/backend-node/prisma/schema.prisma index d91d5603..972156a3 100644 --- a/backend-node/prisma/schema.prisma +++ b/backend-node/prisma/schema.prisma @@ -5331,7 +5331,7 @@ model dataflow_diagrams { // 조건부 연결 관련 컬럼들 control Json? // 조건 설정 (트리거 타입, 조건 트리) - category String? @db.VarChar(50) // 연결 종류 ("simple-key", "data-save", "external-call") + category Json? // 연결 종류 배열 (["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 56f24164..647a4317 100644 --- a/backend-node/src/controllers/dataflowDiagramController.ts +++ b/backend-node/src/controllers/dataflowDiagramController.ts @@ -93,6 +93,9 @@ export const createDataflowDiagram = async (req: Request, res: Response) => { diagram_name, relationships, node_positions, + category, + control, + plan, company_code, created_by, updated_by, @@ -100,6 +103,9 @@ export const createDataflowDiagram = async (req: Request, res: Response) => { logger.info(`새 관계도 생성 요청:`, { diagram_name, company_code }); logger.info(`node_positions:`, node_positions); + logger.info(`category:`, category); + logger.info(`control:`, control); + logger.info(`plan:`, plan); logger.info(`전체 요청 Body:`, JSON.stringify(req.body, null, 2)); const companyCode = company_code || @@ -119,10 +125,27 @@ export const createDataflowDiagram = async (req: Request, res: Response) => { }); } + // 🔍 백엔드에서 받은 실제 데이터 로깅 + console.log( + "🔍 백엔드에서 받은 control 데이터:", + JSON.stringify(control, null, 2) + ); + console.log( + "🔍 백엔드에서 받은 plan 데이터:", + JSON.stringify(plan, null, 2) + ); + console.log( + "🔍 백엔드에서 받은 category 데이터:", + JSON.stringify(category, null, 2) + ); + const newDiagram = await createDataflowDiagramService({ diagram_name, relationships, node_positions, + category, + control, + plan, company_code: companyCode, created_by: userId, updated_by: userId, diff --git a/backend-node/src/services/dataflowDiagramService.ts b/backend-node/src/services/dataflowDiagramService.ts index d84de82c..c391ef4e 100644 --- a/backend-node/src/services/dataflowDiagramService.ts +++ b/backend-node/src/services/dataflowDiagramService.ts @@ -9,10 +9,10 @@ interface CreateDataflowDiagramData { relationships: Record; // JSON 데이터 node_positions?: Record | null; // JSON 데이터 (노드 위치 정보) - // 조건부 연결 관련 필드 - control?: Record | null; // JSON 데이터 (조건 설정) - category?: string; // 연결 종류 ("simple-key", "data-save", "external-call") - plan?: Record | null; // JSON 데이터 (실행 계획) + // 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드 + control?: Array> | null; // JSON 배열 (각 관계별 조건 설정) + category?: Array> | null; // JSON 배열 (각 관계별 연결 종류) + plan?: Array> | null; // JSON 배열 (각 관계별 실행 계획) company_code: string; created_by: string; @@ -24,10 +24,10 @@ interface UpdateDataflowDiagramData { relationships?: Record; // JSON 데이터 node_positions?: Record | null; // JSON 데이터 (노드 위치 정보) - // 조건부 연결 관련 필드 - control?: Record | null; // JSON 데이터 (조건 설정) - category?: string; // 연결 종류 ("simple-key", "data-save", "external-call") - plan?: Record | null; // JSON 데이터 (실행 계획) + // 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드 + control?: Array> | null; // JSON 배열 (각 관계별 조건 설정) + category?: Array> | null; // JSON 배열 (각 관계별 연결 종류) + plan?: Array> | null; // JSON 배열 (각 관계별 실행 계획) updated_by: string; } @@ -142,7 +142,11 @@ export const createDataflowDiagram = async ( node_positions: data.node_positions as | Prisma.InputJsonValue | undefined, - category: data.category || undefined, + category: data.category + ? (data.category as Prisma.InputJsonValue) + : undefined, + control: data.control as Prisma.InputJsonValue | undefined, + plan: data.plan as Prisma.InputJsonValue | undefined, company_code: data.company_code, created_by: data.created_by, updated_by: data.updated_by, @@ -213,7 +217,17 @@ export const updateDataflowDiagram = async ( ? (data.node_positions as Prisma.InputJsonValue) : Prisma.JsonNull, }), - ...(data.category !== undefined && { category: data.category }), + ...(data.category !== undefined && { + category: data.category + ? (data.category as Prisma.InputJsonValue) + : undefined, + }), + ...(data.control !== undefined && { + control: data.control as Prisma.InputJsonValue | undefined, + }), + ...(data.plan !== undefined && { + plan: data.plan as Prisma.InputJsonValue | undefined, + }), updated_by: data.updated_by, updated_at: new Date(), }, diff --git a/backend-node/src/services/eventTriggerService.ts b/backend-node/src/services/eventTriggerService.ts index 03ded0a4..6a26fcb5 100644 --- a/backend-node/src/services/eventTriggerService.ts +++ b/backend-node/src/services/eventTriggerService.ts @@ -91,34 +91,48 @@ export class EventTriggerService { const results: ExecutionResult[] = []; try { - // 해당 테이블과 트리거 타입에 맞는 조건부 연결들 조회 - const diagrams = await prisma.dataflow_diagrams.findMany({ - where: { - company_code: companyCode, - control: { - path: ["triggerType"], - equals: - triggerType === "insert" - ? "insert" - : triggerType === "update" - ? ["update", "insert_update"] - : triggerType === "delete" - ? "delete" - : triggerType, - }, - plan: { - path: ["sourceTable"], - equals: tableName, - }, - }, + // 🔥 수정: Raw SQL을 사용하여 JSON 배열 검색 + const diagrams = (await prisma.$queryRaw` + SELECT * FROM dataflow_diagrams + WHERE company_code = ${companyCode} + AND ( + category::text = '"data-save"' OR + category::jsonb ? 'data-save' OR + category::jsonb @> '["data-save"]' + ) + `) as any[]; + + // 각 관계도에서 해당 테이블을 소스로 하는 데이터 저장 관계들 필터링 + const matchingDiagrams = diagrams.filter((diagram) => { + // category 배열에서 data-save 연결이 있는지 확인 + const categories = diagram.category as any[]; + const hasDataSave = Array.isArray(categories) + ? categories.some((cat) => cat.category === "data-save") + : false; + + if (!hasDataSave) return false; + + // plan 배열에서 해당 테이블을 소스로 하는 항목이 있는지 확인 + const plans = diagram.plan as any[]; + const hasMatchingPlan = Array.isArray(plans) + ? plans.some((plan) => plan.sourceTable === tableName) + : false; + + // control 배열에서 해당 트리거 타입이 있는지 확인 + const controls = diagram.control as any[]; + const hasMatchingControl = Array.isArray(controls) + ? controls.some((control) => control.triggerType === triggerType) + : false; + + return hasDataSave && hasMatchingPlan && hasMatchingControl; }); logger.info( - `Found ${diagrams.length} conditional connections for table ${tableName} with trigger ${triggerType}` + `Found ${matchingDiagrams.length} matching data-save connections for table ${tableName} with trigger ${triggerType}` ); // 각 다이어그램에 대해 조건부 연결 실행 - for (const diagram of diagrams) { + for (const diagram of matchingDiagrams) { try { const result = await this.executeDiagramTrigger( diagram, diff --git a/frontend/components/dataflow/ConnectionSetupModal.tsx b/frontend/components/dataflow/ConnectionSetupModal.tsx index f525c046..de76be9a 100644 --- a/frontend/components/dataflow/ConnectionSetupModal.tsx +++ b/frontend/components/dataflow/ConnectionSetupModal.tsx @@ -62,9 +62,9 @@ interface DataSaveSettings { conditions?: ConditionNode[]; fieldMappings: Array<{ sourceTable?: string; - sourceField: string; + sourceField: string; targetTable?: string; - targetField: string; + targetField: string; defaultValue?: string; transformFunction?: string; }>; @@ -188,6 +188,10 @@ export const ConnectionSetupModal: React.FC = ({ actions: [], }); + // 🔥 필드 선택 상태 초기화 + setSelectedFromColumns([]); + setSelectedToColumns([]); + // 외부 호출 기본값 설정 setExternalCallSettings({ callType: "rest-api", @@ -325,9 +329,9 @@ export const ConnectionSetupModal: React.FC = ({ // 단순 키값 연결일 때만 컬럼 선택 검증 if (config.connectionType === "simple-key") { - if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) { - toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요."); - return; + if (selectedFromColumns.length === 0 || selectedToColumns.length === 0) { + toast.error("선택된 컬럼이 없습니다. From과 To 테이블에서 각각 최소 1개 이상의 컬럼을 선택해주세요."); + return; } } @@ -622,7 +626,7 @@ export const ConnectionSetupModal: React.FC = ({ const renderActionCondition = (condition: ConditionNode, condIndex: number, actionIndex: number) => { // 그룹 시작 렌더링 if (condition.type === "group-start") { - return ( + return (
{/* 그룹 시작 앞의 논리 연산자 */} {condIndex > 0 && ( @@ -663,8 +667,8 @@ export const ConnectionSetupModal: React.FC = ({ > -
- + + ); } @@ -692,13 +696,13 @@ export const ConnectionSetupModal: React.FC = ({ > + - - ); + ); } // 일반 조건 렌더링 (기존 로직 간소화) - return ( + return (
{/* 그룹 내 첫 번째 조건이 아닐 때만 논리 연산자 표시 */} {condIndex > 0 && dataSaveSettings.actions[actionIndex].conditions![condIndex - 1]?.type !== "group-start" && ( @@ -772,7 +776,7 @@ export const ConnectionSetupModal: React.FC = ({ if (dataType.includes("timestamp") || dataType.includes("datetime") || dataType.includes("date")) { return ( - { @@ -785,7 +789,7 @@ export const ConnectionSetupModal: React.FC = ({ ); } else if (dataType.includes("time")) { return ( - { @@ -876,9 +880,9 @@ export const ConnectionSetupModal: React.FC = ({ className="h-6 w-6 p-0" > - -
- + + + ); }; @@ -889,7 +893,7 @@ export const ConnectionSetupModal: React.FC = ({
전체 실행 조건 (언제 이 연결이 동작할지) -
+ {/* 실행 조건 설정 */}
@@ -906,7 +910,7 @@ export const ConnectionSetupModal: React.FC = ({ -
+ {/* 조건 목록 */} @@ -953,14 +957,14 @@ export const ConnectionSetupModal: React.FC = ({ > - - - ); + + + ); } // 그룹 끝 렌더링 if (condition.type === "group-end") { - return ( + return (
= ({ > -
+
); } @@ -991,13 +995,13 @@ export const ConnectionSetupModal: React.FC = ({ onValueChange={(value: "AND" | "OR") => updateCondition(index - 1, "logicalOperator", value)} > - - - + + + AND OR - - + + )} {/* 그룹 레벨에 따른 들여쓰기와 조건 필드들 */} @@ -1052,7 +1056,7 @@ export const ConnectionSetupModal: React.FC = ({ dataType.includes("date") ) { return ( - updateCondition(index, "value", e.target.value)} @@ -1095,18 +1099,18 @@ export const ConnectionSetupModal: React.FC = ({ ); } else if (dataType.includes("bool")) { return ( - + + ); } else { return ( @@ -1124,147 +1128,147 @@ export const ConnectionSetupModal: React.FC = ({ - + ); }) - )} + )} - - - ); + + + ); }; // 연결 종류별 설정 패널 렌더링 const renderConnectionTypeSettings = () => { switch (config.connectionType) { case "simple-key": - return ( -
- {/* 테이블 및 컬럼 선택 */} -
-
테이블 및 컬럼 선택
+ return ( +
+ {/* 테이블 및 컬럼 선택 */} +
+
테이블 및 컬럼 선택
- {/* 현재 선택된 테이블 표시 */} -
-
- -
- - {availableTables.find((t) => t.tableName === selectedFromTable)?.displayName || selectedFromTable} - - ({selectedFromTable}) -
-
- -
- -
- - {availableTables.find((t) => t.tableName === selectedToTable)?.displayName || selectedToTable} - - ({selectedToTable}) -
+ {/* 현재 선택된 테이블 표시 */} +
+
+ +
+ + {availableTables.find((t) => t.tableName === selectedFromTable)?.displayName || selectedFromTable} + + ({selectedFromTable})
- {/* 컬럼 선택 */} -
-
- -
- {fromTableColumns.map((column) => ( - - ))} - {fromTableColumns.length === 0 && ( -
- {selectedFromTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} -
- )} -
-
- -
- -
- {toTableColumns.map((column) => ( - - ))} - {toTableColumns.length === 0 && ( -
- {selectedToTable ? "컬럼을 불러오는 중..." : "테이블을 먼저 선택해주세요"} -
- )} -
+
+ +
+ + {availableTables.find((t) => t.tableName === selectedToTable)?.displayName || selectedToTable} + + ({selectedToTable})
- - {/* 선택된 컬럼 미리보기 */} - {(selectedFromColumns.length > 0 || selectedToColumns.length > 0) && ( -
-
- -
- {selectedFromColumns.length > 0 ? ( - selectedFromColumns.map((column) => ( - - {column} - - )) - ) : ( - 선택된 컬럼 없음 - )} -
-
- -
- -
- {selectedToColumns.length > 0 ? ( - selectedToColumns.map((column) => ( - - {column} - - )) - ) : ( - 선택된 컬럼 없음 - )} -
-
-
- )}
+ {/* 컬럼 선택 */} +
+
+ +
+ {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} + + )) + ) : ( + 선택된 컬럼 없음 + )} +
+
+
+ )} +
+ {/* 단순 키값 연결 설정 */}
@@ -1272,7 +1276,7 @@ export const ConnectionSetupModal: React.FC = ({ 단순 키값 연결 설정
-
+
@@ -1339,7 +1343,7 @@ export const ConnectionSetupModal: React.FC = ({ {dataSaveSettings.actions.map((action, actionIndex) => (
- { const newActions = [...dataSaveSettings.actions]; @@ -1629,18 +1633,26 @@ export const ConnectionSetupModal: React.FC = ({ {/* 소스 */}
+ {/* 🔥 FROM 테이블 클리어 버튼 */} + {mapping.sourceTable && ( + + )} . setExternalCallSettings({ ...externalCallSettings, httpMethod: value }) - } - > - - - - + } + > + + + + GET POST PUT DELETE - - -
+ + +