테스트 프로젝트 테이블 생성 및 오류들 수정
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { toast } from "sonner";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { DynamicFormApi } from "@/lib/api/dynamicForm";
|
||||
import { OptimizedButtonDataflowService, ExtendedControlContext } from "@/lib/services/optimizedButtonDataflowService";
|
||||
|
||||
/**
|
||||
* 버튼 액션 타입 정의
|
||||
@@ -46,6 +47,10 @@ export interface ButtonActionConfig {
|
||||
confirmMessage?: string;
|
||||
successMessage?: string;
|
||||
errorMessage?: string;
|
||||
|
||||
// 제어관리 관련
|
||||
enableDataflowControl?: boolean;
|
||||
dataflowConfig?: any; // ButtonDataflowConfig 타입 (순환 참조 방지를 위해 any 사용)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,6 +121,9 @@ export class ButtonActionExecutor {
|
||||
case "close":
|
||||
return this.handleClose(config, context);
|
||||
|
||||
case "control":
|
||||
return this.handleControl(config, context);
|
||||
|
||||
default:
|
||||
console.warn(`지원되지 않는 액션 타입: ${config.type}`);
|
||||
return false;
|
||||
@@ -712,6 +720,497 @@ export class ButtonActionExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 제어 전용 액션 처리 (조건 체크만 수행)
|
||||
*/
|
||||
private static async handleControl(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> {
|
||||
console.log("🎯 ButtonActionExecutor.handleControl 실행:", {
|
||||
formData: context.formData,
|
||||
selectedRows: context.selectedRows,
|
||||
selectedRowsData: context.selectedRowsData,
|
||||
config,
|
||||
});
|
||||
|
||||
// 🔥 제어 조건이 설정되어 있는지 확인
|
||||
console.log("🔍 제어관리 활성화 상태 확인:", {
|
||||
enableDataflowControl: config.enableDataflowControl,
|
||||
hasDataflowConfig: !!config.dataflowConfig,
|
||||
dataflowConfig: config.dataflowConfig,
|
||||
fullConfig: config,
|
||||
});
|
||||
|
||||
if (!config.dataflowConfig || !config.enableDataflowControl) {
|
||||
console.warn("⚠️ 제어관리가 비활성화되어 있습니다:", {
|
||||
enableDataflowControl: config.enableDataflowControl,
|
||||
hasDataflowConfig: !!config.dataflowConfig,
|
||||
});
|
||||
toast.warning(
|
||||
"제어관리가 활성화되지 않았습니다. 버튼 설정에서 '제어관리 활성화'를 체크하고 조건을 설정해주세요.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 🔥 확장된 제어 컨텍스트 생성
|
||||
// 자동으로 적절한 controlDataSource 결정
|
||||
let controlDataSource = config.dataflowConfig.controlDataSource;
|
||||
|
||||
if (!controlDataSource) {
|
||||
// 설정이 없으면 자동 판단
|
||||
if (context.selectedRowsData && context.selectedRowsData.length > 0) {
|
||||
controlDataSource = "table-selection";
|
||||
console.log("🔄 자동 판단: table-selection 모드 사용");
|
||||
} else if (context.formData && Object.keys(context.formData).length > 0) {
|
||||
controlDataSource = "form";
|
||||
console.log("🔄 자동 판단: form 모드 사용");
|
||||
} else {
|
||||
controlDataSource = "form"; // 기본값
|
||||
console.log("🔄 기본값: form 모드 사용");
|
||||
}
|
||||
}
|
||||
|
||||
const extendedContext: ExtendedControlContext = {
|
||||
formData: context.formData || {},
|
||||
selectedRows: context.selectedRows || [],
|
||||
selectedRowsData: context.selectedRowsData || [],
|
||||
controlDataSource,
|
||||
};
|
||||
|
||||
console.log("🔍 제어 조건 검증 시작:", {
|
||||
dataflowConfig: config.dataflowConfig,
|
||||
extendedContext,
|
||||
});
|
||||
|
||||
// 🔥 실제 제어 조건 검증 수행
|
||||
const validationResult = await OptimizedButtonDataflowService.executeExtendedValidation(
|
||||
config.dataflowConfig,
|
||||
extendedContext,
|
||||
);
|
||||
|
||||
if (validationResult.success) {
|
||||
console.log("✅ 제어 조건 만족 - 액션 실행 시작:", {
|
||||
actions: validationResult.actions,
|
||||
context,
|
||||
});
|
||||
|
||||
// 🔥 조건을 만족했으므로 실제 액션 실행
|
||||
if (validationResult.actions && validationResult.actions.length > 0) {
|
||||
console.log("🚀 액션 실행 시작:", validationResult.actions);
|
||||
await this.executeRelationshipActions(validationResult.actions, context);
|
||||
} else {
|
||||
console.warn("⚠️ 실행할 액션이 없습니다:", {
|
||||
hasActions: !!validationResult.actions,
|
||||
actionsLength: validationResult.actions?.length,
|
||||
validationResult,
|
||||
});
|
||||
toast.success(config.successMessage || "제어 조건을 만족합니다. (실행할 액션 없음)");
|
||||
}
|
||||
|
||||
// 새로고침이 필요한 경우
|
||||
if (context.onRefresh) {
|
||||
context.onRefresh();
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
toast.error(validationResult.message || "제어 조건을 만족하지 않습니다.");
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("제어 조건 검증 중 오류:", error);
|
||||
toast.error("제어 조건 검증 중 오류가 발생했습니다.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 관계도에서 가져온 액션들을 실행
|
||||
*/
|
||||
private static async executeRelationshipActions(actions: any[], context: ButtonActionContext): Promise<void> {
|
||||
console.log("🚀 관계도 액션 실행 시작:", actions);
|
||||
|
||||
for (let i = 0; i < actions.length; i++) {
|
||||
const action = actions[i];
|
||||
|
||||
try {
|
||||
console.log(`🔄 액션 ${i + 1}/${actions.length} 실행:`, action);
|
||||
|
||||
const actionType = action.actionType || action.type; // actionType 우선, type 폴백
|
||||
|
||||
switch (actionType) {
|
||||
case "save":
|
||||
await this.executeActionSave(action, context);
|
||||
break;
|
||||
case "update":
|
||||
await this.executeActionUpdate(action, context);
|
||||
break;
|
||||
case "delete":
|
||||
await this.executeActionDelete(action, context);
|
||||
break;
|
||||
case "insert":
|
||||
await this.executeActionInsert(action, context);
|
||||
break;
|
||||
default:
|
||||
console.warn(`❌ 지원되지 않는 액션 타입 (${i + 1}/${actions.length}):`, {
|
||||
actionType,
|
||||
actionName: action.name,
|
||||
fullAction: action,
|
||||
});
|
||||
// 지원되지 않는 액션은 오류로 처리하여 중단
|
||||
throw new Error(`지원되지 않는 액션 타입: ${actionType}`);
|
||||
}
|
||||
|
||||
console.log(`✅ 액션 ${i + 1}/${actions.length} 완료:`, action.name);
|
||||
|
||||
// 성공 토스트 (개별 액션별)
|
||||
toast.success(`${action.name || `액션 ${i + 1}`} 완료`);
|
||||
} catch (error) {
|
||||
const actionType = action.actionType || action.type;
|
||||
console.error(`❌ 액션 ${i + 1}/${actions.length} 실행 실패:`, action.name, error);
|
||||
|
||||
// 실패 토스트
|
||||
toast.error(
|
||||
`${action.name || `액션 ${i + 1}`} 실행 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`,
|
||||
);
|
||||
|
||||
// 🚨 순차 실행 중단: 하나라도 실패하면 전체 중단
|
||||
throw new Error(
|
||||
`액션 ${i + 1}(${action.name})에서 실패하여 제어 프로세스를 중단합니다: ${error instanceof Error ? error.message : error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🎉 모든 액션 실행 완료!");
|
||||
toast.success(`총 ${actions.length}개 액션이 모두 성공적으로 완료되었습니다.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장 액션 실행
|
||||
*/
|
||||
private static async executeActionSave(action: any, context: ButtonActionContext): Promise<void> {
|
||||
console.log("💾 저장 액션 실행:", action);
|
||||
console.log("🔍 액션 상세 정보:", JSON.stringify(action, null, 2));
|
||||
|
||||
// 🎯 필드 매핑 정보 사용하여 저장 데이터 구성
|
||||
let saveData: Record<string, any> = {};
|
||||
|
||||
// 액션에 필드 매핑 정보가 있는지 확인
|
||||
if (action.fieldMappings && Array.isArray(action.fieldMappings)) {
|
||||
console.log("📋 필드 매핑 정보 발견:", action.fieldMappings);
|
||||
|
||||
// 필드 매핑에 따라 데이터 구성
|
||||
action.fieldMappings.forEach((mapping: any) => {
|
||||
const { sourceField, targetField, defaultValue, valueType } = mapping;
|
||||
|
||||
let value: any;
|
||||
|
||||
// 값 소스에 따라 데이터 가져오기
|
||||
if (valueType === "form" && context.formData && sourceField) {
|
||||
value = context.formData[sourceField];
|
||||
} else if (valueType === "selected" && context.selectedRowsData?.[0] && sourceField) {
|
||||
value = context.selectedRowsData[0][sourceField];
|
||||
} else if (valueType === "default" || !sourceField) {
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
// 타겟 필드에 값 설정
|
||||
if (targetField && value !== undefined) {
|
||||
saveData[targetField] = value;
|
||||
console.log(`📝 필드 매핑: ${sourceField || "default"} -> ${targetField} = ${value}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("⚠️ 필드 매핑 정보가 없음, 기본 데이터 사용");
|
||||
// 폴백: 기존 방식
|
||||
saveData = {
|
||||
...context.formData,
|
||||
...context.selectedRowsData?.[0], // 선택된 데이터도 포함
|
||||
};
|
||||
}
|
||||
|
||||
console.log("📊 최종 저장할 데이터:", saveData);
|
||||
|
||||
try {
|
||||
// 🔥 실제 저장 API 호출
|
||||
if (!context.tableName) {
|
||||
throw new Error("테이블명이 설정되지 않았습니다.");
|
||||
}
|
||||
|
||||
const result = await DynamicFormApi.saveFormData({
|
||||
screenId: 0, // 임시값
|
||||
tableName: context.tableName,
|
||||
data: saveData,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ 저장 성공:", result);
|
||||
toast.success("데이터가 저장되었습니다.");
|
||||
} else {
|
||||
throw new Error(result.message || "저장 실패");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 저장 실패:", error);
|
||||
toast.error(`저장 실패: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 업데이트 액션 실행
|
||||
*/
|
||||
private static async executeActionUpdate(action: any, context: ButtonActionContext): Promise<void> {
|
||||
console.log("🔄 업데이트 액션 실행:", action);
|
||||
console.log("🔍 액션 상세 정보:", JSON.stringify(action, null, 2));
|
||||
|
||||
// 🎯 필드 매핑 정보 사용하여 업데이트 데이터 구성
|
||||
let updateData: Record<string, any> = {};
|
||||
|
||||
// 액션에 필드 매핑 정보가 있는지 확인
|
||||
if (action.fieldMappings && Array.isArray(action.fieldMappings)) {
|
||||
console.log("📋 필드 매핑 정보 발견:", action.fieldMappings);
|
||||
|
||||
// 🔑 먼저 선택된 데이터의 모든 필드를 기본으로 포함 (기본키 보존)
|
||||
if (context.selectedRowsData?.[0]) {
|
||||
updateData = { ...context.selectedRowsData[0] };
|
||||
console.log("🔑 선택된 데이터를 기본으로 설정 (기본키 보존):", updateData);
|
||||
}
|
||||
|
||||
// 필드 매핑에 따라 데이터 구성 (덮어쓰기)
|
||||
action.fieldMappings.forEach((mapping: any) => {
|
||||
const { sourceField, targetField, defaultValue, valueType } = mapping;
|
||||
|
||||
let value: any;
|
||||
|
||||
// 값 소스에 따라 데이터 가져오기
|
||||
if (valueType === "form" && context.formData && sourceField) {
|
||||
value = context.formData[sourceField];
|
||||
} else if (valueType === "selected" && context.selectedRowsData?.[0] && sourceField) {
|
||||
value = context.selectedRowsData[0][sourceField];
|
||||
} else if (valueType === "default" || !sourceField) {
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
// 타겟 필드에 값 설정 (덮어쓰기)
|
||||
if (targetField && value !== undefined) {
|
||||
updateData[targetField] = value;
|
||||
console.log(`📝 필드 매핑: ${sourceField || "default"} -> ${targetField} = ${value}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("⚠️ 필드 매핑 정보가 없음, 기본 데이터 사용");
|
||||
// 폴백: 기존 방식
|
||||
updateData = {
|
||||
...context.formData,
|
||||
...context.selectedRowsData?.[0],
|
||||
};
|
||||
}
|
||||
|
||||
console.log("📊 최종 업데이트할 데이터:", updateData);
|
||||
|
||||
try {
|
||||
// 🔥 실제 업데이트 API 호출
|
||||
if (!context.tableName) {
|
||||
throw new Error("테이블명이 설정되지 않았습니다.");
|
||||
}
|
||||
|
||||
// 먼저 ID 찾기
|
||||
const primaryKeysResult = await DynamicFormApi.getTablePrimaryKeys(context.tableName);
|
||||
let updateId: string | undefined;
|
||||
|
||||
if (primaryKeysResult.success && primaryKeysResult.data && primaryKeysResult.data.length > 0) {
|
||||
updateId = updateData[primaryKeysResult.data[0]];
|
||||
}
|
||||
|
||||
if (!updateId) {
|
||||
// 폴백: 일반적인 ID 필드들 확인
|
||||
const commonIdFields = ["id", "objid", "pk", "sales_no", "contract_no"];
|
||||
for (const field of commonIdFields) {
|
||||
if (updateData[field]) {
|
||||
updateId = updateData[field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!updateId) {
|
||||
throw new Error("업데이트할 항목의 ID를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
const result = await DynamicFormApi.updateFormData(updateId, {
|
||||
tableName: context.tableName,
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ 업데이트 성공:", result);
|
||||
toast.success("데이터가 업데이트되었습니다.");
|
||||
} else {
|
||||
throw new Error(result.message || "업데이트 실패");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 업데이트 실패:", error);
|
||||
toast.error(`업데이트 실패: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 삭제 액션 실행
|
||||
*/
|
||||
private static async executeActionDelete(action: any, context: ButtonActionContext): Promise<void> {
|
||||
console.log("🗑️ 삭제 액션 실행:", action);
|
||||
|
||||
// 실제 삭제 로직 (기존 handleDelete와 유사)
|
||||
if (!context.selectedRowsData || context.selectedRowsData.length === 0) {
|
||||
throw new Error("삭제할 항목을 선택해주세요.");
|
||||
}
|
||||
|
||||
const deleteData = context.selectedRowsData[0];
|
||||
console.log("삭제할 데이터:", deleteData);
|
||||
|
||||
try {
|
||||
// 🔥 실제 삭제 API 호출
|
||||
if (!context.tableName) {
|
||||
throw new Error("테이블명이 설정되지 않았습니다.");
|
||||
}
|
||||
|
||||
// 기존 handleDelete와 동일한 로직으로 ID 찾기
|
||||
const primaryKeysResult = await DynamicFormApi.getTablePrimaryKeys(context.tableName);
|
||||
let deleteId: string | undefined;
|
||||
|
||||
if (primaryKeysResult.success && primaryKeysResult.data && primaryKeysResult.data.length > 0) {
|
||||
deleteId = deleteData[primaryKeysResult.data[0]];
|
||||
}
|
||||
|
||||
if (!deleteId) {
|
||||
// 폴백: 일반적인 ID 필드들 확인
|
||||
const commonIdFields = ["id", "objid", "pk", "sales_no", "contract_no"];
|
||||
for (const field of commonIdFields) {
|
||||
if (deleteData[field]) {
|
||||
deleteId = deleteData[field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!deleteId) {
|
||||
throw new Error("삭제할 항목의 ID를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
const result = await DynamicFormApi.deleteFormDataFromTable(deleteId, context.tableName);
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ 삭제 성공:", result);
|
||||
toast.success("데이터가 삭제되었습니다.");
|
||||
} else {
|
||||
throw new Error(result.message || "삭제 실패");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 삭제 실패:", error);
|
||||
toast.error(`삭제 실패: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 삽입 액션 실행 (체크박스 선택된 데이터를 필드매핑에 따라 새 테이블에 삽입)
|
||||
*/
|
||||
private static async executeActionInsert(action: any, context: ButtonActionContext): Promise<void> {
|
||||
console.log("➕ 삽입 액션 실행:", action);
|
||||
|
||||
let insertData: Record<string, any> = {};
|
||||
|
||||
// 액션에 필드 매핑 정보가 있는지 확인
|
||||
if (action.fieldMappings && Array.isArray(action.fieldMappings)) {
|
||||
console.log("📋 삽입 액션 - 필드 매핑 정보:", action.fieldMappings);
|
||||
|
||||
// 🎯 체크박스로 선택된 데이터가 있는지 확인
|
||||
if (!context.selectedRowsData || context.selectedRowsData.length === 0) {
|
||||
throw new Error("삽입할 소스 데이터를 선택해주세요. (테이블에서 체크박스 선택 필요)");
|
||||
}
|
||||
|
||||
const sourceData = context.selectedRowsData[0]; // 첫 번째 선택된 데이터 사용
|
||||
console.log("🎯 삽입 소스 데이터 (체크박스 선택):", sourceData);
|
||||
console.log("🔍 소스 데이터 사용 가능한 키들:", Object.keys(sourceData));
|
||||
|
||||
// 필드 매핑에 따라 데이터 구성
|
||||
action.fieldMappings.forEach((mapping: any) => {
|
||||
const { sourceField, targetField, defaultValue } = mapping;
|
||||
// valueType이 없으면 기본값을 "selection"으로 설정
|
||||
const valueType = mapping.valueType || "selection";
|
||||
|
||||
let value: any;
|
||||
|
||||
console.log(`🔍 매핑 처리 중: ${sourceField} → ${targetField} (valueType: ${valueType})`);
|
||||
|
||||
// 값 소스에 따라 데이터 가져오기
|
||||
if (valueType === "form" && context.formData && sourceField) {
|
||||
// 폼 데이터에서 가져오기
|
||||
value = context.formData[sourceField];
|
||||
console.log(`📝 폼에서 매핑: ${sourceField} → ${targetField} = ${value}`);
|
||||
} else if (valueType === "selection" && sourceField) {
|
||||
// 선택된 테이블 데이터에서 가져오기 (다양한 필드명 시도)
|
||||
value =
|
||||
sourceData[sourceField] ||
|
||||
sourceData[sourceField + "_name"] || // 조인된 필드 (_name 접미사)
|
||||
sourceData[sourceField + "Name"]; // 카멜케이스
|
||||
console.log(`📊 테이블에서 매핑: ${sourceField} → ${targetField} = ${value} (소스필드: ${sourceField})`);
|
||||
} else if (valueType === "default" || (defaultValue !== undefined && defaultValue !== "")) {
|
||||
// 기본값 사용 (valueType이 "default"이거나 defaultValue가 있을 때)
|
||||
value = defaultValue;
|
||||
console.log(`🔧 기본값 매핑: ${targetField} = ${value}`);
|
||||
} else {
|
||||
console.warn(`⚠️ 매핑 실패: ${sourceField} → ${targetField} (값을 찾을 수 없음)`);
|
||||
console.warn(` - valueType: ${valueType}, defaultValue: ${defaultValue}`);
|
||||
console.warn(` - 소스 데이터 키들:`, Object.keys(sourceData));
|
||||
console.warn(` - sourceData[${sourceField}] =`, sourceData[sourceField]);
|
||||
return; // 값이 없으면 해당 필드는 스킵
|
||||
}
|
||||
|
||||
// 대상 필드에 값 설정
|
||||
if (targetField && value !== undefined && value !== null) {
|
||||
insertData[targetField] = value;
|
||||
}
|
||||
});
|
||||
|
||||
console.log("🎯 최종 삽입 데이터 (필드매핑 적용):", insertData);
|
||||
} else {
|
||||
// 필드 매핑이 없으면 폼 데이터를 기본으로 사용
|
||||
insertData = { ...context.formData };
|
||||
console.log("📝 기본 삽입 데이터 (폼 기반):", insertData);
|
||||
}
|
||||
|
||||
try {
|
||||
// 🔥 실제 삽입 API 호출 - 필수 매개변수 포함
|
||||
// 필드 매핑에서 첫 번째 targetTable을 찾거나 기본값 사용
|
||||
const targetTable = action.fieldMappings?.[0]?.targetTable || action.targetTable || "test_project_info";
|
||||
|
||||
const formDataPayload = {
|
||||
screenId: 0, // 제어 관리에서는 screenId가 없으므로 0 사용
|
||||
tableName: targetTable, // 필드 매핑에서 대상 테이블명 가져오기
|
||||
data: insertData,
|
||||
};
|
||||
|
||||
console.log("🎯 대상 테이블:", targetTable);
|
||||
console.log("📋 삽입할 데이터:", insertData);
|
||||
|
||||
console.log("💾 폼 데이터 저장 요청:", formDataPayload);
|
||||
|
||||
const result = await DynamicFormApi.saveFormData(formDataPayload);
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ 삽입 성공:", result);
|
||||
toast.success(`데이터가 타겟 테이블에 성공적으로 삽입되었습니다.`);
|
||||
} else {
|
||||
throw new Error(result.message || "삽입 실패");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 삽입 실패:", error);
|
||||
toast.error(`삽입 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 폼 데이터 유효성 검사
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user