ERP-node/backend-node/src/controllers/codeMergeController.ts

285 lines
7.5 KiB
TypeScript
Raw Normal View History

import { Request, Response } from "express";
import pool from "../database/db";
import { logger } from "../utils/logger";
interface AuthenticatedRequest extends Request {
user?: {
userId: string;
userName: string;
companyCode: string;
};
}
/**
* -
* () ,
*/
export async function mergeCodeAllTables(
req: AuthenticatedRequest,
res: Response
): Promise<void> {
const { columnName, oldValue, newValue } = req.body;
const companyCode = req.user?.companyCode;
try {
// 입력값 검증
if (!columnName || !oldValue || !newValue) {
res.status(400).json({
success: false,
message: "필수 필드가 누락되었습니다. (columnName, oldValue, newValue)",
});
return;
}
if (!companyCode) {
res.status(401).json({
success: false,
message: "인증 정보가 없습니다.",
});
return;
}
// 같은 값으로 병합 시도 방지
if (oldValue === newValue) {
res.status(400).json({
success: false,
message: "기존 값과 새 값이 동일합니다.",
});
return;
}
logger.info("코드 병합 시작", {
columnName,
oldValue,
newValue,
companyCode,
userId: req.user?.userId,
});
// PostgreSQL 함수 호출
const result = await pool.query(
"SELECT * FROM merge_code_all_tables($1, $2, $3, $4)",
[columnName, oldValue, newValue, companyCode]
);
// 결과 처리 (pool.query 반환 타입 처리)
2025-11-07 10:18:34 +09:00
const affectedTables = Array.isArray(result) ? result : ((result as any).rows || []);
const totalRows = affectedTables.reduce(
2025-11-07 10:18:34 +09:00
(sum: number, row: any) => sum + parseInt(row.rows_updated || 0),
0
);
logger.info("코드 병합 완료", {
columnName,
oldValue,
newValue,
affectedTablesCount: affectedTables.length,
totalRowsUpdated: totalRows,
});
res.json({
success: true,
message: `코드 병합 완료: ${oldValue}${newValue}`,
data: {
columnName,
oldValue,
newValue,
affectedTables: affectedTables.map((row) => ({
tableName: row.table_name,
rowsUpdated: parseInt(row.rows_updated),
})),
totalRowsUpdated: totalRows,
},
});
} catch (error: any) {
logger.error("코드 병합 실패:", {
error: error.message,
stack: error.stack,
columnName,
oldValue,
newValue,
});
res.status(500).json({
success: false,
message: "코드 병합 중 오류가 발생했습니다.",
error: {
code: "CODE_MERGE_ERROR",
details: error.message,
},
});
}
}
/**
*
*/
export async function getTablesWithColumn(
req: AuthenticatedRequest,
res: Response
): Promise<void> {
const { columnName } = req.params;
try {
if (!columnName) {
res.status(400).json({
success: false,
message: "컬럼명이 필요합니다.",
});
return;
}
logger.info("컬럼을 가진 테이블 목록 조회", { columnName });
const query = `
SELECT DISTINCT t.table_name
FROM information_schema.columns c
JOIN information_schema.tables t
ON c.table_name = t.table_name
WHERE c.column_name = $1
AND t.table_schema = 'public'
AND t.table_type = 'BASE TABLE'
AND EXISTS (
SELECT 1 FROM information_schema.columns c2
WHERE c2.table_name = t.table_name
AND c2.column_name = 'company_code'
)
ORDER BY t.table_name
`;
const result = await pool.query(query, [columnName]);
2025-11-07 10:18:34 +09:00
const rows = (result as any).rows || [];
2025-11-07 10:18:34 +09:00
logger.info(`컬럼을 가진 테이블 조회 완료: ${rows.length}`);
res.json({
success: true,
message: "테이블 목록 조회 성공",
data: {
columnName,
2025-11-07 10:18:34 +09:00
tables: rows.map((row: any) => row.table_name),
count: rows.length,
},
});
} catch (error: any) {
logger.error("테이블 목록 조회 실패:", error);
res.status(500).json({
success: false,
message: "테이블 목록 조회 중 오류가 발생했습니다.",
error: {
code: "TABLE_LIST_ERROR",
details: error.message,
},
});
}
}
/**
* ( )
*/
export async function previewCodeMerge(
req: AuthenticatedRequest,
res: Response
): Promise<void> {
const { columnName, oldValue } = req.body;
const companyCode = req.user?.companyCode;
try {
if (!columnName || !oldValue) {
res.status(400).json({
success: false,
message: "필수 필드가 누락되었습니다. (columnName, oldValue)",
});
return;
}
if (!companyCode) {
res.status(401).json({
success: false,
message: "인증 정보가 없습니다.",
});
return;
}
logger.info("코드 병합 미리보기", { columnName, oldValue, companyCode });
// 해당 컬럼을 가진 테이블 찾기
const tablesQuery = `
SELECT DISTINCT t.table_name
FROM information_schema.columns c
JOIN information_schema.tables t
ON c.table_name = t.table_name
WHERE c.column_name = $1
AND t.table_schema = 'public'
AND t.table_type = 'BASE TABLE'
AND EXISTS (
SELECT 1 FROM information_schema.columns c2
WHERE c2.table_name = t.table_name
AND c2.column_name = 'company_code'
)
`;
const tablesResult = await pool.query(tablesQuery, [columnName]);
// 각 테이블에서 영향받을 행 수 계산
const preview = [];
2025-11-07 10:18:34 +09:00
const tableRows = Array.isArray(tablesResult) ? tablesResult : ((tablesResult as any).rows || []);
for (const row of tableRows) {
const tableName = row.table_name;
// 동적 SQL 생성 (테이블명과 컬럼명은 파라미터 바인딩 불가)
// SQL 인젝션 방지: 테이블명과 컬럼명은 information_schema에서 검증된 값
const countQuery = `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${columnName}" = $1 AND company_code = $2`;
try {
const countResult = await pool.query(countQuery, [oldValue, companyCode]);
2025-11-07 10:18:34 +09:00
const rows = (countResult as any).rows || [];
const count = rows.length > 0 ? parseInt(rows[0].count) : 0;
if (count > 0) {
preview.push({
tableName,
affectedRows: count,
});
}
} catch (error: any) {
logger.warn(`테이블 ${tableName} 조회 실패:`, error.message);
// 테이블 접근 실패 시 건너뛰기
continue;
}
}
const totalRows = preview.reduce((sum, item) => sum + item.affectedRows, 0);
logger.info("코드 병합 미리보기 완료", {
tablesCount: preview.length,
totalRows,
});
res.json({
success: true,
message: "코드 병합 미리보기 완료",
data: {
columnName,
oldValue,
preview,
totalAffectedRows: totalRows,
},
});
} catch (error: any) {
logger.error("코드 병합 미리보기 실패:", error);
res.status(500).json({
success: false,
message: "코드 병합 미리보기 중 오류가 발생했습니다.",
error: {
code: "PREVIEW_ERROR",
details: error.message,
},
});
}
}