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

476 lines
15 KiB
TypeScript
Raw Normal View History

// 배치관리 전용 서비스 (기존 소스와 완전 분리)
// 작성일: 2024-12-24
import { query, queryOne } from "../database/db";
import { PasswordEncryption } from "../utils/passwordEncryption";
import { DatabaseConnectorFactory } from "../database/DatabaseConnectorFactory";
// 배치관리 전용 타입 정의
export interface BatchConnectionInfo {
type: "internal" | "external";
id?: number;
name: string;
db_type?: string;
}
export interface BatchTableInfo {
table_name: string;
columns: BatchColumnInfo[];
description?: string | null;
}
export interface BatchColumnInfo {
column_name: string;
data_type: string;
is_nullable?: string;
column_default?: string | null;
}
export interface BatchApiResponse<T = unknown> {
success: boolean;
data?: T;
message?: string;
error?: string;
}
export class BatchManagementService {
/**
* ()
*/
static async getAvailableConnections(
userCompanyCode?: string
): Promise<BatchApiResponse<BatchConnectionInfo[]>> {
try {
const connections: BatchConnectionInfo[] = [];
// 내부 DB 추가
connections.push({
type: "internal",
name: "내부 데이터베이스 (PostgreSQL)",
db_type: "postgresql",
});
// 활성화된 외부 DB 연결 조회 (회사별 필터링)
let query_sql = `SELECT id, connection_name, db_type, description
FROM external_db_connections
WHERE is_active = 'Y'`;
const params: any[] = [];
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
query_sql += ` AND company_code = $1`;
params.push(userCompanyCode);
}
query_sql += ` ORDER BY connection_name ASC`;
const externalConnections = await query<{
id: number;
connection_name: string;
db_type: string;
description: string;
}>(query_sql, params);
// 외부 DB 연결 추가
externalConnections.forEach((conn) => {
connections.push({
type: "external",
id: conn.id,
name: `${conn.connection_name} (${conn.db_type?.toUpperCase()})`,
db_type: conn.db_type || undefined,
});
});
return {
success: true,
data: connections,
message: `${connections.length}개의 연결을 조회했습니다.`,
};
} catch (error) {
console.error("배치관리 연결 목록 조회 실패:", error);
return {
success: false,
message: "연결 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* ()
*/
static async getTablesFromConnection(
connectionType: "internal" | "external",
connectionId?: number,
userCompanyCode?: string
): Promise<BatchApiResponse<BatchTableInfo[]>> {
try {
let tables: BatchTableInfo[] = [];
if (connectionType === "internal") {
// 내부 DB 테이블 조회
const result = await query<{ table_name: string }>(
`SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_type = 'BASE TABLE'
ORDER BY table_name`,
[]
);
tables = result.map((row) => ({
table_name: row.table_name,
columns: [],
}));
} else if (connectionType === "external" && connectionId) {
// 외부 DB 테이블 조회 (회사별 필터링)
const tablesResult = await this.getExternalTables(
connectionId,
userCompanyCode
);
if (tablesResult.success && tablesResult.data) {
tables = tablesResult.data;
}
}
return {
success: true,
data: tables,
message: `${tables.length}개의 테이블을 조회했습니다.`,
};
} catch (error) {
console.error("배치관리 테이블 목록 조회 실패:", error);
return {
success: false,
message: "테이블 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* ()
*/
static async getTableColumns(
connectionType: "internal" | "external",
connectionId: number | undefined,
tableName: string,
userCompanyCode?: string
): Promise<BatchApiResponse<BatchColumnInfo[]>> {
try {
console.log(`[BatchManagementService] getTableColumns 호출:`, {
connectionType,
connectionId,
tableName,
});
let columns: BatchColumnInfo[] = [];
if (connectionType === "internal") {
// 내부 DB 컬럼 조회
console.log(
`[BatchManagementService] 내부 DB 컬럼 조회 시작: ${tableName}`
);
const result = await query<{
column_name: string;
data_type: string;
is_nullable: string;
column_default: string | null;
}>(
`SELECT
column_name,
data_type,
is_nullable,
column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = $1
ORDER BY ordinal_position`,
[tableName]
);
2025-09-26 17:29:20 +09:00
console.log(`[BatchManagementService] 쿼리 결과:`, result);
console.log(`[BatchManagementService] 내부 DB 컬럼 조회 결과:`, result);
columns = result.map((row) => ({
column_name: row.column_name,
data_type: row.data_type,
is_nullable: row.is_nullable,
column_default: row.column_default,
}));
} else if (connectionType === "external" && connectionId) {
// 외부 DB 컬럼 조회 (회사별 필터링)
console.log(
`[BatchManagementService] 외부 DB 컬럼 조회 시작: connectionId=${connectionId}, tableName=${tableName}`
);
const columnsResult = await this.getExternalTableColumns(
connectionId,
tableName,
userCompanyCode
);
console.log(
`[BatchManagementService] 외부 DB 컬럼 조회 결과:`,
columnsResult
);
if (columnsResult.success && columnsResult.data) {
columns = columnsResult.data;
}
}
console.log(`[BatchManagementService] 최종 컬럼 목록:`, columns);
return {
success: true,
data: columns,
message: `${columns.length}개의 컬럼을 조회했습니다.`,
};
} catch (error) {
console.error("[BatchManagementService] 컬럼 정보 조회 오류:", error);
return {
success: false,
message: "컬럼 정보 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* DB ( , )
*/
private static async getExternalTables(
connectionId: number,
userCompanyCode?: string
): Promise<BatchApiResponse<BatchTableInfo[]>> {
try {
// 연결 정보 조회 (회사별 필터링)
let query_sql = `SELECT * FROM external_db_connections WHERE id = $1`;
const params: any[] = [connectionId];
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
query_sql += ` AND company_code = $2`;
params.push(userCompanyCode);
}
const connection = await queryOne<any>(query_sql, params);
if (!connection) {
return {
success: false,
message: "연결 정보를 찾을 수 없거나 권한이 없습니다.",
};
}
// 비밀번호 복호화
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
if (!decryptedPassword) {
return {
success: false,
message: "비밀번호 복호화에 실패했습니다.",
};
}
// 연결 설정 준비
const config = {
host: connection.host,
port: connection.port,
database: connection.database_name,
user: connection.username,
password: decryptedPassword,
connectionTimeoutMillis:
connection.connection_timeout != null
? connection.connection_timeout * 1000
: undefined,
queryTimeoutMillis:
connection.query_timeout != null
? connection.query_timeout * 1000
: undefined,
ssl:
connection.ssl_enabled === "Y"
? { rejectUnauthorized: false }
: false,
};
// DatabaseConnectorFactory를 통한 테이블 목록 조회
const connector = await DatabaseConnectorFactory.createConnector(
connection.db_type,
config,
connectionId
);
const tables = await connector.getTables();
return {
success: true,
message: "테이블 목록을 조회했습니다.",
data: tables,
};
} catch (error) {
console.error("외부 DB 테이블 목록 조회 오류:", error);
return {
success: false,
message: "테이블 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
/**
* DB ( , )
*/
private static async getExternalTableColumns(
connectionId: number,
tableName: string,
userCompanyCode?: string
): Promise<BatchApiResponse<BatchColumnInfo[]>> {
try {
console.log(
`[BatchManagementService] getExternalTableColumns 호출: connectionId=${connectionId}, tableName=${tableName}`
);
// 연결 정보 조회 (회사별 필터링)
let query_sql = `SELECT * FROM external_db_connections WHERE id = $1`;
const params: any[] = [connectionId];
// 회사별 필터링 (최고 관리자가 아닌 경우)
if (userCompanyCode && userCompanyCode !== "*") {
query_sql += ` AND company_code = $2`;
params.push(userCompanyCode);
}
const connection = await queryOne<any>(query_sql, params);
if (!connection) {
console.log(
`[BatchManagementService] 연결 정보를 찾을 수 없거나 권한이 없음: connectionId=${connectionId}`
);
return {
success: false,
message: "연결 정보를 찾을 수 없습니다.",
};
}
console.log(`[BatchManagementService] 연결 정보 조회 성공:`, {
id: connection.id,
connection_name: connection.connection_name,
db_type: connection.db_type,
host: connection.host,
port: connection.port,
database_name: connection.database_name,
});
// 비밀번호 복호화
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
// 연결 설정 준비
const config = {
host: connection.host,
port: connection.port,
database: connection.database_name,
user: connection.username,
password: decryptedPassword,
connectionTimeoutMillis:
connection.connection_timeout != null
? connection.connection_timeout * 1000
: undefined,
queryTimeoutMillis:
connection.query_timeout != null
? connection.query_timeout * 1000
: undefined,
ssl:
connection.ssl_enabled === "Y"
? { rejectUnauthorized: false }
: false,
};
console.log(
`[BatchManagementService] 커넥터 생성 시작: db_type=${connection.db_type}`
);
// 데이터베이스 타입에 따른 커넥터 생성
const connector = await DatabaseConnectorFactory.createConnector(
connection.db_type,
config,
connectionId
);
console.log(
`[BatchManagementService] 커넥터 생성 완료, 컬럼 조회 시작: tableName=${tableName}`
);
// 컬럼 정보 조회
console.log(`[BatchManagementService] connector.getColumns 호출 전`);
const columns = await connector.getColumns(tableName);
console.log(`[BatchManagementService] 원본 컬럼 조회 결과:`, columns);
console.log(
`[BatchManagementService] 원본 컬럼 개수:`,
columns ? columns.length : "null/undefined"
);
// 각 데이터베이스 커넥터의 반환 구조가 다르므로 통일된 구조로 변환
const standardizedColumns: BatchColumnInfo[] = columns.map((col: any) => {
console.log(`[BatchManagementService] 컬럼 변환 중:`, col);
// MySQL/MariaDB 구조: {name, dataType, isNullable, defaultValue} (MySQLConnector만)
if (col.name && col.dataType !== undefined) {
const result = {
column_name: col.name,
data_type: col.dataType,
is_nullable: col.isNullable ? "YES" : "NO",
column_default: col.defaultValue || null,
};
console.log(
`[BatchManagementService] MySQL/MariaDB 구조로 변환:`,
result
);
return result;
}
// PostgreSQL/Oracle/MSSQL/MariaDB 구조: {column_name, data_type, is_nullable, column_default}
else {
const result = {
column_name: col.column_name || col.COLUMN_NAME,
data_type: col.data_type || col.DATA_TYPE,
is_nullable:
col.is_nullable ||
col.IS_NULLABLE ||
(col.nullable === "Y" ? "YES" : "NO"),
column_default: col.column_default || col.COLUMN_DEFAULT || null,
};
console.log(`[BatchManagementService] 표준 구조로 변환:`, result);
return result;
}
});
console.log(
`[BatchManagementService] 표준화된 컬럼 목록:`,
standardizedColumns
);
return {
success: true,
data: standardizedColumns,
message: "컬럼 정보를 조회했습니다.",
};
} catch (error) {
console.error(
"[BatchManagementService] 외부 DB 컬럼 정보 조회 오류:",
error
);
console.error(
"[BatchManagementService] 오류 스택:",
error instanceof Error ? error.stack : "No stack trace"
);
return {
success: false,
message: "컬럼 정보 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
};
}
}
}