feat: 화면 기본 테이블 업데이트 기능 추가

- 화면의 기본 테이블을 업데이트하는 기능을 추가하였습니다. 사용자가 선택한 테이블 이름을 화면 레이아웃에 저장하고, 해당 정보를 기반으로 데이터베이스의 화면 정의를 업데이트합니다.
- 관련된 로그 메시지를 추가하여 업데이트 성공 여부를 콘솔에 기록하도록 하였습니다.
- 화면 디자인에서 현재 선택된 테이블을 기본 테이블로 설정하는 로직을 포함하였습니다.
This commit is contained in:
kjs 2026-01-27 09:44:26 +09:00
parent 1753822211
commit 589f5b9222
3 changed files with 408 additions and 1 deletions

View File

@ -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]);

View File

@ -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()`

View File

@ -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 () => {