From 589f5b92229ccd0e228424e9a2feaae42f1ebe3f Mon Sep 17 00:00:00 2001 From: kjs Date: Tue, 27 Jan 2026 09:44:26 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=99=94=EB=A9=B4=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 화면의 기본 테이블을 업데이트하는 기능을 추가하였습니다. 사용자가 선택한 테이블 이름을 화면 레이아웃에 저장하고, 해당 정보를 기반으로 데이터베이스의 화면 정의를 업데이트합니다. - 관련된 로그 메시지를 추가하여 업데이트 성공 여부를 콘솔에 기록하도록 하였습니다. - 화면 디자인에서 현재 선택된 테이블을 기본 테이블로 설정하는 로직을 포함하였습니다. --- .../src/services/screenManagementService.ts | 11 + docs/COLUMN_LABELS_MIGRATION_ANALYSIS.md | 392 ++++++++++++++++++ frontend/components/screen/ScreenDesigner.tsx | 6 +- 3 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 docs/COLUMN_LABELS_MIGRATION_ANALYSIS.md diff --git a/backend-node/src/services/screenManagementService.ts b/backend-node/src/services/screenManagementService.ts index 013653be..135c5555 100644 --- a/backend-node/src/services/screenManagementService.ts +++ b/backend-node/src/services/screenManagementService.ts @@ -1469,6 +1469,7 @@ export class ScreenManagementService { console.log(`컴포넌트 수: ${layoutData.components.length}`); console.log(`격자 설정:`, layoutData.gridSettings); console.log(`해상도 설정:`, layoutData.screenResolution); + console.log(`기본 테이블:`, (layoutData as any).mainTableName); // 권한 확인 const screens = await query<{ company_code: string | null }>( @@ -1486,6 +1487,16 @@ export class ScreenManagementService { throw new Error("이 화면의 레이아웃을 저장할 권한이 없습니다."); } + // 🆕 화면의 기본 테이블 업데이트 (테이블이 선택된 경우) + const mainTableName = (layoutData as any).mainTableName; + if (mainTableName) { + await query( + `UPDATE screen_definitions SET table_name = $1, updated_date = NOW() WHERE screen_id = $2`, + [mainTableName, screenId] + ); + console.log(`✅ 화면 기본 테이블 업데이트: ${mainTableName}`); + } + // 기존 레이아웃 삭제 (컴포넌트와 메타데이터 모두) await query(`DELETE FROM screen_layouts WHERE screen_id = $1`, [screenId]); diff --git a/docs/COLUMN_LABELS_MIGRATION_ANALYSIS.md b/docs/COLUMN_LABELS_MIGRATION_ANALYSIS.md new file mode 100644 index 00000000..18436c90 --- /dev/null +++ b/docs/COLUMN_LABELS_MIGRATION_ANALYSIS.md @@ -0,0 +1,392 @@ +# column_labels 테이블 제거 영향 분석 + +## 개요 + +현재 시스템은 컬럼 메타데이터를 **두 개의 테이블**에서 관리하고 있습니다: +- `column_labels`: 레거시 테이블 (회사코드 없음, 공통 데이터) +- `table_type_columns`: 새 테이블 (회사코드 있음, 멀티테넌시 지원) + +이 문서는 `column_labels` 테이블을 제거하고 `table_type_columns`로 통합할 때의 영향을 분석합니다. + +--- + +## 1. 두 테이블 스키마 비교 + +### column_labels (레거시) + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | INTEGER | PK | +| table_name | VARCHAR | 테이블명 | +| column_name | VARCHAR | 컬럼명 | +| column_label | VARCHAR | 한글 라벨 | +| web_type | VARCHAR | 레거시 (사용 안 함) | +| input_type | VARCHAR | 입력 타입 | +| detail_settings | TEXT | 상세 설정 (JSON) | +| description | TEXT | 설명 | +| display_order | INTEGER | 표시 순서 | +| is_visible | BOOLEAN | 표시 여부 | +| code_category | VARCHAR | 코드 카테고리 | +| code_value | VARCHAR | 코드 값 | +| reference_table | VARCHAR | 참조 테이블 | +| reference_column | VARCHAR | 참조 컬럼 | +| display_column | VARCHAR | 표시 컬럼 | +| created_date | TIMESTAMP | 생성일 | +| updated_date | TIMESTAMP | 수정일 | + +**특징**: `company_code` 없음 → 멀티테넌시 불가 + +### table_type_columns (신규) + +| 컬럼 | 타입 | 설명 | +|------|------|------| +| id | INTEGER | PK | +| table_name | VARCHAR | 테이블명 | +| column_name | VARCHAR | 컬럼명 | +| input_type | VARCHAR | 입력 타입 | +| detail_settings | TEXT | 상세 설정 (JSON) | +| is_nullable | VARCHAR | NULL 허용 | +| display_order | INTEGER | 표시 순서 | +| company_code | VARCHAR | **회사 코드** | +| created_date | TIMESTAMP | 생성일 | +| updated_date | TIMESTAMP | 수정일 | + +**특징**: `company_code` 있음 → 멀티테넌시 지원 + +### 누락된 컬럼 (table_type_columns에 추가 필요) + +| 컬럼 | 용도 | +|------|------| +| column_label | 한글 라벨 | +| description | 설명 | +| is_visible | 표시 여부 | +| code_category | 코드 카테고리 | +| code_value | 코드 값 | +| reference_table | 참조 테이블 (엔티티) | +| reference_column | 참조 컬럼 | +| display_column | 표시 컬럼 | + +--- + +## 2. 영향 받는 파일 목록 + +### 백엔드 파일 (16개, 87회 참조) + +| 파일 | 참조 횟수 | 영향도 | 용도 | +|------|----------|--------|------| +| `tableManagementService.ts` | 21회 | 🔴 매우 높음 | 컬럼 조회/저장/업데이트 핵심 로직 | +| `screenGroupController.ts` | 13회 | 🟡 중간 | 화면 그룹/메뉴 동기화 시 라벨 조회 | +| `masterDetailExcelService.ts` | 7회 | 🟡 중간 | 엑셀 다운로드 시 엔티티 관계 조회 | +| `ddlExecutionService.ts` | 7회 | 🟡 중간 | 테이블 생성/삭제 시 메타데이터 등록 | +| `tableManagementController.ts` | 7회 | 🟡 중간 | API 엔드포인트 | +| `screenManagementService.ts` | 5회 | 🔴 높음 | 화면에서 컬럼 라벨 조회 | +| `entityJoinService.ts` | 4회 | 🔴 높음 | 엔티티 조인 관계 감지 | +| `entityReferenceController.ts` | 4회 | 🟡 중간 | 엔티티 참조 데이터 조회 | +| `adminController.ts` | 3회 | 🟢 낮음 | 엑셀 업로드 컬럼 매핑 | +| `dataService.ts` | 3회 | 🟢 낮음 | 컬럼 라벨 조회 | +| `flowController.ts` | 3회 | 🟢 낮음 | 플로우 컬럼 라벨 조회 | +| `categoryTreeService.ts` | 1회 | 🟢 낮음 | 카테고리 라벨 조회 | +| `tableManagementRoutes.ts` | 1회 | 🟢 낮음 | 라우트 주석 | +| `multiConnectionQueryService.ts` | 1회 | 🟢 낮음 | 멀티 연결 쿼리 | +| `migrate-input-type-to-web-type.ts` | 6회 | 🟢 낮음 | 마이그레이션 스크립트 | +| `types/ddl.ts` | 1회 | 🟢 낮음 | 타입 정의 | + +### 프론트엔드 파일 (15개, 20회 참조) + +| 파일 | 참조 횟수 | 영향도 | 용도 | +|------|----------|--------|------| +| `UnifiedRepeater.tsx` | 3회 | 🟢 낮음 | 타입 주석 | +| `ScreenDesigner.tsx` | 2회 | 🟢 낮음 | 타입 주석 | +| `ButtonConfigPanel.tsx` | 2회 | 🟢 낮음 | 타입 주석 | +| `ScreenRelationFlow.tsx` | 2회 | 🟢 낮음 | 타입 주석 | +| `buttonActions.ts` | 1회 | 🟢 낮음 | 주석 | +| `webTypeMapping.ts` | 1회 | 🟢 낮음 | 주석 | +| `screenGroup.ts` | 1회 | 🟢 낮음 | API 타입 | +| `tableManagement.ts` | 1회 | 🟢 낮음 | API 타입 | +| `TableSettingModal.tsx` | 1회 | 🟢 낮음 | 주석 | +| `tableSchema.ts` | 1회 | 🟢 낮음 | 타입 | +| `types/ddl.ts` | 1회 | 🟢 낮음 | 타입 정의 | +| `ControlConditionStep.tsx` | 1회 | 🟢 낮음 | 주석 | +| `ActionConditionBuilder.tsx` | 1회 | 🟢 낮음 | 주석 | +| `WebTypeInput.tsx` | 1회 | 🟢 낮음 | 주석 | +| `types/multiConnection.ts` | 1회 | 🟢 낮음 | 타입 | + +--- + +## 3. 주요 사용 패턴 분석 + +### 패턴 1: 컬럼 조회 (가장 많음) + +```sql +-- 현재 방식: column_labels + table_type_columns 조인 +SELECT + c.column_name, + COALESCE(cl.column_label, c.column_name) as "displayName", + COALESCE(ttc.input_type, cl.input_type, 'text') as "inputType", + COALESCE(ttc.detail_settings, cl.detail_settings) as "detailSettings", + cl.reference_table as "referenceTable" -- ❌ 문제: ttc의 detailSettings 무시 +FROM information_schema.columns c +LEFT JOIN column_labels cl ON c.table_name = cl.table_name AND c.column_name = cl.column_name +LEFT JOIN table_type_columns ttc ON c.table_name = ttc.table_name + AND c.column_name = ttc.column_name + AND ttc.company_code = $company_code +``` + +**문제점**: `referenceTable`이 `column_labels`에서만 조회됨 → 회사별 엔티티 설정 무시 + +### 패턴 2: 컬럼 저장 (이중 저장) + +```typescript +// 현재: 두 테이블에 모두 저장 +await query(`INSERT INTO column_labels (...) VALUES (...) ON CONFLICT ... DO UPDATE ...`); +await this.updateColumnInputType(...); // table_type_columns에도 저장 +``` + +**문제점**: 데이터 불일치 가능성, 유지보수 어려움 + +### 패턴 3: 엔티티 관계 조회 + +```sql +SELECT column_name, reference_table, reference_column +FROM column_labels +WHERE table_name = $1 AND input_type = 'entity' +``` + +**문제점**: 회사별 엔티티 설정 무시 (column_labels에 company_code 없음) + +--- + +## 4. 마이그레이션 계획 + +### Phase 1: 스키마 확장 (table_type_columns) + +```sql +-- 마이그레이션 파일: 044_extend_table_type_columns.sql + +-- 1. 누락된 컬럼 추가 +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS column_label VARCHAR(200); +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS description TEXT; +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS is_visible BOOLEAN DEFAULT true; +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS code_category VARCHAR(100); +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS code_value VARCHAR(100); +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS reference_table VARCHAR(100); +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS reference_column VARCHAR(100); +ALTER TABLE table_type_columns ADD COLUMN IF NOT EXISTS display_column VARCHAR(100); + +-- 2. 인덱스 추가 +CREATE INDEX IF NOT EXISTS idx_ttc_reference_table ON table_type_columns(reference_table); +CREATE INDEX IF NOT EXISTS idx_ttc_input_type ON table_type_columns(input_type); +``` + +### Phase 2: 데이터 마이그레이션 + +```sql +-- column_labels 데이터를 table_type_columns로 이관 (company_code = '*' 로 공통 데이터) +INSERT INTO table_type_columns ( + table_name, column_name, input_type, detail_settings, + column_label, description, is_visible, code_category, code_value, + reference_table, reference_column, display_column, display_order, + company_code, created_date, updated_date +) +SELECT + table_name, column_name, + COALESCE(input_type, 'text'), + detail_settings, + column_label, + description, + COALESCE(is_visible, true), + code_category, + code_value, + reference_table, + reference_column, + display_column, + display_order, + '*', -- 공통 데이터 (회사별 설정 없으면 폴백) + COALESCE(created_date, NOW()), + COALESCE(updated_date, NOW()) +FROM column_labels +ON CONFLICT (table_name, column_name, company_code) +DO UPDATE SET + column_label = COALESCE(EXCLUDED.column_label, table_type_columns.column_label), + description = COALESCE(EXCLUDED.description, table_type_columns.description), + reference_table = COALESCE(EXCLUDED.reference_table, table_type_columns.reference_table), + reference_column = COALESCE(EXCLUDED.reference_column, table_type_columns.reference_column), + display_column = COALESCE(EXCLUDED.display_column, table_type_columns.display_column), + updated_date = NOW(); +``` + +### Phase 3: 코드 수정 + +#### 3.1 조회 쿼리 변경 패턴 + +```sql +-- 변경 전 +SELECT ... FROM column_labels WHERE table_name = $1 + +-- 변경 후 (회사코드 폴백 포함) +SELECT * FROM ( + SELECT *, + ROW_NUMBER() OVER ( + PARTITION BY table_name, column_name + ORDER BY CASE WHEN company_code = $company_code THEN 0 ELSE 1 END + ) as rn + FROM table_type_columns + WHERE table_name = $1 + AND company_code IN ($company_code, '*') +) ranked +WHERE rn = 1 +``` + +#### 3.2 저장 쿼리 변경 패턴 + +```sql +-- 변경 전: 두 테이블에 저장 +INSERT INTO column_labels (...) ...; +INSERT INTO table_type_columns (...) ...; + +-- 변경 후: 하나의 테이블만 +INSERT INTO table_type_columns ( + table_name, column_name, input_type, detail_settings, + column_label, description, is_visible, code_category, code_value, + reference_table, reference_column, display_column, display_order, + company_code, created_date, updated_date +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW(), NOW()) +ON CONFLICT (table_name, column_name, company_code) +DO UPDATE SET ...; +``` + +### Phase 4: 레거시 코드 정리 + +1. `column_labels` 관련 INSERT/UPDATE 제거 +2. `column_labels` LEFT JOIN을 `table_type_columns`로 변경 +3. 프론트엔드 주석/타입 업데이트 + +### Phase 5: 테이블 삭제 (최종) + +```sql +-- 모든 코드 마이그레이션 완료 후 +DROP TABLE IF EXISTS column_labels; +``` + +--- + +## 5. 수정해야 할 파일 상세 + +### 🔴 우선순위 1: 핵심 서비스 (3개) + +#### tableManagementService.ts (21개 쿼리) + +| 함수 | 라인 | 수정 내용 | +|------|------|----------| +| `checkCodeTypeColumn` | 30-36 | column_labels → table_type_columns | +| `getColumnList` | 215-218, 257-258 | JOIN 변경 + referenceTable 추출 | +| `updateColumnSettings` | 460-494 | column_labels INSERT 제거 | +| `getColumnLabels` | 670-673 | table_type_columns로 변경 | +| `setColumnInputType` | 735-740 | column_labels INSERT 제거 | +| `getFileColumns` | 1288 | table_type_columns로 변경 | +| `getColumnMetaInfo` | 1956 | table_type_columns로 변경 | +| `findEntityRelation` | 3579-3590 | table_type_columns로 변경 | +| `upsertColumnLabel` | 3723 | table_type_columns로 변경 | +| `getColumnInputTypes` | 4129 | 이미 table_type_columns 사용 중 ✅ | +| `detectEntityRelation` | 4810, 4838 | table_type_columns로 변경 | + +#### screenManagementService.ts (5개 쿼리) + +| 함수 | 라인 | 수정 내용 | +|------|------|----------| +| `getTableColumns` | 1279 | table_type_columns로 변경 | +| `getTableColumns` | 1334 | 라벨 추가 로직 수정 | +| `getColumnInfo` | 2083 | table_type_columns로 변경 | +| `saveColumnSettings` | 2104 | table_type_columns로 변경 | + +#### entityJoinService.ts (4개 쿼리) + +| 함수 | 라인 | 수정 내용 | +|------|------|----------| +| `detectEntityColumns` | 36 | table_type_columns로 변경 | +| `getTableColumns` | 755 | table_type_columns로 변경 | + +### 🟡 우선순위 2: 보조 서비스 (5개) + +| 파일 | 수정 쿼리 수 | 수정 내용 | +|------|-------------|----------| +| `screenGroupController.ts` | 13개 | 라벨 조회, FK 조회 쿼리 변경 | +| `masterDetailExcelService.ts` | 7개 | 엔티티 관계 조회 변경 | +| `ddlExecutionService.ts` | 7개 | 테이블 생성 시 메타데이터 등록 변경 | +| `entityReferenceController.ts` | 4개 | 참조 데이터 조회 변경 | +| `adminController.ts` | 3개 | 스키마 조회 변경 | + +### 🟢 우선순위 3: 기타 (8개) + +| 파일 | 수정 내용 | +|------|----------| +| `dataService.ts` | 라벨 조회 변경 | +| `flowController.ts` | 라벨 조회 변경 | +| `categoryTreeService.ts` | JOIN 변경 | +| `tableManagementRoutes.ts` | 주석 수정 | +| `multiConnectionQueryService.ts` | 주석/타입 수정 | +| `migrate-input-type-to-web-type.ts` | 마이그레이션 스크립트 (이미 실행됨, 삭제 가능) | +| `types/ddl.ts` | 타입 정의 수정 | +| 프론트엔드 15개 파일 | 주석/타입 수정 | + +--- + +## 6. 예상 작업 시간 + +| 단계 | 작업 | 예상 시간 | +|------|------|----------| +| Phase 1 | 스키마 확장 (마이그레이션 SQL) | 30분 | +| Phase 2 | 데이터 마이그레이션 (SQL) | 30분 | +| Phase 3.1 | tableManagementService.ts 수정 | 2시간 | +| Phase 3.2 | screenManagementService.ts 수정 | 1시간 | +| Phase 3.3 | entityJoinService.ts 수정 | 30분 | +| Phase 3.4 | 보조 서비스 5개 수정 | 2시간 | +| Phase 3.5 | 기타 파일 8개 수정 | 1시간 | +| Phase 4 | 테스트 및 검증 | 2시간 | +| Phase 5 | column_labels 삭제 | 10분 | +| **합계** | | **약 10시간** | + +--- + +## 7. 리스크 및 주의사항 + +### 높은 리스크 + +1. **데이터 불일치**: 마이그레이션 중 column_labels와 table_type_columns 데이터 충돌 가능 +2. **회사코드 폴백 로직 복잡성**: 모든 조회에 `company_code IN ($code, '*')` + 우선순위 필요 +3. **기존 운영 데이터 손실**: 마이그레이션 실수 시 column_labels 데이터 유실 가능 + +### 완화 방안 + +1. **단계적 마이그레이션**: column_labels는 당분간 유지, 조회만 table_type_columns 우선으로 변경 +2. **폴백 헬퍼 함수**: 회사코드 폴백 로직을 공통 함수로 추출 +3. **백업 필수**: 마이그레이션 전 column_labels 전체 백업 + +--- + +## 8. 결론 + +### 현재 문제점 + +1. **이중 관리**: 같은 데이터가 두 테이블에 저장됨 +2. **멀티테넌시 불완전**: `referenceTable` 등이 `column_labels`에서만 조회되어 회사별 설정 무시 +3. **유지보수 어려움**: 변경 시 두 곳 모두 수정 필요 + +### 권장 방향 + +**장기적으로 `table_type_columns`로 통합 권장** + +하지만 작업량이 상당하므로: + +1. **단기 (즉시)**: 조회 시 `detailSettings`에서 `referenceTable` 우선 추출하도록 수정 +2. **중기 (1-2주)**: `table_type_columns` 스키마 확장 + 데이터 마이그레이션 +3. **장기 (한 달)**: 모든 코드 수정 후 `column_labels` 제거 + +--- + +## 참고 자료 + +- `table_type_columns` 관련 마이그레이션: `db/migrations/030_create_table_type_columns.sql` +- 테이블 타입 관리 UI: `frontend/app/(main)/admin/systemMng/tableMngList/page.tsx` +- 컬럼 조회 핵심 로직: `backend-node/src/services/tableManagementService.ts:getColumnList()` + diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index c8941515..2e7a5a06 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -1650,10 +1650,14 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD }); // 해상도 정보를 포함한 레이아웃 데이터 생성 + // 현재 선택된 테이블을 화면의 기본 테이블로 저장 + const currentMainTableName = tables.length > 0 ? tables[0].tableName : null; + const layoutWithResolution = { ...layout, components: updatedComponents, screenResolution: screenResolution, + mainTableName: currentMainTableName, // 화면의 기본 테이블 }; // 🔍 버튼 컴포넌트들의 action.type 확인 const buttonComponents = layoutWithResolution.components.filter( @@ -1687,7 +1691,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD } finally { setIsSaving(false); } - }, [selectedScreen, layout, screenResolution]); + }, [selectedScreen, layout, screenResolution, tables]); // 다국어 자동 생성 핸들러 const handleGenerateMultilang = useCallback(async () => {