From 10526da1ac9369ce2e0339bfa902b40f102a40ea Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 21 Nov 2025 15:58:00 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=8D=AE=EC=96=B4=EC=93=B0=EA=B8=B0=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 동작: - 카테고리 컬럼 매핑: 중복 시 스킵 - 카테고리 값: 중복 시 스킵 - 결과: 일부 값만 복사되어 불완전 새로운 동작 (덮어쓰기): - 카테고리 컬럼 매핑: 기존 것 삭제 후 재생성 - 카테고리 값: 테이블+컬럼 단위로 기존 것 전체 삭제 후 재생성 - 부모-자식 관계는 유지 (depth 순으로 정렬 후 복사) 장점: 1. 메뉴 재복사 시 항상 최신 카테고리 설정으로 덮어씀 2. 누락된 값 없이 완전한 복사 보장 3. 테스트 시 기존 데이터 정리 불필요 주의사항: - 기존 카테고리 값이 다른 데이터에서 참조되는 경우 외래키 제약조건 위반 가능 - 실무에서는 사용자 선택 옵션(덮어쓰기/병합)을 추가하는 것이 안전 관련 파일: - backend-node/src/services/menuCopyService.ts 테스트: - COMPANY_11로 재복사 시 모든 카테고리 값 정상 복사됨 --- backend-node/src/services/menuCopyService.ts | 62 ++++++++------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/backend-node/src/services/menuCopyService.ts b/backend-node/src/services/menuCopyService.ts index 7187dd2e..3471cdad 100644 --- a/backend-node/src/services/menuCopyService.ts +++ b/backend-node/src/services/menuCopyService.ts @@ -1610,25 +1610,19 @@ export class MenuCopyService { let mappingCount = 0; let valueCount = 0; - // 1) 카테고리 컬럼 매핑 복사 + // 1) 카테고리 컬럼 매핑 복사 (덮어쓰기 모드) for (const mapping of settings.columnMappings) { const newMenuObjid = menuIdMap.get(mapping.menu_objid); if (!newMenuObjid) continue; - // 중복 체크 - const existsResult = await client.query( - `SELECT mapping_id FROM category_column_mapping + // 기존 매핑 삭제 (덮어쓰기) + await client.query( + `DELETE FROM category_column_mapping WHERE table_name = $1 AND physical_column_name = $2 AND company_code = $3`, [mapping.table_name, mapping.physical_column_name, targetCompanyCode] ); - if (existsResult.rows.length > 0) { - logger.debug( - ` ⏭️ 카테고리 매핑 이미 존재: ${mapping.table_name}.${mapping.physical_column_name}` - ); - continue; - } - + // 새 매핑 추가 await client.query( `INSERT INTO category_column_mapping ( table_name, logical_column_name, physical_column_name, @@ -1648,38 +1642,34 @@ export class MenuCopyService { mappingCount++; } - // 2) 테이블 컬럼 카테고리 값 복사 (부모-자식 관계 유지) + // 2) 테이블 컬럼 카테고리 값 복사 (덮어쓰기 모드, 부모-자식 관계 유지) const sortedValues = settings.categoryValues.sort( (a, b) => a.depth - b.depth ); - let skippedValues = 0; + // 먼저 기존 값들을 모두 삭제 (테이블+컬럼 단위) + const uniqueTableColumns = new Set(); + for (const value of sortedValues) { + uniqueTableColumns.add(`${value.table_name}:${value.column_name}`); + } + + for (const tableColumn of uniqueTableColumns) { + const [tableName, columnName] = tableColumn.split(":"); + await client.query( + `DELETE FROM table_column_category_values + WHERE table_name = $1 AND column_name = $2 AND company_code = $3`, + [tableName, columnName, targetCompanyCode] + ); + logger.debug( + ` 🗑️ 기존 카테고리 값 삭제: ${tableName}.${columnName}` + ); + } + + // 새 값 추가 for (const value of sortedValues) { const newMenuObjid = menuIdMap.get(value.menu_objid); if (!newMenuObjid) continue; - // 중복 체크 - const existsResult = await client.query( - `SELECT value_id FROM table_column_category_values - WHERE table_name = $1 AND column_name = $2 AND value_code = $3 AND company_code = $4`, - [ - value.table_name, - value.column_name, - value.value_code, - targetCompanyCode, - ] - ); - - if (existsResult.rows.length > 0) { - skippedValues++; - logger.debug( - ` ⏭️ 카테고리 값 이미 존재: ${value.table_name}.${value.column_name}.${value.value_code}` - ); - // 기존 값의 ID를 매핑에 저장 (자식 항목의 parent_id 재매핑용) - valueIdMap.set(value.value_id, existsResult.rows[0].value_id); - continue; - } - // 부모 ID 재매핑 let newParentValueId = null; if (value.parent_value_id) { @@ -1721,7 +1711,7 @@ export class MenuCopyService { } logger.info( - `✅ 카테고리 설정 복사 완료: 컬럼 매핑 ${mappingCount}개, 카테고리 값 ${valueCount}개 (${skippedValues}개 스킵)` + `✅ 카테고리 설정 복사 완료: 컬럼 매핑 ${mappingCount}개, 카테고리 값 ${valueCount}개 (덮어쓰기)` ); }