fix: 필터 select 옵션에서 카테고리/엔티티 라벨이 올바르게 표시되도록 수정
- 백엔드: entityJoinService에서 _label 필드를 SELECT에 추가 - 백엔드: tableManagementService에 멀티테넌시 필터링 추가 (company_code) - 백엔드: categorizeJoins에서 table_column_category_values를 명시적으로 dbJoins로 분류 - 백엔드: executeCachedLookup와 getTableData에 companyCode 파라미터 추가 - 프론트엔드: getColumnUniqueValues가 백엔드 조인 결과의 _name 필드를 사용하도록 수정 - 프론트엔드: TableSearchWidget에서 select 옵션 로드 로직 개선 이제 필터 select 박스에서 코드 대신 실제 이름(라벨)이 표시됩니다. 예: CATEGORY_148700 → 정상, topseal_admin → 탑씰 관리자 계정
This commit is contained in:
@@ -24,20 +24,19 @@ export class EntityJoinService {
|
||||
try {
|
||||
logger.info(`Entity 컬럼 감지 시작: ${tableName}`);
|
||||
|
||||
// column_labels에서 entity 타입인 컬럼들 조회
|
||||
// column_labels에서 entity 및 category 타입인 컬럼들 조회 (input_type 사용)
|
||||
const entityColumns = await query<{
|
||||
column_name: string;
|
||||
input_type: string;
|
||||
reference_table: string;
|
||||
reference_column: string;
|
||||
display_column: string | null;
|
||||
}>(
|
||||
`SELECT column_name, reference_table, reference_column, display_column
|
||||
`SELECT column_name, input_type, reference_table, reference_column, display_column
|
||||
FROM column_labels
|
||||
WHERE table_name = $1
|
||||
AND web_type = $2
|
||||
AND reference_table IS NOT NULL
|
||||
AND reference_column IS NOT NULL`,
|
||||
[tableName, "entity"]
|
||||
AND input_type IN ('entity', 'category')`,
|
||||
[tableName]
|
||||
);
|
||||
|
||||
logger.info(`🔍 Entity 컬럼 조회 결과: ${entityColumns.length}개 발견`);
|
||||
@@ -77,18 +76,34 @@ export class EntityJoinService {
|
||||
}
|
||||
|
||||
for (const column of entityColumns) {
|
||||
// 카테고리 타입인 경우 자동으로 category_values 테이블 참조 설정
|
||||
let referenceTable = column.reference_table;
|
||||
let referenceColumn = column.reference_column;
|
||||
let displayColumn = column.display_column;
|
||||
|
||||
if (column.input_type === 'category') {
|
||||
// 카테고리 타입: reference 정보가 비어있어도 자동 설정
|
||||
referenceTable = referenceTable || 'table_column_category_values';
|
||||
referenceColumn = referenceColumn || 'value_code';
|
||||
displayColumn = displayColumn || 'value_label';
|
||||
|
||||
logger.info(`🏷️ 카테고리 타입 자동 설정: ${column.column_name}`, {
|
||||
referenceTable,
|
||||
referenceColumn,
|
||||
displayColumn,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`🔍 Entity 컬럼 상세 정보:`, {
|
||||
column_name: column.column_name,
|
||||
reference_table: column.reference_table,
|
||||
reference_column: column.reference_column,
|
||||
display_column: column.display_column,
|
||||
input_type: column.input_type,
|
||||
reference_table: referenceTable,
|
||||
reference_column: referenceColumn,
|
||||
display_column: displayColumn,
|
||||
});
|
||||
|
||||
if (
|
||||
!column.column_name ||
|
||||
!column.reference_table ||
|
||||
!column.reference_column
|
||||
) {
|
||||
if (!column.column_name || !referenceTable || !referenceColumn) {
|
||||
logger.warn(`⚠️ 필수 정보 누락으로 스킵: ${column.column_name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -112,27 +127,28 @@ export class EntityJoinService {
|
||||
separator,
|
||||
screenConfig,
|
||||
});
|
||||
} else if (column.display_column && column.display_column !== "none") {
|
||||
} else if (displayColumn && displayColumn !== "none") {
|
||||
// 기존 설정된 단일 표시 컬럼 사용 (none이 아닌 경우만)
|
||||
displayColumns = [column.display_column];
|
||||
displayColumns = [displayColumn];
|
||||
logger.info(
|
||||
`🔧 기존 display_column 사용: ${column.column_name} → ${column.display_column}`
|
||||
`🔧 기존 display_column 사용: ${column.column_name} → ${displayColumn}`
|
||||
);
|
||||
} else {
|
||||
// display_column이 "none"이거나 없는 경우 기본 표시 컬럼 설정
|
||||
// 🚨 display_column이 항상 "none"이므로 이 로직을 기본으로 사용
|
||||
let defaultDisplayColumn = column.reference_column;
|
||||
if (column.reference_table === "dept_info") {
|
||||
let defaultDisplayColumn = referenceColumn;
|
||||
if (referenceTable === "dept_info") {
|
||||
defaultDisplayColumn = "dept_name";
|
||||
} else if (column.reference_table === "company_info") {
|
||||
} else if (referenceTable === "company_info") {
|
||||
defaultDisplayColumn = "company_name";
|
||||
} else if (column.reference_table === "user_info") {
|
||||
} else if (referenceTable === "user_info") {
|
||||
defaultDisplayColumn = "user_name";
|
||||
} else if (referenceTable === "category_values") {
|
||||
defaultDisplayColumn = "category_name";
|
||||
}
|
||||
|
||||
displayColumns = [defaultDisplayColumn];
|
||||
logger.info(
|
||||
`🔧 Entity 조인 기본 표시 컬럼 설정: ${column.column_name} → ${defaultDisplayColumn} (${column.reference_table})`
|
||||
`🔧 Entity 조인 기본 표시 컬럼 설정: ${column.column_name} → ${defaultDisplayColumn} (${referenceTable})`
|
||||
);
|
||||
logger.info(`🔍 생성된 displayColumns 배열:`, displayColumns);
|
||||
}
|
||||
@@ -143,8 +159,8 @@ export class EntityJoinService {
|
||||
const joinConfig: EntityJoinConfig = {
|
||||
sourceTable: tableName,
|
||||
sourceColumn: column.column_name,
|
||||
referenceTable: column.reference_table,
|
||||
referenceColumn: column.reference_column,
|
||||
referenceTable: referenceTable, // 카테고리의 경우 자동 설정된 값 사용
|
||||
referenceColumn: referenceColumn, // 카테고리의 경우 자동 설정된 값 사용
|
||||
displayColumns: displayColumns,
|
||||
displayColumn: displayColumns[0], // 하위 호환성
|
||||
aliasColumn: aliasColumn,
|
||||
@@ -245,11 +261,14 @@ export class EntityJoinService {
|
||||
config.displayColumn,
|
||||
];
|
||||
const separator = config.separator || " - ";
|
||||
|
||||
// 결과 컬럼 배열 (aliasColumn + _label 필드)
|
||||
const resultColumns: string[] = [];
|
||||
|
||||
if (displayColumns.length === 0 || !displayColumns[0]) {
|
||||
// displayColumns가 빈 배열이거나 첫 번째 값이 null/undefined인 경우
|
||||
// 조인 테이블의 referenceColumn을 기본값으로 사용
|
||||
return `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.aliasColumn}`;
|
||||
resultColumns.push(`COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.aliasColumn}`);
|
||||
} else if (displayColumns.length === 1) {
|
||||
// 단일 컬럼인 경우
|
||||
const col = displayColumns[0];
|
||||
@@ -265,12 +284,18 @@ export class EntityJoinService {
|
||||
"company_name",
|
||||
"sales_yn",
|
||||
"status",
|
||||
"value_label", // table_column_category_values
|
||||
"user_name", // user_info
|
||||
].includes(col);
|
||||
|
||||
if (isJoinTableColumn) {
|
||||
return `COALESCE(${alias}.${col}::TEXT, '') AS ${config.aliasColumn}`;
|
||||
resultColumns.push(`COALESCE(${alias}.${col}::TEXT, '') AS ${config.aliasColumn}`);
|
||||
|
||||
// _label 필드도 함께 SELECT (프론트엔드 getColumnUniqueValues용)
|
||||
// sourceColumn_label 형식으로 추가
|
||||
resultColumns.push(`COALESCE(${alias}.${col}::TEXT, '') AS ${config.sourceColumn}_label`);
|
||||
} else {
|
||||
return `COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}`;
|
||||
resultColumns.push(`COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}`);
|
||||
}
|
||||
} else {
|
||||
// 여러 컬럼인 경우 CONCAT으로 연결
|
||||
@@ -291,6 +316,8 @@ export class EntityJoinService {
|
||||
"company_name",
|
||||
"sales_yn",
|
||||
"status",
|
||||
"value_label", // table_column_category_values
|
||||
"user_name", // user_info
|
||||
].includes(col);
|
||||
|
||||
if (isJoinTableColumn) {
|
||||
@@ -303,8 +330,11 @@ export class EntityJoinService {
|
||||
})
|
||||
.join(` || '${separator}' || `);
|
||||
|
||||
return `(${concatParts}) AS ${config.aliasColumn}`;
|
||||
resultColumns.push(`(${concatParts}) AS ${config.aliasColumn}`);
|
||||
}
|
||||
|
||||
// 모든 resultColumns를 반환
|
||||
return resultColumns.join(", ");
|
||||
})
|
||||
.join(", ");
|
||||
|
||||
@@ -320,6 +350,12 @@ export class EntityJoinService {
|
||||
const joinClauses = uniqueReferenceTableConfigs
|
||||
.map((config) => {
|
||||
const alias = aliasMap.get(config.referenceTable);
|
||||
|
||||
// table_column_category_values는 특별한 조인 조건 필요
|
||||
if (config.referenceTable === 'table_column_category_values') {
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn} AND ${alias}.table_name = '${tableName}' AND ${alias}.column_name = '${config.sourceColumn}'`;
|
||||
}
|
||||
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn}`;
|
||||
})
|
||||
.join("\n");
|
||||
@@ -380,6 +416,14 @@ export class EntityJoinService {
|
||||
return "join";
|
||||
}
|
||||
|
||||
// table_column_category_values는 특수 조인 조건이 필요하므로 캐시 불가
|
||||
if (config.referenceTable === 'table_column_category_values') {
|
||||
logger.info(
|
||||
`🎯 table_column_category_values는 캐시 전략 불가: ${config.sourceColumn}`
|
||||
);
|
||||
return "join";
|
||||
}
|
||||
|
||||
// 참조 테이블의 캐시 가능성 확인
|
||||
const displayCol =
|
||||
config.displayColumn ||
|
||||
|
||||
@@ -1494,6 +1494,7 @@ export class TableManagementService {
|
||||
search?: Record<string, any>;
|
||||
sortBy?: string;
|
||||
sortOrder?: string;
|
||||
companyCode?: string;
|
||||
}
|
||||
): Promise<{
|
||||
data: any[];
|
||||
@@ -1503,7 +1504,7 @@ export class TableManagementService {
|
||||
totalPages: number;
|
||||
}> {
|
||||
try {
|
||||
const { page, size, search = {}, sortBy, sortOrder = "asc" } = options;
|
||||
const { page, size, search = {}, sortBy, sortOrder = "asc", companyCode } = options;
|
||||
const offset = (page - 1) * size;
|
||||
|
||||
logger.info(`테이블 데이터 조회: ${tableName}`, options);
|
||||
@@ -1517,6 +1518,14 @@ export class TableManagementService {
|
||||
let searchValues: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// 멀티테넌시 필터 추가 (company_code)
|
||||
if (companyCode) {
|
||||
whereConditions.push(`company_code = $${paramIndex}`);
|
||||
searchValues.push(companyCode);
|
||||
paramIndex++;
|
||||
logger.info(`🔒 멀티테넌시 필터 추가 (기본 조회): company_code = ${companyCode}`);
|
||||
}
|
||||
|
||||
if (search && Object.keys(search).length > 0) {
|
||||
for (const [column, value] of Object.entries(search)) {
|
||||
if (value !== null && value !== undefined && value !== "") {
|
||||
@@ -2213,11 +2222,20 @@ export class TableManagementService {
|
||||
const selectColumns = columns.data.map((col: any) => col.column_name);
|
||||
|
||||
// WHERE 절 구성
|
||||
const whereClause = await this.buildWhereClause(
|
||||
let whereClause = await this.buildWhereClause(
|
||||
tableName,
|
||||
options.search
|
||||
);
|
||||
|
||||
// 멀티테넌시 필터 추가 (company_code)
|
||||
if (options.companyCode) {
|
||||
const companyFilter = `main.company_code = '${options.companyCode.replace(/'/g, "''")}'`;
|
||||
whereClause = whereClause
|
||||
? `${whereClause} AND ${companyFilter}`
|
||||
: companyFilter;
|
||||
logger.info(`🔒 멀티테넌시 필터 추가 (Entity 조인): company_code = ${options.companyCode}`);
|
||||
}
|
||||
|
||||
// ORDER BY 절 구성
|
||||
const orderBy = options.sortBy
|
||||
? `main.${options.sortBy} ${options.sortOrder === "desc" ? "DESC" : "ASC"}`
|
||||
@@ -2343,6 +2361,7 @@ export class TableManagementService {
|
||||
search?: Record<string, any>;
|
||||
sortBy?: string;
|
||||
sortOrder?: string;
|
||||
companyCode?: string;
|
||||
},
|
||||
startTime: number
|
||||
): Promise<EntityJoinResponse> {
|
||||
@@ -2530,11 +2549,11 @@ export class TableManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
basicResult = await this.getTableData(tableName, fallbackOptions);
|
||||
basicResult = await this.getTableData(tableName, { ...fallbackOptions, companyCode: options.companyCode });
|
||||
}
|
||||
} else {
|
||||
// Entity 조인 컬럼 검색이 없는 경우 기존 캐시 방식 사용
|
||||
basicResult = await this.getTableData(tableName, options);
|
||||
basicResult = await this.getTableData(tableName, { ...options, companyCode: options.companyCode });
|
||||
}
|
||||
|
||||
// Entity 값들을 캐시에서 룩업하여 변환
|
||||
@@ -2807,10 +2826,14 @@ export class TableManagementService {
|
||||
}
|
||||
// 모든 조인이 캐시 가능한 경우: 기본 쿼리 + 캐시 룩업
|
||||
else {
|
||||
// whereClause에서 company_code 추출 (멀티테넌시 필터)
|
||||
const companyCodeMatch = whereClause.match(/main\.company_code\s*=\s*'([^']+)'/);
|
||||
const companyCode = companyCodeMatch ? companyCodeMatch[1] : undefined;
|
||||
|
||||
return await this.executeCachedLookup(
|
||||
tableName,
|
||||
cacheableJoins,
|
||||
{ page: Math.floor(offset / limit) + 1, size: limit, search: {} },
|
||||
{ page: Math.floor(offset / limit) + 1, size: limit, search: {}, companyCode },
|
||||
startTime
|
||||
);
|
||||
}
|
||||
@@ -2831,6 +2854,13 @@ export class TableManagementService {
|
||||
const dbJoins: EntityJoinConfig[] = [];
|
||||
|
||||
for (const config of joinConfigs) {
|
||||
// table_column_category_values는 특수 조인 조건이 필요하므로 항상 DB 조인
|
||||
if (config.referenceTable === 'table_column_category_values') {
|
||||
dbJoins.push(config);
|
||||
console.log(`🔗 DB 조인 (특수 조건): ${config.referenceTable}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 캐시 가능성 확인
|
||||
const cachedData = await referenceCacheService.getCachedReference(
|
||||
config.referenceTable,
|
||||
|
||||
Reference in New Issue
Block a user