feat: 수정 모드 UPSERT 기능 구현
- SelectedItemsDetailInput 컴포넌트 수정 모드 지원 - 그룹화된 데이터 UPSERT API 추가 (/api/data/upsert-grouped) - 부모 키 기준으로 기존 레코드 조회 후 INSERT/UPDATE/DELETE - 각 레코드의 모든 필드 조합을 고유 키로 사용 - created_date 보존 (UPDATE 시) - 수정 모드에서 groupByColumns 기준으로 관련 레코드 조회 - 날짜 타입 ISO 형식 자동 감지 및 포맷팅 (YYYY.MM.DD) 주요 변경사항: - backend: dataService.upsertGroupedRecords() 메서드 구현 - backend: dataRoutes POST /api/data/upsert-grouped 엔드포인트 추가 - frontend: ScreenModal에서 groupByColumns 파라미터 전달 - frontend: SelectedItemsDetailInput 수정 모드 로직 추가 - frontend: 날짜 필드 타임존 제거 및 포맷팅 개선
This commit is contained in:
@@ -81,18 +81,18 @@ export class EntityJoinService {
|
||||
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,
|
||||
});
|
||||
}
|
||||
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,
|
||||
@@ -214,8 +214,14 @@ export class EntityJoinService {
|
||||
): { query: string; aliasMap: Map<string, string> } {
|
||||
try {
|
||||
// 기본 SELECT 컬럼들 (TEXT로 캐스팅하여 record 타입 오류 방지)
|
||||
// "*"는 특별 처리: AS 없이 그냥 main.*만
|
||||
const baseColumns = selectColumns
|
||||
.map((col) => `main.${col}::TEXT AS ${col}`)
|
||||
.map((col) => {
|
||||
if (col === "*") {
|
||||
return "main.*";
|
||||
}
|
||||
return `main.${col}::TEXT AS ${col}`;
|
||||
})
|
||||
.join(", ");
|
||||
|
||||
// Entity 조인 컬럼들 (COALESCE로 NULL을 빈 문자열로 처리)
|
||||
@@ -255,7 +261,9 @@ export class EntityJoinService {
|
||||
// 같은 테이블이라도 sourceColumn이 다르면 별도 별칭 생성 (table_column_category_values 대응)
|
||||
const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
|
||||
aliasMap.set(aliasKey, alias);
|
||||
logger.info(`🔧 별칭 생성: ${config.referenceTable}.${config.sourceColumn} → ${alias}`);
|
||||
logger.info(
|
||||
`🔧 별칭 생성: ${config.referenceTable}.${config.sourceColumn} → ${alias}`
|
||||
);
|
||||
});
|
||||
|
||||
const joinColumns = joinConfigs
|
||||
@@ -266,64 +274,48 @@ export class EntityJoinService {
|
||||
config.displayColumn,
|
||||
];
|
||||
const separator = config.separator || " - ";
|
||||
|
||||
|
||||
// 결과 컬럼 배열 (aliasColumn + _label 필드)
|
||||
const resultColumns: string[] = [];
|
||||
|
||||
if (displayColumns.length === 0 || !displayColumns[0]) {
|
||||
// displayColumns가 빈 배열이거나 첫 번째 값이 null/undefined인 경우
|
||||
// 조인 테이블의 referenceColumn을 기본값으로 사용
|
||||
resultColumns.push(`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];
|
||||
const isJoinTableColumn = [
|
||||
"dept_name",
|
||||
"dept_code",
|
||||
"master_user_id",
|
||||
"location_name",
|
||||
"parent_dept_code",
|
||||
"master_sabun",
|
||||
"location",
|
||||
"data_type",
|
||||
"company_name",
|
||||
"sales_yn",
|
||||
"status",
|
||||
"value_label", // table_column_category_values
|
||||
"user_name", // user_info
|
||||
].includes(col);
|
||||
|
||||
// ✅ 개선: referenceTable이 설정되어 있으면 조인 테이블에서 가져옴
|
||||
// 이렇게 하면 item_info.size, item_info.material 등 모든 조인 테이블 컬럼 지원
|
||||
const isJoinTableColumn =
|
||||
config.referenceTable && config.referenceTable !== tableName;
|
||||
|
||||
if (isJoinTableColumn) {
|
||||
resultColumns.push(`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`);
|
||||
resultColumns.push(
|
||||
`COALESCE(${alias}.${col}::TEXT, '') AS ${config.sourceColumn}_label`
|
||||
);
|
||||
} else {
|
||||
resultColumns.push(`COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}`);
|
||||
resultColumns.push(
|
||||
`COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 여러 컬럼인 경우 CONCAT으로 연결
|
||||
// 기본 테이블과 조인 테이블의 컬럼을 구분해서 처리
|
||||
const concatParts = displayColumns
|
||||
.map((col) => {
|
||||
// 조인 테이블의 컬럼인지 확인 (조인 테이블에 존재하는 컬럼만 조인 별칭 사용)
|
||||
// 현재는 dept_info 테이블의 컬럼들을 확인
|
||||
const isJoinTableColumn = [
|
||||
"dept_name",
|
||||
"dept_code",
|
||||
"master_user_id",
|
||||
"location_name",
|
||||
"parent_dept_code",
|
||||
"master_sabun",
|
||||
"location",
|
||||
"data_type",
|
||||
"company_name",
|
||||
"sales_yn",
|
||||
"status",
|
||||
"value_label", // table_column_category_values
|
||||
"user_name", // user_info
|
||||
].includes(col);
|
||||
// ✅ 개선: referenceTable이 설정되어 있으면 조인 테이블에서 가져옴
|
||||
const isJoinTableColumn =
|
||||
config.referenceTable && config.referenceTable !== tableName;
|
||||
|
||||
if (isJoinTableColumn) {
|
||||
// 조인 테이블 컬럼은 조인 별칭 사용
|
||||
@@ -337,7 +329,7 @@ export class EntityJoinService {
|
||||
|
||||
resultColumns.push(`(${concatParts}) AS ${config.aliasColumn}`);
|
||||
}
|
||||
|
||||
|
||||
// 모든 resultColumns를 반환
|
||||
return resultColumns.join(", ");
|
||||
})
|
||||
@@ -356,13 +348,13 @@ export class EntityJoinService {
|
||||
.map((config) => {
|
||||
const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
|
||||
const alias = aliasMap.get(aliasKey);
|
||||
|
||||
|
||||
// table_column_category_values는 특별한 조인 조건 필요 (회사별 필터링만)
|
||||
if (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}' 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}`;
|
||||
})
|
||||
.join("\n");
|
||||
@@ -424,7 +416,7 @@ export class EntityJoinService {
|
||||
}
|
||||
|
||||
// table_column_category_values는 특수 조인 조건이 필요하므로 캐시 불가
|
||||
if (config.referenceTable === 'table_column_category_values') {
|
||||
if (config.referenceTable === "table_column_category_values") {
|
||||
logger.info(
|
||||
`🎯 table_column_category_values는 캐시 전략 불가: ${config.sourceColumn}`
|
||||
);
|
||||
@@ -578,13 +570,13 @@ export class EntityJoinService {
|
||||
.map((config) => {
|
||||
const aliasKey = `${config.referenceTable}:${config.sourceColumn}`;
|
||||
const alias = aliasMap.get(aliasKey);
|
||||
|
||||
|
||||
// table_column_category_values는 특별한 조인 조건 필요 (회사별 필터링만)
|
||||
if (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}' 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}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
Reference in New Issue
Block a user