2025-09-15 15:12:02 +09:00
|
|
|
import { PrismaClient, Prisma } from "@prisma/client";
|
2025-09-10 15:30:14 +09:00
|
|
|
import { logger } from "../utils/logger";
|
|
|
|
|
|
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
|
|
|
|
|
|
// 타입 정의
|
|
|
|
|
interface CreateDataflowDiagramData {
|
|
|
|
|
diagram_name: string;
|
2025-09-15 15:12:02 +09:00
|
|
|
relationships: Record<string, unknown>; // JSON 데이터
|
|
|
|
|
node_positions?: Record<string, unknown> | null; // JSON 데이터 (노드 위치 정보)
|
2025-09-12 09:49:53 +09:00
|
|
|
|
2025-09-15 20:07:28 +09:00
|
|
|
// 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드
|
|
|
|
|
control?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 조건 설정)
|
|
|
|
|
category?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 연결 종류)
|
|
|
|
|
plan?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 실행 계획)
|
2025-09-12 09:49:53 +09:00
|
|
|
|
2025-09-10 15:30:14 +09:00
|
|
|
company_code: string;
|
|
|
|
|
created_by: string;
|
|
|
|
|
updated_by: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface UpdateDataflowDiagramData {
|
|
|
|
|
diagram_name?: string;
|
2025-09-15 15:12:02 +09:00
|
|
|
relationships?: Record<string, unknown>; // JSON 데이터
|
|
|
|
|
node_positions?: Record<string, unknown> | null; // JSON 데이터 (노드 위치 정보)
|
2025-09-12 09:49:53 +09:00
|
|
|
|
2025-09-15 20:07:28 +09:00
|
|
|
// 🔥 수정: 배열 구조로 변경된 조건부 연결 관련 필드
|
|
|
|
|
control?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 조건 설정)
|
|
|
|
|
category?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 연결 종류)
|
|
|
|
|
plan?: Array<Record<string, unknown>> | null; // JSON 배열 (각 관계별 실행 계획)
|
2025-09-12 09:49:53 +09:00
|
|
|
|
2025-09-10 15:30:14 +09:00
|
|
|
updated_by: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 관계도 목록 조회 (페이지네이션)
|
|
|
|
|
*/
|
|
|
|
|
export const getDataflowDiagrams = async (
|
|
|
|
|
companyCode: string,
|
|
|
|
|
page: number = 1,
|
|
|
|
|
size: number = 20,
|
|
|
|
|
searchTerm?: string
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const offset = (page - 1) * size;
|
|
|
|
|
|
|
|
|
|
// 검색 조건 구성
|
2025-09-15 15:12:02 +09:00
|
|
|
const whereClause: {
|
|
|
|
|
company_code?: string;
|
|
|
|
|
diagram_name?: {
|
|
|
|
|
contains: string;
|
|
|
|
|
mode: "insensitive";
|
|
|
|
|
};
|
|
|
|
|
} = {};
|
2025-09-10 15:30:14 +09:00
|
|
|
|
|
|
|
|
// company_code가 '*'가 아닌 경우에만 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
whereClause.company_code = companyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (searchTerm) {
|
|
|
|
|
whereClause.diagram_name = {
|
|
|
|
|
contains: searchTerm,
|
|
|
|
|
mode: "insensitive",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 총 개수 조회
|
|
|
|
|
const total = await prisma.dataflow_diagrams.count({
|
|
|
|
|
where: whereClause,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 데이터 조회
|
|
|
|
|
const diagrams = await prisma.dataflow_diagrams.findMany({
|
|
|
|
|
where: whereClause,
|
|
|
|
|
orderBy: {
|
|
|
|
|
updated_at: "desc",
|
|
|
|
|
},
|
|
|
|
|
skip: offset,
|
|
|
|
|
take: size,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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 {
|
2025-09-15 15:12:02 +09:00
|
|
|
const whereClause: {
|
|
|
|
|
diagram_id: number;
|
|
|
|
|
company_code?: string;
|
|
|
|
|
} = {
|
2025-09-10 15:30:14 +09:00
|
|
|
diagram_id: diagramId,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// company_code가 '*'가 아닌 경우에만 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
whereClause.company_code = companyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const diagram = await prisma.dataflow_diagrams.findFirst({
|
|
|
|
|
where: whereClause,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return diagram;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("관계도 조회 서비스 오류:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 새로운 관계도 생성
|
|
|
|
|
*/
|
|
|
|
|
export const createDataflowDiagram = async (
|
|
|
|
|
data: CreateDataflowDiagramData
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const newDiagram = await prisma.dataflow_diagrams.create({
|
|
|
|
|
data: {
|
|
|
|
|
diagram_name: data.diagram_name,
|
2025-09-15 15:12:02 +09:00
|
|
|
relationships: data.relationships as Prisma.InputJsonValue,
|
|
|
|
|
node_positions: data.node_positions as
|
|
|
|
|
| Prisma.InputJsonValue
|
|
|
|
|
| undefined,
|
2025-09-15 20:07:28 +09:00
|
|
|
category: data.category
|
|
|
|
|
? (data.category as Prisma.InputJsonValue)
|
|
|
|
|
: undefined,
|
|
|
|
|
control: data.control as Prisma.InputJsonValue | undefined,
|
|
|
|
|
plan: data.plan as Prisma.InputJsonValue | undefined,
|
2025-09-10 15:30:14 +09:00
|
|
|
company_code: data.company_code,
|
|
|
|
|
created_by: data.created_by,
|
|
|
|
|
updated_by: data.updated_by,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return newDiagram;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("관계도 생성 서비스 오류:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 관계도 수정
|
|
|
|
|
*/
|
|
|
|
|
export const updateDataflowDiagram = async (
|
|
|
|
|
diagramId: number,
|
|
|
|
|
data: UpdateDataflowDiagramData,
|
|
|
|
|
companyCode: string
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
2025-09-15 15:12:02 +09:00
|
|
|
logger.info(
|
|
|
|
|
`관계도 수정 서비스 시작 - ID: ${diagramId}, Company: ${companyCode}`
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-10 15:30:14 +09:00
|
|
|
// 먼저 해당 관계도가 존재하는지 확인
|
2025-09-15 15:12:02 +09:00
|
|
|
const whereClause: {
|
|
|
|
|
diagram_id: number;
|
|
|
|
|
company_code?: string;
|
|
|
|
|
} = {
|
2025-09-10 15:30:14 +09:00
|
|
|
diagram_id: diagramId,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// company_code가 '*'가 아닌 경우에만 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
whereClause.company_code = companyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingDiagram = await prisma.dataflow_diagrams.findFirst({
|
|
|
|
|
where: whereClause,
|
|
|
|
|
});
|
|
|
|
|
|
2025-09-15 15:12:02 +09:00
|
|
|
logger.info(
|
|
|
|
|
`기존 관계도 조회 결과:`,
|
|
|
|
|
existingDiagram ? `ID ${existingDiagram.diagram_id} 발견` : "관계도 없음"
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-10 15:30:14 +09:00
|
|
|
if (!existingDiagram) {
|
2025-09-15 15:12:02 +09:00
|
|
|
logger.warn(
|
|
|
|
|
`관계도 ID ${diagramId}를 찾을 수 없음 - Company: ${companyCode}`
|
|
|
|
|
);
|
2025-09-10 15:30:14 +09:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 업데이트 실행
|
|
|
|
|
const updatedDiagram = await prisma.dataflow_diagrams.update({
|
|
|
|
|
where: {
|
|
|
|
|
diagram_id: diagramId,
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
...(data.diagram_name && { diagram_name: data.diagram_name }),
|
2025-09-15 15:12:02 +09:00
|
|
|
...(data.relationships && {
|
|
|
|
|
relationships: data.relationships as Prisma.InputJsonValue,
|
|
|
|
|
}),
|
2025-09-10 17:48:55 +09:00
|
|
|
...(data.node_positions !== undefined && {
|
2025-09-15 15:12:02 +09:00
|
|
|
node_positions: data.node_positions
|
|
|
|
|
? (data.node_positions as Prisma.InputJsonValue)
|
|
|
|
|
: Prisma.JsonNull,
|
2025-09-10 17:48:55 +09:00
|
|
|
}),
|
2025-09-15 20:07:28 +09:00
|
|
|
...(data.category !== undefined && {
|
|
|
|
|
category: data.category
|
|
|
|
|
? (data.category as Prisma.InputJsonValue)
|
|
|
|
|
: undefined,
|
|
|
|
|
}),
|
|
|
|
|
...(data.control !== undefined && {
|
|
|
|
|
control: data.control as Prisma.InputJsonValue | undefined,
|
|
|
|
|
}),
|
|
|
|
|
...(data.plan !== undefined && {
|
|
|
|
|
plan: data.plan as Prisma.InputJsonValue | undefined,
|
|
|
|
|
}),
|
2025-09-10 15:30:14 +09:00
|
|
|
updated_by: data.updated_by,
|
|
|
|
|
updated_at: new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return updatedDiagram;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("관계도 수정 서비스 오류:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 관계도 삭제
|
|
|
|
|
*/
|
|
|
|
|
export const deleteDataflowDiagram = async (
|
|
|
|
|
diagramId: number,
|
|
|
|
|
companyCode: string
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
// 먼저 해당 관계도가 존재하는지 확인
|
2025-09-15 15:12:02 +09:00
|
|
|
const whereClause: {
|
|
|
|
|
diagram_id: number;
|
|
|
|
|
company_code?: string;
|
|
|
|
|
} = {
|
2025-09-10 15:30:14 +09:00
|
|
|
diagram_id: diagramId,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// company_code가 '*'가 아닌 경우에만 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
whereClause.company_code = companyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingDiagram = await prisma.dataflow_diagrams.findFirst({
|
|
|
|
|
where: whereClause,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!existingDiagram) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 삭제 실행
|
|
|
|
|
await prisma.dataflow_diagrams.delete({
|
|
|
|
|
where: {
|
|
|
|
|
diagram_id: diagramId,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("관계도 삭제 서비스 오류:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 관계도 복제
|
|
|
|
|
*/
|
|
|
|
|
export const copyDataflowDiagram = async (
|
|
|
|
|
diagramId: number,
|
|
|
|
|
companyCode: string,
|
|
|
|
|
newName?: string,
|
|
|
|
|
userId: string = "SYSTEM"
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
// 원본 관계도 조회
|
2025-09-15 15:12:02 +09:00
|
|
|
const whereClause: {
|
|
|
|
|
diagram_id: number;
|
|
|
|
|
company_code?: string;
|
|
|
|
|
} = {
|
2025-09-10 15:30:14 +09:00
|
|
|
diagram_id: diagramId,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// company_code가 '*'가 아닌 경우에만 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
whereClause.company_code = companyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const originalDiagram = await prisma.dataflow_diagrams.findFirst({
|
|
|
|
|
where: whereClause,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// 같은 패턴의 이름들을 찾아서 가장 큰 번호 찾기
|
2025-09-15 15:12:02 +09:00
|
|
|
const copyWhereClause: {
|
|
|
|
|
diagram_name: {
|
|
|
|
|
startsWith: string;
|
|
|
|
|
};
|
|
|
|
|
company_code?: string;
|
|
|
|
|
} = {
|
2025-09-10 15:30:14 +09:00
|
|
|
diagram_name: {
|
|
|
|
|
startsWith: baseName,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// company_code가 '*'가 아닌 경우에만 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
copyWhereClause.company_code = companyCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingCopies = await prisma.dataflow_diagrams.findMany({
|
|
|
|
|
where: copyWhereClause,
|
|
|
|
|
select: {
|
|
|
|
|
diagram_name: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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 prisma.dataflow_diagrams.create({
|
|
|
|
|
data: {
|
|
|
|
|
diagram_name: copyName,
|
2025-09-15 15:12:02 +09:00
|
|
|
relationships: originalDiagram.relationships as Prisma.InputJsonValue,
|
|
|
|
|
node_positions: originalDiagram.node_positions
|
|
|
|
|
? (originalDiagram.node_positions as Prisma.InputJsonValue)
|
|
|
|
|
: Prisma.JsonNull,
|
|
|
|
|
category: originalDiagram.category || undefined,
|
2025-09-10 15:30:14 +09:00
|
|
|
company_code: companyCode,
|
|
|
|
|
created_by: userId,
|
|
|
|
|
updated_by: userId,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return copiedDiagram;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error("관계도 복제 서비스 오류:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|