/** * 노드 플로우 기반 버튼 실행기 */ import { executeNodeFlow, ExecutionResult } from "../api/nodeFlows"; import { logger } from "../utils/logger"; import { toast } from "sonner"; import type { ButtonDataflowConfig, ExtendedControlContext } from "@/types/control-management"; export interface ButtonExecutionContext { buttonId: string; screenId?: number; companyCode?: string; userId?: string; formData: Record; selectedRows?: any[]; selectedRowsData?: Record[]; controlDataSource?: "form" | "table-selection" | "table-all" | "flow-selection" | "flow-step-all" | "both" | "all-sources"; // 🆕 테이블 전체 데이터 (table-all 모드용) tableAllData?: Record[]; // 🆕 플로우 스텝 전체 데이터 (flow-step-all 모드용) flowStepAllData?: Record[]; flowStepId?: number; // 🆕 플로우 선택 데이터 (flow-selection 모드용) flowSelectedData?: Record[]; onRefresh?: () => void; onClose?: () => void; } export interface FlowExecutionResult { success: boolean; message: string; executionTime?: number; data?: ExecutionResult; } /** * 노드 플로우 실행 함수 */ export async function executeButtonWithFlow( flowConfig: ButtonDataflowConfig["flowConfig"], context: ButtonExecutionContext, originalAction?: () => Promise, ): Promise { if (!flowConfig) { throw new Error("플로우 설정이 없습니다."); } const { flowId, flowName, executionTiming = "before" } = flowConfig; logger.info(`🚀 노드 플로우 실행 시작:`, { flowId, flowName, timing: executionTiming, contextKeys: Object.keys(context), }); try { // 컨텍스트 데이터 준비 const contextData = prepareContextData(context); // 타이밍에 따라 실행 switch (executionTiming) { case "before": // 1. 플로우 먼저 실행 const beforeResult = await executeNodeFlow(flowId, contextData); if (!beforeResult.success) { toast.error(`플로우 실행 실패: ${beforeResult.message}`); return { success: false, message: beforeResult.message, data: beforeResult, }; } toast.success(`플로우 실행 완료: ${flowName}`); // 2. 원래 버튼 액션 실행 if (originalAction) { await originalAction(); } return { success: true, message: "플로우 및 버튼 액션이 성공적으로 실행되었습니다.", executionTime: beforeResult.executionTime, data: beforeResult, }; case "after": // 1. 원래 버튼 액션 먼저 실행 if (originalAction) { await originalAction(); } // 2. 플로우 실행 const afterResult = await executeNodeFlow(flowId, contextData); if (!afterResult.success) { toast.warning(`버튼 액션은 성공했으나 플로우 실행 실패: ${afterResult.message}`); } else { toast.success(`플로우 실행 완료: ${flowName}`); } return { success: afterResult.success, message: afterResult.message, executionTime: afterResult.executionTime, data: afterResult, }; case "replace": // 플로우만 실행 (원래 액션 대체) const replaceResult = await executeNodeFlow(flowId, contextData); if (!replaceResult.success) { toast.error(`플로우 실행 실패: ${replaceResult.message}`); } else { toast.success(`플로우 실행 완료: ${flowName}`, { description: `${replaceResult.summary.success}/${replaceResult.summary.total} 노드 성공`, }); } return { success: replaceResult.success, message: replaceResult.message, executionTime: replaceResult.executionTime, data: replaceResult, }; default: throw new Error(`지원하지 않는 실행 타이밍: ${executionTiming}`); } } catch (error) { logger.error("플로우 실행 오류:", error); const errorMessage = error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다."; toast.error(`플로우 실행 오류: ${errorMessage}`); return { success: false, message: errorMessage, }; } } /** * 컨텍스트 데이터 준비 */ function prepareContextData(context: ButtonExecutionContext): Record { // 🔥 controlDataSource 자동 감지 (명시적으로 설정되지 않은 경우) let dataSource = context.controlDataSource; if (!dataSource) { // 1. 플로우 선택 데이터가 있으면 flow-selection if (context.flowSelectedData && context.flowSelectedData.length > 0) { dataSource = "flow-selection"; logger.info("🔄 자동 판단: flow-selection 모드 사용", { flowSelectedDataLength: context.flowSelectedData.length, }); } // 2. 플로우 스텝 전체 데이터가 있으면 flow-step-all else if (context.flowStepAllData && context.flowStepAllData.length > 0) { dataSource = "flow-step-all"; logger.info("🔄 자동 판단: flow-step-all 모드 사용", { flowStepAllDataLength: context.flowStepAllData.length, }); } // 3. 테이블 선택 데이터가 있으면 table-selection else if (context.selectedRowsData && context.selectedRowsData.length > 0) { dataSource = "table-selection"; logger.info("🔄 자동 판단: table-selection 모드 사용", { selectedRowsDataLength: context.selectedRowsData.length, }); } // 4. 테이블 전체 데이터가 있으면 table-all else if (context.tableAllData && context.tableAllData.length > 0) { dataSource = "table-all"; logger.info("🔄 자동 판단: table-all 모드 사용", { tableAllDataLength: context.tableAllData.length, }); } // 5. 폼 데이터만 있으면 form else { dataSource = "form"; logger.info("🔄 자동 판단: form 모드 사용"); } } const baseContext = { buttonId: context.buttonId, screenId: context.screenId, companyCode: context.companyCode, userId: context.userId, controlDataSource: dataSource, }; // 데이터 소스에 따라 데이터 준비 switch (dataSource) { case "form": return { ...baseContext, formData: context.formData || {}, sourceData: [context.formData || {}], // 배열로 통일 }; case "table-selection": return { ...baseContext, formData: context.formData || {}, selectedRowsData: context.selectedRowsData || [], sourceData: context.selectedRowsData || [], }; case "table-all": return { ...baseContext, formData: context.formData || {}, tableAllData: context.tableAllData || [], sourceData: context.tableAllData || [], }; case "flow-selection": return { ...baseContext, formData: context.formData || {}, flowSelectedData: context.flowSelectedData || [], sourceData: context.flowSelectedData || [], }; case "flow-step-all": return { ...baseContext, formData: context.formData || {}, flowStepAllData: context.flowStepAllData || [], flowStepId: context.flowStepId, sourceData: context.flowStepAllData || [], }; case "both": // 폼 + 테이블 선택 return { ...baseContext, formData: context.formData || {}, selectedRowsData: context.selectedRowsData || [], sourceData: [ context.formData || {}, ...(context.selectedRowsData || []), ], }; case "all-sources": // 모든 소스 결합 return { ...baseContext, formData: context.formData || {}, selectedRowsData: context.selectedRowsData || [], tableAllData: context.tableAllData || [], flowSelectedData: context.flowSelectedData || [], flowStepAllData: context.flowStepAllData || [], sourceData: [ context.formData || {}, ...(context.selectedRowsData || []), ...(context.tableAllData || []), ...(context.flowSelectedData || []), ...(context.flowStepAllData || []), ].filter(item => Object.keys(item).length > 0), // 빈 객체 제거 }; default: logger.warn(`알 수 없는 데이터 소스: ${dataSource}, 기본값(form) 사용`); return { ...baseContext, formData: context.formData || {}, sourceData: [context.formData || {}], }; } } /** * 플로우 실행 결과 처리 */ export function handleFlowExecutionResult(result: FlowExecutionResult, context: ButtonExecutionContext): void { if (result.success) { logger.info("✅ 플로우 실행 성공:", result); // 성공 시 데이터 새로고침 if (context.onRefresh) { context.onRefresh(); } // 실행 결과 요약 표시 if (result.data) { const { summary } = result.data; console.log("📊 플로우 실행 요약:", { 전체: summary.total, 성공: summary.success, 실패: summary.failed, 스킵: summary.skipped, 실행시간: `${result.executionTime}ms`, }); } } else { logger.error("❌ 플로우 실행 실패:", result); // 실패한 노드 정보 표시 if (result.data) { const failedNodes = result.data.nodes.filter((n) => n.status === "failed"); if (failedNodes.length > 0) { console.error("❌ 실패한 노드들:", failedNodes); } } } }