Merge branch 'feature/screen-management' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management

This commit is contained in:
kjs
2025-11-17 15:59:43 +09:00
5 changed files with 184 additions and 51 deletions

View File

@@ -187,6 +187,16 @@ export const deleteCategoryValue = async (req: AuthenticatedRequest, res: Respon
});
} catch (error: any) {
logger.error(`카테고리 값 삭제 실패: ${error.message}`);
// 사용 중인 경우 상세 에러 메시지 반환 (400)
if (error.message.includes("삭제할 수 없습니다")) {
return res.status(400).json({
success: false,
message: error.message,
});
}
// 기타 에러 (500)
return res.status(500).json({
success: false,
message: error.message || "카테고리 값 삭제 중 오류가 발생했습니다",

View File

@@ -445,7 +445,129 @@ class TableCategoryValueService {
}
/**
* 카테고리 값 삭제 (비활성화)
* 카테고리 값 사용 여부 확인
* 실제 데이터 테이블에서 해당 카테고리 값이 사용되고 있는지 확인
*/
async checkCategoryValueUsage(
valueId: number,
companyCode: string
): Promise<{ isUsed: boolean; usedInTables: any[]; totalCount: number }> {
const pool = getPool();
try {
logger.info("카테고리 값 사용 여부 확인", { valueId, companyCode });
// 1. 카테고리 값 정보 조회
let valueQuery: string;
let valueParams: any[];
if (companyCode === "*") {
valueQuery = `
SELECT table_name, column_name, value_code
FROM table_column_category_values
WHERE value_id = $1
`;
valueParams = [valueId];
} else {
valueQuery = `
SELECT table_name, column_name, value_code
FROM table_column_category_values
WHERE value_id = $1
AND company_code = $2
`;
valueParams = [valueId, companyCode];
}
const valueResult = await pool.query(valueQuery, valueParams);
if (valueResult.rowCount === 0) {
throw new Error("카테고리 값을 찾을 수 없습니다");
}
const { table_name, column_name, value_code } = valueResult.rows[0];
// 2. 실제 데이터 테이블에서 사용 여부 확인
// 테이블이 존재하는지 먼저 확인
const tableExistsQuery = `
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = $1
) as exists
`;
const tableExistsResult = await pool.query(tableExistsQuery, [table_name]);
if (!tableExistsResult.rows[0].exists) {
logger.info("테이블이 존재하지 않음", { table_name });
return { isUsed: false, usedInTables: [], totalCount: 0 };
}
// 3. 해당 테이블에서 value_code를 사용하는 데이터 개수 확인
let dataCountQuery: string;
let dataCountParams: any[];
if (companyCode === "*") {
dataCountQuery = `
SELECT COUNT(*) as count
FROM ${table_name}
WHERE ${column_name} = $1
`;
dataCountParams = [value_code];
} else {
dataCountQuery = `
SELECT COUNT(*) as count
FROM ${table_name}
WHERE ${column_name} = $1
AND company_code = $2
`;
dataCountParams = [value_code, companyCode];
}
const dataCountResult = await pool.query(dataCountQuery, dataCountParams);
const totalCount = parseInt(dataCountResult.rows[0].count);
const isUsed = totalCount > 0;
// 4. 사용 중인 메뉴 목록 조회 (해당 테이블을 사용하는 화면/메뉴)
const menuQuery = `
SELECT DISTINCT
mi.objid as menu_objid,
mi.menu_name_kor as menu_name,
mi.menu_url
FROM menu_info mi
INNER JOIN screen_menu_assignments sma ON sma.menu_objid = mi.objid
INNER JOIN screen_definitions sd ON sd.screen_id = sma.screen_id
WHERE sd.table_name = $1
AND mi.company_code = $2
ORDER BY mi.menu_name_kor
`;
const menuResult = await pool.query(menuQuery, [table_name, companyCode]);
const usedInTables = menuResult.rows.map((row) => ({
menuObjid: row.menu_objid,
menuName: row.menu_name,
menuUrl: row.menu_url,
tableName: table_name,
columnName: column_name,
}));
logger.info("카테고리 값 사용 여부 확인 완료", {
valueId,
isUsed,
totalCount,
usedInMenusCount: usedInTables.length,
});
return { isUsed, usedInTables, totalCount };
} catch (error: any) {
logger.error(`카테고리 값 사용 여부 확인 실패: ${error.message}`);
throw error;
}
}
/**
* 카테고리 값 삭제 (물리적 삭제)
*/
async deleteCategoryValue(
valueId: number,
@@ -455,7 +577,24 @@ class TableCategoryValueService {
const pool = getPool();
try {
// 하위 값 체크 (멀티테넌시 적용)
// 1. 사용 여부 확인
const usage = await this.checkCategoryValueUsage(valueId, companyCode);
if (usage.isUsed) {
let errorMessage = "이 카테고리 값을 삭제할 수 없습니다.\n";
errorMessage += `\n현재 ${usage.totalCount}개의 데이터에서 사용 중입니다.`;
if (usage.usedInTables.length > 0) {
const menuNames = usage.usedInTables.map((t) => t.menuName).join(", ");
errorMessage += `\n\n다음 메뉴에서 사용 중입니다:\n${menuNames}`;
}
errorMessage += "\n\n메뉴에서 사용하는 카테고리 항목을 수정한 후 다시 삭제해주세요.";
throw new Error(errorMessage);
}
// 2. 하위 값 체크 (멀티테넌시 적용)
let checkQuery: string;
let checkParams: any[];
@@ -465,7 +604,6 @@ class TableCategoryValueService {
SELECT COUNT(*) as count
FROM table_column_category_values
WHERE parent_value_id = $1
AND is_active = true
`;
checkParams = [valueId];
} else {
@@ -475,7 +613,6 @@ class TableCategoryValueService {
FROM table_column_category_values
WHERE parent_value_id = $1
AND company_code = $2
AND is_active = true
`;
checkParams = [valueId, companyCode];
}
@@ -486,27 +623,25 @@ class TableCategoryValueService {
throw new Error("하위 카테고리 값이 있어 삭제할 수 없습니다");
}
// 비활성화 (멀티테넌시 적용)
// 3. 물리적 삭제 (멀티테넌시 적용)
let deleteQuery: string;
let deleteParams: any[];
if (companyCode === "*") {
// 최고 관리자: 모든 카테고리 값 삭제 가능
deleteQuery = `
UPDATE table_column_category_values
SET is_active = false, updated_at = NOW(), updated_by = $2
DELETE FROM table_column_category_values
WHERE value_id = $1
`;
deleteParams = [valueId, userId];
deleteParams = [valueId];
} else {
// 일반 회사: 자신의 카테고리 값만 삭제 가능
deleteQuery = `
UPDATE table_column_category_values
SET is_active = false, updated_at = NOW(), updated_by = $3
DELETE FROM table_column_category_values
WHERE value_id = $1
AND company_code = $2
`;
deleteParams = [valueId, companyCode, userId];
deleteParams = [valueId, companyCode];
}
const result = await pool.query(deleteQuery, deleteParams);
@@ -515,7 +650,7 @@ class TableCategoryValueService {
throw new Error("카테고리 값을 찾을 수 없거나 권한이 없습니다");
}
logger.info("카테고리 값 삭제(비활성화) 완료", {
logger.info("카테고리 값 삭제 완료", {
valueId,
companyCode,
});