443 lines
12 KiB
TypeScript
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;
|
|
}
|
|
};
|