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

443 lines
12 KiB
TypeScript

import { query, queryOne, transaction } from "../database/db";
import { logger } from "../utils/logger";
// 타입 정의
interface CreateDataflowDiagramData {
diagram_name: string;
relationships: Record<string, unknown>; // JSON 데이터
node_positions?: Record<string, unknown> | null; // JSON 데이터 (노드 위치 정보)
// 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드
control?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 조건 설정)
category?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 연결 종류)
plan?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 실행 계획)
company_code: string;
created_by: string;
updated_by: string;
}
interface UpdateDataflowDiagramData {
diagram_name?: string;
relationships?: Record<string, unknown>; // JSON 데이터
node_positions?: Record<string, unknown> | null; // JSON 데이터 (노드 위치 정보)
// 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드
control?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 조건 설정)
category?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 연결 종류)
plan?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 실행 계획)
updated_by: string;
}
/**
* 관계도 목록 조회 (페이지네이션)
*/
export const getDataflowDiagrams = async (
companyCode: string,
page: number = 1,
size: number = 20,
searchTerm?: string
) => {
try {
const offset = (page - 1) * size;
const whereConditions: string[] = [];
const values: any[] = [];
let paramIndex = 1;
// company_code가 '*'가 아닌 경우에만 필터링
if (companyCode !== "*") {
whereConditions.push(`company_code = $${paramIndex++}`);
values.push(companyCode);
}
if (searchTerm) {
whereConditions.push(`diagram_name ILIKE $${paramIndex++}`);
values.push(`%${searchTerm}%`);
}
const whereClause =
whereConditions.length > 0
? `WHERE ${whereConditions.join(" AND ")}`
: "";
// 총 개수 조회
const countResult = await queryOne<{ count: string }>(
`SELECT COUNT(*) as count FROM dataflow_diagrams ${whereClause}`,
values
);
const total = parseInt(countResult?.count || "0");
// 데이터 조회
const diagrams = await query<any>(
`SELECT * FROM dataflow_diagrams
${whereClause}
ORDER BY updated_at DESC
LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
[...values, size, offset]
);
const totalPages = Math.ceil(total / size);
return {
diagrams,
pagination: {
page,
size,
total,
totalPages,
},
};
} catch (error) {
logger.error("관계도 목록 조회 서비스 오류:", error);
throw error;
}
};
/**
* 특정 관계도 조회
*/
export const getDataflowDiagramById = async (
diagramId: number,
companyCode: string
) => {
try {
const whereConditions: string[] = ["diagram_id = $1"];
const values: any[] = [diagramId];
// company_code가 '*'가 아닌 경우에만 필터링
if (companyCode !== "*") {
whereConditions.push("company_code = $2");
values.push(companyCode);
}
const whereClause = `WHERE ${whereConditions.join(" AND ")}`;
const diagram = await queryOne<any>(
`SELECT * FROM dataflow_diagrams ${whereClause} LIMIT 1`,
values
);
return diagram;
} catch (error) {
logger.error("관계도 조회 서비스 오류:", error);
throw error;
}
};
/**
* 새로운 관계도 생성
*/
export const createDataflowDiagram = async (
data: CreateDataflowDiagramData
) => {
try {
const newDiagram = await queryOne<any>(
`INSERT INTO dataflow_diagrams
(diagram_name, relationships, node_positions, category, control, plan,
company_code, created_by, updated_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW())
RETURNING *`,
[
data.diagram_name,
JSON.stringify(data.relationships),
data.node_positions ? JSON.stringify(data.node_positions) : null,
data.category ? JSON.stringify(data.category) : null,
data.control ? JSON.stringify(data.control) : null,
data.plan ? JSON.stringify(data.plan) : null,
data.company_code,
data.created_by,
data.updated_by,
]
);
return newDiagram;
} catch (error) {
logger.error("관계도 생성 서비스 오류:", error);
throw error;
}
};
/**
* 관계도 수정
*/
export const updateDataflowDiagram = async (
diagramId: number,
data: UpdateDataflowDiagramData,
companyCode: string
) => {
try {
logger.info(
`관계도 수정 서비스 시작 - ID: ${diagramId}, Company: ${companyCode}`
);
// 먼저 해당 관계도가 존재하는지 확인
const whereConditions: string[] = ["diagram_id = $1"];
const checkValues: any[] = [diagramId];
if (companyCode !== "*") {
whereConditions.push("company_code = $2");
checkValues.push(companyCode);
}
const existingDiagram = await queryOne<any>(
`SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`,
checkValues
);
logger.info(
`기존 관계도 조회 결과:`,
existingDiagram ? `ID ${existingDiagram.diagram_id} 발견` : "관계도 없음"
);
if (!existingDiagram) {
logger.warn(
`관계도 ID ${diagramId}를 찾을 수 없음 - Company: ${companyCode}`
);
return null;
}
// 동적 UPDATE 쿼리 생성
const updateFields: string[] = ["updated_by = $1", "updated_at = NOW()"];
const values: any[] = [data.updated_by];
let paramIndex = 2;
if (data.diagram_name) {
updateFields.push(`diagram_name = $${paramIndex++}`);
values.push(data.diagram_name);
}
if (data.relationships) {
updateFields.push(`relationships = $${paramIndex++}`);
values.push(JSON.stringify(data.relationships));
}
if (data.node_positions !== undefined) {
updateFields.push(`node_positions = $${paramIndex++}`);
values.push(
data.node_positions ? JSON.stringify(data.node_positions) : null
);
}
if (data.category !== undefined) {
updateFields.push(`category = $${paramIndex++}`);
values.push(data.category ? JSON.stringify(data.category) : null);
}
if (data.control !== undefined) {
updateFields.push(`control = $${paramIndex++}`);
values.push(data.control ? JSON.stringify(data.control) : null);
}
if (data.plan !== undefined) {
updateFields.push(`plan = $${paramIndex++}`);
values.push(data.plan ? JSON.stringify(data.plan) : null);
}
const updatedDiagram = await queryOne<any>(
`UPDATE dataflow_diagrams
SET ${updateFields.join(", ")}
WHERE diagram_id = $${paramIndex}
RETURNING *`,
[...values, diagramId]
);
return updatedDiagram;
} catch (error) {
logger.error("관계도 수정 서비스 오류:", error);
throw error;
}
};
/**
* 관계도 삭제
*/
export const deleteDataflowDiagram = async (
diagramId: number,
companyCode: string
) => {
try {
// 먼저 해당 관계도가 존재하는지 확인
const whereConditions: string[] = ["diagram_id = $1"];
const values: any[] = [diagramId];
if (companyCode !== "*") {
whereConditions.push("company_code = $2");
values.push(companyCode);
}
const existingDiagram = await queryOne<any>(
`SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`,
values
);
if (!existingDiagram) {
return false;
}
// 삭제 실행
await query(`DELETE FROM dataflow_diagrams WHERE diagram_id = $1`, [
diagramId,
]);
return true;
} catch (error) {
logger.error("관계도 삭제 서비스 오류:", error);
throw error;
}
};
/**
* 관계도 복제
*/
export const copyDataflowDiagram = async (
diagramId: number,
companyCode: string,
newName?: string,
userId: string = "SYSTEM"
) => {
try {
// 원본 관계도 조회
const whereConditions: string[] = ["diagram_id = $1"];
const values: any[] = [diagramId];
if (companyCode !== "*") {
whereConditions.push("company_code = $2");
values.push(companyCode);
}
const originalDiagram = await queryOne<any>(
`SELECT * FROM dataflow_diagrams WHERE ${whereConditions.join(" AND ")} LIMIT 1`,
values
);
if (!originalDiagram) {
return null;
}
// 새로운 이름 생성 (제공되지 않은 경우)
let copyName = newName;
if (!copyName) {
// 기존 이름에서 (n) 패턴을 찾아서 증가
const baseNameMatch = originalDiagram.diagram_name.match(
/^(.+?)(\s*\((\d+)\))?$/
);
const baseName = baseNameMatch
? baseNameMatch[1]
: originalDiagram.diagram_name;
// 같은 패턴의 이름들을 찾아서 가장 큰 번호 찾기
const copyWhereConditions: string[] = ["diagram_name LIKE $1"];
const copyValues: any[] = [`${baseName}%`];
if (companyCode !== "*") {
copyWhereConditions.push("company_code = $2");
copyValues.push(companyCode);
}
const existingCopies = await query<{ diagram_name: string }>(
`SELECT diagram_name FROM dataflow_diagrams
WHERE ${copyWhereConditions.join(" AND ")}`,
copyValues
);
let maxNumber = 0;
existingCopies.forEach((copy) => {
const match = copy.diagram_name.match(/\((\d+)\)$/);
if (match) {
const num = parseInt(match[1]);
if (num > maxNumber) {
maxNumber = num;
}
}
});
copyName = `${baseName} (${maxNumber + 1})`;
}
// 새로운 관계도 생성
const copiedDiagram = await queryOne<any>(
`INSERT INTO dataflow_diagrams
(diagram_name, relationships, node_positions, category, company_code,
created_by, updated_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
RETURNING *`,
[
copyName,
JSON.stringify(originalDiagram.relationships),
originalDiagram.node_positions
? JSON.stringify(originalDiagram.node_positions)
: null,
originalDiagram.category || null,
companyCode,
userId,
userId,
]
);
return copiedDiagram;
} catch (error) {
logger.error("관계도 복제 서비스 오류:", error);
throw error;
}
};
/**
* 🔥 전체 관계 목록 조회 (버튼 제어용)
* dataflow_diagrams 테이블에서 관계도 데이터를 조회 (데이터 흐름 관계 화면과 동일)
*/
export const getAllRelationshipsForButtonControl = async (
companyCode: string
): Promise<
Array<{
id: string;
name: string;
sourceTable: string;
targetTable: string;
category: string;
}>
> => {
try {
logger.info(`전체 관계 목록 조회 시작 - companyCode: ${companyCode}`);
// dataflow_diagrams 테이블에서 관계도들을 조회
const diagrams = await query<{
diagram_id: number;
diagram_name: string;
relationships: any;
}>(
`SELECT diagram_id, diagram_name, relationships
FROM dataflow_diagrams
WHERE company_code = $1
ORDER BY updated_at DESC`,
[companyCode]
);
const allRelationships = diagrams.map((diagram) => {
// relationships 구조에서 테이블 정보 추출
const relationships = (diagram.relationships as any) || {};
// 테이블 정보 추출
let sourceTable = "";
let targetTable = "";
if (relationships.fromTable?.tableName) {
sourceTable = relationships.fromTable.tableName;
}
if (relationships.toTable?.tableName) {
targetTable = relationships.toTable.tableName;
}
return {
id: diagram.diagram_id.toString(),
name: diagram.diagram_name || `관계 ${diagram.diagram_id}`,
sourceTable: sourceTable,
targetTable: targetTable,
category: "데이터 흐름",
};
});
logger.info(`전체 관계 ${allRelationships.length}개 조회 완료`);
return allRelationships;
} catch (error) {
logger.error("전체 관계 목록 조회 서비스 오류:", error);
throw error;
}
};