Merge branch 'feature/v2-unified-renewal' of http://39.117.244.52:3000/kjs/ERP-node into feature/v2-renewal

; Please enter a commit message to explain why this merge is necessary,
; especially if it merges an updated upstream into a topic branch.
;
; Lines starting with ';' will be ignored, and an empty message aborts
; the commit.
This commit is contained in:
DDD1542
2026-02-05 14:08:38 +09:00
23 changed files with 3340 additions and 63 deletions

View File

@@ -1261,5 +1261,56 @@ export const setRepresentativeFile = async (
}
};
/**
* 파일 정보 조회 (메타데이터만, 파일 내용 없음)
* 공개 접근 허용
*/
export const getFileInfo = async (req: Request, res: Response) => {
try {
const { objid } = req.params;
if (!objid) {
return res.status(400).json({
success: false,
message: "파일 ID가 필요합니다.",
});
}
// 파일 정보 조회
const fileRecord = await queryOne<any>(
`SELECT objid, real_file_name, file_size, file_ext, file_path, regdate, is_representative
FROM attach_file_info
WHERE objid = $1 AND status = 'ACTIVE'`,
[parseInt(objid)]
);
if (!fileRecord) {
return res.status(404).json({
success: false,
message: "파일을 찾을 수 없습니다.",
});
}
res.json({
success: true,
data: {
objid: fileRecord.objid.toString(),
realFileName: fileRecord.real_file_name,
fileSize: fileRecord.file_size,
fileExt: fileRecord.file_ext,
filePath: fileRecord.file_path,
regdate: fileRecord.regdate,
isRepresentative: fileRecord.is_representative,
},
});
} catch (error) {
console.error("파일 정보 조회 오류:", error);
res.status(500).json({
success: false,
message: "파일 정보 조회 중 오류가 발생했습니다.",
});
}
};
// Multer 미들웨어 export
export const uploadMiddleware = upload.array("files", 10); // 최대 10개 파일

View File

@@ -2344,6 +2344,8 @@ export async function getTableEntityRelations(
*
* table_type_columns에서 reference_table이 현재 테이블인 레코드를 찾아서
* 해당 테이블과 FK 컬럼 정보를 반환합니다.
*
* 우선순위: 현재 사용자의 company_code > 공통('*')
*/
export async function getReferencedByTables(
req: AuthenticatedRequest,
@@ -2351,9 +2353,11 @@ export async function getReferencedByTables(
): Promise<void> {
try {
const { tableName } = req.params;
// 현재 사용자의 회사 코드 (없으면 '*' 사용)
const userCompanyCode = req.user?.companyCode || "*";
logger.info(
`=== 테이블 참조 관계 조회 시작: ${tableName} 을 참조하는 테이블 ===`
`=== 테이블 참조 관계 조회 시작: ${tableName} 을 참조하는 테이블 (회사코드: ${userCompanyCode}) ===`
);
if (!tableName) {
@@ -2371,23 +2375,41 @@ export async function getReferencedByTables(
// table_type_columns에서 reference_table이 현재 테이블인 레코드 조회
// input_type이 'entity'인 것만 조회 (실제 FK 관계)
// 우선순위: 현재 사용자의 company_code > 공통('*')
// ROW_NUMBER를 사용해서 같은 테이블/컬럼 조합에서 회사코드 우선순위로 하나만 선택
const sqlQuery = `
WITH ranked AS (
SELECT
ttc.table_name,
ttc.column_name,
ttc.column_label,
ttc.reference_table,
ttc.reference_column,
ttc.display_column,
ttc.company_code,
ROW_NUMBER() OVER (
PARTITION BY ttc.table_name, ttc.column_name
ORDER BY CASE WHEN ttc.company_code = $2 THEN 1 ELSE 2 END
) as rn
FROM table_type_columns ttc
WHERE ttc.reference_table = $1
AND ttc.input_type = 'entity'
AND ttc.company_code IN ($2, '*')
)
SELECT DISTINCT
ttc.table_name,
ttc.column_name,
ttc.column_label,
ttc.reference_table,
ttc.reference_column,
ttc.display_column,
ttc.table_name as table_label
FROM table_type_columns ttc
WHERE ttc.reference_table = $1
AND ttc.input_type = 'entity'
AND ttc.company_code = '*'
ORDER BY ttc.table_name, ttc.column_name
table_name,
column_name,
column_label,
reference_table,
reference_column,
display_column,
table_name as table_label
FROM ranked
WHERE rn = 1
ORDER BY table_name, column_name
`;
const result = await query(sqlQuery, [tableName]);
const result = await query(sqlQuery, [tableName, userCompanyCode]);
const referencedByTables = result.map((row: any) => ({
tableName: row.table_name,
@@ -2400,7 +2422,7 @@ export async function getReferencedByTables(
}));
logger.info(
`테이블 참조 관계 조회 완료: ${referencedByTables.length}개 발견`
`테이블 참조 관계 조회 완료: ${referencedByTables.length}개 발견 (회사코드: ${userCompanyCode})`
);
const response: ApiResponse<any> = {

View File

@@ -11,6 +11,7 @@ import {
generateTempToken,
getFileByToken,
setRepresentativeFile,
getFileInfo,
} from "../controllers/fileController";
import { authenticateToken } from "../middleware/authMiddleware";
@@ -31,6 +32,13 @@ router.get("/public/:token", getFileByToken);
*/
router.get("/preview/:objid", previewFile);
/**
* @route GET /api/files/info/:objid
* @desc 파일 정보 조회 (메타데이터만, 파일 내용 없음) - 공개 접근 허용
* @access Public
*/
router.get("/info/:objid", getFileInfo);
// 모든 파일 API는 인증 필요
router.use(authenticateToken);