ERP-node/backend-node/src/controllers/flowController.ts

841 lines
22 KiB
TypeScript

// @ts-nocheck
/**
* 플로우 관리 컨트롤러
*/
import { Request, Response } from "express";
import { FlowDefinitionService } from "../services/flowDefinitionService";
import { FlowStepService } from "../services/flowStepService";
import { FlowConnectionService } from "../services/flowConnectionService";
import { FlowExecutionService } from "../services/flowExecutionService";
import { FlowDataMoveService } from "../services/flowDataMoveService";
export class FlowController {
private flowDefinitionService: FlowDefinitionService;
private flowStepService: FlowStepService;
private flowConnectionService: FlowConnectionService;
private flowExecutionService: FlowExecutionService;
private flowDataMoveService: FlowDataMoveService;
constructor() {
this.flowDefinitionService = new FlowDefinitionService();
this.flowStepService = new FlowStepService();
this.flowConnectionService = new FlowConnectionService();
this.flowExecutionService = new FlowExecutionService();
this.flowDataMoveService = new FlowDataMoveService();
}
// ==================== 플로우 정의 ====================
/**
* 플로우 정의 생성
*/
createFlowDefinition = async (req: Request, res: Response): Promise<void> => {
try {
const {
name,
description,
tableName,
dbSourceType,
dbConnectionId,
// REST API 관련 필드
restApiConnectionId,
restApiEndpoint,
restApiJsonPath,
} = req.body;
const userId = (req as any).user?.userId || "system";
const userCompanyCode = (req as any).user?.companyCode;
console.log("🔍 createFlowDefinition called with:", {
name,
description,
tableName,
dbSourceType,
dbConnectionId,
restApiConnectionId,
restApiEndpoint,
restApiJsonPath,
userCompanyCode,
});
if (!name) {
res.status(400).json({
success: false,
message: "Name is required",
});
return;
}
// REST API 또는 다중 연결인 경우 테이블 존재 확인 스킵
const isRestApi = dbSourceType === "restapi" || dbSourceType === "multi_restapi";
const isMultiConnection = dbSourceType === "multi_restapi" || dbSourceType === "multi_external_db";
// 테이블 이름이 제공된 경우에만 존재 확인 (REST API 및 다중 연결 제외)
if (tableName && !isRestApi && !isMultiConnection && !tableName.startsWith("_restapi_") && !tableName.startsWith("_multi_restapi_") && !tableName.startsWith("_multi_external_db_")) {
const tableExists =
await this.flowDefinitionService.checkTableExists(tableName);
if (!tableExists) {
res.status(400).json({
success: false,
message: `Table '${tableName}' does not exist`,
});
return;
}
}
const flowDef = await this.flowDefinitionService.create(
{
name,
description,
tableName,
dbSourceType,
dbConnectionId,
restApiConnectionId,
restApiEndpoint,
restApiJsonPath,
restApiConnections: req.body.restApiConnections, // 다중 REST API 설정
},
userId,
userCompanyCode
);
res.json({
success: true,
data: flowDef,
});
} catch (error: any) {
console.error("Error creating flow definition:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to create flow definition",
});
}
};
/**
* 플로우 정의 목록 조회
*/
getFlowDefinitions = async (req: Request, res: Response): Promise<void> => {
try {
const { tableName, isActive } = req.query;
const user = (req as any).user;
const userCompanyCode = user?.companyCode;
console.log("🎯 getFlowDefinitions called:", {
userId: user?.userId,
userCompanyCode: userCompanyCode,
userType: user?.userType,
tableName,
isActive,
});
const flows = await this.flowDefinitionService.findAll(
tableName as string | undefined,
isActive !== undefined ? isActive === "true" : undefined,
userCompanyCode
);
console.log(`✅ Returning ${flows.length} flows to user ${user?.userId}`);
res.json({
success: true,
data: flows,
});
} catch (error: any) {
console.error("Error fetching flow definitions:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to fetch flow definitions",
});
}
};
/**
* 플로우 정의 상세 조회 (단계 및 연결 포함)
*/
getFlowDefinitionDetail = async (
req: Request,
res: Response
): Promise<void> => {
try {
const { id } = req.params;
const flowId = parseInt(id);
const definition = await this.flowDefinitionService.findById(flowId);
if (!definition) {
res.status(404).json({
success: false,
message: "Flow definition not found",
});
return;
}
const steps = await this.flowStepService.findByFlowId(flowId);
const connections = await this.flowConnectionService.findByFlowId(flowId);
res.json({
success: true,
data: {
definition,
steps,
connections,
},
});
} catch (error: any) {
console.error("Error fetching flow definition detail:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to fetch flow definition detail",
});
}
};
/**
* 플로우 정의 수정
*/
updateFlowDefinition = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const flowId = parseInt(id);
const { name, description, isActive } = req.body;
const flowDef = await this.flowDefinitionService.update(flowId, {
name,
description,
isActive,
});
if (!flowDef) {
res.status(404).json({
success: false,
message: "Flow definition not found",
});
return;
}
res.json({
success: true,
data: flowDef,
});
} catch (error: any) {
console.error("Error updating flow definition:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to update flow definition",
});
}
};
/**
* 플로우 정의 삭제
*/
deleteFlowDefinition = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const flowId = parseInt(id);
const success = await this.flowDefinitionService.delete(flowId);
if (!success) {
res.status(404).json({
success: false,
message: "Flow definition not found",
});
return;
}
res.json({
success: true,
message: "Flow definition deleted successfully",
});
} catch (error: any) {
console.error("Error deleting flow definition:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to delete flow definition",
});
}
};
// ==================== 플로우 단계 ====================
/**
* 플로우 단계 목록 조회
*/
getFlowSteps = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId } = req.params;
const flowDefinitionId = parseInt(flowId);
const steps = await this.flowStepService.findByFlowId(flowDefinitionId);
res.json({
success: true,
data: steps,
});
return;
} catch (error: any) {
console.error("Error fetching flow steps:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to fetch flow steps",
});
return;
}
};
/**
* 플로우 단계 생성
*/
createFlowStep = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId } = req.params;
const flowDefinitionId = parseInt(flowId);
const {
stepName,
stepOrder,
tableName,
conditionJson,
color,
positionX,
positionY,
} = req.body;
if (!stepName || stepOrder === undefined) {
res.status(400).json({
success: false,
message: "stepName and stepOrder are required",
});
return;
}
const step = await this.flowStepService.create({
flowDefinitionId,
stepName,
stepOrder,
tableName,
conditionJson,
color,
positionX,
positionY,
});
res.json({
success: true,
data: step,
});
} catch (error: any) {
console.error("Error creating flow step:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to create flow step",
});
}
};
/**
* 플로우 단계 수정
*/
updateFlowStep = async (req: Request, res: Response): Promise<void> => {
try {
const { stepId } = req.params;
const id = parseInt(stepId);
const {
stepName,
stepOrder,
tableName,
conditionJson,
color,
positionX,
positionY,
moveType,
statusColumn,
statusValue,
targetTable,
fieldMappings,
integrationType,
integrationConfig,
displayConfig,
} = req.body;
const step = await this.flowStepService.update(id, {
stepName,
stepOrder,
tableName,
conditionJson,
color,
positionX,
positionY,
moveType,
statusColumn,
statusValue,
targetTable,
fieldMappings,
integrationType,
integrationConfig,
displayConfig,
});
if (!step) {
res.status(404).json({
success: false,
message: "Flow step not found",
});
return;
}
res.json({
success: true,
data: step,
});
} catch (error: any) {
console.error("Error updating flow step:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to update flow step",
});
}
};
/**
* 플로우 단계 삭제
*/
deleteFlowStep = async (req: Request, res: Response): Promise<void> => {
try {
const { stepId } = req.params;
const id = parseInt(stepId);
const success = await this.flowStepService.delete(id);
if (!success) {
res.status(404).json({
success: false,
message: "Flow step not found",
});
return;
}
res.json({
success: true,
message: "Flow step deleted successfully",
});
} catch (error: any) {
console.error("Error deleting flow step:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to delete flow step",
});
}
};
// ==================== 플로우 연결 ====================
/**
* 플로우 연결 목록 조회
*/
getFlowConnections = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId } = req.params;
const flowDefinitionId = parseInt(flowId);
const connections =
await this.flowConnectionService.findByFlowId(flowDefinitionId);
res.json({
success: true,
data: connections,
});
return;
} catch (error: any) {
console.error("Error fetching flow connections:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to fetch flow connections",
});
return;
}
};
/**
* 플로우 연결 생성
*/
createConnection = async (req: Request, res: Response): Promise<void> => {
try {
const { flowDefinitionId, fromStepId, toStepId, label } = req.body;
if (!flowDefinitionId || !fromStepId || !toStepId) {
res.status(400).json({
success: false,
message: "flowDefinitionId, fromStepId, and toStepId are required",
});
return;
}
const connection = await this.flowConnectionService.create({
flowDefinitionId,
fromStepId,
toStepId,
label,
});
res.json({
success: true,
data: connection,
});
} catch (error: any) {
console.error("Error creating connection:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to create connection",
});
}
};
/**
* 플로우 연결 삭제
*/
deleteConnection = async (req: Request, res: Response): Promise<void> => {
try {
const { connectionId } = req.params;
const id = parseInt(connectionId);
const success = await this.flowConnectionService.delete(id);
if (!success) {
res.status(404).json({
success: false,
message: "Connection not found",
});
return;
}
res.json({
success: true,
message: "Connection deleted successfully",
});
} catch (error: any) {
console.error("Error deleting connection:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to delete connection",
});
}
};
// ==================== 플로우 실행 ====================
/**
* 단계별 데이터 카운트 조회
*/
getStepDataCount = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId, stepId } = req.params;
const count = await this.flowExecutionService.getStepDataCount(
parseInt(flowId),
parseInt(stepId)
);
res.json({
success: true,
data: { count },
});
} catch (error: any) {
console.error("Error getting step data count:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to get step data count",
});
}
};
/**
* 단계별 데이터 리스트 조회
*/
getStepDataList = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId, stepId } = req.params;
const { page = 1, pageSize = 20 } = req.query;
const data = await this.flowExecutionService.getStepDataList(
parseInt(flowId),
parseInt(stepId),
parseInt(page as string),
parseInt(pageSize as string)
);
res.json({
success: true,
data,
});
} catch (error: any) {
console.error("Error getting step data list:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to get step data list",
});
}
};
/**
* 플로우 스텝의 컬럼 라벨 조회
*/
getStepColumnLabels = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId, stepId } = req.params;
console.log("🏷️ [FlowController] 컬럼 라벨 조회 요청:", {
flowId,
stepId,
});
const step = await this.flowStepService.findById(parseInt(stepId));
if (!step) {
console.warn("⚠️ [FlowController] 스텝을 찾을 수 없음:", stepId);
res.status(404).json({
success: false,
message: "Step not found",
});
return;
}
const flowDef = await this.flowDefinitionService.findById(
parseInt(flowId)
);
if (!flowDef) {
console.warn("⚠️ [FlowController] 플로우를 찾을 수 없음:", flowId);
res.status(404).json({
success: false,
message: "Flow definition not found",
});
return;
}
// 테이블명 결정 (스텝 테이블 우선, 없으면 플로우 테이블)
const tableName = step.tableName || flowDef.tableName;
console.log("📋 [FlowController] 테이블명 결정:", {
stepTableName: step.tableName,
flowTableName: flowDef.tableName,
selectedTableName: tableName,
});
if (!tableName) {
console.warn("⚠️ [FlowController] 테이블명이 지정되지 않음");
res.json({
success: true,
data: {},
});
return;
}
// column_labels 테이블에서 라벨 정보 조회
const { query } = await import("../database/db");
const labelRows = await query<{
column_name: string;
column_label: string | null;
}>(
`SELECT column_name, column_label
FROM column_labels
WHERE table_name = $1 AND column_label IS NOT NULL`,
[tableName]
);
console.log(`✅ [FlowController] column_labels 조회 완료:`, {
tableName,
rowCount: labelRows.length,
labels: labelRows.map((r) => ({
col: r.column_name,
label: r.column_label,
})),
});
// { columnName: label } 형태의 객체로 변환
const labels: Record<string, string> = {};
labelRows.forEach((row) => {
if (row.column_label) {
labels[row.column_name] = row.column_label;
}
});
console.log("📦 [FlowController] 반환할 라벨 객체:", labels);
res.json({
success: true,
data: labels,
});
} catch (error: any) {
console.error("❌ [FlowController] 컬럼 라벨 조회 오류:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to get step column labels",
});
}
};
/**
* 플로우의 모든 단계별 카운트 조회
*/
getAllStepCounts = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId } = req.params;
const counts = await this.flowExecutionService.getAllStepCounts(
parseInt(flowId)
);
res.json({
success: true,
data: counts,
});
} catch (error: any) {
console.error("Error getting all step counts:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to get all step counts",
});
}
};
/**
* 데이터를 다음 단계로 이동
*/
moveData = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId, recordId, toStepId, note } = req.body;
const userId = (req as any).user?.userId || "system";
if (!flowId || !recordId || !toStepId) {
res.status(400).json({
success: false,
message: "flowId, recordId, and toStepId are required",
});
return;
}
await this.flowDataMoveService.moveDataToStep(
flowId,
recordId,
toStepId,
userId,
note
);
res.json({
success: true,
message: "Data moved successfully",
});
} catch (error: any) {
console.error("Error moving data:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to move data",
});
}
};
/**
* 여러 데이터를 동시에 이동
*/
moveBatchData = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId, fromStepId, toStepId, dataIds } = req.body;
const userId = (req as any).user?.userId || "system";
if (
!flowId ||
!fromStepId ||
!toStepId ||
!dataIds ||
!Array.isArray(dataIds)
) {
res.status(400).json({
success: false,
message:
"flowId, fromStepId, toStepId, and dataIds (array) are required",
});
return;
}
const result = await this.flowDataMoveService.moveBatchData(
flowId,
fromStepId,
toStepId,
dataIds,
userId
);
const successCount = result.results.filter((r) => r.success).length;
const failureCount = result.results.filter((r) => !r.success).length;
res.json({
success: result.success,
message: result.success
? `${successCount}건의 데이터를 성공적으로 이동했습니다`
: `${successCount}건 성공, ${failureCount}건 실패`,
data: {
successCount,
failureCount,
total: dataIds.length,
},
results: result.results,
});
} catch (error: any) {
console.error("Error moving batch data:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to move batch data",
});
}
};
/**
* 데이터의 플로우 이력 조회
*/
getAuditLogs = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId, recordId } = req.params;
const logs = await this.flowDataMoveService.getAuditLogs(
parseInt(flowId),
recordId
);
res.json({
success: true,
data: logs,
});
} catch (error: any) {
console.error("Error getting audit logs:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to get audit logs",
});
}
};
/**
* 플로우의 모든 이력 조회
*/
getFlowAuditLogs = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId } = req.params;
const { limit = 100 } = req.query;
const logs = await this.flowDataMoveService.getFlowAuditLogs(
parseInt(flowId),
parseInt(limit as string)
);
res.json({
success: true,
data: logs,
});
} catch (error: any) {
console.error("Error getting flow audit logs:", error);
res.status(500).json({
success: false,
message: error.message || "Failed to get flow audit logs",
});
}
};
}