feat: 카테고리 컬럼 메뉴별 매핑 기능 구현
- category_column_mapping 테이블 생성 (마이그레이션 054) - 테이블 타입 관리에서 2레벨 메뉴 선택 기능 추가 - 카테고리 컬럼 조회 시 현재 메뉴 및 상위 메뉴 매핑 자동 조회 - 캐시 무효화 로직 개선 - 메뉴별 카테고리 설정 저장 및 불러오기 기능 구현
This commit is contained in:
@@ -249,21 +249,78 @@ export class TableManagementService {
|
||||
[tableName, size, offset]
|
||||
);
|
||||
|
||||
// 🆕 category_column_mapping 조회
|
||||
const tableExistsResult = await query<any>(
|
||||
`SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'category_column_mapping'
|
||||
) as table_exists`
|
||||
);
|
||||
const mappingTableExists = tableExistsResult[0]?.table_exists === true;
|
||||
|
||||
let categoryMappings: Map<string, number[]> = new Map();
|
||||
if (mappingTableExists && companyCode) {
|
||||
logger.info("📥 getColumnList: 카테고리 매핑 조회 시작", { tableName, companyCode });
|
||||
|
||||
const mappings = await query<any>(
|
||||
`SELECT
|
||||
logical_column_name as "columnName",
|
||||
menu_objid as "menuObjid"
|
||||
FROM category_column_mapping
|
||||
WHERE table_name = $1
|
||||
AND company_code = $2`,
|
||||
[tableName, companyCode]
|
||||
);
|
||||
|
||||
logger.info("✅ getColumnList: 카테고리 매핑 조회 완료", {
|
||||
tableName,
|
||||
companyCode,
|
||||
mappingCount: mappings.length,
|
||||
mappings: mappings
|
||||
});
|
||||
|
||||
mappings.forEach((m: any) => {
|
||||
if (!categoryMappings.has(m.columnName)) {
|
||||
categoryMappings.set(m.columnName, []);
|
||||
}
|
||||
categoryMappings.get(m.columnName)!.push(Number(m.menuObjid));
|
||||
});
|
||||
|
||||
logger.info("✅ getColumnList: categoryMappings Map 생성 완료", {
|
||||
size: categoryMappings.size,
|
||||
entries: Array.from(categoryMappings.entries())
|
||||
});
|
||||
}
|
||||
|
||||
// BigInt를 Number로 변환하여 JSON 직렬화 문제 해결
|
||||
const columns: ColumnTypeInfo[] = rawColumns.map((column) => ({
|
||||
...column,
|
||||
maxLength: column.maxLength ? Number(column.maxLength) : null,
|
||||
numericPrecision: column.numericPrecision
|
||||
? Number(column.numericPrecision)
|
||||
: null,
|
||||
numericScale: column.numericScale ? Number(column.numericScale) : null,
|
||||
displayOrder: column.displayOrder ? Number(column.displayOrder) : null,
|
||||
// 자동 매핑: webType이 기본값('text')인 경우 DB 타입에 따라 자동 추론
|
||||
webType:
|
||||
column.webType === "text"
|
||||
? this.inferWebType(column.dataType)
|
||||
: column.webType,
|
||||
}));
|
||||
const columns: ColumnTypeInfo[] = rawColumns.map((column) => {
|
||||
const baseColumn = {
|
||||
...column,
|
||||
maxLength: column.maxLength ? Number(column.maxLength) : null,
|
||||
numericPrecision: column.numericPrecision
|
||||
? Number(column.numericPrecision)
|
||||
: null,
|
||||
numericScale: column.numericScale ? Number(column.numericScale) : null,
|
||||
displayOrder: column.displayOrder ? Number(column.displayOrder) : null,
|
||||
// 자동 매핑: webType이 기본값('text')인 경우 DB 타입에 따라 자동 추론
|
||||
webType:
|
||||
column.webType === "text"
|
||||
? this.inferWebType(column.dataType)
|
||||
: column.webType,
|
||||
};
|
||||
|
||||
// 카테고리 타입인 경우 categoryMenus 추가
|
||||
if (column.inputType === "category" && categoryMappings.has(column.columnName)) {
|
||||
const menus = categoryMappings.get(column.columnName);
|
||||
logger.info(`✅ getColumnList: 컬럼 ${column.columnName}에 카테고리 메뉴 추가`, { menus });
|
||||
return {
|
||||
...baseColumn,
|
||||
categoryMenus: menus,
|
||||
};
|
||||
}
|
||||
|
||||
return baseColumn;
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(total / size);
|
||||
|
||||
@@ -429,7 +486,7 @@ export class TableManagementService {
|
||||
// 캐시 무효화 - 해당 테이블의 컬럼 캐시 삭제
|
||||
cache.deleteByPattern(`table_columns:${tableName}:`);
|
||||
cache.delete(CacheKeys.TABLE_COLUMN_COUNT(tableName));
|
||||
|
||||
|
||||
logger.info(`컬럼 설정 업데이트 완료: ${tableName}.${columnName}`);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
@@ -484,7 +541,7 @@ export class TableManagementService {
|
||||
cache.deleteByPattern(`table_columns:${tableName}:`);
|
||||
cache.delete(CacheKeys.TABLE_COLUMN_COUNT(tableName));
|
||||
|
||||
logger.info(`전체 컬럼 설정 일괄 업데이트 완료: ${tableName}, company: ${companyCode}`);
|
||||
logger.info(`전체 컬럼 설정 일괄 업데이트 완료: ${tableName}`);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`전체 컬럼 설정 일괄 업데이트 중 오류 발생: ${tableName}`,
|
||||
@@ -3152,19 +3209,83 @@ export class TableManagementService {
|
||||
[tableName, companyCode]
|
||||
);
|
||||
|
||||
const inputTypes: ColumnTypeInfo[] = rawInputTypes.map((col) => ({
|
||||
tableName: tableName,
|
||||
columnName: col.columnName,
|
||||
displayName: col.displayName,
|
||||
dataType: col.dataType || "varchar",
|
||||
inputType: col.inputType,
|
||||
detailSettings: col.detailSettings,
|
||||
description: "", // 필수 필드 추가
|
||||
isNullable: col.isNullable === "Y" ? "Y" : "N", // 🔥 FIX: string 타입으로 변환
|
||||
isPrimaryKey: false,
|
||||
displayOrder: 0,
|
||||
isVisible: true,
|
||||
}));
|
||||
// category_column_mapping 테이블 존재 여부 확인
|
||||
const tableExistsResult = await query<any>(
|
||||
`SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'category_column_mapping'
|
||||
) as table_exists`
|
||||
);
|
||||
const mappingTableExists = tableExistsResult[0]?.table_exists === true;
|
||||
|
||||
// 카테고리 컬럼의 경우, 매핑된 메뉴 목록 조회
|
||||
let categoryMappings: Map<string, number[]> = new Map();
|
||||
if (mappingTableExists) {
|
||||
logger.info("카테고리 매핑 조회 시작", { tableName, companyCode });
|
||||
|
||||
const mappings = await query<any>(
|
||||
`SELECT
|
||||
logical_column_name as "columnName",
|
||||
menu_objid as "menuObjid"
|
||||
FROM category_column_mapping
|
||||
WHERE table_name = $1
|
||||
AND company_code = $2`,
|
||||
[tableName, companyCode]
|
||||
);
|
||||
|
||||
logger.info("카테고리 매핑 조회 완료", {
|
||||
tableName,
|
||||
companyCode,
|
||||
mappingCount: mappings.length,
|
||||
mappings: mappings
|
||||
});
|
||||
|
||||
mappings.forEach((m: any) => {
|
||||
if (!categoryMappings.has(m.columnName)) {
|
||||
categoryMappings.set(m.columnName, []);
|
||||
}
|
||||
categoryMappings.get(m.columnName)!.push(Number(m.menuObjid));
|
||||
});
|
||||
|
||||
logger.info("categoryMappings Map 생성 완료", {
|
||||
size: categoryMappings.size,
|
||||
entries: Array.from(categoryMappings.entries())
|
||||
});
|
||||
} else {
|
||||
logger.warn("category_column_mapping 테이블이 존재하지 않음");
|
||||
}
|
||||
|
||||
const inputTypes: ColumnTypeInfo[] = rawInputTypes.map((col) => {
|
||||
const baseInfo = {
|
||||
tableName: tableName,
|
||||
columnName: col.columnName,
|
||||
displayName: col.displayName,
|
||||
dataType: col.dataType || "varchar",
|
||||
inputType: col.inputType,
|
||||
detailSettings: col.detailSettings,
|
||||
description: "", // 필수 필드 추가
|
||||
isNullable: col.isNullable === "Y" ? "Y" : "N", // 🔥 FIX: string 타입으로 변환
|
||||
isPrimaryKey: false,
|
||||
displayOrder: 0,
|
||||
isVisible: true,
|
||||
};
|
||||
|
||||
// 카테고리 타입인 경우 categoryMenus 추가
|
||||
if (col.inputType === "category" && categoryMappings.has(col.columnName)) {
|
||||
const menus = categoryMappings.get(col.columnName);
|
||||
logger.info(`✅ 컬럼 ${col.columnName}에 카테고리 메뉴 추가`, { menus });
|
||||
return {
|
||||
...baseInfo,
|
||||
categoryMenus: menus,
|
||||
};
|
||||
}
|
||||
|
||||
if (col.inputType === "category") {
|
||||
logger.warn(`⚠️ 카테고리 컬럼 ${col.columnName}에 매핑 없음`);
|
||||
}
|
||||
|
||||
return baseInfo;
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`컬럼 입력타입 정보 조회 완료: ${tableName}, company: ${companyCode}, ${inputTypes.length}개 컬럼`
|
||||
|
||||
Reference in New Issue
Block a user