diff --git a/backend-node/src/routes/dataflow/node-flows.ts b/backend-node/src/routes/dataflow/node-flows.ts index 6de84866..177b4304 100644 --- a/backend-node/src/routes/dataflow/node-flows.ts +++ b/backend-node/src/routes/dataflow/node-flows.ts @@ -214,6 +214,73 @@ router.delete("/:flowId", async (req: Request, res: Response) => { } }); +/** + * 플로우 소스 테이블 조회 + * GET /api/dataflow/node-flows/:flowId/source-table + * 플로우의 첫 번째 소스 노드(tableSource, externalDBSource)에서 테이블명 추출 + */ +router.get("/:flowId/source-table", async (req: Request, res: Response) => { + try { + const { flowId } = req.params; + + const flow = await queryOne<{ flow_data: any }>( + `SELECT flow_data FROM node_flows WHERE flow_id = $1`, + [flowId] + ); + + if (!flow) { + return res.status(404).json({ + success: false, + message: "플로우를 찾을 수 없습니다.", + }); + } + + const flowData = + typeof flow.flow_data === "string" + ? JSON.parse(flow.flow_data) + : flow.flow_data; + + const nodes = flowData.nodes || []; + + // 소스 노드 찾기 (tableSource, externalDBSource 타입) + const sourceNode = nodes.find( + (node: any) => + node.type === "tableSource" || node.type === "externalDBSource" + ); + + if (!sourceNode || !sourceNode.data?.tableName) { + return res.json({ + success: true, + data: { + sourceTable: null, + sourceNodeType: null, + message: "소스 노드가 없거나 테이블명이 설정되지 않았습니다.", + }, + }); + } + + logger.info( + `플로우 소스 테이블 조회: flowId=${flowId}, table=${sourceNode.data.tableName}` + ); + + return res.json({ + success: true, + data: { + sourceTable: sourceNode.data.tableName, + sourceNodeType: sourceNode.type, + sourceNodeId: sourceNode.id, + displayName: sourceNode.data.displayName, + }, + }); + } catch (error) { + logger.error("플로우 소스 테이블 조회 실패:", error); + return res.status(500).json({ + success: false, + message: "플로우 소스 테이블을 조회하지 못했습니다.", + }); + } +}); + /** * 플로우 실행 * POST /api/dataflow/node-flows/:flowId/execute diff --git a/frontend/lib/api/nodeFlows.ts b/frontend/lib/api/nodeFlows.ts index b42340d7..27bb1b96 100644 --- a/frontend/lib/api/nodeFlows.ts +++ b/frontend/lib/api/nodeFlows.ts @@ -120,3 +120,41 @@ export interface NodeExecutionSummary { duration?: number; error?: string; } + +/** + * 플로우 소스 테이블 정보 인터페이스 + */ +export interface FlowSourceTableInfo { + sourceTable: string | null; + sourceNodeType: string | null; + sourceNodeId?: string; + displayName?: string; + message?: string; +} + +/** + * 플로우 소스 테이블 조회 + * 플로우의 첫 번째 소스 노드(tableSource, externalDBSource)에서 테이블명 추출 + */ +export async function getFlowSourceTable(flowId: number): Promise { + try { + const response = await apiClient.get>( + `/dataflow/node-flows/${flowId}/source-table`, + ); + if (response.data.success && response.data.data) { + return response.data.data; + } + return { + sourceTable: null, + sourceNodeType: null, + message: response.data.message || "소스 테이블 정보를 가져올 수 없습니다.", + }; + } catch (error) { + console.error("플로우 소스 테이블 조회 실패:", error); + return { + sourceTable: null, + sourceNodeType: null, + message: "API 호출 중 오류가 발생했습니다.", + }; + } +} diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 944f7126..327cb87f 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -1864,6 +1864,84 @@ export class ButtonActionExecutor { console.log(`✅ [handleUniversalFormModalTableSectionSave] 완료: ${resultMessage}`); toast.success(`저장 완료: ${resultMessage}`); + // 🆕 저장 성공 후 제어 관리 실행 (다중 테이블 저장 시 소스 테이블과 일치하는 섹션만 실행) + if (config.enableDataflowControl && config.dataflowConfig?.flowConfig?.flowId) { + const flowId = config.dataflowConfig.flowConfig.flowId; + console.log("🎯 [handleUniversalFormModalTableSectionSave] 제어 관리 실행 시작:", { flowId }); + + try { + // 플로우 소스 테이블 조회 + const { getFlowSourceTable } = await import("@/lib/api/nodeFlows"); + const flowSourceInfo = await getFlowSourceTable(flowId); + + console.log("📊 [handleUniversalFormModalTableSectionSave] 플로우 소스 테이블:", flowSourceInfo); + + if (flowSourceInfo.sourceTable) { + // 각 섹션 확인하여 소스 테이블과 일치하는 섹션 찾기 + let controlExecuted = false; + + for (const [sectionId, sectionItems] of Object.entries(tableSectionData)) { + const sectionConfig = sections.find((s: any) => s.id === sectionId); + const sectionTargetTable = sectionConfig?.tableConfig?.saveConfig?.targetTable || tableName; + + console.log(`🔍 [handleUniversalFormModalTableSectionSave] 섹션 ${sectionId} 테이블 비교:`, { + sectionTargetTable, + flowSourceTable: flowSourceInfo.sourceTable, + isMatch: sectionTargetTable === flowSourceInfo.sourceTable, + }); + + // 소스 테이블과 일치하는 섹션만 제어 실행 + if (sectionTargetTable === flowSourceInfo.sourceTable && sectionItems.length > 0) { + console.log( + `✅ [handleUniversalFormModalTableSectionSave] 섹션 ${sectionId} → 플로우 소스 테이블 일치! 제어 실행`, + ); + + // 공통 필드 + 해당 섹션 데이터 병합하여 sourceData 생성 + const sourceData = sectionItems.map((item: any) => ({ + ...commonFieldsData, + ...item, + })); + + console.log( + `📦 [handleUniversalFormModalTableSectionSave] 제어 전달 데이터: ${sourceData.length}건`, + sourceData[0], + ); + + // 제어 관리용 컨텍스트 생성 + const controlContext: ButtonActionContext = { + ...context, + selectedRowsData: sourceData, + formData: commonFieldsData, + }; + + // 제어 관리 실행 + await this.executeAfterSaveControl(config, controlContext); + controlExecuted = true; + break; // 첫 번째 매칭 섹션만 실행 + } + } + + // 매칭되는 섹션이 없으면 메인 테이블 확인 + if (!controlExecuted && tableName === flowSourceInfo.sourceTable) { + console.log("✅ [handleUniversalFormModalTableSectionSave] 메인 테이블 일치! 공통 필드로 제어 실행"); + + const controlContext: ButtonActionContext = { + ...context, + selectedRowsData: [commonFieldsData], + formData: commonFieldsData, + }; + + await this.executeAfterSaveControl(config, controlContext); + } + } else { + console.log("⚠️ [handleUniversalFormModalTableSectionSave] 플로우 소스 테이블 없음 - 제어 스킵"); + } + } catch (controlError) { + console.error("❌ [handleUniversalFormModalTableSectionSave] 제어 관리 실행 오류:", controlError); + // 제어 관리 실패는 저장 성공에 영향주지 않음 + } + } + // 저장 성공 이벤트 발생 window.dispatchEvent(new CustomEvent("saveSuccess")); window.dispatchEvent(new CustomEvent("refreshTable"));