2025-09-18 10:05:50 +09:00
|
|
|
/**
|
|
|
|
|
* 🔥 버튼 데이터플로우 컨트롤러
|
|
|
|
|
*
|
|
|
|
|
* 성능 최적화를 위한 API 엔드포인트:
|
|
|
|
|
* 1. 즉시 응답 패턴
|
|
|
|
|
* 2. 백그라운드 작업 처리
|
|
|
|
|
* 3. 캐시 활용
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { Request, Response } from "express";
|
|
|
|
|
import { AuthenticatedRequest } from "../types/auth";
|
|
|
|
|
import EventTriggerService from "../services/eventTriggerService";
|
|
|
|
|
import * as dataflowDiagramService from "../services/dataflowDiagramService";
|
|
|
|
|
import logger from "../utils/logger";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 버튼 설정 조회 (캐시 지원)
|
|
|
|
|
*/
|
|
|
|
|
export async function getButtonDataflowConfig(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { buttonId } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode;
|
|
|
|
|
|
|
|
|
|
if (!buttonId) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "버튼 ID가 필요합니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 버튼별 제어관리 설정 조회
|
|
|
|
|
// TODO: 실제 버튼 설정 테이블에서 조회
|
|
|
|
|
// 현재는 mock 데이터 반환
|
|
|
|
|
const mockConfig = {
|
|
|
|
|
controlMode: "simple",
|
|
|
|
|
selectedDiagramId: 1,
|
|
|
|
|
selectedRelationshipId: "rel-123",
|
|
|
|
|
executionOptions: {
|
|
|
|
|
rollbackOnError: true,
|
|
|
|
|
enableLogging: true,
|
|
|
|
|
asyncExecution: true,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: mockConfig,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to get button dataflow config:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "버튼 설정 조회 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 버튼 설정 업데이트
|
|
|
|
|
*/
|
|
|
|
|
export async function updateButtonDataflowConfig(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { buttonId } = req.params;
|
|
|
|
|
const config = req.body;
|
|
|
|
|
const companyCode = req.user?.companyCode;
|
|
|
|
|
|
|
|
|
|
if (!buttonId) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "버튼 ID가 필요합니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: 실제 버튼 설정 테이블에 저장
|
|
|
|
|
logger.info(`Button dataflow config updated: ${buttonId}`, config);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "버튼 설정이 업데이트되었습니다.",
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to update button dataflow config:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "버튼 설정 업데이트 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 사용 가능한 관계도 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
export async function getAvailableDiagrams(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user?.companyCode;
|
|
|
|
|
|
|
|
|
|
if (!companyCode) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "회사 코드가 필요합니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const diagramsResult = await dataflowDiagramService.getDataflowDiagrams(
|
|
|
|
|
companyCode,
|
|
|
|
|
1,
|
|
|
|
|
100
|
|
|
|
|
);
|
|
|
|
|
const diagrams = diagramsResult.diagrams;
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: diagrams,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to get available diagrams:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "관계도 목록 조회 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 특정 관계도의 관계 목록 조회
|
|
|
|
|
*/
|
|
|
|
|
export async function getDiagramRelationships(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { diagramId } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode;
|
|
|
|
|
|
|
|
|
|
if (!diagramId || !companyCode) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "관계도 ID와 회사 코드가 필요합니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const diagram = await dataflowDiagramService.getDataflowDiagramById(
|
|
|
|
|
parseInt(diagramId),
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!diagram) {
|
|
|
|
|
res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "관계도를 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const relationships = (diagram.relationships as any)?.relationships || [];
|
|
|
|
|
|
2025-09-19 12:19:34 +09:00
|
|
|
console.log("🔍 백엔드 - 관계도 데이터:", {
|
|
|
|
|
diagramId: diagram.diagram_id,
|
|
|
|
|
diagramName: diagram.diagram_name,
|
|
|
|
|
relationshipsRaw: diagram.relationships,
|
|
|
|
|
relationshipsArray: relationships,
|
|
|
|
|
relationshipsCount: relationships.length,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 각 관계의 구조도 로깅
|
|
|
|
|
relationships.forEach((rel: any, index: number) => {
|
|
|
|
|
console.log(`🔍 백엔드 - 관계 ${index + 1}:`, rel);
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-18 10:05:50 +09:00
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: relationships,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to get diagram relationships:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "관계 목록 조회 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 관계 미리보기 정보 조회
|
|
|
|
|
*/
|
|
|
|
|
export async function getRelationshipPreview(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { diagramId, relationshipId } = req.params;
|
|
|
|
|
const companyCode = req.user?.companyCode;
|
|
|
|
|
|
|
|
|
|
if (!diagramId || !relationshipId || !companyCode) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "관계도 ID, 관계 ID, 회사 코드가 필요합니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const diagram = await dataflowDiagramService.getDataflowDiagramById(
|
|
|
|
|
parseInt(diagramId),
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!diagram) {
|
|
|
|
|
res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "관계도를 찾을 수 없습니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 관계 정보 찾기
|
2025-09-19 12:19:34 +09:00
|
|
|
console.log("🔍 관계 미리보기 요청:", {
|
|
|
|
|
diagramId,
|
|
|
|
|
relationshipId,
|
|
|
|
|
diagramRelationships: diagram.relationships,
|
|
|
|
|
relationshipsArray: (diagram.relationships as any)?.relationships,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const relationships = (diagram.relationships as any)?.relationships || [];
|
|
|
|
|
console.log(
|
|
|
|
|
"🔍 사용 가능한 관계 목록:",
|
|
|
|
|
relationships.map((rel: any) => ({
|
|
|
|
|
id: rel.id,
|
|
|
|
|
name: rel.relationshipName || rel.name, // relationshipName 사용
|
|
|
|
|
sourceTable: rel.fromTable || rel.sourceTable, // fromTable 사용
|
|
|
|
|
targetTable: rel.toTable || rel.targetTable, // toTable 사용
|
|
|
|
|
originalData: rel, // 디버깅용
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const relationship = relationships.find(
|
2025-09-18 10:05:50 +09:00
|
|
|
(rel: any) => rel.id === relationshipId
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-19 12:19:34 +09:00
|
|
|
console.log("🔍 찾은 관계:", relationship);
|
|
|
|
|
|
2025-09-18 10:05:50 +09:00
|
|
|
if (!relationship) {
|
2025-09-19 12:19:34 +09:00
|
|
|
console.log("❌ 관계를 찾을 수 없음:", {
|
|
|
|
|
requestedId: relationshipId,
|
|
|
|
|
availableIds: relationships.map((rel: any) => rel.id),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 🔧 임시 해결책: 첫 번째 관계를 사용하거나 기본 응답 반환
|
|
|
|
|
if (relationships.length > 0) {
|
|
|
|
|
console.log("🔧 첫 번째 관계를 대신 사용:", relationships[0].id);
|
|
|
|
|
|
|
|
|
|
const fallbackRelationship = relationships[0];
|
|
|
|
|
|
|
|
|
|
console.log("🔍 fallback 관계 선택:", fallbackRelationship);
|
|
|
|
|
console.log("🔍 diagram.control 전체 구조:", diagram.control);
|
|
|
|
|
console.log("🔍 diagram.plan 전체 구조:", diagram.plan);
|
|
|
|
|
|
|
|
|
|
const fallbackControl = Array.isArray(diagram.control)
|
|
|
|
|
? diagram.control.find((c: any) => c.id === fallbackRelationship.id)
|
|
|
|
|
: null;
|
|
|
|
|
const fallbackPlan = Array.isArray(diagram.plan)
|
|
|
|
|
? diagram.plan.find((p: any) => p.id === fallbackRelationship.id)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
console.log("🔍 찾은 fallback control:", fallbackControl);
|
|
|
|
|
console.log("🔍 찾은 fallback plan:", fallbackPlan);
|
|
|
|
|
|
|
|
|
|
const fallbackPreviewData = {
|
|
|
|
|
relationship: fallbackRelationship,
|
|
|
|
|
control: fallbackControl,
|
|
|
|
|
plan: fallbackPlan,
|
|
|
|
|
conditionsCount: (fallbackControl as any)?.conditions?.length || 0,
|
|
|
|
|
actionsCount: (fallbackPlan as any)?.actions?.length || 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
console.log("🔍 최종 fallback 응답 데이터:", fallbackPreviewData);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: fallbackPreviewData,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 10:05:50 +09:00
|
|
|
res.status(404).json({
|
|
|
|
|
success: false,
|
2025-09-19 12:19:34 +09:00
|
|
|
message: `관계를 찾을 수 없습니다. 요청된 ID: ${relationshipId}, 사용 가능한 ID: ${relationships.map((rel: any) => rel.id).join(", ")}`,
|
2025-09-18 10:05:50 +09:00
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 제어 및 계획 정보 추출
|
|
|
|
|
const control = Array.isArray(diagram.control)
|
|
|
|
|
? diagram.control.find((c: any) => c.id === relationshipId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const plan = Array.isArray(diagram.plan)
|
|
|
|
|
? diagram.plan.find((p: any) => p.id === relationshipId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const previewData = {
|
|
|
|
|
relationship,
|
|
|
|
|
control,
|
|
|
|
|
plan,
|
|
|
|
|
conditionsCount: (control as any)?.conditions?.length || 0,
|
|
|
|
|
actionsCount: (plan as any)?.actions?.length || 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: previewData,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to get relationship preview:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "관계 미리보기 조회 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 최적화된 버튼 실행 (즉시 응답)
|
|
|
|
|
*/
|
|
|
|
|
export async function executeOptimizedButton(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const {
|
|
|
|
|
buttonId,
|
|
|
|
|
actionType,
|
|
|
|
|
buttonConfig,
|
|
|
|
|
contextData,
|
|
|
|
|
timing = "after",
|
|
|
|
|
} = req.body;
|
|
|
|
|
const companyCode = req.user?.companyCode;
|
|
|
|
|
|
|
|
|
|
if (!buttonId || !actionType || !companyCode) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "필수 파라미터가 누락되었습니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
|
|
// 🔥 타이밍에 따른 즉시 응답 처리
|
|
|
|
|
if (timing === "after") {
|
|
|
|
|
// After: 기존 액션 즉시 실행 + 백그라운드 제어관리
|
|
|
|
|
const immediateResult = await executeOriginalAction(
|
|
|
|
|
actionType,
|
|
|
|
|
contextData
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 제어관리는 백그라운드에서 처리 (실제로는 큐에 추가)
|
|
|
|
|
const jobId = `job_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
|
|
|
|
|
|
|
|
// TODO: 실제 작업 큐에 추가
|
|
|
|
|
processDataflowInBackground(
|
|
|
|
|
jobId,
|
|
|
|
|
buttonConfig,
|
|
|
|
|
contextData,
|
|
|
|
|
companyCode,
|
|
|
|
|
"normal"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const responseTime = Date.now() - startTime;
|
|
|
|
|
logger.info(`Button executed (after): ${responseTime}ms`);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
jobId,
|
|
|
|
|
immediateResult,
|
|
|
|
|
isBackground: true,
|
|
|
|
|
timing: "after",
|
|
|
|
|
responseTime,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} else if (timing === "before") {
|
|
|
|
|
// Before: 간단한 검증 후 기존 액션
|
|
|
|
|
const isSimpleValidation = checkIfSimpleValidation(buttonConfig);
|
|
|
|
|
|
|
|
|
|
if (isSimpleValidation) {
|
|
|
|
|
// 간단한 검증: 즉시 처리
|
|
|
|
|
const validationResult = await validateQuickly(
|
|
|
|
|
buttonConfig,
|
|
|
|
|
contextData
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!validationResult.success) {
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
jobId: "validation_failed",
|
|
|
|
|
immediateResult: validationResult,
|
|
|
|
|
timing: "before",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 검증 통과 시 기존 액션 실행
|
|
|
|
|
const actionResult = await executeOriginalAction(
|
|
|
|
|
actionType,
|
|
|
|
|
contextData
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const responseTime = Date.now() - startTime;
|
|
|
|
|
logger.info(`Button executed (before-simple): ${responseTime}ms`);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
jobId: "immediate",
|
|
|
|
|
immediateResult: actionResult,
|
|
|
|
|
timing: "before",
|
|
|
|
|
responseTime,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 복잡한 검증: 백그라운드 처리
|
|
|
|
|
const jobId = `job_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
|
|
|
|
|
|
|
|
// TODO: 실제 작업 큐에 추가 (높은 우선순위)
|
|
|
|
|
processDataflowInBackground(
|
|
|
|
|
jobId,
|
|
|
|
|
buttonConfig,
|
|
|
|
|
contextData,
|
|
|
|
|
companyCode,
|
|
|
|
|
"high"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
jobId,
|
|
|
|
|
immediateResult: {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "검증 중입니다. 잠시만 기다려주세요.",
|
|
|
|
|
processing: true,
|
|
|
|
|
},
|
|
|
|
|
isBackground: true,
|
|
|
|
|
timing: "before",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (timing === "replace") {
|
|
|
|
|
// Replace: 제어관리만 실행
|
|
|
|
|
const isSimpleControl = checkIfSimpleControl(buttonConfig);
|
|
|
|
|
|
|
|
|
|
if (isSimpleControl) {
|
|
|
|
|
// 간단한 제어: 즉시 실행
|
|
|
|
|
const result = await executeSimpleDataflowAction(
|
|
|
|
|
buttonConfig,
|
|
|
|
|
contextData,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const responseTime = Date.now() - startTime;
|
|
|
|
|
logger.info(`Button executed (replace-simple): ${responseTime}ms`);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
jobId: "immediate",
|
|
|
|
|
immediateResult: result,
|
|
|
|
|
timing: "replace",
|
|
|
|
|
responseTime,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 복잡한 제어: 백그라운드 실행
|
|
|
|
|
const jobId = `job_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
|
|
|
|
|
|
|
|
// TODO: 실제 작업 큐에 추가
|
|
|
|
|
processDataflowInBackground(
|
|
|
|
|
jobId,
|
|
|
|
|
buttonConfig,
|
|
|
|
|
contextData,
|
|
|
|
|
companyCode,
|
|
|
|
|
"normal"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
jobId,
|
|
|
|
|
immediateResult: {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "사용자 정의 작업을 처리 중입니다...",
|
|
|
|
|
processing: true,
|
|
|
|
|
},
|
|
|
|
|
isBackground: true,
|
|
|
|
|
timing: "replace",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to execute optimized button:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "버튼 실행 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 간단한 데이터플로우 즉시 실행
|
|
|
|
|
*/
|
|
|
|
|
export async function executeSimpleDataflow(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { config, contextData } = req.body;
|
|
|
|
|
const companyCode = req.user?.companyCode;
|
|
|
|
|
|
|
|
|
|
if (!companyCode) {
|
|
|
|
|
res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "회사 코드가 필요합니다.",
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await executeSimpleDataflowAction(
|
|
|
|
|
config,
|
|
|
|
|
contextData,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to execute simple dataflow:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "간단한 제어관리 실행 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 백그라운드 작업 상태 조회
|
|
|
|
|
*/
|
|
|
|
|
export async function getJobStatus(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const { jobId } = req.params;
|
|
|
|
|
|
|
|
|
|
// TODO: 실제 작업 큐에서 상태 조회
|
|
|
|
|
// 현재는 mock 응답
|
|
|
|
|
const mockStatus = {
|
|
|
|
|
status: "completed",
|
|
|
|
|
result: {
|
|
|
|
|
success: true,
|
|
|
|
|
executedActions: 2,
|
|
|
|
|
message: "백그라운드 처리가 완료되었습니다.",
|
|
|
|
|
},
|
|
|
|
|
progress: 100,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: mockStatus,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Failed to get job status:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "작업 상태 조회 중 오류가 발생했습니다.",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// 🔥 헬퍼 함수들
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 기존 액션 실행 (mock)
|
|
|
|
|
*/
|
|
|
|
|
async function executeOriginalAction(
|
|
|
|
|
actionType: string,
|
|
|
|
|
contextData: Record<string, any>
|
|
|
|
|
): Promise<any> {
|
|
|
|
|
// 간단한 지연 시뮬레이션
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: `${actionType} 액션이 완료되었습니다.`,
|
|
|
|
|
actionType,
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
data: contextData,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 간단한 검증인지 확인
|
|
|
|
|
*/
|
|
|
|
|
function checkIfSimpleValidation(buttonConfig: any): boolean {
|
|
|
|
|
if (buttonConfig?.dataflowConfig?.controlMode !== "advanced") {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const conditions =
|
|
|
|
|
buttonConfig?.dataflowConfig?.directControl?.conditions || [];
|
|
|
|
|
return (
|
|
|
|
|
conditions.length <= 5 &&
|
|
|
|
|
conditions.every(
|
|
|
|
|
(c: any) =>
|
|
|
|
|
c.type === "condition" &&
|
|
|
|
|
["=", "!=", ">", "<", ">=", "<="].includes(c.operator || "")
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 간단한 제어관리인지 확인
|
|
|
|
|
*/
|
|
|
|
|
function checkIfSimpleControl(buttonConfig: any): boolean {
|
|
|
|
|
if (buttonConfig?.dataflowConfig?.controlMode === "simple") {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const actions = buttonConfig?.dataflowConfig?.directControl?.actions || [];
|
|
|
|
|
const conditions =
|
|
|
|
|
buttonConfig?.dataflowConfig?.directControl?.conditions || [];
|
|
|
|
|
|
|
|
|
|
return actions.length <= 3 && conditions.length <= 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 빠른 검증 실행
|
|
|
|
|
*/
|
|
|
|
|
async function validateQuickly(
|
|
|
|
|
buttonConfig: any,
|
|
|
|
|
contextData: Record<string, any>
|
|
|
|
|
): Promise<any> {
|
|
|
|
|
// 간단한 mock 검증
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "검증이 완료되었습니다.",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 간단한 데이터플로우 실행
|
|
|
|
|
*/
|
|
|
|
|
async function executeSimpleDataflowAction(
|
|
|
|
|
config: any,
|
|
|
|
|
contextData: Record<string, any>,
|
|
|
|
|
companyCode: string
|
|
|
|
|
): Promise<any> {
|
|
|
|
|
try {
|
|
|
|
|
// 실제로는 EventTriggerService 사용
|
|
|
|
|
const result = await EventTriggerService.executeEventTriggers(
|
|
|
|
|
"insert", // TODO: 동적으로 결정
|
|
|
|
|
"test_table", // TODO: 설정에서 가져오기
|
|
|
|
|
contextData,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
executedActions: result.length,
|
|
|
|
|
message: `${result.length}개의 액션이 실행되었습니다.`,
|
|
|
|
|
results: result,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("Simple dataflow execution failed:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 백그라운드에서 데이터플로우 처리 (비동기)
|
|
|
|
|
*/
|
|
|
|
|
function processDataflowInBackground(
|
|
|
|
|
jobId: string,
|
|
|
|
|
buttonConfig: any,
|
|
|
|
|
contextData: Record<string, any>,
|
|
|
|
|
companyCode: string,
|
|
|
|
|
priority: string = "normal"
|
|
|
|
|
): void {
|
|
|
|
|
// 실제로는 작업 큐에 추가
|
|
|
|
|
// 여기서는 간단한 setTimeout으로 시뮬레이션
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
try {
|
|
|
|
|
logger.info(`Background job started: ${jobId}`);
|
|
|
|
|
|
|
|
|
|
// 실제 제어관리 로직 실행
|
|
|
|
|
const result = await executeSimpleDataflowAction(
|
|
|
|
|
buttonConfig.dataflowConfig,
|
|
|
|
|
contextData,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info(`Background job completed: ${jobId}`, result);
|
|
|
|
|
|
|
|
|
|
// 실제로는 WebSocket이나 polling으로 클라이언트에 알림
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error(`Background job failed: ${jobId}`, error);
|
|
|
|
|
}
|
|
|
|
|
}, 1000); // 1초 후 실행 시뮬레이션
|
|
|
|
|
}
|
2025-09-29 12:17:10 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 🔥 전체 관계 목록 조회 (버튼 제어용)
|
|
|
|
|
*/
|
|
|
|
|
export async function getAllRelationships(
|
|
|
|
|
req: AuthenticatedRequest,
|
|
|
|
|
res: Response
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user?.companyCode || "*";
|
|
|
|
|
|
|
|
|
|
logger.info(`전체 관계 목록 조회 요청 - companyCode: ${companyCode}`);
|
|
|
|
|
|
|
|
|
|
// 모든 관계도에서 관계 목록을 가져옴
|
|
|
|
|
const allRelationships = await dataflowDiagramService.getAllRelationshipsForButtonControl(companyCode);
|
|
|
|
|
|
|
|
|
|
logger.info(`전체 관계 ${allRelationships.length}개 조회 완료`);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: allRelationships,
|
|
|
|
|
message: `전체 관계 ${allRelationships.length}개 조회 완료`,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("전체 관계 목록 조회 실패:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error instanceof Error ? error.message : "전체 관계 목록 조회 실패",
|
|
|
|
|
errorCode: "GET_ALL_RELATIONSHIPS_ERROR",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|