feat: enhance data mapping and entity join handling in components

- Updated ButtonPrimaryComponent to utilize entity join metadata for improved data mapping.
- Introduced getEntityJoinColumns method in TableListComponent to retrieve entity join column metadata.
- Enhanced applyMappingRules function to support optional entity join columns, allowing for more flexible data resolution.
- Added utility functions to build join alias maps and resolve values from entity joins, improving data handling capabilities.

These enhancements aim to provide a more robust and dynamic data mapping experience, facilitating better integration of entity relationships in the application.
This commit is contained in:
kjs 2026-03-17 23:27:39 +09:00
parent 2772c2296c
commit a6aa57fece
4 changed files with 104 additions and 8 deletions

View File

@ -938,8 +938,14 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
effectiveMappingRules = multiTableMappings[0]?.mappingRules || [];
}
// 소스 DataProvider에서 엔티티 조인 메타데이터 가져오기
const entityJoinColumns = sourceProvider?.getEntityJoinColumns?.() || [];
if (entityJoinColumns.length > 0) {
console.log(`🔗 [ButtonPrimary] 엔티티 조인 메타데이터 ${entityJoinColumns.length}개 감지`, entityJoinColumns);
}
const mappedData = sourceData.map((row) => {
const mappedRow = applyMappingRules(row, effectiveMappingRules);
const mappedRow = applyMappingRules(row, effectiveMappingRules, entityJoinColumns);
return {
...mappedRow,

View File

@ -1164,6 +1164,17 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
setSelectedRows(new Set());
setIsAllSelected(false);
},
getEntityJoinColumns: () => {
return (tableConfig.columns || [])
.filter((col) => col.additionalJoinInfo)
.map((col) => ({
sourceTable: col.additionalJoinInfo!.sourceTable || tableConfig.selectedTable,
sourceColumn: col.additionalJoinInfo!.sourceColumn,
joinAlias: col.additionalJoinInfo!.joinAlias,
referenceTable: col.additionalJoinInfo!.referenceTable,
}));
},
};
// DataReceivable 인터페이스 구현

View File

@ -8,21 +8,27 @@ import type {
Condition,
TransformFunction,
} from "@/types/screen-embedding";
import type { EntityJoinColumnMeta } from "@/types/data-transfer";
import { logger } from "./logger";
/**
*
* @param data
* @param rules
* @param entityJoinColumns () - sourceField alias에서
* @returns
*/
export function applyMappingRules(data: any[] | any, rules: MappingRule[]): any[] {
export function applyMappingRules(
data: any[] | any,
rules: MappingRule[],
entityJoinColumns?: EntityJoinColumnMeta[],
): any[] {
// 빈 데이터 처리
if (!data) {
return [];
}
// 🆕 배열이 아닌 경우 배열로 변환
// 배열이 아닌 경우 배열로 변환
const dataArray = Array.isArray(data) ? data : [data];
if (dataArray.length === 0) {
@ -42,22 +48,34 @@ export function applyMappingRules(data: any[] | any, rules: MappingRule[]): any[
return [applyTransformRules(dataArray, rules)];
}
// 엔티티 조인 alias 역방향 맵 구성: { referenceColumn → joinAlias }
// ex) joinAlias "part_code_item_name" → sourceColumn "part_code", referenceColumn "item_name"
const joinAliasMap = buildJoinAliasMap(entityJoinColumns);
// 일반 매핑 (각 행에 대해 매핑)
// 🆕 원본 데이터를 복사한 후 매핑 규칙 적용 (매핑되지 않은 필드도 유지)
// 원본 데이터를 복사한 후 매핑 규칙 적용 (매핑되지 않은 필드도 유지)
return dataArray.map((row) => {
// 원본 데이터 복사
const mappedRow: any = { ...row };
for (const rule of rules) {
// sourceField와 targetField가 모두 있어야 매핑 적용
if (!rule.sourceField || !rule.targetField) {
continue;
}
const sourceValue = getNestedValue(row, rule.sourceField);
let sourceValue = getNestedValue(row, rule.sourceField);
// sourceField 값이 비어있으면 엔티티 조인 alias에서 해결 시도
if (isEmptyValue(sourceValue) && joinAliasMap.size > 0) {
sourceValue = resolveFromEntityJoin(row, rule.targetField, joinAliasMap);
if (sourceValue !== undefined) {
logger.info(
`[dataMapping] 엔티티 조인 해결: ${rule.sourceField}(비어있음) → targetField "${rule.targetField}" → alias에서 값 획득`,
);
}
}
const targetValue = sourceValue ?? rule.defaultValue;
// 소스 필드와 타겟 필드가 다르면 소스 필드 제거 후 타겟 필드에 설정
if (rule.sourceField !== rule.targetField) {
delete mappedRow[rule.sourceField];
}
@ -69,6 +87,50 @@ export function applyMappingRules(data: any[] | any, rules: MappingRule[]): any[
});
}
/**
* alias에서
* joinAlias : {sourceColumn}_{referenceColumn}
* : "part_code_item_name" sourceColumn="part_code", referenceColumn="item_name"
*
* Map: referenceColumn joinAlias
* : "item_name" "part_code_item_name"
*/
function buildJoinAliasMap(
entityJoinColumns?: EntityJoinColumnMeta[],
): Map<string, string> {
const map = new Map<string, string>();
if (!entityJoinColumns || entityJoinColumns.length === 0) return map;
for (const meta of entityJoinColumns) {
const prefix = `${meta.sourceColumn}_`;
if (meta.joinAlias.startsWith(prefix)) {
const referenceColumn = meta.joinAlias.slice(prefix.length);
map.set(referenceColumn, meta.joinAlias);
}
}
return map;
}
/**
* alias에서 targetField에
* targetField alias를 row에서
*/
function resolveFromEntityJoin(
row: any,
targetField: string,
joinAliasMap: Map<string, string>,
): any {
const joinAlias = joinAliasMap.get(targetField);
if (!joinAlias) return undefined;
const value = row[joinAlias];
return isEmptyValue(value) ? undefined : value;
}
function isEmptyValue(value: any): boolean {
return value === null || value === undefined || value === "";
}
/**
*
*/

View File

@ -157,6 +157,17 @@ export interface DataReceivable {
getData(): any;
}
/**
*
* FK가
*/
export interface EntityJoinColumnMeta {
sourceColumn: string;
joinAlias: string;
referenceTable: string;
sourceTable?: string;
}
/**
*
*
@ -180,5 +191,11 @@ export interface DataProvidable {
*
*/
clearSelection(): void;
/**
* ()
* alias
*/
getEntityJoinColumns?(): EntityJoinColumnMeta[];
}