조인컬럼수정(조인 컬럼 추가시 엔티티 타입 표시 오류)

This commit is contained in:
kjs 2025-09-24 14:31:46 +09:00
parent 86dc961968
commit 649ed5c6d7
4 changed files with 238 additions and 56 deletions

View File

@ -42,9 +42,23 @@ export class EntityJoinService {
},
});
logger.info(`🔍 Entity 컬럼 조회 결과: ${entityColumns.length}개 발견`);
entityColumns.forEach((col, index) => {
logger.info(
` ${index + 1}. ${col.column_name} -> ${col.reference_table}.${col.reference_column} (display: ${col.display_column})`
);
});
const joinConfigs: EntityJoinConfig[] = [];
for (const column of entityColumns) {
logger.info(`🔍 Entity 컬럼 상세 정보:`, {
column_name: column.column_name,
reference_table: column.reference_table,
reference_column: column.reference_column,
display_column: column.display_column,
});
if (
!column.column_name ||
!column.reference_table ||
@ -58,6 +72,12 @@ export class EntityJoinService {
let displayColumns: string[] = [];
let separator = " - ";
logger.info(`🔍 조건 확인 - 컬럼: ${column.column_name}`, {
hasScreenConfig: !!screenConfig,
hasDisplayColumns: screenConfig?.displayColumns,
displayColumn: column.display_column,
});
if (screenConfig && screenConfig.displayColumns) {
// 화면에서 설정된 표시 컬럼들 사용 (기본 테이블 + 조인 테이블 조합 지원)
displayColumns = screenConfig.displayColumns;
@ -70,9 +90,12 @@ export class EntityJoinService {
} else if (column.display_column && column.display_column !== "none") {
// 기존 설정된 단일 표시 컬럼 사용 (none이 아닌 경우만)
displayColumns = [column.display_column];
logger.info(
`🔧 기존 display_column 사용: ${column.column_name}${column.display_column}`
);
} else {
// 조인 탭에서 보여줄 기본 표시 컬럼 설정
// dept_info 테이블의 경우 dept_name을 기본으로 사용
// display_column이 "none"이거나 없는 경우 기본 표시 컬럼 설정
// 🚨 display_column이 항상 "none"이므로 이 로직을 기본으로 사용
let defaultDisplayColumn = column.reference_column;
if (column.reference_table === "dept_info") {
defaultDisplayColumn = "dept_name";
@ -83,9 +106,10 @@ export class EntityJoinService {
}
displayColumns = [defaultDisplayColumn];
console.log(
`🔧 조인 탭용 기본 표시 컬럼 설정: ${column.column_name}${defaultDisplayColumn} (${column.reference_table})`
logger.info(
`🔧 Entity 조인 기본 표시 컬럼 설정: ${column.column_name}${defaultDisplayColumn} (${column.reference_table})`
);
logger.info(`🔍 생성된 displayColumns 배열:`, displayColumns);
}
// 별칭 컬럼명 생성 (writer -> writer_name)
@ -102,13 +126,32 @@ export class EntityJoinService {
separator: separator,
};
logger.info(`🔧 기본 조인 설정 생성:`, {
sourceTable: joinConfig.sourceTable,
sourceColumn: joinConfig.sourceColumn,
referenceTable: joinConfig.referenceTable,
aliasColumn: joinConfig.aliasColumn,
displayColumns: joinConfig.displayColumns,
});
// 조인 설정 유효성 검증
logger.info(
`🔍 조인 설정 검증 중: ${joinConfig.sourceColumn} -> ${joinConfig.referenceTable}`
);
if (await this.validateJoinConfig(joinConfig)) {
joinConfigs.push(joinConfig);
logger.info(`✅ 조인 설정 추가됨: ${joinConfig.aliasColumn}`);
} else {
logger.warn(`❌ 조인 설정 검증 실패: ${joinConfig.sourceColumn}`);
}
}
logger.info(`Entity 조인 설정 생성 완료: ${joinConfigs.length}`);
logger.info(`🎯 Entity 조인 설정 생성 완료: ${joinConfigs.length}`);
joinConfigs.forEach((config, index) => {
logger.info(
` ${index + 1}. ${config.sourceColumn} -> ${config.referenceTable}.${config.referenceColumn} AS ${config.aliasColumn}`
);
});
return joinConfigs;
} catch (error) {
logger.error(`Entity 조인 감지 실패: ${tableName}`, error);
@ -273,7 +316,7 @@ export class EntityJoinService {
.filter(Boolean)
.join("\n");
logger.debug(`생성된 Entity 조인 쿼리:`, query);
logger.info(`🔍 생성된 Entity 조인 쿼리:`, query);
return {
query: query,
aliasMap: aliasMap,
@ -303,10 +346,18 @@ export class EntityJoinService {
}
// 참조 테이블의 캐시 가능성 확인
const displayCol =
config.displayColumn ||
config.displayColumns?.[0] ||
config.referenceColumn;
logger.info(
`🔍 캐시 확인용 표시 컬럼: ${config.referenceTable} - ${displayCol}`
);
const cachedData = await referenceCacheService.getCachedReference(
config.referenceTable,
config.referenceColumn,
config.displayColumn || config.displayColumns[0]
displayCol
);
return cachedData ? "cache" : "join";
@ -336,6 +387,14 @@ export class EntityJoinService {
*/
private async validateJoinConfig(config: EntityJoinConfig): Promise<boolean> {
try {
logger.info("🔍 조인 설정 검증 상세:", {
sourceColumn: config.sourceColumn,
referenceTable: config.referenceTable,
displayColumns: config.displayColumns,
displayColumn: config.displayColumn,
aliasColumn: config.aliasColumn,
});
// 참조 테이블 존재 확인
const tableExists = await prisma.$queryRaw`
SELECT 1 FROM information_schema.tables
@ -350,23 +409,32 @@ export class EntityJoinService {
// 참조 컬럼 존재 확인 (displayColumns[0] 사용)
const displayColumn = config.displayColumns?.[0] || config.displayColumn;
if (!displayColumn) {
logger.warn(`표시 컬럼이 설정되지 않음: ${config.sourceColumn}`);
return false;
}
logger.info(
`🔍 표시 컬럼 확인: ${displayColumn} (from displayColumns: ${config.displayColumns}, displayColumn: ${config.displayColumn})`
);
const columnExists = await prisma.$queryRaw`
SELECT 1 FROM information_schema.columns
WHERE table_name = ${config.referenceTable}
AND column_name = ${displayColumn}
LIMIT 1
`;
// 🚨 display_column이 항상 "none"이므로, 표시 컬럼이 없어도 조인 허용
if (displayColumn && displayColumn !== "none") {
const columnExists = await prisma.$queryRaw`
SELECT 1 FROM information_schema.columns
WHERE table_name = ${config.referenceTable}
AND column_name = ${displayColumn}
LIMIT 1
`;
if (!Array.isArray(columnExists) || columnExists.length === 0) {
logger.warn(
`표시 컬럼이 존재하지 않음: ${config.referenceTable}.${displayColumn}`
if (!Array.isArray(columnExists) || columnExists.length === 0) {
logger.warn(
`표시 컬럼이 존재하지 않음: ${config.referenceTable}.${displayColumn}`
);
return false;
}
logger.info(
`✅ 표시 컬럼 확인 완료: ${config.referenceTable}.${displayColumn}`
);
} else {
logger.info(
`🔧 표시 컬럼 검증 생략: display_column이 none이거나 설정되지 않음`
);
return false;
}
return true;

View File

@ -2049,6 +2049,17 @@ export class TableManagementService {
options.screenEntityConfigs
);
logger.info(
`🔍 detectEntityJoins 결과: ${joinConfigs.length}개 조인 설정`
);
if (joinConfigs.length > 0) {
joinConfigs.forEach((config, index) => {
logger.info(
` 조인 ${index + 1}: ${config.sourceColumn} -> ${config.referenceTable} AS ${config.aliasColumn}`
);
});
}
// 추가 조인 컬럼 정보가 있으면 조인 설정에 추가
if (
options.additionalJoinColumns &&
@ -2057,6 +2068,10 @@ export class TableManagementService {
logger.info(
`추가 조인 컬럼 처리: ${options.additionalJoinColumns.length}`
);
logger.info(
"📋 전달받은 additionalJoinColumns:",
options.additionalJoinColumns
);
for (const additionalColumn of options.additionalJoinColumns) {
// 기존 조인 설정에서 같은 참조 테이블을 사용하는 설정 찾기
@ -2251,10 +2266,18 @@ export class TableManagementService {
try {
// 캐시 데이터 미리 로드
for (const config of joinConfigs) {
const displayCol =
config.displayColumn ||
config.displayColumns?.[0] ||
config.referenceColumn;
logger.info(
`🔍 캐시 로드 - ${config.referenceTable}: keyCol=${config.referenceColumn}, displayCol=${displayCol}`
);
await referenceCacheService.getCachedReference(
config.referenceTable,
config.referenceColumn,
config.displayColumn || config.displayColumns[0]
displayCol
);
}

View File

@ -22,6 +22,7 @@ interface SingleTableWithStickyProps {
renderCheckboxCell: (row: any, index: number) => React.ReactNode;
formatCellValue: (value: any, format?: string, columnName?: string) => string;
getColumnWidth: (column: ColumnConfig) => number;
joinColumnMapping: Record<string, string>; // 조인 컬럼 매핑 추가
}
export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
@ -39,6 +40,7 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
renderCheckboxCell,
formatCellValue,
getColumnWidth,
joinColumnMapping,
}) => {
const checkboxConfig = tableConfig.checkbox || {};
@ -174,7 +176,25 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
>
{column.columnName === "__checkbox__"
? renderCheckboxCell(row, index)
: formatCellValue(row[column.columnName], column.format, column.columnName) || "\u00A0"}
: (() => {
// 🎯 매핑된 컬럼명으로 데이터 찾기 (기본 테이블과 동일한 로직)
const mappedColumnName = joinColumnMapping[column.columnName] || column.columnName;
// 조인 컬럼 매핑 정보 로깅
if (column.columnName !== mappedColumnName && index === 0) {
console.log(`🔗 Sticky 조인 컬럼 매핑: ${column.columnName}${mappedColumnName}`);
}
const cellValue = row[mappedColumnName];
if (index === 0) {
// 첫 번째 행만 로그 출력 (디버깅용)
console.log(
`🔍 Sticky 셀 데이터 [${column.columnName}${mappedColumnName}]:`,
cellValue,
);
}
return formatCellValue(cellValue, column.format, column.columnName) || "\u00A0";
})()}
</TableCell>
);
})}

View File

@ -290,28 +290,39 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
// 🎯 조인 탭에서 추가한 컬럼들도 추가 (실제로 존재하는 컬럼만)
...joinTabColumns
.filter((col) => {
// 실제 API 응답에 존재하는 컬럼만 필터링
const validJoinColumns = ["dept_code_name", "dept_name"];
const isValid = validJoinColumns.includes(col.columnName);
if (!isValid) {
console.log(`🔍 조인 탭 컬럼 제외: ${col.columnName} (유효하지 않음)`);
// 조인 컬럼인지 확인 (언더스코어가 포함된 컬럼)
const isJoinColumn = col.columnName.includes("_") && col.columnName !== "__checkbox__";
if (!isJoinColumn) {
console.log(`🔍 조인 탭 컬럼 제외: ${col.columnName} (조인 컬럼이 아님)`);
}
return isValid;
return isJoinColumn;
})
.map((col) => {
// 실제 존재하는 조인 컬럼만 처리
let sourceTable = tableConfig.selectedTable;
let sourceColumn = col.columnName;
// 동적으로 조인 컬럼 정보 추출
console.log(`🔍 조인 컬럼 분석: ${col.columnName}`);
if (col.columnName === "dept_code_name" || col.columnName === "dept_name") {
sourceTable = "dept_info";
// 컬럼명에서 기본 컬럼과 참조 테이블 추출
// 예: dept_code_company_name -> dept_code (기본), company_name (참조)
const parts = col.columnName.split("_");
let sourceColumn = "";
let referenceTable = "";
// dept_code로 시작하는 경우
if (col.columnName.startsWith("dept_code_")) {
sourceColumn = "dept_code";
referenceTable = "dept_info";
}
// 다른 패턴들도 추가 가능
else {
// 기본적으로 첫 번째 부분을 소스 컬럼으로 사용
sourceColumn = parts[0];
referenceTable = tableConfig.selectedTable || "unknown";
}
console.log(`🔍 조인 탭 컬럼 처리: ${col.columnName} -> ${sourceTable}.${sourceColumn}`);
console.log(`🔗 조인 설정: ${col.columnName} -> ${sourceColumn} (${referenceTable})`);
return {
sourceTable: sourceTable || tableConfig.selectedTable || "unknown",
sourceTable: referenceTable,
sourceColumn: sourceColumn,
joinAlias: col.columnName,
};
@ -410,6 +421,14 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
console.log("🎯 데이터 개수:", result.data?.length || 0);
console.log("🎯 전체 페이지:", result.totalPages);
console.log("🎯 총 아이템:", result.total);
// 🚨 데이터 샘플 확인 (첫 번째 행의 모든 컬럼과 값)
if (result.data && result.data.length > 0) {
console.log("🔍 첫 번째 행 데이터 샘플:", result.data[0]);
Object.entries(result.data[0]).forEach(([key, value]) => {
console.log(` 📊 ${key}: "${value}" (타입: ${typeof value})`);
});
}
setData(result.data || []);
setTotalPages(result.totalPages || 1);
setTotalItems(result.total || 0);
@ -507,24 +526,62 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
}
}
// 3. 부분 문자열 매칭 (컬럼명에 일부가 포함된 경우)
// 3. 조인 컬럼 검증 및 처리
if (!foundMatch) {
const partialMatches = actualApiColumns.filter(
(apiCol) => apiCol.includes(userColumn.columnName) || userColumn.columnName.includes(apiCol),
);
// 🚨 조인 컬럼인지 확인 (더 정확한 감지 로직)
const hasUnderscore = userColumn.columnName.includes("_");
let isJoinColumn = false;
let baseColumnName = "";
if (partialMatches.length > 0) {
// 가장 길이가 비슷한 것 선택
const bestMatch = partialMatches.reduce((best, current) =>
Math.abs(current.length - userColumn.columnName.length) <
Math.abs(best.length - userColumn.columnName.length)
? current
: best,
if (hasUnderscore) {
// 가능한 모든 기본 컬럼명을 확인 (dept_code_company_name -> dept_code, dept 순으로)
const parts = userColumn.columnName.split("_");
for (let i = parts.length - 1; i >= 1; i--) {
const possibleBase = parts.slice(0, i).join("_");
if (actualApiColumns.includes(possibleBase)) {
baseColumnName = possibleBase;
isJoinColumn = true;
break;
}
}
}
console.log(`🔍 조인 컬럼 검사: "${userColumn.columnName}"`, {
hasUnderscore,
baseColumnName,
isJoinColumn,
});
if (isJoinColumn) {
console.log(`🔍 조인 컬럼 기본 컬럼 확인: "${baseColumnName}"`, {
existsInApi: actualApiColumns.includes(baseColumnName),
actualApiColumns: actualApiColumns.slice(0, 10), // 처음 10개만 표시
});
console.warn(
`⚠️ 조인 실패: "${userColumn.columnName}" - 백엔드에서 Entity 조인이 실행되지 않음. 기본 컬럼값 표시합니다.`,
);
// 조인 실패 시 기본 컬럼값을 표시하도록 매핑
newJoinColumnMapping[userColumn.columnName] = baseColumnName;
foundMatch = true;
} else {
// 일반 컬럼인 경우 부분 매칭 시도
const partialMatches = actualApiColumns.filter(
(apiCol) => apiCol.includes(userColumn.columnName) || userColumn.columnName.includes(apiCol),
);
newJoinColumnMapping[userColumn.columnName] = bestMatch;
console.log(`🔍 부분 매핑: ${userColumn.columnName}${bestMatch}`);
foundMatch = true;
if (partialMatches.length > 0) {
const bestMatch = partialMatches.reduce((best, current) =>
Math.abs(current.length - userColumn.columnName.length) <
Math.abs(best.length - userColumn.columnName.length)
? current
: best,
);
newJoinColumnMapping[userColumn.columnName] = bestMatch;
console.log(`🔍 부분 매핑: ${userColumn.columnName}${bestMatch}`);
foundMatch = true;
}
}
}
@ -1245,6 +1302,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
renderCheckboxCell={renderCheckboxCell}
formatCellValue={formatCellValue}
getColumnWidth={getColumnWidth}
joinColumnMapping={joinColumnMapping}
/>
) : (
// 기존 테이블 (가로 스크롤이 필요 없는 경우)
@ -1325,15 +1383,28 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
: (() => {
// 🎯 매핑된 컬럼명으로 데이터 찾기
const mappedColumnName = joinColumnMapping[column.columnName] || column.columnName;
// 조인 컬럼 매핑 정보 로깅
if (column.columnName !== mappedColumnName && index === 0) {
console.log(`🔗 조인 컬럼 매핑: ${column.columnName}${mappedColumnName}`);
}
const cellValue = row[mappedColumnName];
if (index === 0) {
// 첫 번째 행만 로그 출력
console.log(
`🔍 셀 데이터 [${column.columnName}${mappedColumnName}]:`,
cellValue,
"전체 row:",
row,
);
console.log(`🔍 셀 데이터 [${column.columnName}${mappedColumnName}]:`, cellValue);
// 🚨 조인된 컬럼인 경우 추가 디버깅
if (column.columnName !== mappedColumnName) {
console.log(" 🔗 조인 컬럼 분석:");
console.log(` 👤 사용자 설정 컬럼: "${column.columnName}"`);
console.log(` 📡 매핑된 API 컬럼: "${mappedColumnName}"`);
console.log(` 📋 컬럼 라벨: "${column.displayName}"`);
console.log(` 💾 실제 데이터: "${cellValue}"`);
console.log(
` 🔄 원본 컬럼 데이터 (${column.columnName}): "${row[column.columnName]}"`,
);
}
}
return formatCellValue(cellValue, column.format, column.columnName) || "\u00A0";
})()}