ERP-node/backend-node/src/services/dataflowControlService.ts

521 lines
15 KiB
TypeScript

import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export interface ControlCondition {
id: string;
type: "condition" | "group-start" | "group-end";
field?: string;
value?: any;
operator?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN";
dataType?: "string" | "number" | "date" | "boolean";
logicalOperator?: "AND" | "OR";
groupId?: string;
groupLevel?: number;
}
export interface ControlAction {
id: string;
name: string;
actionType: "insert" | "update" | "delete";
conditions: ControlCondition[];
fieldMappings: {
sourceField?: string;
sourceTable?: string;
targetField: string;
targetTable: string;
defaultValue?: any;
}[];
splitConfig?: {
delimiter?: string;
sourceField?: string;
targetField?: string;
};
}
export interface ControlPlan {
id: string;
sourceTable: string;
actions: ControlAction[];
}
export interface ControlRule {
id: string;
triggerType: "insert" | "update" | "delete";
conditions: ControlCondition[];
}
export class DataflowControlService {
/**
* 제어관리 실행 메인 함수
*/
async executeDataflowControl(
diagramId: number,
relationshipId: string,
triggerType: "insert" | "update" | "delete",
sourceData: Record<string, any>,
tableName: string
): Promise<{
success: boolean;
message: string;
executedActions?: any[];
errors?: string[];
}> {
try {
console.log(`🎯 제어관리 실행 시작:`, {
diagramId,
relationshipId,
triggerType,
sourceData,
tableName,
});
// 관계도 정보 조회
const diagram = await prisma.dataflow_diagrams.findUnique({
where: { diagram_id: diagramId },
});
if (!diagram) {
return {
success: false,
message: `관계도를 찾을 수 없습니다. (ID: ${diagramId})`,
};
}
// 제어 규칙과 실행 계획 추출
const controlRules = (diagram.control as unknown as ControlRule[]) || [];
const executionPlans = (diagram.plan as unknown as ControlPlan[]) || [];
console.log(`📋 제어 규칙:`, controlRules);
console.log(`📋 실행 계획:`, executionPlans);
// 해당 관계의 제어 규칙 찾기
const targetRule = controlRules.find(
(rule) => rule.id === relationshipId && rule.triggerType === triggerType
);
if (!targetRule) {
console.log(
`⚠️ 해당 관계의 제어 규칙을 찾을 수 없습니다: ${relationshipId}`
);
return {
success: true,
message: "해당 관계의 제어 규칙이 없습니다.",
};
}
// 제어 조건 검증
const conditionResult = await this.evaluateConditions(
targetRule.conditions,
sourceData
);
console.log(`🔍 조건 검증 결과:`, conditionResult);
if (!conditionResult.satisfied) {
return {
success: true,
message: `제어 조건을 만족하지 않습니다: ${conditionResult.reason}`,
};
}
// 실행 계획 찾기
const targetPlan = executionPlans.find(
(plan) => plan.id === relationshipId
);
if (!targetPlan) {
return {
success: true,
message: "실행할 계획이 없습니다.",
};
}
// 액션 실행
const executedActions = [];
const errors = [];
for (const action of targetPlan.actions) {
try {
console.log(`⚡ 액션 실행: ${action.name} (${action.actionType})`);
// 액션 조건 검증 (있는 경우)
if (action.conditions && action.conditions.length > 0) {
const actionConditionResult = await this.evaluateConditions(
action.conditions,
sourceData
);
if (!actionConditionResult.satisfied) {
console.log(
`⚠️ 액션 조건 미충족: ${actionConditionResult.reason}`
);
continue;
}
}
const actionResult = await this.executeAction(action, sourceData);
executedActions.push({
actionId: action.id,
actionName: action.name,
result: actionResult,
});
} catch (error) {
console.error(`❌ 액션 실행 오류: ${action.name}`, error);
errors.push(
`액션 '${action.name}' 실행 오류: ${error instanceof Error ? error.message : String(error)}`
);
}
}
return {
success: true,
message: `제어관리 실행 완료. ${executedActions.length}개 액션 실행됨.`,
executedActions,
errors: errors.length > 0 ? errors : undefined,
};
} catch (error) {
console.error("❌ 제어관리 실행 오류:", error);
return {
success: false,
message: `제어관리 실행 중 오류 발생: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
/**
* 조건 평가
*/
private async evaluateConditions(
conditions: ControlCondition[],
data: Record<string, any>
): Promise<{ satisfied: boolean; reason?: string }> {
if (!conditions || conditions.length === 0) {
return { satisfied: true };
}
try {
// 조건을 SQL WHERE 절로 변환
const whereClause = this.buildWhereClause(conditions, data);
console.log(`🔍 생성된 WHERE 절:`, whereClause);
// 간단한 조건 평가 (실제로는 더 복잡한 로직 필요)
for (const condition of conditions) {
if (condition.type === "condition" && condition.field) {
const fieldValue = data[condition.field];
const conditionValue = condition.value;
console.log(
`🔍 조건 평가: ${condition.field} ${condition.operator} ${conditionValue} (실제값: ${fieldValue})`
);
const result = this.evaluateSingleCondition(
fieldValue,
condition.operator || "=",
conditionValue,
condition.dataType || "string"
);
if (!result) {
return {
satisfied: false,
reason: `조건 미충족: ${condition.field} ${condition.operator} ${conditionValue}`,
};
}
}
}
return { satisfied: true };
} catch (error) {
console.error("조건 평가 오류:", error);
return {
satisfied: false,
reason: `조건 평가 오류: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
/**
* 단일 조건 평가
*/
private evaluateSingleCondition(
fieldValue: any,
operator: string,
conditionValue: any,
dataType: string
): boolean {
// 타입 변환
let actualValue = fieldValue;
let expectedValue = conditionValue;
if (dataType === "number") {
actualValue = parseFloat(fieldValue) || 0;
expectedValue = parseFloat(conditionValue) || 0;
} else if (dataType === "string") {
actualValue = String(fieldValue || "");
expectedValue = String(conditionValue || "");
}
// 연산자별 평가
switch (operator) {
case "=":
return actualValue === expectedValue;
case "!=":
return actualValue !== expectedValue;
case ">":
return actualValue > expectedValue;
case "<":
return actualValue < expectedValue;
case ">=":
return actualValue >= expectedValue;
case "<=":
return actualValue <= expectedValue;
case "LIKE":
return String(actualValue).includes(String(expectedValue));
default:
console.warn(`지원되지 않는 연산자: ${operator}`);
return false;
}
}
/**
* WHERE 절 생성 (복잡한 그룹 조건 처리)
*/
private buildWhereClause(
conditions: ControlCondition[],
data: Record<string, any>
): string {
// 실제로는 더 복잡한 그룹 처리 로직이 필요
// 현재는 간단한 AND/OR 처리만 구현
const clauses = [];
for (const condition of conditions) {
if (condition.type === "condition") {
const clause = `${condition.field} ${condition.operator} '${condition.value}'`;
clauses.push(clause);
}
}
return clauses.join(" AND ");
}
/**
* 액션 실행
*/
private async executeAction(
action: ControlAction,
sourceData: Record<string, any>
): Promise<any> {
console.log(`🚀 액션 실행: ${action.actionType}`, action);
switch (action.actionType) {
case "insert":
return await this.executeInsertAction(action, sourceData);
case "update":
return await this.executeUpdateAction(action, sourceData);
case "delete":
return await this.executeDeleteAction(action, sourceData);
default:
throw new Error(`지원되지 않는 액션 타입: ${action.actionType}`);
}
}
/**
* INSERT 액션 실행
*/
private async executeInsertAction(
action: ControlAction,
sourceData: Record<string, any>
): Promise<any> {
const results = [];
for (const mapping of action.fieldMappings) {
const { targetTable, targetField, defaultValue, sourceField } = mapping;
// 삽입할 데이터 준비
const insertData: Record<string, any> = {};
if (sourceField && sourceData[sourceField]) {
insertData[targetField] = sourceData[sourceField];
} else if (defaultValue !== undefined) {
insertData[targetField] = defaultValue;
}
// 동적으로 테이블 컬럼 정보 조회하여 기본 필드 추가
await this.addDefaultFieldsForTable(targetTable, insertData);
console.log(`📝 INSERT 실행: ${targetTable}.${targetField}`, insertData);
try {
// 동적 테이블 INSERT 실행
const result = await prisma.$executeRawUnsafe(
`
INSERT INTO ${targetTable} (${Object.keys(insertData).join(", ")})
VALUES (${Object.keys(insertData)
.map((_, index) => `$${index + 1}`)
.join(", ")})
`,
...Object.values(insertData)
);
results.push({
table: targetTable,
field: targetField,
data: insertData,
result,
});
console.log(`✅ INSERT 성공: ${targetTable}.${targetField}`);
} catch (error) {
console.error(`❌ INSERT 실패: ${targetTable}.${targetField}`, error);
throw error;
}
}
return results;
}
/**
* UPDATE 액션 실행
*/
private async executeUpdateAction(
action: ControlAction,
sourceData: Record<string, any>
): Promise<any> {
// UPDATE 로직 구현
console.log("UPDATE 액션 실행 (미구현)");
return { message: "UPDATE 액션은 아직 구현되지 않았습니다." };
}
/**
* DELETE 액션 실행
*/
private async executeDeleteAction(
action: ControlAction,
sourceData: Record<string, any>
): Promise<any> {
// DELETE 로직 구현
console.log("DELETE 액션 실행 (미구현)");
return { message: "DELETE 액션은 아직 구현되지 않았습니다." };
}
/**
* 테이블의 컬럼 정보를 동적으로 조회하여 기본 필드 추가
*/
private async addDefaultFieldsForTable(
tableName: string,
insertData: Record<string, any>
): Promise<void> {
try {
// 테이블의 컬럼 정보 조회
const columns = await prisma.$queryRawUnsafe<
Array<{ column_name: string; data_type: string; is_nullable: string }>
>(
`
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = $1
ORDER BY ordinal_position
`,
tableName
);
console.log(`📋 ${tableName} 테이블 컬럼 정보:`, columns);
const currentDate = new Date();
// 일반적인 타임스탬프 필드들 확인 및 추가
const timestampFields = [
{
names: ["created_at", "create_date", "reg_date", "regdate"],
value: currentDate,
},
{
names: ["updated_at", "update_date", "mod_date", "moddate"],
value: currentDate,
},
];
for (const fieldGroup of timestampFields) {
for (const fieldName of fieldGroup.names) {
const column = columns.find(
(col) => col.column_name.toLowerCase() === fieldName.toLowerCase()
);
if (column && !insertData[column.column_name]) {
// 해당 컬럼이 존재하고 아직 값이 설정되지 않은 경우
if (
column.data_type.includes("timestamp") ||
column.data_type.includes("date")
) {
insertData[column.column_name] = fieldGroup.value;
console.log(
`📅 기본 타임스탬프 필드 추가: ${column.column_name} = ${fieldGroup.value}`
);
}
}
}
}
// 필수 필드 중 값이 없는 경우 기본값 설정
for (const column of columns) {
if (column.is_nullable === "NO" && !insertData[column.column_name]) {
// NOT NULL 필드인데 값이 없는 경우 기본값 설정
const defaultValue = this.getDefaultValueForColumn(column);
if (defaultValue !== null) {
insertData[column.column_name] = defaultValue;
console.log(
`🔧 필수 필드 기본값 설정: ${column.column_name} = ${defaultValue}`
);
}
}
}
} catch (error) {
console.error(`${tableName} 테이블 컬럼 정보 조회 실패:`, error);
// 에러가 발생해도 INSERT는 계속 진행 (기본 필드 없이)
}
}
/**
* 컬럼 타입에 따른 기본값 반환
*/
private getDefaultValueForColumn(column: {
column_name: string;
data_type: string;
}): any {
const dataType = column.data_type.toLowerCase();
const columnName = column.column_name.toLowerCase();
// 컬럼명 기반 기본값
if (columnName.includes("status")) {
return "Y"; // 상태 필드는 보통 'Y'
}
if (columnName.includes("type")) {
return "default"; // 타입 필드는 'default'
}
// 데이터 타입 기반 기본값
if (
dataType.includes("varchar") ||
dataType.includes("text") ||
dataType.includes("char")
) {
return ""; // 문자열은 빈 문자열
}
if (
dataType.includes("int") ||
dataType.includes("numeric") ||
dataType.includes("decimal")
) {
return 0; // 숫자는 0
}
if (dataType.includes("bool")) {
return false; // 불린은 false
}
if (dataType.includes("timestamp") || dataType.includes("date")) {
return new Date(); // 날짜는 현재 시간
}
return null; // 기본값을 설정할 수 없는 경우
}
}