/** * πŸ”₯ κ°œμ„ λœ λ²„νŠΌ μ•‘μ…˜ μ‹€ν–‰κΈ° * * κ³„νšμ„œμ— λ”°λ₯Έ μƒˆλ‘œμš΄ μ‹€ν–‰ ν”Œλ‘œμš°: * 1. Before 타이밍 μ œμ–΄ μ‹€ν–‰ * 2. 메인 μ•‘μ…˜ μ‹€ν–‰ (replaceκ°€ μ•„λ‹Œ 경우) * 3. After 타이밍 μ œμ–΄ μ‹€ν–‰ */ import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { ExtendedButtonTypeConfig, ButtonDataflowConfig } from "@/types/control-management"; import { ButtonActionType } from "@/types/unified-core"; // ===== μΈν„°νŽ˜μ΄μŠ€ μ •μ˜ ===== export interface ButtonExecutionContext { buttonId: string; screenId: string; userId: string; companyCode: string; startTime: number; formData?: Record; selectedRows?: any[]; tableData?: any[]; } export interface ExecutionResult { success: boolean; message: string; executionTime: number; data?: any; error?: string; } export interface ButtonExecutionResult { success: boolean; results: ExecutionResult[]; executionTime: number; error?: string; } interface ControlConfig { type: "relationship"; relationshipConfig: { relationshipId: string; relationshipName: string; executionTiming: "before" | "after" | "replace"; contextData?: Record; }; } interface ExecutionPlan { beforeControls: ControlConfig[]; afterControls: ControlConfig[]; hasReplaceControl: boolean; } // ===== 메인 μ‹€ν–‰κΈ° 클래슀 ===== export class ImprovedButtonActionExecutor { /** * πŸ”₯ κ°œμ„ λœ λ²„νŠΌ μ•‘μ…˜ μ‹€ν–‰ */ static async executeButtonAction( buttonConfig: ExtendedButtonTypeConfig, formData: Record, context: ButtonExecutionContext ): Promise { console.log("πŸ”₯ ImprovedButtonActionExecutor μ‹œμž‘:", { buttonConfig, formData, context, }); const executionPlan = this.createExecutionPlan(buttonConfig); const results: ExecutionResult[] = []; console.log("πŸ“‹ μƒμ„±λœ μ‹€ν–‰ κ³„νš:", { beforeControls: executionPlan.beforeControls, afterControls: executionPlan.afterControls, hasReplaceControl: executionPlan.hasReplaceControl, }); try { console.log("πŸš€ λ²„νŠΌ μ•‘μ…˜ μ‹€ν–‰ μ‹œμž‘:", { actionType: buttonConfig.actionType, hasControls: executionPlan.beforeControls.length + executionPlan.afterControls.length > 0, hasReplace: executionPlan.hasReplaceControl, }); // 1. Before 타이밍 μ œμ–΄ μ‹€ν–‰ if (executionPlan.beforeControls.length > 0) { console.log("⏰ Before μ œμ–΄ μ‹€ν–‰ μ‹œμž‘"); const beforeResults = await this.executeControls( executionPlan.beforeControls, formData, context ); results.push(...beforeResults); // Before μ œμ–΄ 쀑 μ‹€νŒ¨κ°€ 있으면 쀑단 const hasFailure = beforeResults.some(r => !r.success); if (hasFailure) { throw new Error("Before μ œμ–΄ μ‹€ν–‰ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); } } // 2. 메인 μ•‘μ…˜ μ‹€ν–‰ (replaceκ°€ μ•„λ‹Œ κ²½μš°μ—λ§Œ) if (!executionPlan.hasReplaceControl) { console.log("⚑ 메인 μ•‘μ…˜ μ‹€ν–‰:", buttonConfig.actionType); const mainResult = await this.executeMainAction( buttonConfig, formData, context ); results.push(mainResult); if (!mainResult.success) { throw new Error("메인 μ•‘μ…˜ μ‹€ν–‰ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); } } else { console.log("πŸ”„ Replace λͺ¨λ“œ: 메인 μ•‘μ…˜ κ±΄λ„ˆλœ€"); } // 3. After 타이밍 μ œμ–΄ μ‹€ν–‰ if (executionPlan.afterControls.length > 0) { console.log("⏰ After μ œμ–΄ μ‹€ν–‰ μ‹œμž‘"); const afterResults = await this.executeControls( executionPlan.afterControls, formData, context ); results.push(...afterResults); } const totalExecutionTime = Date.now() - context.startTime; console.log("βœ… λ²„νŠΌ μ•‘μ…˜ μ‹€ν–‰ μ™„λ£Œ:", `${totalExecutionTime}ms`); return { success: true, results, executionTime: totalExecutionTime, }; } catch (error) { console.error("❌ λ²„νŠΌ μ•‘μ…˜ μ‹€ν–‰ μ‹€νŒ¨:", error); // λ‘€λ°± 처리 await this.handleExecutionError(error, results, buttonConfig); return { success: false, results, executionTime: Date.now() - context.startTime, error: error.message, }; } } /** * πŸ”₯ μ‹€ν–‰ κ³„νš 생성 */ private static createExecutionPlan(buttonConfig: ExtendedButtonTypeConfig): ExecutionPlan { const plan: ExecutionPlan = { beforeControls: [], afterControls: [], hasReplaceControl: false, }; const dataflowConfig = buttonConfig.dataflowConfig; if (!dataflowConfig) { console.log("⚠️ dataflowConfigκ°€ μ—†μŠ΅λ‹ˆλ‹€"); return plan; } // enableDataflowControl 체크λ₯Ό μ œκ±°ν•˜κ³  dataflowConfig만 있으면 μ‹€ν–‰ console.log("πŸ“‹ μ‹€ν–‰ κ³„νš 생성:", { controlMode: dataflowConfig.controlMode, hasRelationshipConfig: !!dataflowConfig.relationshipConfig, enableDataflowControl: buttonConfig.enableDataflowControl, }); // 관계 기반 μ œμ–΄λ§Œ 지원 if (dataflowConfig.controlMode === "relationship" && dataflowConfig.relationshipConfig) { const control: ControlConfig = { type: "relationship", relationshipConfig: dataflowConfig.relationshipConfig, }; switch (dataflowConfig.relationshipConfig.executionTiming) { case "before": plan.beforeControls.push(control); break; case "after": plan.afterControls.push(control); break; case "replace": plan.afterControls.push(control); // ReplaceλŠ” after둜 μ²˜λ¦¬ν•˜λ˜ ν”Œλž˜κ·Έ μ„€μ • plan.hasReplaceControl = true; break; } } return plan; } /** * πŸ”₯ μ œμ–΄ μ‹€ν–‰ (관계 λ˜λŠ” μ™ΈλΆ€ν˜ΈμΆœ) */ private static async executeControls( controls: ControlConfig[], formData: Record, context: ButtonExecutionContext ): Promise { const results: ExecutionResult[] = []; for (const control of controls) { try { // 관계 μ‹€ν–‰λ§Œ 지원 const result = await this.executeRelationship( control.relationshipConfig, formData, context ); results.push(result); // μ œμ–΄ μ‹€ν–‰ μ‹€νŒ¨ μ‹œ 쀑단 if (!result.success) { throw new Error(result.message); } } catch (error) { console.error(`μ œμ–΄ μ‹€ν–‰ μ‹€νŒ¨ (${control.type}):`, error); results.push({ success: false, message: `${control.type} μ œμ–΄ μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`, executionTime: 0, error: error.message, }); throw error; } } return results; } /** * πŸ”₯ 관계 μ‹€ν–‰ */ private static async executeRelationship( config: { relationshipId: string; relationshipName: string; executionTiming: "before" | "after" | "replace"; contextData?: Record; }, formData: Record, context: ButtonExecutionContext ): Promise { try { console.log(`πŸ”— 관계 μ‹€ν–‰ μ‹œμž‘: ${config.relationshipName} (ID: ${config.relationshipId})`); // 1. 관계 정보 쑰회 const relationshipData = await this.getRelationshipData(config.relationshipId); if (!relationshipData) { throw new Error(`관계 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: ${config.relationshipId}`); } console.log(`πŸ“‹ 관계 데이터 λ‘œλ“œ μ™„λ£Œ:`, relationshipData); // 2. 관계 νƒ€μž…μ— λ”°λ₯Έ μ‹€ν–‰ const relationships = relationshipData.relationships; const connectionType = relationships.connectionType; console.log(`πŸ” 관계 상세 정보:`, { connectionType, hasExternalCallConfig: !!relationships.externalCallConfig, externalCallConfig: relationships.externalCallConfig, hasDataSaveConfig: !!relationships.dataSaveConfig, dataSaveConfig: relationships.dataSaveConfig, }); let result: ExecutionResult; if (connectionType === "external_call") { // μ™ΈλΆ€ 호좜 μ‹€ν–‰ result = await this.executeExternalCall(relationships, formData, context); } else if (connectionType === "data_save") { // 데이터 μ €μž₯ μ‹€ν–‰ result = await this.executeDataSave(relationships, formData, context); } else { throw new Error(`μ§€μ›ν•˜μ§€ μ•ŠλŠ” μ—°κ²° νƒ€μž…: ${connectionType}`); } console.log(`βœ… 관계 μ‹€ν–‰ μ™„λ£Œ: ${config.relationshipName}`, result); if (result.success) { toast.success(`관계 '${config.relationshipName}' μ‹€ν–‰ μ™„λ£Œ`); } else { toast.error(`관계 '${config.relationshipName}' μ‹€ν–‰ μ‹€νŒ¨: ${result.message}`); } return result; } catch (error: any) { console.error(`❌ 관계 μ‹€ν–‰ μ‹€νŒ¨: ${config.relationshipName}`, error); const errorResult = { success: false, message: `관계 '${config.relationshipName}' μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`, executionTime: 0, error: error.message, }; toast.error(errorResult.message); return errorResult; } } /** * 관계 데이터 쑰회 */ private static async getRelationshipData(relationshipId: string): Promise { try { console.log(`πŸ” 관계 데이터 쑰회 μ‹œμž‘: ${relationshipId}`); const response = await apiClient.get(`/dataflow-diagrams/${relationshipId}`); console.log(`βœ… 관계 데이터 쑰회 성곡:`, response.data); if (!response.data.success) { throw new Error(response.data.message || '관계 데이터 쑰회 μ‹€νŒ¨'); } return response.data.data; } catch (error) { console.error('관계 데이터 쑰회 였λ₯˜:', error); throw error; } } /** * μ™ΈλΆ€ 호좜 μ‹€ν–‰ */ private static async executeExternalCall( relationships: any, formData: Record, context: ButtonExecutionContext ): Promise { try { console.log(`πŸ” μ™ΈλΆ€ 호좜 μ‹€ν–‰ μ‹œμž‘ - relationships ꡬ쑰:`, relationships); const externalCallConfig = relationships.externalCallConfig; console.log(`πŸ” externalCallConfig:`, externalCallConfig); if (!externalCallConfig) { console.error('❌ μ™ΈλΆ€ 호좜 섀정이 μ—†μŠ΅λ‹ˆλ‹€. relationships ꡬ쑰:', relationships); throw new Error('μ™ΈλΆ€ 호좜 섀정이 μ—†μŠ΅λ‹ˆλ‹€'); } const restApiSettings = externalCallConfig.restApiSettings; if (!restApiSettings) { throw new Error('REST API 섀정이 μ—†μŠ΅λ‹ˆλ‹€'); } console.log(`🌐 μ™ΈλΆ€ API 호좜: ${restApiSettings.apiUrl}`); // API 호좜 μ€€λΉ„ const headers: Record = { 'Content-Type': 'application/json', ...restApiSettings.headers, }; // 인증 처리 if (restApiSettings.authentication?.type === 'api-key') { headers['Authorization'] = `Bearer ${restApiSettings.authentication.apiKey}`; } // μš”μ²­ λ°”λ”” μ€€λΉ„ (ν…œν”Œλ¦Ώ 처리) let requestBody = restApiSettings.bodyTemplate || ''; if (requestBody) { // κ°„λ‹¨ν•œ ν…œν”Œλ¦Ώ μΉ˜ν™˜ ({{λ³€μˆ˜λͺ…}} ν˜•νƒœ) requestBody = requestBody.replace(/\{\{(\w+)\}\}/g, (match: string, key: string) => { return formData[key] || (context as any).contextData?.[key] || new Date().toISOString(); }); } // λ°±μ—”λ“œ ν”„λ‘μ‹œλ₯Ό ν†΅ν•œ μ™ΈλΆ€ API 호좜 (CORS 문제 ν•΄κ²°) console.log(`🌐 λ°±μ—”λ“œ ν”„λ‘μ‹œλ₯Ό ν†΅ν•œ μ™ΈλΆ€ API 호좜 μ€€λΉ„:`, { originalUrl: restApiSettings.apiUrl, method: restApiSettings.httpMethod || 'GET', headers, body: restApiSettings.httpMethod !== 'GET' ? requestBody : undefined, }); // λ°±μ—”λ“œ ν”„λ‘μ‹œ API 호좜 - GenericApiSettings ν˜•μ‹μ— 맞게 전달 const requestPayload = { diagramId: relationships.diagramId || 45, // 관계 ID μ‚¬μš© relationshipId: relationships.relationshipId || "relationship-45", settings: { callType: "rest-api", apiType: "generic", url: restApiSettings.apiUrl, method: restApiSettings.httpMethod || 'POST', headers: restApiSettings.headers || {}, body: requestBody, authentication: restApiSettings.authentication || { type: 'none' }, timeout: restApiSettings.timeout || 30000, retryCount: restApiSettings.retryCount || 3, }, templateData: restApiSettings.httpMethod !== 'GET' && requestBody ? JSON.parse(requestBody) : formData, }; console.log(`πŸ“€ λ°±μ—”λ“œλ‘œ 전솑할 데이터:`, requestPayload); const proxyResponse = await apiClient.post(`/external-calls/execute`, requestPayload); console.log(`πŸ“‘ λ°±μ—”λ“œ ν”„λ‘μ‹œ 응닡:`, proxyResponse.data); if (!proxyResponse.data.success) { throw new Error(`ν”„λ‘μ‹œ API 호좜 μ‹€νŒ¨: ${proxyResponse.data.error || proxyResponse.data.message}`); } const responseData = proxyResponse.data.result; console.log(`βœ… μ™ΈλΆ€ API 호좜 성곡 (ν”„λ‘μ‹œ):`, responseData); // 데이터 λ§€ν•‘ 처리 (inbound mapping) if (externalCallConfig.dataMappingConfig?.inboundMapping) { console.log(`πŸ“₯ 데이터 λ§€ν•‘ μ„€μ • 발견 - HTTP λ©”μ„œλ“œ: ${restApiSettings.httpMethod}`); console.log(`πŸ“₯ λ§€ν•‘ μ„€μ •:`, externalCallConfig.dataMappingConfig.inboundMapping); console.log(`πŸ“₯ 응닡 데이터:`, responseData); await this.processInboundMapping( externalCallConfig.dataMappingConfig.inboundMapping, responseData, context ); } else { console.log(`ℹ️ 데이터 λ§€ν•‘ 섀정이 μ—†μŠ΅λ‹ˆλ‹€ - HTTP λ©”μ„œλ“œ: ${restApiSettings.httpMethod}`); } return { success: true, message: 'μ™ΈλΆ€ 호좜 μ‹€ν–‰ μ™„λ£Œ', executionTime: Date.now() - context.startTime, data: responseData, }; } catch (error: any) { console.error('μ™ΈλΆ€ 호좜 μ‹€ν–‰ 였λ₯˜:', error); return { success: false, message: `μ™ΈλΆ€ 호좜 μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`, executionTime: Date.now() - context.startTime, error: error.message, }; } } /** * 데이터 μ €μž₯ μ‹€ν–‰ */ private static async executeDataSave( relationships: any, formData: Record, context: ButtonExecutionContext ): Promise { try { console.log(`πŸ’Ύ 데이터 μ €μž₯ μ‹€ν–‰ μ‹œμž‘`); // μ œμ–΄ 쑰건 확인 const controlConditions = relationships.controlConditions || []; if (controlConditions.length > 0) { const conditionsMet = this.evaluateConditions(controlConditions, formData, context); if (!conditionsMet) { return { success: false, message: 'μ œμ–΄ 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•Šμ•„ 데이터 μ €μž₯을 κ±΄λ„ˆλœλ‹ˆλ‹€', executionTime: Date.now() - context.startTime, }; } } // μ•‘μ…˜ κ·Έλ£Ή μ‹€ν–‰ const actionGroups = relationships.actionGroups || []; const results = []; for (const actionGroup of actionGroups) { if (!actionGroup.isEnabled) { console.log(`⏭️ λΉ„ν™œμ„±ν™”λœ μ•‘μ…˜ κ·Έλ£Ή κ±΄λ„ˆλœ€: ${actionGroup.name}`); continue; } console.log(`🎯 μ•‘μ…˜ κ·Έλ£Ή μ‹€ν–‰: ${actionGroup.name}`); for (const action of actionGroup.actions) { if (!action.isEnabled) { console.log(`⏭️ λΉ„ν™œμ„±ν™”λœ μ•‘μ…˜ κ±΄λ„ˆλœ€: ${action.name}`); continue; } const actionResult = await this.executeDataAction( action, relationships, formData, context ); results.push(actionResult); if (!actionResult.success) { console.error(`❌ μ•‘μ…˜ μ‹€ν–‰ μ‹€νŒ¨: ${action.name}`, actionResult); } } } const successCount = results.filter(r => r.success).length; const totalCount = results.length; return { success: successCount > 0, message: `데이터 μ €μž₯ μ™„λ£Œ: ${successCount}/${totalCount} μ•‘μ…˜ 성곡`, executionTime: Date.now() - context.startTime, data: { results, successCount, totalCount, }, }; } catch (error: any) { console.error('데이터 μ €μž₯ μ‹€ν–‰ 였λ₯˜:', error); return { success: false, message: `데이터 μ €μž₯ μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`, executionTime: Date.now() - context.startTime, error: error.message, }; } } /** * κ°œλ³„ 데이터 μ•‘μ…˜ μ‹€ν–‰ */ private static async executeDataAction( action: any, relationships: any, formData: Record, context: ButtonExecutionContext ): Promise { try { console.log(`πŸ”§ 데이터 μ•‘μ…˜ μ‹€ν–‰: ${action.name} (${action.actionType})`); // ν•„λ“œ λ§€ν•‘ 처리 const mappedData: Record = {}; for (const mapping of action.fieldMappings) { if (mapping.valueType === 'static') { // 정적 κ°’ 처리 let value = mapping.value; if (value === '#NOW') { value = new Date().toISOString(); } mappedData[mapping.targetField] = value; } else { // ν•„λ“œ λ§€ν•‘ 처리 const sourceField = mapping.fromField?.columnName; if (sourceField && formData[sourceField] !== undefined) { mappedData[mapping.toField.columnName] = formData[sourceField]; } } } console.log(`πŸ“‹ λ§€ν•‘λœ 데이터:`, mappedData); // λŒ€μƒ μ—°κ²° 정보 const toConnection = relationships.toConnection; const targetTable = relationships.toTable?.tableName; if (!targetTable) { throw new Error('λŒ€μƒ ν…Œμ΄λΈ”μ΄ μ§€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€'); } // 데이터 μ €μž₯ API 호좜 const saveResult = await this.saveDataToTable( targetTable, mappedData, action.actionType, toConnection ); return { success: true, message: `데이터 μ•‘μ…˜ "${action.name}" μ‹€ν–‰ μ™„λ£Œ`, executionTime: Date.now() - context.startTime, data: saveResult, }; } catch (error: any) { console.error(`데이터 μ•‘μ…˜ μ‹€ν–‰ 였λ₯˜: ${action.name}`, error); return { success: false, message: `데이터 μ•‘μ…˜ μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`, executionTime: Date.now() - context.startTime, error: error.message, }; } } /** * ν…Œμ΄λΈ”μ— 데이터 μ €μž₯ */ private static async saveDataToTable( tableName: string, data: Record, actionType: string, connection?: any ): Promise { try { console.log(`πŸ’Ύ ν…Œμ΄λΈ” 데이터 μ €μž₯ μ‹œμž‘: ${tableName}`, { actionType, data, connection }); // 데이터 μ €μž₯ API 호좜 (apiClient μ‚¬μš©) const response = await apiClient.post('/dataflow/execute-data-action', { tableName, data, actionType, connection, }); console.log(`βœ… ν…Œμ΄λΈ” 데이터 μ €μž₯ 성곡: ${tableName}`, response.data); return response.data; } catch (error) { console.error('ν…Œμ΄λΈ” 데이터 μ €μž₯ 였λ₯˜:', error); throw error; } } /** * 쑰건 평가 */ private static evaluateConditions( conditions: any[], formData: Record, context: ButtonExecutionContext ): boolean { for (const condition of conditions) { const fieldValue = formData[condition.field]; const conditionValue = condition.value; const operator = condition.operator; let conditionMet = false; switch (operator) { case '=': conditionMet = fieldValue === conditionValue; break; case '!=': conditionMet = fieldValue !== conditionValue; break; case '>': conditionMet = Number(fieldValue) > Number(conditionValue); break; case '<': conditionMet = Number(fieldValue) < Number(conditionValue); break; case '>=': conditionMet = Number(fieldValue) >= Number(conditionValue); break; case '<=': conditionMet = Number(fieldValue) <= Number(conditionValue); break; default: console.warn(`μ§€μ›ν•˜μ§€ μ•ŠλŠ” μ—°μ‚°μž: ${operator}`); conditionMet = true; } if (!conditionMet) { console.log(`❌ 쑰건 뢈만쑱: ${condition.field} ${operator} ${conditionValue} (μ‹€μ œκ°’: ${fieldValue})`); return false; } } console.log(`βœ… λͺ¨λ“  쑰건 만쑱`); return true; } /** * λ‹€μ–‘ν•œ API 응닡 κ΅¬μ‘°μ—μ„œ μ‹€μ œ 데이터 μΆ”μΆœ */ private static extractActualData(responseData: any): any { console.log(`πŸ” 데이터 μΆ”μΆœ μ‹œμž‘ - 원본 νƒ€μž…: ${typeof responseData}`); // nullμ΄λ‚˜ undefined인 경우 if (!responseData) { console.log(`⚠️ 응닡 데이터가 null λ˜λŠ” undefined`); return []; } // 이미 배열인 경우 (직접 λ°°μ—΄ 응닡) if (Array.isArray(responseData)) { console.log(`βœ… 직접 λ°°μ—΄ 응닡 감지`); return responseData; } // λ¬Έμžμ—΄μΈ 경우 JSON νŒŒμ‹± μ‹œλ„ if (typeof responseData === 'string') { console.log(`πŸ”„ JSON λ¬Έμžμ—΄ νŒŒμ‹± μ‹œλ„`); try { const parsed = JSON.parse(responseData); console.log(`βœ… JSON νŒŒμ‹± 성곡, μž¬κ·€ 호좜`); return this.extractActualData(parsed); } catch (error) { console.log(`⚠️ JSON νŒŒμ‹± μ‹€νŒ¨, 원본 λ¬Έμžμ—΄ λ°˜ν™˜:`, error); return [responseData]; } } // 객체가 μ•„λ‹Œ 경우 (숫자 λ“±) if (typeof responseData !== 'object') { console.log(`⚠️ 객체가 μ•„λ‹Œ 응닡: ${typeof responseData}`); return [responseData]; } // 일반적인 데이터 ν•„λ“œλͺ…듀을 μš°μ„ μˆœμœ„λŒ€λ‘œ 확인 const commonDataFields = [ 'data', // { data: [...] } 'result', // { result: [...] } 'results', // { results: [...] } 'items', // { items: [...] } 'list', // { list: [...] } 'records', // { records: [...] } 'rows', // { rows: [...] } 'content', // { content: [...] } 'payload', // { payload: [...] } 'response', // { response: [...] } ]; for (const field of commonDataFields) { if (responseData[field] !== undefined) { console.log(`βœ… '${field}' ν•„λ“œμ—μ„œ 데이터 μΆ”μΆœ`); const extractedData = responseData[field]; // μΆ”μΆœλœ 데이터가 λ¬Έμžμ—΄μΈ 경우 JSON νŒŒμ‹± μ‹œλ„ if (typeof extractedData === 'string') { console.log(`πŸ”„ μΆ”μΆœλœ 데이터가 JSON λ¬Έμžμ—΄, νŒŒμ‹± μ‹œλ„`); try { const parsed = JSON.parse(extractedData); console.log(`βœ… JSON νŒŒμ‹± 성곡, μž¬κ·€ 호좜`); return this.extractActualData(parsed); } catch (error) { console.log(`⚠️ JSON νŒŒμ‹± μ‹€νŒ¨, 원본 λ¬Έμžμ—΄ λ°˜ν™˜:`, error); return [extractedData]; } } // μΆ”μΆœλœ 데이터가 객체이고 또 λ‹€λ₯Έ 쀑첩 ꡬ쑰일 수 μžˆμœΌλ―€λ‘œ μž¬κ·€ 호좜 if (typeof extractedData === 'object' && !Array.isArray(extractedData)) { console.log(`πŸ”„ μ€‘μ²©λœ 객체 감지, μž¬κ·€ μΆ”μΆœ μ‹œλ„`); return this.extractActualData(extractedData); } return extractedData; } } // νŠΉλ³„ν•œ ν•„λ“œκ°€ μ—†λŠ” 경우, 객체의 κ°’λ“€ μ€‘μ—μ„œ 배열을 μ°ΎκΈ° const objectValues = Object.values(responseData); const arrayValue = objectValues.find(value => Array.isArray(value)); if (arrayValue) { console.log(`βœ… 객체 κ°’ 쀑 λ°°μ—΄ 발견`); return arrayValue; } // 객체의 κ°’λ“€ μ€‘μ—μ„œ 객체λ₯Ό μ°Ύμ•„μ„œ μž¬κ·€ 탐색 for (const value of objectValues) { if (value && typeof value === 'object' && !Array.isArray(value)) { console.log(`πŸ”„ 객체 κ°’μ—μ„œ μž¬κ·€ 탐색`); const nestedResult = this.extractActualData(value); if (Array.isArray(nestedResult) && nestedResult.length > 0) { return nestedResult; } } } // λͺ¨λ“  μ‹œλ„κ°€ μ‹€νŒ¨ν•œ 경우, 원본 객체λ₯Ό 단일 ν•­λͺ© λ°°μ—΄λ‘œ λ°˜ν™˜ console.log(`πŸ“¦ 원본 객체λ₯Ό 단일 ν•­λͺ©μœΌλ‘œ 처리`); return [responseData]; } /** * μΈλ°”μš΄λ“œ 데이터 λ§€ν•‘ 처리 */ private static async processInboundMapping( inboundMapping: any, responseData: any, context: ButtonExecutionContext ): Promise { try { console.log(`πŸ“₯ μΈλ°”μš΄λ“œ 데이터 λ§€ν•‘ 처리 μ‹œμž‘`); console.log(`πŸ“₯ 원본 응닡 데이터:`, responseData); const targetTable = inboundMapping.targetTable; const fieldMappings = inboundMapping.fieldMappings || []; const insertMode = inboundMapping.insertMode || 'insert'; console.log(`πŸ“₯ λ§€ν•‘ μ„€μ •:`, { targetTable, fieldMappings, insertMode }); // 응닡 λ°μ΄ν„°μ—μ„œ μ‹€μ œ 데이터 μΆ”μΆœ (λ‹€μ–‘ν•œ ꡬ쑰 지원) let actualData = this.extractActualData(responseData); console.log(`πŸ“₯ μΆ”μΆœλœ μ‹€μ œ 데이터:`, actualData); // 배열이 μ•„λ‹Œ 경우 λ°°μ—΄λ‘œ λ³€ν™˜ const dataArray = Array.isArray(actualData) ? actualData : [actualData]; console.log(`πŸ“₯ μ²˜λ¦¬ν•  데이터 λ°°μ—΄:`, dataArray); if (dataArray.length === 0) { console.log(`⚠️ μ²˜λ¦¬ν•  데이터가 μ—†μŠ΅λ‹ˆλ‹€`); return; } for (const item of dataArray) { const mappedData: Record = {}; console.log(`πŸ“₯ κ°œλ³„ μ•„μ΄ν…œ 처리:`, item); // ν•„λ“œ λ§€ν•‘ 적용 for (const mapping of fieldMappings) { const sourceValue = item[mapping.sourceField]; console.log(`πŸ“₯ ν•„λ“œ λ§€ν•‘: ${mapping.sourceField} -> ${mapping.targetField} = ${sourceValue}`); if (sourceValue !== undefined && sourceValue !== null) { mappedData[mapping.targetField] = sourceValue; } } console.log(`πŸ“‹ λ§€ν•‘λœ 데이터:`, mappedData); // λ§€ν•‘λœ 데이터가 λΉ„μ–΄μžˆμ§€ μ•Šμ€ κ²½μš°μ—λ§Œ μ €μž₯ if (Object.keys(mappedData).length > 0) { await this.saveDataToTable(targetTable, mappedData, insertMode); } else { console.log(`⚠️ λ§€ν•‘λœ 데이터가 λΉ„μ–΄μžˆμ–΄ μ €μž₯을 κ±΄λ„ˆλœλ‹ˆλ‹€`); } } console.log(`βœ… μΈλ°”μš΄λ“œ 데이터 λ§€ν•‘ μ™„λ£Œ`); } catch (error) { console.error('μΈλ°”μš΄λ“œ 데이터 λ§€ν•‘ 였λ₯˜:', error); throw error; } } /** * πŸ”₯ 메인 μ•‘μ…˜ μ‹€ν–‰ */ private static async executeMainAction( buttonConfig: ExtendedButtonTypeConfig, formData: Record, context: ButtonExecutionContext ): Promise { try { // κΈ°μ‘΄ ButtonActionExecutor λ‘œμ§μ„ μ—¬κΈ°μ„œ ν˜ΈμΆœν•˜κ±°λ‚˜ // κ°„λ‹¨ν•œ μ•‘μ…˜λ“€μ„ 직접 κ΅¬ν˜„ const startTime = performance.now(); // μž„μ‹œ κ΅¬ν˜„ - μ‹€μ œλ‘œλŠ” κΈ°μ‘΄ ButtonActionExecutorλ₯Ό ν˜ΈμΆœν•΄μ•Ό 함 const result = { success: true, message: `${buttonConfig.actionType} μ•‘μ…˜ μ‹€ν–‰ μ™„λ£Œ`, executionTime: performance.now() - startTime, data: { actionType: buttonConfig.actionType, formData }, }; console.log("βœ… 메인 μ•‘μ…˜ μ‹€ν–‰ μ™„λ£Œ:", result.message); return result; } catch (error) { console.error("메인 μ•‘μ…˜ μ‹€ν–‰ 였λ₯˜:", error); return { success: false, message: `${buttonConfig.actionType} μ•‘μ…˜ μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`, executionTime: 0, error: error.message, }; } } /** * πŸ”₯ μ‹€ν–‰ 였λ₯˜ 처리 및 λ‘€λ°± */ private static async handleExecutionError( error: Error, results: ExecutionResult[], buttonConfig: ExtendedButtonTypeConfig ): Promise { console.error("πŸ”„ μ‹€ν–‰ 였λ₯˜ 처리 μ‹œμž‘:", error.message); // 둀백이 ν•„μš”ν•œ 경우 처리 const rollbackNeeded = buttonConfig.dataflowConfig?.executionOptions?.rollbackOnError; if (rollbackNeeded) { console.log("πŸ”„ λ‘€λ°± 처리 μ‹œμž‘..."); // μ„±κ³΅ν•œ 결과듀을 μ—­μˆœμœΌλ‘œ λ‘€λ°± const successfulResults = results.filter(r => r.success).reverse(); for (const result of successfulResults) { try { // λ‘€λ°± 둜직 κ΅¬ν˜„ (ν•„μš”μ‹œ) console.log("πŸ”„ λ‘€λ°±:", result.message); } catch (rollbackError) { console.error("λ‘€λ°± μ‹€νŒ¨:", rollbackError); } } } // 였λ₯˜ ν† μŠ€νŠΈ ν‘œμ‹œ toast.error(error.message || "μž‘μ—… 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); } }