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

917 lines
30 KiB
TypeScript
Raw Normal View History

2025-09-29 12:17:10 +09:00
/**
* 🔥
*
2025-09-29 12:17:10 +09:00
* :
* 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,
2025-09-29 12:17:10 +09:00
): 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);
2025-09-29 12:17:10 +09:00
results.push(...beforeResults);
// Before 제어 중 실패가 있으면 중단
const hasFailure = beforeResults.some((r) => !r.success);
2025-09-29 12:17:10 +09:00
if (hasFailure) {
throw new Error("Before 제어 실행 중 오류가 발생했습니다.");
}
}
// 2. 메인 액션 실행 (replace가 아닌 경우에만)
if (!executionPlan.hasReplaceControl) {
console.log("⚡ 메인 액션 실행:", buttonConfig.actionType);
const mainResult = await this.executeMainAction(buttonConfig, formData, context);
2025-09-29 12:17:10 +09:00
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);
2025-09-29 12:17:10 +09:00
results.push(...afterResults);
}
const totalExecutionTime = Date.now() - context.startTime;
console.log("✅ 버튼 액션 실행 완료:", `${totalExecutionTime}ms`);
return {
success: true,
results,
executionTime: totalExecutionTime,
};
} catch (error) {
console.error("❌ 버튼 액션 실행 실패:", error);
2025-09-29 12:17:10 +09:00
// 롤백 처리
await this.handleExecutionError(error, results, buttonConfig);
2025-09-29 12:17:10 +09:00
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,
2025-09-29 12:17:10 +09:00
): Promise<ExecutionResult[]> {
const results: ExecutionResult[] = [];
for (const control of controls) {
try {
// 관계 실행만 지원
const result = await this.executeRelationship(control.relationshipConfig, formData, context);
2025-09-29 12:17:10 +09:00
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,
2025-09-29 12:17:10 +09:00
): 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;
2025-09-29 13:32:59 +09:00
console.log(`🔍 관계 상세 정보:`, {
connectionType,
hasExternalCallConfig: !!relationships.externalCallConfig,
externalCallConfig: relationships.externalCallConfig,
hasDataSaveConfig: !!relationships.dataSaveConfig,
dataSaveConfig: relationships.dataSaveConfig,
});
2025-09-29 12:17:10 +09:00
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,
};
2025-09-29 12:17:10 +09:00
toast.error(errorResult.message);
return errorResult;
}
}
/**
*
*/
private static async getRelationshipData(relationshipId: string): Promise<any> {
try {
console.log(`🔍 관계 데이터 조회 시작: ${relationshipId}`);
2025-09-29 12:17:10 +09:00
const response = await apiClient.get(`/dataflow-diagrams/${relationshipId}`);
2025-09-29 12:17:10 +09:00
console.log(`✅ 관계 데이터 조회 성공:`, response.data);
2025-09-29 12:17:10 +09:00
if (!response.data.success) {
throw new Error(response.data.message || "관계 데이터 조회 실패");
2025-09-29 12:17:10 +09:00
}
2025-09-29 12:17:10 +09:00
return response.data.data;
} catch (error) {
console.error("관계 데이터 조회 오류:", error);
2025-09-29 12:17:10 +09:00
throw error;
}
}
/**
*
*/
private static async executeExternalCall(
relationships: any,
formData: Record<string, any>,
context: ButtonExecutionContext,
2025-09-29 12:17:10 +09:00
): Promise<ExecutionResult> {
try {
2025-09-29 13:32:59 +09:00
console.log(`🔍 외부 호출 실행 시작 - relationships 구조:`, relationships);
2025-09-29 12:17:10 +09:00
const externalCallConfig = relationships.externalCallConfig;
2025-09-29 13:32:59 +09:00
console.log(`🔍 externalCallConfig:`, externalCallConfig);
2025-09-29 12:17:10 +09:00
if (!externalCallConfig) {
console.error("❌ 외부 호출 설정이 없습니다. relationships 구조:", relationships);
throw new Error("외부 호출 설정이 없습니다");
2025-09-29 12:17:10 +09:00
}
const restApiSettings = externalCallConfig.restApiSettings;
if (!restApiSettings) {
throw new Error("REST API 설정이 없습니다");
2025-09-29 12:17:10 +09:00
}
console.log(`🌐 외부 API 호출: ${restApiSettings.apiUrl}`);
// API 호출 준비
const headers: Record<string, string> = {
"Content-Type": "application/json",
2025-09-29 12:17:10 +09:00
...restApiSettings.headers,
};
// 인증 처리
if (restApiSettings.authentication?.type === "api-key") {
headers["Authorization"] = `Bearer ${restApiSettings.authentication.apiKey}`;
2025-09-29 12:17:10 +09:00
}
// 요청 바디 준비 (템플릿 처리)
let requestBody = restApiSettings.bodyTemplate || "";
2025-09-29 12:17:10 +09:00
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",
2025-09-29 12:17:10 +09:00
headers,
body: restApiSettings.httpMethod !== "GET" ? requestBody : undefined,
2025-09-29 12:17:10 +09:00
});
// 백엔드 프록시 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",
2025-09-29 12:17:10 +09:00
headers: restApiSettings.headers || {},
body: requestBody,
authentication: restApiSettings.authentication || { type: "none" },
2025-09-29 12:17:10 +09:00
timeout: restApiSettings.timeout || 30000,
retryCount: restApiSettings.retryCount || 3,
},
templateData: restApiSettings.httpMethod !== "GET" && requestBody ? JSON.parse(requestBody) : formData,
2025-09-29 12:17:10 +09:00
};
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) {
2025-09-29 13:32:59 +09:00
console.log(`📥 데이터 매핑 설정 발견 - HTTP 메서드: ${restApiSettings.httpMethod}`);
console.log(`📥 매핑 설정:`, externalCallConfig.dataMappingConfig.inboundMapping);
console.log(`📥 응답 데이터:`, responseData);
await this.processInboundMapping(externalCallConfig.dataMappingConfig.inboundMapping, responseData, context);
2025-09-29 13:32:59 +09:00
} else {
console.log(` 데이터 매핑 설정이 없습니다 - HTTP 메서드: ${restApiSettings.httpMethod}`);
2025-09-29 12:17:10 +09:00
}
return {
success: true,
message: "외부 호출 실행 완료",
2025-09-29 12:17:10 +09:00
executionTime: Date.now() - context.startTime,
data: responseData,
};
} catch (error: any) {
console.error("외부 호출 실행 오류:", error);
2025-09-29 12:17:10 +09:00
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,
2025-09-29 12:17:10 +09:00
): 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: "제어 조건을 만족하지 않아 데이터 저장을 건너뜁니다",
2025-09-29 12:17:10 +09:00
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);
2025-09-29 12:17:10 +09:00
results.push(actionResult);
if (!actionResult.success) {
console.error(`❌ 액션 실행 실패: ${action.name}`, actionResult);
}
}
}
const successCount = results.filter((r) => r.success).length;
2025-09-29 12:17:10 +09:00
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);
2025-09-29 12:17:10 +09:00
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,
2025-09-29 12:17:10 +09:00
): Promise<ExecutionResult> {
try {
console.log(`🔧 데이터 액션 실행: ${action.name} (${action.actionType})`);
console.log(`📥 받은 formData:`, formData);
// 🔥 UPDATE 액션의 경우 formData를 기본으로 시작
const mappedData: Record<string, any> = action.actionType === "update" ? { ...formData } : {};
2025-09-29 12:17:10 +09:00
for (const mapping of action.fieldMappings) {
if (mapping.valueType === "static") {
2025-09-29 12:17:10 +09:00
// 정적 값 처리
let value = mapping.value;
if (value === "#NOW") {
2025-09-29 12:17:10 +09:00
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);
console.log(`🔑 기본키 포함 여부 체크:`, {
hasId: "id" in mappedData,
keys: Object.keys(mappedData),
});
2025-09-29 12:17:10 +09:00
// 대상 연결 정보
const toConnection = relationships.toConnection;
const targetTable = relationships.toTable?.tableName;
if (!targetTable) {
throw new Error("대상 테이블이 지정되지 않았습니다");
2025-09-29 12:17:10 +09:00
}
// 데이터 저장 API 호출
const saveResult = await this.saveDataToTable(targetTable, mappedData, action.actionType, toConnection);
2025-09-29 12:17:10 +09:00
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,
2025-09-29 12:17:10 +09:00
): Promise<any> {
try {
2025-09-29 13:32:59 +09:00
console.log(`💾 테이블 데이터 저장 시작: ${tableName}`, {
actionType,
data,
connection,
2025-09-29 12:17:10 +09:00
});
2025-09-29 13:32:59 +09:00
// 데이터 저장 API 호출 (apiClient 사용)
const response = await apiClient.post("/dataflow/execute-data-action", {
2025-09-29 13:32:59 +09:00
tableName,
data,
actionType,
connection,
});
2025-09-29 12:17:10 +09:00
2025-09-29 13:32:59 +09:00
console.log(`✅ 테이블 데이터 저장 성공: ${tableName}`, response.data);
return response.data;
2025-09-29 12:17:10 +09:00
} catch (error) {
console.error("테이블 데이터 저장 오류:", error);
2025-09-29 12:17:10 +09:00
throw error;
}
}
/**
*
*/
private static evaluateConditions(
conditions: any[],
formData: Record<string, any>,
context: ButtonExecutionContext,
2025-09-29 12:17:10 +09:00
): boolean {
2025-09-29 15:21:14 +09:00
console.log(`🔍 조건 평가 시작:`, {
conditions,
formDataKeys: Object.keys(formData),
formData,
contextData: context.contextData,
});
2025-09-29 12:17:10 +09:00
for (const condition of conditions) {
const fieldValue = formData[condition.field];
const conditionValue = condition.value;
const operator = condition.operator;
2025-09-29 15:21:14 +09:00
console.log(`🔍 개별 조건 검증:`, {
field: condition.field,
operator,
expectedValue: conditionValue,
actualValue: fieldValue,
formDataHasField: condition.field in formData,
allFormDataKeys: Object.keys(formData),
});
2025-09-29 12:17:10 +09:00
let conditionMet = false;
switch (operator) {
case "=":
2025-09-29 12:17:10 +09:00
conditionMet = fieldValue === conditionValue;
break;
case "!=":
2025-09-29 12:17:10 +09:00
conditionMet = fieldValue !== conditionValue;
break;
case ">":
2025-09-29 12:17:10 +09:00
conditionMet = Number(fieldValue) > Number(conditionValue);
break;
case "<":
2025-09-29 12:17:10 +09:00
conditionMet = Number(fieldValue) < Number(conditionValue);
break;
case ">=":
2025-09-29 12:17:10 +09:00
conditionMet = Number(fieldValue) >= Number(conditionValue);
break;
case "<=":
2025-09-29 12:17:10 +09:00
conditionMet = Number(fieldValue) <= Number(conditionValue);
break;
default:
console.warn(`지원하지 않는 연산자: ${operator}`);
conditionMet = true;
}
if (!conditionMet) {
console.log(`❌ 조건 불만족: ${condition.field} ${operator} ${conditionValue} (실제값: ${fieldValue})`);
2025-09-29 15:21:14 +09:00
console.log(`❌ 사용 가능한 필드들:`, Object.keys(formData));
console.log(`❌ 전체 formData:`, formData);
2025-09-29 12:17:10 +09:00
return false;
}
}
console.log(`✅ 모든 조건 만족`);
return true;
}
2025-09-29 13:32:59 +09:00
/**
* API
*/
private static extractActualData(responseData: any): any {
console.log(`🔍 데이터 추출 시작 - 원본 타입: ${typeof responseData}`);
2025-09-29 13:32:59 +09:00
// null이나 undefined인 경우
if (!responseData) {
console.log(`⚠️ 응답 데이터가 null 또는 undefined`);
return [];
}
// 이미 배열인 경우 (직접 배열 응답)
if (Array.isArray(responseData)) {
console.log(`✅ 직접 배열 응답 감지`);
return responseData;
}
// 문자열인 경우 JSON 파싱 시도
if (typeof responseData === "string") {
2025-09-29 13:32:59 +09:00
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") {
2025-09-29 13:32:59 +09:00
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: [...] }
2025-09-29 13:32:59 +09:00
];
for (const field of commonDataFields) {
if (responseData[field] !== undefined) {
console.log(`✅ '${field}' 필드에서 데이터 추출`);
2025-09-29 13:32:59 +09:00
const extractedData = responseData[field];
2025-09-29 13:32:59 +09:00
// 추출된 데이터가 문자열인 경우 JSON 파싱 시도
if (typeof extractedData === "string") {
2025-09-29 13:32:59 +09:00
console.log(`🔄 추출된 데이터가 JSON 문자열, 파싱 시도`);
try {
const parsed = JSON.parse(extractedData);
console.log(`✅ JSON 파싱 성공, 재귀 호출`);
return this.extractActualData(parsed);
} catch (error) {
console.log(`⚠️ JSON 파싱 실패, 원본 문자열 반환:`, error);
return [extractedData];
}
}
2025-09-29 13:32:59 +09:00
// 추출된 데이터가 객체이고 또 다른 중첩 구조일 수 있으므로 재귀 호출
if (typeof extractedData === "object" && !Array.isArray(extractedData)) {
2025-09-29 13:32:59 +09:00
console.log(`🔄 중첩된 객체 감지, 재귀 추출 시도`);
return this.extractActualData(extractedData);
}
2025-09-29 13:32:59 +09:00
return extractedData;
}
}
// 특별한 필드가 없는 경우, 객체의 값들 중에서 배열을 찾기
const objectValues = Object.values(responseData);
const arrayValue = objectValues.find((value) => Array.isArray(value));
2025-09-29 13:32:59 +09:00
if (arrayValue) {
console.log(`✅ 객체 값 중 배열 발견`);
return arrayValue;
}
// 객체의 값들 중에서 객체를 찾아서 재귀 탐색
for (const value of objectValues) {
if (value && typeof value === "object" && !Array.isArray(value)) {
2025-09-29 13:32:59 +09:00
console.log(`🔄 객체 값에서 재귀 탐색`);
const nestedResult = this.extractActualData(value);
if (Array.isArray(nestedResult) && nestedResult.length > 0) {
return nestedResult;
}
}
}
// 모든 시도가 실패한 경우, 원본 객체를 단일 항목 배열로 반환
console.log(`📦 원본 객체를 단일 항목으로 처리`);
return [responseData];
}
2025-09-29 12:17:10 +09:00
/**
*
*/
private static async processInboundMapping(
inboundMapping: any,
responseData: any,
context: ButtonExecutionContext,
2025-09-29 12:17:10 +09:00
): Promise<void> {
try {
console.log(`📥 인바운드 데이터 매핑 처리 시작`);
2025-09-29 13:32:59 +09:00
console.log(`📥 원본 응답 데이터:`, responseData);
2025-09-29 12:17:10 +09:00
const targetTable = inboundMapping.targetTable;
const fieldMappings = inboundMapping.fieldMappings || [];
const insertMode = inboundMapping.insertMode || "insert";
2025-09-29 12:17:10 +09:00
2025-09-29 13:32:59 +09:00
console.log(`📥 매핑 설정:`, {
targetTable,
fieldMappings,
insertMode,
2025-09-29 13:32:59 +09:00
});
// 응답 데이터에서 실제 데이터 추출 (다양한 구조 지원)
let actualData = this.extractActualData(responseData);
2025-09-29 13:32:59 +09:00
console.log(`📥 추출된 실제 데이터:`, actualData);
// 배열이 아닌 경우 배열로 변환
const dataArray = Array.isArray(actualData) ? actualData : [actualData];
2025-09-29 13:32:59 +09:00
console.log(`📥 처리할 데이터 배열:`, dataArray);
if (dataArray.length === 0) {
console.log(`⚠️ 처리할 데이터가 없습니다`);
return;
}
2025-09-29 12:17:10 +09:00
for (const item of dataArray) {
const mappedData: Record<string, any> = {};
2025-09-29 13:32:59 +09:00
console.log(`📥 개별 아이템 처리:`, item);
2025-09-29 12:17:10 +09:00
// 필드 매핑 적용
for (const mapping of fieldMappings) {
const sourceValue = item[mapping.sourceField];
2025-09-29 13:32:59 +09:00
console.log(`📥 필드 매핑: ${mapping.sourceField} -> ${mapping.targetField} = ${sourceValue}`);
2025-09-29 13:32:59 +09:00
if (sourceValue !== undefined && sourceValue !== null) {
2025-09-29 12:17:10 +09:00
mappedData[mapping.targetField] = sourceValue;
}
}
console.log(`📋 매핑된 데이터:`, mappedData);
2025-09-29 13:32:59 +09:00
// 매핑된 데이터가 비어있지 않은 경우에만 저장
if (Object.keys(mappedData).length > 0) {
await this.saveDataToTable(targetTable, mappedData, insertMode);
} else {
console.log(`⚠️ 매핑된 데이터가 비어있어 저장을 건너뜁니다`);
}
2025-09-29 12:17:10 +09:00
}
console.log(`✅ 인바운드 데이터 매핑 완료`);
} catch (error) {
console.error("인바운드 데이터 매핑 오류:", error);
2025-09-29 12:17:10 +09:00
throw error;
}
}
/**
* 🔥
*/
private static async executeMainAction(
buttonConfig: ExtendedButtonTypeConfig,
formData: Record<string, any>,
context: ButtonExecutionContext,
2025-09-29 12:17:10 +09:00
): 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,
2025-09-29 12:17:10 +09:00
): Promise<void> {
console.error("🔄 실행 오류 처리 시작:", error.message);
// 롤백이 필요한 경우 처리
const rollbackNeeded = buttonConfig.dataflowConfig?.executionOptions?.rollbackOnError;
if (rollbackNeeded) {
console.log("🔄 롤백 처리 시작...");
2025-09-29 12:17:10 +09:00
// 성공한 결과들을 역순으로 롤백
const successfulResults = results.filter((r) => r.success).reverse();
2025-09-29 12:17:10 +09:00
for (const result of successfulResults) {
try {
// 롤백 로직 구현 (필요시)
console.log("🔄 롤백:", result.message);
} catch (rollbackError) {
console.error("롤백 실패:", rollbackError);
}
}
}
// 오류 토스트 표시
toast.error(error.message || "작업 중 오류가 발생했습니다.");
}
}