|
|
|
|
@ -1314,7 +1314,7 @@ export class TableManagementService {
|
|
|
|
|
// 각 값을 LIKE 또는 = 조건으로 처리
|
|
|
|
|
const conditions: string[] = [];
|
|
|
|
|
const values: any[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
value.forEach((v: any, idx: number) => {
|
|
|
|
|
const safeValue = String(v).trim();
|
|
|
|
|
// 정확히 일치하거나, 콤마로 구분된 값 중 하나로 포함
|
|
|
|
|
@ -1323,17 +1323,24 @@ export class TableManagementService {
|
|
|
|
|
// - "2," 로 시작
|
|
|
|
|
// - ",2" 로 끝남
|
|
|
|
|
// - ",2," 중간에 포함
|
|
|
|
|
const paramBase = paramIndex + (idx * 4);
|
|
|
|
|
const paramBase = paramIndex + idx * 4;
|
|
|
|
|
conditions.push(`(
|
|
|
|
|
${columnName}::text = $${paramBase} OR
|
|
|
|
|
${columnName}::text LIKE $${paramBase + 1} OR
|
|
|
|
|
${columnName}::text LIKE $${paramBase + 2} OR
|
|
|
|
|
${columnName}::text LIKE $${paramBase + 3}
|
|
|
|
|
)`);
|
|
|
|
|
values.push(safeValue, `${safeValue},%`, `%,${safeValue}`, `%,${safeValue},%`);
|
|
|
|
|
values.push(
|
|
|
|
|
safeValue,
|
|
|
|
|
`${safeValue},%`,
|
|
|
|
|
`%,${safeValue}`,
|
|
|
|
|
`%,${safeValue},%`
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info(`🔍 다중 값 배열 검색: ${columnName} IN [${value.join(", ")}]`);
|
|
|
|
|
logger.info(
|
|
|
|
|
`🔍 다중 값 배열 검색: ${columnName} IN [${value.join(", ")}]`
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
whereClause: `(${conditions.join(" OR ")})`,
|
|
|
|
|
values,
|
|
|
|
|
@ -1772,21 +1779,29 @@ export class TableManagementService {
|
|
|
|
|
// contains 연산자 (기본): 참조 테이블의 표시 컬럼으로 검색
|
|
|
|
|
const referenceColumn = entityTypeInfo.referenceColumn || "id";
|
|
|
|
|
const referenceTable = entityTypeInfo.referenceTable;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// displayColumn이 비어있거나 "none"이면 참조 테이블에서 자동 감지 (entityJoinService와 동일한 로직)
|
|
|
|
|
let displayColumn = entityTypeInfo.displayColumn;
|
|
|
|
|
if (!displayColumn || displayColumn === "none" || displayColumn === "") {
|
|
|
|
|
displayColumn = await this.findDisplayColumnForTable(referenceTable, referenceColumn);
|
|
|
|
|
if (
|
|
|
|
|
!displayColumn ||
|
|
|
|
|
displayColumn === "none" ||
|
|
|
|
|
displayColumn === ""
|
|
|
|
|
) {
|
|
|
|
|
displayColumn = await this.findDisplayColumnForTable(
|
|
|
|
|
referenceTable,
|
|
|
|
|
referenceColumn
|
|
|
|
|
);
|
|
|
|
|
logger.info(
|
|
|
|
|
`🔍 [buildEntitySearchCondition] displayColumn 자동 감지: ${referenceTable} -> ${displayColumn}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 참조 테이블의 표시 컬럼으로 검색
|
|
|
|
|
// 🔧 main. 접두사 추가: EXISTS 서브쿼리에서 외부 테이블 참조 시 명시적으로 지정
|
|
|
|
|
return {
|
|
|
|
|
whereClause: `EXISTS (
|
|
|
|
|
SELECT 1 FROM ${referenceTable} ref
|
|
|
|
|
WHERE ref.${referenceColumn} = ${columnName}
|
|
|
|
|
WHERE ref.${referenceColumn} = main.${columnName}
|
|
|
|
|
AND ref.${displayColumn} ILIKE $${paramIndex}
|
|
|
|
|
)`,
|
|
|
|
|
values: [`%${value}%`],
|
|
|
|
|
@ -2150,14 +2165,14 @@ export class TableManagementService {
|
|
|
|
|
// 안전한 테이블명 검증
|
|
|
|
|
const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, "");
|
|
|
|
|
|
|
|
|
|
// 전체 개수 조회
|
|
|
|
|
const countQuery = `SELECT COUNT(*) as count FROM ${safeTableName} ${whereClause}`;
|
|
|
|
|
// 전체 개수 조회 (main 별칭 추가 - buildWhereClause가 main. 접두사를 사용하므로 필요)
|
|
|
|
|
const countQuery = `SELECT COUNT(*) as count FROM ${safeTableName} main ${whereClause}`;
|
|
|
|
|
const countResult = await query<any>(countQuery, searchValues);
|
|
|
|
|
const total = parseInt(countResult[0].count);
|
|
|
|
|
|
|
|
|
|
// 데이터 조회
|
|
|
|
|
// 데이터 조회 (main 별칭 추가)
|
|
|
|
|
const dataQuery = `
|
|
|
|
|
SELECT * FROM ${safeTableName}
|
|
|
|
|
SELECT main.* FROM ${safeTableName} main
|
|
|
|
|
${whereClause}
|
|
|
|
|
${orderClause}
|
|
|
|
|
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
|
|
|
|
@ -2494,7 +2509,7 @@ export class TableManagementService {
|
|
|
|
|
skippedColumns.push(column);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dataType = columnTypeMap.get(column) || "text";
|
|
|
|
|
setConditions.push(
|
|
|
|
|
`"${column}" = $${paramIndex}::${this.getPostgreSQLType(dataType)}`
|
|
|
|
|
@ -2506,7 +2521,9 @@ export class TableManagementService {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (skippedColumns.length > 0) {
|
|
|
|
|
logger.info(`⚠️ 테이블에 존재하지 않는 컬럼 스킵: ${skippedColumns.join(", ")}`);
|
|
|
|
|
logger.info(
|
|
|
|
|
`⚠️ 테이블에 존재하지 않는 컬럼 스킵: ${skippedColumns.join(", ")}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WHERE 조건 생성 (PRIMARY KEY 우선, 없으면 모든 원본 데이터 사용)
|
|
|
|
|
@ -2776,10 +2793,14 @@ export class TableManagementService {
|
|
|
|
|
// 실제 소스 컬럼이 partner_id인데 프론트엔드가 customer_id로 추론하는 경우 대응
|
|
|
|
|
if (!baseJoinConfig && (additionalColumn as any).referenceTable) {
|
|
|
|
|
baseJoinConfig = joinConfigs.find(
|
|
|
|
|
(config) => config.referenceTable === (additionalColumn as any).referenceTable
|
|
|
|
|
(config) =>
|
|
|
|
|
config.referenceTable ===
|
|
|
|
|
(additionalColumn as any).referenceTable
|
|
|
|
|
);
|
|
|
|
|
if (baseJoinConfig) {
|
|
|
|
|
logger.info(`🔄 referenceTable로 조인 설정 찾음: ${(additionalColumn as any).referenceTable} → ${baseJoinConfig.sourceColumn}`);
|
|
|
|
|
logger.info(
|
|
|
|
|
`🔄 referenceTable로 조인 설정 찾음: ${(additionalColumn as any).referenceTable} → ${baseJoinConfig.sourceColumn}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -2787,25 +2808,31 @@ export class TableManagementService {
|
|
|
|
|
// joinAlias에서 실제 컬럼명 추출
|
|
|
|
|
const sourceColumn = baseJoinConfig.sourceColumn; // 실제 소스 컬럼 (예: partner_id)
|
|
|
|
|
const originalJoinAlias = additionalColumn.joinAlias; // 프론트엔드가 보낸 별칭 (예: customer_id_customer_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 🔄 프론트엔드가 잘못된 소스 컬럼으로 추론한 경우 처리
|
|
|
|
|
// customer_id_customer_name → customer_name 추출 (customer_id_ 부분 제거)
|
|
|
|
|
// 또는 partner_id_customer_name → customer_name 추출 (partner_id_ 부분 제거)
|
|
|
|
|
let actualColumnName: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 프론트엔드가 보낸 joinAlias에서 실제 컬럼명 추출
|
|
|
|
|
const frontendSourceColumn = additionalColumn.sourceColumn; // 프론트엔드가 추론한 소스 컬럼 (customer_id)
|
|
|
|
|
if (originalJoinAlias.startsWith(`${frontendSourceColumn}_`)) {
|
|
|
|
|
// 프론트엔드가 추론한 소스 컬럼으로 시작하면 그 부분 제거
|
|
|
|
|
actualColumnName = originalJoinAlias.replace(`${frontendSourceColumn}_`, "");
|
|
|
|
|
actualColumnName = originalJoinAlias.replace(
|
|
|
|
|
`${frontendSourceColumn}_`,
|
|
|
|
|
""
|
|
|
|
|
);
|
|
|
|
|
} else if (originalJoinAlias.startsWith(`${sourceColumn}_`)) {
|
|
|
|
|
// 실제 소스 컬럼으로 시작하면 그 부분 제거
|
|
|
|
|
actualColumnName = originalJoinAlias.replace(`${sourceColumn}_`, "");
|
|
|
|
|
actualColumnName = originalJoinAlias.replace(
|
|
|
|
|
`${sourceColumn}_`,
|
|
|
|
|
""
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// 어느 것도 아니면 원본 사용
|
|
|
|
|
actualColumnName = originalJoinAlias;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 🆕 올바른 joinAlias 재생성 (실제 소스 컬럼 기반)
|
|
|
|
|
const correctedJoinAlias = `${sourceColumn}_${actualColumnName}`;
|
|
|
|
|
|
|
|
|
|
@ -3199,8 +3226,10 @@ export class TableManagementService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Entity 조인 컬럼 검색이 있는지 확인 (기본 조인 + 추가 조인 컬럼 모두 포함)
|
|
|
|
|
// 🔧 sourceColumn도 포함: search={"order_no":"..."} 형태도 Entity 검색으로 인식
|
|
|
|
|
const allEntityColumns = [
|
|
|
|
|
...joinConfigs.map((config) => config.aliasColumn),
|
|
|
|
|
...joinConfigs.map((config) => config.sourceColumn), // 🔧 소스 컬럼도 포함
|
|
|
|
|
// 추가 조인 컬럼들도 포함 (writer_dept_code, company_code_status 등)
|
|
|
|
|
...joinConfigs.flatMap((config) => {
|
|
|
|
|
const additionalColumns = [];
|
|
|
|
|
@ -3606,8 +3635,10 @@ export class TableManagementService {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// main. 접두사 추가 (조인 쿼리용)
|
|
|
|
|
// 🔧 이미 접두사(. 앞)가 있는 경우는 교체하지 않음 (ref.column, main.column 등)
|
|
|
|
|
// Negative lookbehind (?<!\.) 사용: 앞에 .이 없는 경우만 매칭
|
|
|
|
|
condition = condition.replace(
|
|
|
|
|
new RegExp(`\\b${columnName}\\b`, "g"),
|
|
|
|
|
new RegExp(`(?<!\\.)\\b${columnName}\\b`, "g"),
|
|
|
|
|
`main.${columnName}`
|
|
|
|
|
);
|
|
|
|
|
conditions.push(condition);
|
|
|
|
|
@ -3809,9 +3840,12 @@ export class TableManagementService {
|
|
|
|
|
// 🔒 멀티테넌시: 회사별 데이터 테이블은 캐시 사용 불가 (company_code 필터링 필요)
|
|
|
|
|
const companySpecificTables = [
|
|
|
|
|
"supplier_mng",
|
|
|
|
|
"customer_mng",
|
|
|
|
|
"customer_mng",
|
|
|
|
|
"item_info",
|
|
|
|
|
"dept_info",
|
|
|
|
|
"sales_order_mng", // 🔧 수주관리 테이블 추가
|
|
|
|
|
"sales_order_detail", // 🔧 수주상세 테이블 추가
|
|
|
|
|
"partner_info", // 🔧 거래처 테이블 추가
|
|
|
|
|
// 필요시 추가
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
@ -4722,7 +4756,7 @@ export class TableManagementService {
|
|
|
|
|
/**
|
|
|
|
|
* 두 테이블 간의 엔티티 관계 자동 감지
|
|
|
|
|
* column_labels에서 엔티티 타입 설정을 기반으로 테이블 간 관계를 찾습니다.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param leftTable 좌측 테이블명
|
|
|
|
|
* @param rightTable 우측 테이블명
|
|
|
|
|
* @returns 감지된 엔티티 관계 배열
|
|
|
|
|
@ -4730,16 +4764,20 @@ export class TableManagementService {
|
|
|
|
|
async detectTableEntityRelations(
|
|
|
|
|
leftTable: string,
|
|
|
|
|
rightTable: string
|
|
|
|
|
): Promise<Array<{
|
|
|
|
|
leftColumn: string;
|
|
|
|
|
rightColumn: string;
|
|
|
|
|
direction: "left_to_right" | "right_to_left";
|
|
|
|
|
inputType: string;
|
|
|
|
|
displayColumn?: string;
|
|
|
|
|
}>> {
|
|
|
|
|
): Promise<
|
|
|
|
|
Array<{
|
|
|
|
|
leftColumn: string;
|
|
|
|
|
rightColumn: string;
|
|
|
|
|
direction: "left_to_right" | "right_to_left";
|
|
|
|
|
inputType: string;
|
|
|
|
|
displayColumn?: string;
|
|
|
|
|
}>
|
|
|
|
|
> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info(`두 테이블 간 엔티티 관계 감지 시작: ${leftTable} <-> ${rightTable}`);
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
`두 테이블 간 엔티티 관계 감지 시작: ${leftTable} <-> ${rightTable}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const relations: Array<{
|
|
|
|
|
leftColumn: string;
|
|
|
|
|
rightColumn: string;
|
|
|
|
|
@ -4806,12 +4844,17 @@ export class TableManagementService {
|
|
|
|
|
|
|
|
|
|
logger.info(`엔티티 관계 감지 완료: ${relations.length}개 발견`);
|
|
|
|
|
relations.forEach((rel, idx) => {
|
|
|
|
|
logger.info(` ${idx + 1}. ${leftTable}.${rel.leftColumn} <-> ${rightTable}.${rel.rightColumn} (${rel.direction})`);
|
|
|
|
|
logger.info(
|
|
|
|
|
` ${idx + 1}. ${leftTable}.${rel.leftColumn} <-> ${rightTable}.${rel.rightColumn} (${rel.direction})`
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return relations;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error(`엔티티 관계 감지 실패: ${leftTable} <-> ${rightTable}`, error);
|
|
|
|
|
logger.error(
|
|
|
|
|
`엔티티 관계 감지 실패: ${leftTable} <-> ${rightTable}`,
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|