Merge branch 'feature/v2-renewal' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
commit
8253be0048
|
|
@ -457,17 +457,18 @@ export class EntityJoinService {
|
|||
|
||||
// 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}' AND ${alias}.company_code = main.company_code AND ${alias}.is_active = true`;
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT AND ${alias}.table_name = '${tableName}' AND ${alias}.column_name = '${config.sourceColumn}' AND ${alias}.company_code = main.company_code AND ${alias}.is_active = true`;
|
||||
}
|
||||
|
||||
// user_info는 전역 테이블이므로 company_code 조건 없이 조인
|
||||
if (config.referenceTable === "user_info") {
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn}`;
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT`;
|
||||
}
|
||||
|
||||
// 일반 테이블: company_code가 있으면 같은 회사 데이터만 조인 (멀티테넌시)
|
||||
// supplier_mng, customer_mng, item_info 등 회사별 데이터 테이블
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn} AND ${alias}.company_code = main.company_code`;
|
||||
// ::TEXT 캐스팅으로 varchar/integer 등 타입 불일치 방지
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT AND ${alias}.company_code = main.company_code`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
|
|
@ -580,6 +581,7 @@ export class EntityJoinService {
|
|||
logger.info("🔍 조인 설정 검증 상세:", {
|
||||
sourceColumn: config.sourceColumn,
|
||||
referenceTable: config.referenceTable,
|
||||
referenceColumn: config.referenceColumn,
|
||||
displayColumns: config.displayColumns,
|
||||
displayColumn: config.displayColumn,
|
||||
aliasColumn: config.aliasColumn,
|
||||
|
|
@ -598,7 +600,45 @@ export class EntityJoinService {
|
|||
return false;
|
||||
}
|
||||
|
||||
// 참조 컬럼 존재 확인 (displayColumns[0] 사용)
|
||||
// 참조 컬럼(JOIN 키) 존재 확인 - 참조 테이블에 reference_column이 실제로 있는지 검증
|
||||
if (config.referenceColumn) {
|
||||
const refColExists = await query<{ exists: number }>(
|
||||
`SELECT 1 as exists FROM information_schema.columns
|
||||
WHERE table_name = $1
|
||||
AND column_name = $2
|
||||
LIMIT 1`,
|
||||
[config.referenceTable, config.referenceColumn]
|
||||
);
|
||||
|
||||
if (refColExists.length === 0) {
|
||||
// reference_column이 없으면 'id' 컬럼으로 자동 대체 시도
|
||||
const idColExists = await query<{ exists: number }>(
|
||||
`SELECT 1 as exists FROM information_schema.columns
|
||||
WHERE table_name = $1
|
||||
AND column_name = 'id'
|
||||
LIMIT 1`,
|
||||
[config.referenceTable]
|
||||
);
|
||||
|
||||
if (idColExists.length > 0) {
|
||||
logger.warn(
|
||||
`⚠️ 참조 컬럼 ${config.referenceTable}.${config.referenceColumn}이 존재하지 않음 → 'id'로 자동 대체`
|
||||
);
|
||||
config.referenceColumn = "id";
|
||||
} else {
|
||||
logger.warn(
|
||||
`❌ 참조 컬럼 ${config.referenceTable}.${config.referenceColumn}이 존재하지 않고 'id' 컬럼도 없음 → 스킵`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
`✅ 참조 컬럼 확인 완료: ${config.referenceTable}.${config.referenceColumn}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 표시 컬럼 존재 확인 (displayColumns[0] 사용)
|
||||
const displayColumn = config.displayColumns?.[0] || config.displayColumn;
|
||||
logger.info(
|
||||
`🔍 표시 컬럼 확인: ${displayColumn} (from displayColumns: ${config.displayColumns}, displayColumn: ${config.displayColumn})`
|
||||
|
|
@ -686,10 +726,10 @@ export class EntityJoinService {
|
|||
// 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}' AND ${alias}.company_code = main.company_code AND ${alias}.is_active = true`;
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT AND ${alias}.table_name = '${tableName}' AND ${alias}.column_name = '${config.sourceColumn}' AND ${alias}.company_code = main.company_code AND ${alias}.is_active = true`;
|
||||
}
|
||||
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main.${config.sourceColumn} = ${alias}.${config.referenceColumn}`;
|
||||
return `LEFT JOIN ${config.referenceTable} ${alias} ON main."${config.sourceColumn}"::TEXT = ${alias}."${config.referenceColumn}"::TEXT`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
|
|
|
|||
|
|
@ -2979,31 +2979,49 @@ export class TableManagementService {
|
|||
continue; // 기본 Entity 조인과 중복되면 추가하지 않음
|
||||
}
|
||||
|
||||
// 추가 조인 컬럼 설정 생성
|
||||
const additionalJoinConfig: EntityJoinConfig = {
|
||||
sourceTable: tableName,
|
||||
sourceColumn: sourceColumn, // 실제 소스 컬럼 (partner_id)
|
||||
referenceTable:
|
||||
(additionalColumn as any).referenceTable ||
|
||||
baseJoinConfig.referenceTable, // 참조 테이블 (customer_mng)
|
||||
referenceColumn: baseJoinConfig.referenceColumn, // 참조 키 (customer_code)
|
||||
displayColumns: [actualColumnName], // 표시할 컬럼들 (customer_name)
|
||||
displayColumn: actualColumnName, // 하위 호환성
|
||||
aliasColumn: correctedJoinAlias, // 수정된 별칭 (partner_id_customer_name)
|
||||
separator: " - ", // 기본 구분자
|
||||
};
|
||||
|
||||
joinConfigs.push(additionalJoinConfig);
|
||||
logger.info(
|
||||
`✅ 추가 조인 컬럼 설정 추가: ${additionalJoinConfig.aliasColumn} -> ${actualColumnName}`
|
||||
// 🆕 같은 sourceColumn + referenceTable 조합의 기존 config가 있으면 displayColumns에 병합
|
||||
const existingConfig = joinConfigs.find(
|
||||
(config) =>
|
||||
config.sourceColumn === sourceColumn &&
|
||||
config.referenceTable === ((additionalColumn as any).referenceTable || baseJoinConfig.referenceTable)
|
||||
);
|
||||
logger.info(`🔍 추가된 조인 설정 상세:`, {
|
||||
sourceTable: additionalJoinConfig.sourceTable,
|
||||
sourceColumn: additionalJoinConfig.sourceColumn,
|
||||
referenceTable: additionalJoinConfig.referenceTable,
|
||||
displayColumns: additionalJoinConfig.displayColumns,
|
||||
aliasColumn: additionalJoinConfig.aliasColumn,
|
||||
});
|
||||
|
||||
if (existingConfig) {
|
||||
// 기존 config에 display column 추가 (중복 방지)
|
||||
if (!existingConfig.displayColumns?.includes(actualColumnName)) {
|
||||
existingConfig.displayColumns = existingConfig.displayColumns || [];
|
||||
existingConfig.displayColumns.push(actualColumnName);
|
||||
logger.info(
|
||||
`🔄 기존 조인 설정에 컬럼 병합: ${existingConfig.aliasColumn} ← ${actualColumnName} (총 ${existingConfig.displayColumns.length}개)`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 새 조인 설정 생성
|
||||
const additionalJoinConfig: EntityJoinConfig = {
|
||||
sourceTable: tableName,
|
||||
sourceColumn: sourceColumn, // 실제 소스 컬럼 (partner_id)
|
||||
referenceTable:
|
||||
(additionalColumn as any).referenceTable ||
|
||||
baseJoinConfig.referenceTable, // 참조 테이블 (customer_mng)
|
||||
referenceColumn: baseJoinConfig.referenceColumn, // 참조 키 (customer_code)
|
||||
displayColumns: [actualColumnName], // 표시할 컬럼들 (customer_name)
|
||||
displayColumn: actualColumnName, // 하위 호환성
|
||||
aliasColumn: correctedJoinAlias, // 수정된 별칭 (partner_id_customer_name)
|
||||
separator: " - ", // 기본 구분자
|
||||
};
|
||||
|
||||
joinConfigs.push(additionalJoinConfig);
|
||||
logger.info(
|
||||
`✅ 추가 조인 컬럼 설정 추가: ${additionalJoinConfig.aliasColumn} -> ${actualColumnName}`
|
||||
);
|
||||
logger.info(`🔍 추가된 조인 설정 상세:`, {
|
||||
sourceTable: additionalJoinConfig.sourceTable,
|
||||
sourceColumn: additionalJoinConfig.sourceColumn,
|
||||
referenceTable: additionalJoinConfig.referenceTable,
|
||||
displayColumns: additionalJoinConfig.displayColumns,
|
||||
aliasColumn: additionalJoinConfig.aliasColumn,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1250,24 +1250,59 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
|
||||
setRightData(filteredData);
|
||||
} else {
|
||||
// 단일키 (하위 호환성)
|
||||
// 단일키 (하위 호환성) → entityJoinApi 사용으로 전환 (entity 조인 컬럼 지원)
|
||||
const leftColumn = componentConfig.rightPanel?.relation?.leftColumn;
|
||||
const rightColumn = componentConfig.rightPanel?.relation?.foreignKey;
|
||||
|
||||
if (leftColumn && rightColumn && leftTable) {
|
||||
const leftValue = leftItem[leftColumn];
|
||||
const joinedData = await dataApi.getJoinedData(
|
||||
leftTable,
|
||||
const { entityJoinApi } = await import("@/lib/api/entityJoin");
|
||||
|
||||
// 단일키를 복합키 형식으로 변환
|
||||
const searchConditions: Record<string, any> = {};
|
||||
searchConditions[rightColumn] = leftValue;
|
||||
|
||||
// Entity 조인 컬럼 추출
|
||||
const rightJoinColumnsLegacy = extractAdditionalJoinColumns(
|
||||
componentConfig.rightPanel?.columns,
|
||||
rightTableName,
|
||||
leftColumn,
|
||||
rightColumn,
|
||||
leftValue,
|
||||
componentConfig.rightPanel?.dataFilter, // 🆕 데이터 필터 전달
|
||||
true, // 🆕 Entity 조인 활성화
|
||||
componentConfig.rightPanel?.columns, // 🆕 표시 컬럼 전달 (item_info.item_name 등)
|
||||
componentConfig.rightPanel?.deduplication, // 🆕 중복 제거 설정 전달
|
||||
);
|
||||
setRightData(joinedData || []); // 모든 관련 레코드 (배열)
|
||||
if (rightJoinColumnsLegacy) {
|
||||
console.log("🔗 [분할패널] 단일키 모드 additionalJoinColumns:", rightJoinColumnsLegacy);
|
||||
}
|
||||
|
||||
const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
|
||||
search: searchConditions,
|
||||
enableEntityJoin: true,
|
||||
size: 1000,
|
||||
companyCodeOverride: companyCode,
|
||||
additionalJoinColumns: rightJoinColumnsLegacy,
|
||||
});
|
||||
|
||||
let filteredDataLegacy = result.data || [];
|
||||
|
||||
// 데이터 필터 적용
|
||||
const dataFilterLegacy = componentConfig.rightPanel?.dataFilter;
|
||||
if (dataFilterLegacy?.enabled && dataFilterLegacy.conditions?.length > 0) {
|
||||
filteredDataLegacy = filteredDataLegacy.filter((item: any) => {
|
||||
return dataFilterLegacy.conditions.every((cond: any) => {
|
||||
const value = item[cond.column];
|
||||
const condValue = cond.value;
|
||||
switch (cond.operator) {
|
||||
case "equals":
|
||||
return value === condValue;
|
||||
case "notEquals":
|
||||
return value !== condValue;
|
||||
case "contains":
|
||||
return String(value).includes(String(condValue));
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setRightData(filteredDataLegacy || []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3802,23 +3837,20 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
|
||||
if (rightColumns && rightColumns.length > 0) {
|
||||
// 설정된 컬럼만 표시 (엔티티 조인 컬럼 처리)
|
||||
// 설정된 컬럼은 null/empty여도 항상 표시 (사용자가 명시적으로 설정한 컬럼이므로)
|
||||
const summaryCount = componentConfig.rightPanel?.summaryColumnCount ?? 3;
|
||||
firstValues = rightColumns
|
||||
.slice(0, summaryCount)
|
||||
.map((col) => {
|
||||
// 🆕 엔티티 조인 컬럼 처리 (getEntityJoinValue 사용)
|
||||
const value = getEntityJoinValue(item, col.name);
|
||||
return [col.name, value, col.label] as [string, any, string];
|
||||
})
|
||||
.filter(([_, value]) => value !== null && value !== undefined && value !== "");
|
||||
});
|
||||
|
||||
allValues = rightColumns
|
||||
.map((col) => {
|
||||
// 🆕 엔티티 조인 컬럼 처리 (getEntityJoinValue 사용)
|
||||
const value = getEntityJoinValue(item, col.name);
|
||||
return [col.name, value, col.label] as [string, any, string];
|
||||
})
|
||||
.filter(([_, value]) => value !== null && value !== undefined && value !== "");
|
||||
});
|
||||
} else {
|
||||
// 설정 없으면 모든 컬럼 표시 (기존 로직)
|
||||
const summaryCount = componentConfig.rightPanel?.summaryColumnCount ?? 3;
|
||||
|
|
@ -3851,8 +3883,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
const format = colConfig?.format;
|
||||
const boldValue = colConfig?.bold ?? false;
|
||||
|
||||
// 🆕 포맷 적용 (날짜/숫자/카테고리)
|
||||
const displayValue = formatCellValue(key, value, rightCategoryMappings, format);
|
||||
// 🆕 포맷 적용 (날짜/숫자/카테고리) - null/empty는 "-"로 표시
|
||||
const displayValue = (value === null || value === undefined || value === "")
|
||||
? "-"
|
||||
: formatCellValue(key, value, rightCategoryMappings, format);
|
||||
|
||||
const showLabel = componentConfig.rightPanel?.summaryShowLabel ?? true;
|
||||
|
||||
|
|
@ -3929,8 +3963,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
const colConfig = rightColumns?.find((c) => c.name === key);
|
||||
const format = colConfig?.format;
|
||||
|
||||
// 🆕 포맷 적용 (날짜/숫자/카테고리)
|
||||
const displayValue = formatCellValue(key, value, rightCategoryMappings, format);
|
||||
// 🆕 포맷 적용 (날짜/숫자/카테고리) - null/empty는 "-"로 표시
|
||||
const displayValue = (value === null || value === undefined || value === "")
|
||||
? "-"
|
||||
: formatCellValue(key, value, rightCategoryMappings, format);
|
||||
|
||||
return (
|
||||
<tr key={key} className="hover:bg-muted">
|
||||
|
|
@ -3993,13 +4029,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
|
||||
return [col.name, value, col.label] as [string, any, string];
|
||||
})
|
||||
.filter(([key, value]) => {
|
||||
const filtered = value === null || value === undefined || value === "";
|
||||
if (filtered) {
|
||||
console.log(` ❌ 필터링됨: "${key}" (값: ${value})`);
|
||||
}
|
||||
return !filtered;
|
||||
});
|
||||
; // 설정된 컬럼은 null/empty여도 항상 표시
|
||||
|
||||
console.log(" ✅ 최종 표시할 항목:", displayEntries.length, "개");
|
||||
} else {
|
||||
|
|
@ -4017,7 +4047,9 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
<div className="text-muted-foreground mb-1 text-xs font-semibold tracking-wide uppercase">
|
||||
{label || getColumnLabel(key)}
|
||||
</div>
|
||||
<div className="text-sm">{String(value)}</div>
|
||||
<div className="text-sm">
|
||||
{(value === null || value === undefined || value === "") ? <span className="text-muted-foreground">-</span> : String(value)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue