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

893 lines
24 KiB
TypeScript
Raw Normal View History

2025-09-08 16:46:53 +09:00
import { PrismaClient } from "@prisma/client";
import { logger } from "../utils/logger";
const prisma = new PrismaClient();
// 테이블 관계 생성 데이터 타입
interface CreateTableRelationshipData {
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);
// 중복 관계 확인
const existingRelationship = await prisma.table_relationships.findFirst({
where: {
from_table_name: data.fromTableName,
from_column_name: data.fromColumnName,
to_table_name: data.toTableName,
to_column_name: data.toColumnName,
company_code: data.companyCode,
is_active: "Y",
},
});
if (existingRelationship) {
throw new Error(
`이미 존재하는 관계입니다: ${data.fromTableName}.${data.fromColumnName}${data.toTableName}.${data.toColumnName}`
);
}
2025-09-09 09:42:15 +09:00
// 새 관계 생성 (중계 테이블은 별도로 생성하지 않음)
const relationship = await prisma.table_relationships.create({
data: {
relationship_name: data.relationshipName,
from_table_name: data.fromTableName,
from_column_name: data.fromColumnName,
to_table_name: data.toTableName,
to_column_name: data.toColumnName,
relationship_type: data.relationshipType,
connection_type: data.connectionType,
company_code: data.companyCode,
settings: data.settings,
created_by: data.createdBy,
updated_by: data.createdBy,
},
2025-09-08 16:46:53 +09:00
});
logger.info(
2025-09-09 09:42:15 +09:00
`DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}`
2025-09-08 16:46:53 +09:00
);
2025-09-09 09:42:15 +09:00
return relationship;
2025-09-08 16:46:53 +09:00
} catch (error) {
logger.error("DataflowService: 테이블 관계 생성 실패", error);
throw error;
}
}
/**
*
*/
async getTableRelationships(companyCode: string) {
try {
logger.info(
`DataflowService: 테이블 관계 목록 조회 시작 - 회사코드: ${companyCode}`
);
// 관리자는 모든 회사의 관계를 볼 수 있음
const whereCondition: any = {
is_active: "Y",
};
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const relationships = await prisma.table_relationships.findMany({
where: whereCondition,
orderBy: {
created_date: "desc",
},
});
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}`
);
const whereCondition: any = {
relationship_id: relationshipId,
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const relationship = await prisma.table_relationships.findFirst({
where: whereCondition,
});
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 relationship = await prisma.table_relationships.update({
where: {
relationship_id: relationshipId,
},
data: {
...updateData,
updated_date: new Date(),
},
});
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 prisma.table_relationships.update({
where: {
relationship_id: relationshipId,
},
data: {
is_active: "N",
updated_date: new Date(),
},
});
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}`
);
const whereCondition: any = {
OR: [{ from_table_name: tableName }, { to_table_name: tableName }],
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const relationships = await prisma.table_relationships.findMany({
where: whereCondition,
orderBy: {
created_date: "desc",
},
});
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}`
);
const whereCondition: any = {
connection_type: connectionType,
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const relationships = await prisma.table_relationships.findMany({
where: whereCondition,
orderBy: {
created_date: "desc",
},
});
logger.info(
`DataflowService: 연결타입별 관계 조회 완료 - ${relationships.length}`
);
return relationships;
} catch (error) {
logger.error("DataflowService: 연결타입별 관계 조회 실패", error);
throw error;
}
}
/**
*
*/
async getRelationshipStats(companyCode: string) {
try {
logger.info(
`DataflowService: 관계 통계 조회 시작 - 회사코드: ${companyCode}`
);
const whereCondition: any = {
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
// 전체 관계 수
const totalCount = await prisma.table_relationships.count({
where: whereCondition,
});
// 관계 타입별 통계
const relationshipTypeStats = await prisma.table_relationships.groupBy({
by: ["relationship_type"],
where: whereCondition,
_count: {
relationship_id: true,
},
});
// 연결 타입별 통계
const connectionTypeStats = await prisma.table_relationships.groupBy({
by: ["connection_type"],
where: whereCondition,
_count: {
relationship_id: true,
},
});
const stats = {
totalCount,
relationshipTypeStats: relationshipTypeStats.map((stat) => ({
type: stat.relationship_type,
count: stat._count.relationship_id,
})),
connectionTypeStats: connectionTypeStats.map((stat) => ({
type: stat.connection_type,
count: stat._count.relationship_id,
})),
};
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 prisma.data_relationship_bridge.create({
data: {
relationship_id: linkData.relationshipId,
from_table_name: linkData.fromTableName,
from_column_name: linkData.fromColumnName,
to_table_name: linkData.toTableName,
to_column_name: linkData.toColumnName,
connection_type: linkData.connectionType,
company_code: linkData.companyCode,
bridge_data: linkData.bridgeData || {},
created_by: 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}`
);
const whereCondition: any = {
relationship_id: relationshipId,
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const linkedData = await prisma.data_relationship_bridge.findMany({
where: whereCondition,
orderBy: { created_at: "desc" },
include: {
relationship: {
select: {
relationship_name: true,
relationship_type: true,
connection_type: true,
},
},
},
});
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}`
);
const whereCondition: any = {
OR: [{ from_table_name: tableName }, { to_table_name: tableName }],
is_active: "Y",
};
// keyValue 파라미터는 더 이상 사용하지 않음 (key_value 필드 제거됨)
2025-09-08 16:46:53 +09:00
// 회사코드 필터링
if (companyCode && companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const linkedData = await prisma.data_relationship_bridge.findMany({
where: whereCondition,
orderBy: { created_at: "desc" },
include: {
relationship: {
select: {
relationship_name: true,
relationship_type: true,
connection_type: true,
},
},
},
});
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}`
);
const whereCondition: any = {
bridge_id: bridgeId,
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const updatedBridge = await prisma.data_relationship_bridge.update({
where: whereCondition,
data: {
...updateData,
updated_at: new Date(),
},
});
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}`
);
const whereCondition: any = {
bridge_id: bridgeId,
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
await prisma.data_relationship_bridge.update({
where: whereCondition,
data: {
is_active: "N",
updated_at: new Date(),
updated_by: deletedBy,
},
});
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}`
);
const whereCondition: any = {
relationship_id: relationshipId,
is_active: "Y",
};
// 관리자가 아닌 경우 회사코드 제한
if (companyCode !== "*") {
whereCondition.company_code = companyCode;
}
const result = await prisma.data_relationship_bridge.updateMany({
where: whereCondition,
data: {
is_active: "N",
updated_at: new Date(),
updated_by: deletedBy,
},
});
logger.info(
`DataflowService: 관계별 모든 데이터 연결 삭제 완료 - ${result.count}`
);
return result.count;
} 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 prisma.$queryRaw`
SELECT table_name
FROM information_schema.tables
WHERE table_name = ${tableName.toLowerCase()}
AND table_schema = 'public'
`;
if (
!tableExists ||
(Array.isArray(tableExists) && tableExists.length === 0)
) {
throw new Error(`테이블 '${tableName}'이 존재하지 않습니다.`);
}
// 전체 데이터 개수 조회
let totalCountQuery = `SELECT COUNT(*) as total FROM "${tableName}"`;
let dataQuery = `SELECT * FROM "${tableName}"`;
// 검색 조건 추가
if (search && searchColumn) {
const whereCondition = `WHERE "${searchColumn}" ILIKE '%${search}%'`;
totalCountQuery += ` ${whereCondition}`;
dataQuery += ` ${whereCondition}`;
}
// 페이징 처리
const offset = (page - 1) * limit;
dataQuery += ` ORDER BY 1 LIMIT ${limit} OFFSET ${offset}`;
// 실제 쿼리 실행
const [totalResult, dataResult] = await Promise.all([
prisma.$queryRawUnsafe(totalCountQuery),
prisma.$queryRawUnsafe(dataQuery),
]);
const total =
Array.isArray(totalResult) && totalResult.length > 0
? Number((totalResult[0] as any).total)
: 0;
const data = Array.isArray(dataResult) ? 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;
}
}
2025-09-09 11:35:05 +09:00
/**
* ( )
*/
async getDataFlowDiagrams(
companyCode: string,
page: number = 1,
size: number = 20,
searchTerm: string = ""
) {
try {
logger.info(
`DataflowService: 관계도 목록 조회 시작 - ${companyCode}, page: ${page}, size: ${size}, search: ${searchTerm}`
);
// 관계도 이름별로 그룹화하여 조회
const whereCondition = {
company_code: companyCode,
is_active: "Y",
...(searchTerm && {
OR: [
{
relationship_name: {
contains: searchTerm,
mode: "insensitive" as any,
},
},
{
from_table_name: {
contains: searchTerm,
mode: "insensitive" as any,
},
},
{
to_table_name: {
contains: searchTerm,
mode: "insensitive" as any,
},
},
],
}),
};
// 관계도별로 그룹화된 데이터 조회 (관계도 이름을 기준으로)
const relationships = await prisma.table_relationships.findMany({
where: whereCondition,
select: {
relationship_name: true,
from_table_name: true,
to_table_name: true,
connection_type: true,
relationship_type: true,
created_date: true,
created_by: true,
updated_date: true,
updated_by: true,
},
orderBy: [{ relationship_name: "asc" }, { created_date: "desc" }],
});
// 관계도 이름별로 그룹화
const diagramMap = new Map<string, any>();
relationships.forEach((rel) => {
const diagramName = rel.relationship_name;
if (!diagramMap.has(diagramName)) {
diagramMap.set(diagramName, {
diagramName: diagramName,
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: [],
});
}
const diagram = diagramMap.get(diagramName);
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 prisma.table_relationships.findMany({
where: {
company_code: companyCode,
relationship_name: diagramName,
is_active: "Y",
},
orderBy: {
created_date: "asc",
},
});
logger.info(
`DataflowService: 관계도 관계 조회 완료 - ${diagramName}, ${relationships.length}개 관계`
);
return relationships;
} catch (error) {
logger.error(
`DataflowService: 관계도 관계 조회 실패 - ${diagramName}`,
error
);
throw error;
}
}
2025-09-08 16:46:53 +09:00
}