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

1173 lines
34 KiB
TypeScript

import { query, queryOne, transaction } from "../database/db";
import { logger } from "../utils/logger";
// 테이블 관계 생성 데이터 타입
interface CreateTableRelationshipData {
diagramId?: number; // 기존 관계도에 추가하는 경우
relationshipName: string;
fromTableName: string;
fromColumnName: string;
toTableName: string;
toColumnName: string;
relationshipType: string;
connectionType: string;
companyCode: string;
settings: any;
createdBy: string;
}
// 테이블 관계 수정 데이터 타입
interface UpdateTableRelationshipData {
relationshipName?: string;
fromTableName?: string;
fromColumnName?: string;
toTableName?: string;
toColumnName?: string;
relationshipType?: string;
connectionType?: string;
settings?: any;
updatedBy: string;
}
export class DataflowService {
/**
* 테이블 관계 생성
*/
async createTableRelationship(data: CreateTableRelationshipData) {
try {
logger.info("DataflowService: 테이블 관계 생성 시작", data);
// diagram_id 결정 로직
let diagramId = data.diagramId;
if (!diagramId) {
// 새로운 관계도인 경우, 새로운 diagram_id 생성
// 현재 최대 diagram_id + 1
const maxDiagramId = await queryOne<{ diagram_id: number }>(
`SELECT diagram_id FROM table_relationships
WHERE company_code = $1
ORDER BY diagram_id DESC
LIMIT 1`,
[data.companyCode]
);
diagramId = (maxDiagramId?.diagram_id || 0) + 1;
}
// 중복 관계 확인 (같은 diagram_id 내에서)
const existingRelationship = await queryOne(
`SELECT * FROM table_relationships
WHERE diagram_id = $1
AND from_table_name = $2
AND from_column_name = $3
AND to_table_name = $4
AND to_column_name = $5
AND company_code = $6
AND is_active = 'Y'`,
[
diagramId,
data.fromTableName,
data.fromColumnName,
data.toTableName,
data.toColumnName,
data.companyCode,
]
);
if (existingRelationship) {
throw new Error(
`이미 존재하는 관계입니다: ${data.fromTableName}.${data.fromColumnName}${data.toTableName}.${data.toColumnName}`
);
}
// 새 관계 생성
const relationship = await queryOne(
`INSERT INTO table_relationships (
diagram_id, relationship_name, from_table_name, from_column_name,
to_table_name, to_column_name, relationship_type, connection_type,
company_code, settings, created_by, updated_by, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), now())
RETURNING *`,
[
diagramId,
data.relationshipName,
data.fromTableName,
data.fromColumnName,
data.toTableName,
data.toColumnName,
data.relationshipType,
data.connectionType,
data.companyCode,
JSON.stringify(data.settings),
data.createdBy,
data.createdBy,
]
);
logger.info(
`DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}, Diagram ID: ${relationship.diagram_id}`
);
return relationship;
} catch (error) {
logger.error("DataflowService: 테이블 관계 생성 실패", error);
throw error;
}
}
/**
* 회사별 테이블 관계 목록 조회
*/
async getTableRelationships(companyCode: string) {
try {
logger.info(
`DataflowService: 테이블 관계 목록 조회 시작 - 회사코드: ${companyCode}`
);
// 관리자는 모든 회사의 관계를 볼 수 있음
let queryText = `SELECT * FROM table_relationships WHERE is_active = 'Y'`;
const params: any[] = [];
if (companyCode !== "*") {
queryText += ` AND company_code = $1`;
params.push(companyCode);
}
queryText += ` ORDER BY created_date DESC`;
const relationships = await query(queryText, params);
logger.info(
`DataflowService: 테이블 관계 목록 조회 완료 - ${relationships.length}`
);
return relationships;
} catch (error) {
logger.error("DataflowService: 테이블 관계 목록 조회 실패", error);
throw error;
}
}
/**
* 특정 테이블 관계 조회
*/
async getTableRelationship(relationshipId: number, companyCode: string) {
try {
logger.info(
`DataflowService: 테이블 관계 조회 시작 - ID: ${relationshipId}, 회사코드: ${companyCode}`
);
let queryText = `SELECT * FROM table_relationships WHERE relationship_id = $1 AND is_active = 'Y'`;
const params: any[] = [relationshipId];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
queryText += ` AND company_code = $2`;
params.push(companyCode);
}
const relationship = await queryOne(queryText, params);
if (relationship) {
logger.info(
`DataflowService: 테이블 관계 조회 완료 - ID: ${relationshipId}`
);
} else {
logger.warn(
`DataflowService: 테이블 관계를 찾을 수 없음 - ID: ${relationshipId}`
);
}
return relationship;
} catch (error) {
logger.error("DataflowService: 테이블 관계 조회 실패", error);
throw error;
}
}
/**
* 테이블 관계 수정
*/
async updateTableRelationship(
relationshipId: number,
updateData: UpdateTableRelationshipData,
companyCode: string
) {
try {
logger.info(
`DataflowService: 테이블 관계 수정 시작 - ID: ${relationshipId}`,
updateData
);
// 기존 관계 확인
const existingRelationship = await this.getTableRelationship(
relationshipId,
companyCode
);
if (!existingRelationship) {
return null;
}
// 관계 수정
const updates: string[] = [];
const params: any[] = [];
let paramIndex = 1;
if (updateData.relationshipName !== undefined) {
updates.push(`relationship_name = $${paramIndex++}`);
params.push(updateData.relationshipName);
}
if (updateData.fromTableName !== undefined) {
updates.push(`from_table_name = $${paramIndex++}`);
params.push(updateData.fromTableName);
}
if (updateData.fromColumnName !== undefined) {
updates.push(`from_column_name = $${paramIndex++}`);
params.push(updateData.fromColumnName);
}
if (updateData.toTableName !== undefined) {
updates.push(`to_table_name = $${paramIndex++}`);
params.push(updateData.toTableName);
}
if (updateData.toColumnName !== undefined) {
updates.push(`to_column_name = $${paramIndex++}`);
params.push(updateData.toColumnName);
}
if (updateData.relationshipType !== undefined) {
updates.push(`relationship_type = $${paramIndex++}`);
params.push(updateData.relationshipType);
}
if (updateData.connectionType !== undefined) {
updates.push(`connection_type = $${paramIndex++}`);
params.push(updateData.connectionType);
}
if (updateData.settings !== undefined) {
updates.push(`settings = $${paramIndex++}`);
params.push(JSON.stringify(updateData.settings));
}
updates.push(`updated_by = $${paramIndex++}`);
params.push(updateData.updatedBy);
updates.push(`updated_date = now()`);
params.push(relationshipId);
const relationship = await queryOne(
`UPDATE table_relationships
SET ${updates.join(", ")}
WHERE relationship_id = $${paramIndex}
RETURNING *`,
params
);
logger.info(
`DataflowService: 테이블 관계 수정 완료 - ID: ${relationshipId}`
);
return relationship;
} catch (error) {
logger.error("DataflowService: 테이블 관계 수정 실패", error);
throw error;
}
}
/**
* 테이블 관계 삭제 (소프트 삭제)
*/
async deleteTableRelationship(relationshipId: number, companyCode: string) {
try {
logger.info(
`DataflowService: 테이블 관계 삭제 시작 - ID: ${relationshipId}, 회사코드: ${companyCode}`
);
// 기존 관계 확인
const existingRelationship = await this.getTableRelationship(
relationshipId,
companyCode
);
if (!existingRelationship) {
return false;
}
// 소프트 삭제 (is_active = 'N')
await query(
`UPDATE table_relationships
SET is_active = 'N', updated_date = now()
WHERE relationship_id = $1`,
[relationshipId]
);
logger.info(
`DataflowService: 테이블 관계 삭제 완료 - ID: ${relationshipId}`
);
return true;
} catch (error) {
logger.error("DataflowService: 테이블 관계 삭제 실패", error);
throw error;
}
}
/**
* 특정 테이블과 관련된 모든 관계 조회
*/
async getRelationshipsByTable(tableName: string, companyCode: string) {
try {
logger.info(
`DataflowService: 테이블별 관계 조회 시작 - 테이블: ${tableName}, 회사코드: ${companyCode}`
);
let queryText = `
SELECT * FROM table_relationships
WHERE (from_table_name = $1 OR to_table_name = $1)
AND is_active = 'Y'
`;
const params: any[] = [tableName];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
queryText += ` AND company_code = $2`;
params.push(companyCode);
}
queryText += ` ORDER BY created_date DESC`;
const relationships = await query(queryText, params);
logger.info(
`DataflowService: 테이블별 관계 조회 완료 - ${relationships.length}`
);
return relationships;
} catch (error) {
logger.error("DataflowService: 테이블별 관계 조회 실패", error);
throw error;
}
}
/**
* 연결 타입별 관계 조회
*/
async getRelationshipsByConnectionType(
connectionType: string,
companyCode: string
) {
try {
logger.info(
`DataflowService: 연결타입별 관계 조회 시작 - 타입: ${connectionType}, 회사코드: ${companyCode}`
);
let queryText = `
SELECT * FROM table_relationships
WHERE connection_type = $1 AND is_active = 'Y'
`;
const params: any[] = [connectionType];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
queryText += ` AND company_code = $2`;
params.push(companyCode);
}
queryText += ` ORDER BY created_date DESC`;
const relationships = await query(queryText, params);
logger.info(
`DataflowService: 연결타입별 관계 조회 완료 - ${relationships.length}`
);
return relationships;
} catch (error) {
logger.error("DataflowService: 연결타입별 관계 조회 실패", error);
throw error;
}
}
/**
* 관계 통계 조회
*/
async getRelationshipStats(companyCode: string) {
try {
logger.info(
`DataflowService: 관계 통계 조회 시작 - 회사코드: ${companyCode}`
);
let whereClause = `WHERE is_active = 'Y'`;
const params: any[] = [];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereClause += ` AND company_code = $1`;
params.push(companyCode);
}
// 전체 관계 수
const totalCountResult = await queryOne<{ count: string }>(
`SELECT COUNT(*) as count FROM table_relationships ${whereClause}`,
params
);
const totalCount = parseInt(totalCountResult?.count || "0", 10);
// 관계 타입별 통계
const relationshipTypeStats = await query<{
relationship_type: string;
count: string;
}>(
`SELECT relationship_type, COUNT(*) as count
FROM table_relationships ${whereClause}
GROUP BY relationship_type`,
params
);
// 연결 타입별 통계
const connectionTypeStats = await query<{
connection_type: string;
count: string;
}>(
`SELECT connection_type, COUNT(*) as count
FROM table_relationships ${whereClause}
GROUP BY connection_type`,
params
);
const stats = {
totalCount,
relationshipTypeStats: relationshipTypeStats.map((stat) => ({
type: stat.relationship_type,
count: parseInt(stat.count, 10),
})),
connectionTypeStats: connectionTypeStats.map((stat) => ({
type: stat.connection_type,
count: parseInt(stat.count, 10),
})),
};
logger.info(`DataflowService: 관계 통계 조회 완료`, stats);
return stats;
} catch (error) {
logger.error("DataflowService: 관계 통계 조회 실패", error);
throw error;
}
}
// ==================== 데이터 중계 관리 ====================
/**
* 데이터 관계 연결 생성
*/
async createDataLink(linkData: {
relationshipId: number;
fromTableName: string;
fromColumnName: string;
toTableName: string;
toColumnName: string;
connectionType: string;
companyCode: string;
bridgeData?: any;
createdBy: string;
}) {
try {
logger.info(
`DataflowService: 데이터 연결 생성 시작 - 관계ID: ${linkData.relationshipId}`
);
const bridge = await queryOne(
`INSERT INTO data_relationship_bridge (
relationship_id, from_table_name, from_column_name, to_table_name,
to_column_name, connection_type, company_code, bridge_data,
created_by, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now())
RETURNING *`,
[
linkData.relationshipId,
linkData.fromTableName,
linkData.fromColumnName,
linkData.toTableName,
linkData.toColumnName,
linkData.connectionType,
linkData.companyCode,
JSON.stringify(linkData.bridgeData || {}),
linkData.createdBy,
]
);
logger.info(
`DataflowService: 데이터 연결 생성 완료 - Bridge ID: ${bridge.bridge_id}`
);
return bridge;
} catch (error) {
logger.error("DataflowService: 데이터 연결 생성 실패", error);
throw error;
}
}
/**
* 관계별 연결된 데이터 조회
*/
async getLinkedDataByRelationship(
relationshipId: number,
companyCode: string
) {
try {
logger.info(
`DataflowService: 관계별 연결 데이터 조회 시작 - 관계ID: ${relationshipId}`
);
let queryText = `
SELECT * FROM data_relationship_bridge
WHERE relationship_id = $1 AND is_active = 'Y'
`;
const params: any[] = [relationshipId];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
queryText += ` AND company_code = $2`;
params.push(companyCode);
}
queryText += ` ORDER BY created_at DESC`;
const linkedData = await query(queryText, params);
logger.info(
`DataflowService: 관계별 연결 데이터 조회 완료 - ${linkedData.length}`
);
return linkedData;
} catch (error) {
logger.error("DataflowService: 관계별 연결 데이터 조회 실패", error);
throw error;
}
}
/**
* 특정 테이블의 연결된 데이터 조회
*/
async getLinkedDataByTable(
tableName: string,
keyValue?: string,
companyCode?: string
) {
try {
logger.info(
`DataflowService: 테이블별 연결 데이터 조회 시작 - 테이블: ${tableName}`
);
let queryText = `
SELECT * FROM data_relationship_bridge
WHERE (from_table_name = $1 OR to_table_name = $1) AND is_active = 'Y'
`;
const params: any[] = [tableName];
// keyValue 파라미터는 더 이상 사용하지 않음 (key_value 필드 제거됨)
// 회사코드 필터링
if (companyCode && companyCode !== "*") {
queryText += ` AND company_code = $2`;
params.push(companyCode);
}
queryText += ` ORDER BY created_at DESC`;
const linkedData = await query(queryText, params);
logger.info(
`DataflowService: 테이블별 연결 데이터 조회 완료 - ${linkedData.length}`
);
return linkedData;
} catch (error) {
logger.error("DataflowService: 테이블별 연결 데이터 조회 실패", error);
throw error;
}
}
/**
* 데이터 연결 수정
*/
async updateDataLink(
bridgeId: number,
updateData: {
bridgeData?: any;
updatedBy: string;
},
companyCode: string
) {
try {
logger.info(
`DataflowService: 데이터 연결 수정 시작 - Bridge ID: ${bridgeId}`
);
let queryText = `
UPDATE data_relationship_bridge
SET bridge_data = $1, updated_by = $2, updated_at = now()
WHERE bridge_id = $3 AND is_active = 'Y'
`;
const params: any[] = [
JSON.stringify(updateData.bridgeData),
updateData.updatedBy,
bridgeId,
];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
queryText += ` AND company_code = $4`;
params.push(companyCode);
}
queryText += ` RETURNING *`;
const updatedBridge = await queryOne(queryText, params);
logger.info(
`DataflowService: 데이터 연결 수정 완료 - Bridge ID: ${bridgeId}`
);
return updatedBridge;
} catch (error) {
logger.error("DataflowService: 데이터 연결 수정 실패", error);
throw error;
}
}
/**
* 데이터 연결 삭제 (소프트 삭제)
*/
async deleteDataLink(
bridgeId: number,
companyCode: string,
deletedBy: string
) {
try {
logger.info(
`DataflowService: 데이터 연결 삭제 시작 - Bridge ID: ${bridgeId}`
);
let queryText = `
UPDATE data_relationship_bridge
SET is_active = 'N', updated_at = now(), updated_by = $1
WHERE bridge_id = $2 AND is_active = 'Y'
`;
const params: any[] = [deletedBy, bridgeId];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
queryText += ` AND company_code = $3`;
params.push(companyCode);
}
await query(queryText, params);
logger.info(
`DataflowService: 데이터 연결 삭제 완료 - Bridge ID: ${bridgeId}`
);
return true;
} catch (error) {
logger.error("DataflowService: 데이터 연결 삭제 실패", error);
throw error;
}
}
/**
* 관계 삭제 시 연결된 모든 데이터도 삭제
*/
async deleteAllLinkedDataByRelationship(
relationshipId: number,
companyCode: string,
deletedBy: string
) {
try {
logger.info(
`DataflowService: 관계별 모든 데이터 연결 삭제 시작 - 관계ID: ${relationshipId}`
);
let queryText = `
UPDATE data_relationship_bridge
SET is_active = 'N', updated_at = now(), updated_by = $1
WHERE relationship_id = $2 AND is_active = 'Y'
`;
const params: any[] = [deletedBy, relationshipId];
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
queryText += ` AND company_code = $3`;
params.push(companyCode);
}
const result = await query(queryText, params);
logger.info(
`DataflowService: 관계별 모든 데이터 연결 삭제 완료 - ${result.length}`
);
return result.length;
} catch (error) {
logger.error("DataflowService: 관계별 모든 데이터 연결 삭제 실패", error);
throw error;
}
}
// ==================== 테이블 데이터 조회 ====================
/**
* 테이블 실제 데이터 조회 (페이징)
*/
async getTableData(
tableName: string,
page: number = 1,
limit: number = 10,
search: string = "",
searchColumn: string = "",
companyCode: string = "*"
) {
try {
logger.info(`DataflowService: 테이블 데이터 조회 시작 - ${tableName}`);
// 테이블 존재 여부 확인 (정보 스키마 사용)
const tableExists = await query(
`SELECT table_name
FROM information_schema.tables
WHERE table_name = $1 AND table_schema = 'public'`,
[tableName.toLowerCase()]
);
if (!tableExists || tableExists.length === 0) {
throw new Error(`테이블 '${tableName}'이 존재하지 않습니다.`);
}
// 전체 데이터 개수 조회 및 데이터 조회
let totalCountQuery = `SELECT COUNT(*) as total FROM "${tableName}"`;
let dataQuery = `SELECT * FROM "${tableName}"`;
const queryParams: any[] = [];
// 검색 조건 추가 (SQL Injection 방지를 위해 파라미터 바인딩 사용)
if (search && searchColumn) {
const paramIndex = queryParams.length + 1;
const whereCondition = `WHERE "${searchColumn}" ILIKE $${paramIndex}`;
totalCountQuery += ` ${whereCondition}`;
dataQuery += ` ${whereCondition}`;
queryParams.push(`%${search}%`);
}
// 페이징 처리
const offset = (page - 1) * limit;
const limitIndex = queryParams.length + 1;
const offsetIndex = queryParams.length + 2;
dataQuery += ` ORDER BY 1 LIMIT $${limitIndex} OFFSET $${offsetIndex}`;
const dataQueryParams = [...queryParams, limit, offset];
// 실제 쿼리 실행
const [totalResult, dataResult] = await Promise.all([
query(totalCountQuery, queryParams.length > 0 ? queryParams : []),
query(dataQuery, dataQueryParams),
]);
const total =
totalResult && totalResult.length > 0
? Number((totalResult[0] as any).total)
: 0;
const data = dataResult || [];
const result = {
data,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page < Math.ceil(total / limit),
hasPrev: page > 1,
},
};
logger.info(
`DataflowService: 테이블 데이터 조회 완료 - ${tableName}, 총 ${total}건 중 ${data.length}건 조회`
);
return result;
} catch (error) {
logger.error(
`DataflowService: 테이블 데이터 조회 실패 - ${tableName}`,
error
);
throw error;
}
}
/**
* 관계도 그룹 목록 조회 (diagram_id별로 그룹화)
*/
async getDataFlowDiagrams(
companyCode: string,
page: number = 1,
size: number = 20,
searchTerm: string = ""
) {
try {
logger.info(
`DataflowService: 관계도 목록 조회 시작 - ${companyCode}, page: ${page}, size: ${size}, search: ${searchTerm}`
);
// WHERE 조건 구성
const params: any[] = [companyCode];
let whereClause = `WHERE company_code = $1 AND is_active = 'Y'`;
if (searchTerm) {
whereClause += ` AND (
relationship_name ILIKE $2 OR
from_table_name ILIKE $2 OR
to_table_name ILIKE $2
)`;
params.push(`%${searchTerm}%`);
}
// diagram_id별로 그룹화된 데이터 조회
const relationships = await query<{
relationship_id: number;
diagram_id: number;
relationship_name: string;
from_table_name: string;
to_table_name: string;
connection_type: string;
relationship_type: string;
created_date: Date;
created_by: string;
updated_date: Date;
updated_by: string;
}>(
`SELECT
relationship_id, diagram_id, relationship_name,
from_table_name, to_table_name, connection_type,
relationship_type, created_date, created_by,
updated_date, updated_by
FROM table_relationships
${whereClause}
ORDER BY diagram_id ASC, created_date DESC`,
params
);
// diagram_id별로 그룹화
const diagramMap = new Map<number, any>();
relationships.forEach((rel) => {
const diagramId = rel.diagram_id;
if (diagramId && !diagramMap.has(diagramId)) {
diagramMap.set(diagramId, {
diagramId: diagramId,
diagramName: rel.relationship_name, // 첫 번째 관계의 이름을 사용
connectionType: rel.connection_type,
relationshipType: rel.relationship_type,
tableCount: new Set<string>(),
relationshipCount: 0,
createdAt: rel.created_date,
createdBy: rel.created_by,
updatedAt: rel.updated_date,
updatedBy: rel.updated_by,
tables: [],
});
}
if (diagramId) {
const diagram = diagramMap.get(diagramId);
if (diagram) {
diagram.tableCount.add(rel.from_table_name || "");
diagram.tableCount.add(rel.to_table_name || "");
}
diagram.relationshipCount++;
// 최신 업데이트 시간 유지
if (rel.updated_date && rel.updated_date > diagram.updatedAt) {
diagram.updatedAt = rel.updated_date;
diagram.updatedBy = rel.updated_by;
}
}
});
// Set을 배열로 변환하고 테이블 개수 계산
const diagrams = Array.from(diagramMap.values()).map((diagram) => ({
...diagram,
tableCount: diagram.tableCount.size,
tables: Array.from(diagram.tableCount),
}));
// 페이징 처리
const total = diagrams.length;
const startIndex = (page - 1) * size;
const endIndex = startIndex + size;
const paginatedDiagrams = diagrams.slice(startIndex, endIndex);
const result = {
diagrams: paginatedDiagrams,
total,
page,
size,
totalPages: Math.ceil(total / size),
hasNext: page < Math.ceil(total / size),
hasPrev: page > 1,
};
logger.info(
`DataflowService: 관계도 목록 조회 완료 - 총 ${total}개 관계도 중 ${paginatedDiagrams.length}개 조회`
);
return result;
} catch (error) {
logger.error("DataflowService: 관계도 목록 조회 실패", error);
throw error;
}
}
/**
* 특정 관계도의 모든 관계 조회
*/
async getDiagramRelationships(companyCode: string, diagramName: string) {
try {
logger.info(
`DataflowService: 관계도 관계 조회 시작 - ${companyCode}, diagram: ${diagramName}`
);
const relationships = await query(
`SELECT * FROM table_relationships
WHERE company_code = $1
AND relationship_name = $2
AND is_active = 'Y'
ORDER BY created_date ASC`,
[companyCode, diagramName]
);
logger.info(
`DataflowService: 관계도 관계 조회 완료 - ${diagramName}, ${relationships.length}개 관계`
);
return relationships;
} catch (error) {
logger.error(
`DataflowService: 관계도 관계 조회 실패 - ${diagramName}`,
error
);
throw error;
}
}
/**
* 관계도 복사 (diagram_id 기반)
*/
async copyDiagram(
companyCode: string,
originalDiagramName: string
): Promise<string> {
try {
logger.info(`DataflowService: 관계도 복사 시작 - ${originalDiagramName}`);
// 원본 관계도의 모든 관계 조회
const originalRelationships = await query<{
relationship_id: number;
diagram_id: number;
relationship_name: string;
from_table_name: string;
from_column_name: string;
to_table_name: string;
to_column_name: string;
relationship_type: string;
connection_type: string;
settings: any;
company_code: string;
created_by: string;
updated_by: string;
}>(
`SELECT * FROM table_relationships
WHERE company_code = $1
AND relationship_name = $2
AND is_active = 'Y'`,
[companyCode, originalDiagramName]
);
if (originalRelationships.length === 0) {
throw new Error("복사할 관계도를 찾을 수 없습니다.");
}
// 새로운 관계도 이름 생성 (중복 검사)
let newDiagramName = `${originalDiagramName} (1)`;
let counter = 1;
while (true) {
const existingDiagram = await queryOne(
`SELECT relationship_id FROM table_relationships
WHERE company_code = $1
AND relationship_name = $2
AND is_active = 'Y'
LIMIT 1`,
[companyCode, newDiagramName]
);
if (!existingDiagram) {
break;
}
counter++;
newDiagramName = `${originalDiagramName} (${counter})`;
}
// 새로운 diagram_id 생성
const maxDiagramId = await queryOne<{ diagram_id: number }>(
`SELECT diagram_id FROM table_relationships
WHERE company_code = $1
ORDER BY diagram_id DESC
LIMIT 1`,
[companyCode]
);
const newDiagramId = (maxDiagramId?.diagram_id || 0) + 1;
// 트랜잭션으로 모든 관계 복사
const copiedRelationships = await transaction(async (client) => {
const results: any[] = [];
for (const rel of originalRelationships) {
const result = await client.query(
`INSERT INTO table_relationships (
diagram_id, relationship_name, from_table_name, from_column_name,
to_table_name, to_column_name, relationship_type, connection_type,
settings, company_code, is_active, created_by, updated_by, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'Y', $11, $12, now(), now())
RETURNING *`,
[
newDiagramId,
newDiagramName,
rel.from_table_name,
rel.from_column_name,
rel.to_table_name,
rel.to_column_name,
rel.relationship_type,
rel.connection_type,
rel.settings,
rel.company_code,
rel.created_by,
rel.updated_by,
]
);
if (result.rows && result.rows.length > 0) {
results.push(result.rows[0]);
}
}
return results;
});
logger.info(
`DataflowService: 관계도 복사 완료 - ${originalDiagramName}${newDiagramName} (diagram_id: ${newDiagramId}), ${copiedRelationships.length}개 관계 복사`
);
return newDiagramName;
} catch (error) {
logger.error(
`DataflowService: 관계도 복사 실패 - ${originalDiagramName}`,
error
);
throw error;
}
}
/**
* 관계도 삭제
*/
async deleteDiagram(
companyCode: string,
diagramName: string
): Promise<number> {
try {
logger.info(`DataflowService: 관계도 삭제 시작 - ${diagramName}`);
// 관계도의 모든 관계 삭제 (하드 삭제)
const deleteResult = await query<{ count: number }>(
`DELETE FROM table_relationships
WHERE company_code = $1 AND relationship_name = $2
RETURNING relationship_id`,
[companyCode, diagramName]
);
const count = deleteResult.length;
logger.info(
`DataflowService: 관계도 삭제 완료 - ${diagramName}, ${count}개 관계 삭제`
);
return count;
} catch (error) {
logger.error(`DataflowService: 관계도 삭제 실패 - ${diagramName}`, error);
throw error;
}
}
/**
* diagram_id로 해당 관계도의 모든 관계 조회
*/
async getDiagramRelationshipsByDiagramId(
companyCode: string,
diagramId: number
) {
try {
logger.info(
`DataflowService: diagram_id로 관계도 관계 조회 - ${diagramId}`
);
// diagram_id로 모든 관계 조회
const relationships = await query(
`SELECT * FROM table_relationships
WHERE diagram_id = $1
AND company_code = $2
AND is_active = 'Y'
ORDER BY relationship_id ASC`,
[diagramId, companyCode]
);
logger.info(
`DataflowService: diagram_id로 관계도 관계 조회 완료 - ${relationships.length}개 관계`
);
return relationships.map((rel: any) => ({
...rel,
settings: rel.settings as any,
}));
} catch (error) {
logger.error(
`DataflowService: diagram_id로 관계도 관계 조회 실패 - ${diagramId}`,
error
);
throw error;
}
}
/**
* relationship_id로 해당 관계도의 모든 관계 조회 (하위 호환성 유지)
*/
async getDiagramRelationshipsByRelationshipId(
companyCode: string,
relationshipId: number
) {
try {
logger.info(
`DataflowService: relationship_id로 관계도 관계 조회 - ${relationshipId}`
);
// 먼저 해당 relationship_id의 diagram_id를 찾음
const targetRelationship = await queryOne<{ diagram_id: number }>(
`SELECT diagram_id FROM table_relationships
WHERE relationship_id = $1
AND company_code = $2
AND is_active = 'Y'
LIMIT 1`,
[relationshipId, companyCode]
);
if (!targetRelationship) {
throw new Error("해당 관계 ID를 찾을 수 없습니다.");
}
// diagram_id로 모든 관계 조회
if (targetRelationship.diagram_id) {
return this.getDiagramRelationshipsByDiagramId(
companyCode,
targetRelationship.diagram_id
);
} else {
throw new Error("관계에 diagram_id가 없습니다.");
}
} catch (error) {
logger.error(
`DataflowService: relationship_id로 관계도 관계 조회 실패 - ${relationshipId}`,
error
);
throw error;
}
}
}