ERP-node/frontend/lib/utils/improvedButtonActionExecuto...

922 lines
30 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 🔥 개선된 버튼 액션 실행기
*
* 계획서에 따른 새로운 실행 플로우:
* 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<string, any>;
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<string, any>;
};
}
interface ExecutionPlan {
beforeControls: ControlConfig[];
afterControls: ControlConfig[];
hasReplaceControl: boolean;
}
// ===== 메인 실행기 클래스 =====
export class ImprovedButtonActionExecutor {
/**
* 🔥 개선된 버튼 액션 실행
*/
static async executeButtonAction(
buttonConfig: ExtendedButtonTypeConfig,
formData: Record<string, any>,
context: ButtonExecutionContext,
): Promise<ButtonExecutionResult> {
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<string, any>,
context: ButtonExecutionContext,
): Promise<ExecutionResult[]> {
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<string, any>;
},
formData: Record<string, any>,
context: ButtonExecutionContext,
): Promise<ExecutionResult> {
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<any> {
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<string, any>,
context: ButtonExecutionContext,
): Promise<ExecutionResult> {
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<string, string> = {
"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<string, any>,
context: ButtonExecutionContext,
): Promise<ExecutionResult> {
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<string, any>,
context: ButtonExecutionContext,
): Promise<ExecutionResult> {
try {
console.log(`🔧 데이터 액션 실행: ${action.name} (${action.actionType})`);
console.log("📥 받은 formData:", formData);
console.log("📥 formData 키들:", Object.keys(formData));
// 🔥 UPDATE 액션의 경우 formData를 기본으로 시작 (기본키 포함)
const mappedData: Record<string, any> = action.actionType === "update" ? { ...formData } : {};
// 필드 매핑 처리 (기존 데이터에 덮어쓰기)
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;
console.log(`🔧 정적 값 매핑: ${mapping.targetField} = ${value}`);
} else {
// 필드 매핑 처리
const sourceField = mapping.fromField?.columnName;
if (sourceField && formData[sourceField] !== undefined) {
mappedData[mapping.toField.columnName] = formData[sourceField];
console.log(`🔧 필드 매핑: ${sourceField}${mapping.toField.columnName} = ${formData[sourceField]}`);
}
}
}
console.log("📋 최종 매핑된 데이터:", mappedData);
console.log("🔑 기본키 포함 여부 체크:", {
hasId: "id" in mappedData,
keys: Object.keys(mappedData),
values: Object.values(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<string, any>,
actionType: string,
connection?: any,
): Promise<any> {
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<string, any>,
context: ButtonExecutionContext,
): boolean {
console.log("🔍 조건 평가 시작:", {
conditions,
formDataKeys: Object.keys(formData),
formData,
contextData: context.contextData,
});
for (const condition of conditions) {
const fieldValue = formData[condition.field];
const conditionValue = condition.value;
const operator = condition.operator;
console.log("🔍 개별 조건 검증:", {
field: condition.field,
operator,
expectedValue: conditionValue,
actualValue: fieldValue,
formDataHasField: condition.field in formData,
allFormDataKeys: Object.keys(formData),
});
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})`);
console.log("❌ 사용 가능한 필드들:", Object.keys(formData));
console.log("❌ 전체 formData:", formData);
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<void> {
try {
console.log("📥 인바운드 데이터 매핑 처리 시작");
console.log("📥 원본 응답 데이터:", responseData);
const targetTable = inboundMapping.targetTable;
const fieldMappings = inboundMapping.fieldMappings || [];
const insertMode = inboundMapping.insertMode || "insert";
console.log("📥 매핑 설정:", {
targetTable,
fieldMappings,
insertMode,
});
// 응답 데이터에서 실제 데이터 추출 (다양한 구조 지원)
const 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<string, any> = {};
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<string, any>,
context: ButtonExecutionContext,
): Promise<ExecutionResult> {
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<void> {
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 || "작업 중 오류가 발생했습니다.");
}
}