362 lines
9.7 KiB
Markdown
362 lines
9.7 KiB
Markdown
|
|
# 카테고리 컴포넌트 DB 호환성 분석 및 수정
|
||
|
|
|
||
|
|
> **작성일**: 2025-11-04
|
||
|
|
> **상태**: 호환성 문제 발견 및 수정 완료
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 발견된 호환성 문제
|
||
|
|
|
||
|
|
### 1. 테이블명 불일치
|
||
|
|
|
||
|
|
| 예상 테이블명 | 실제 테이블명 | 상태 |
|
||
|
|
|--------------|--------------|------|
|
||
|
|
| `table_columns` | `table_type_columns` | ❌ 불일치 |
|
||
|
|
| `company_info` | `company_mng` | ❌ 불일치 |
|
||
|
|
|
||
|
|
### 2. 컬럼명 불일치
|
||
|
|
|
||
|
|
#### table_type_columns 테이블
|
||
|
|
|
||
|
|
| 예상 컬럼명 | 실제 컬럼명 | 상태 |
|
||
|
|
|------------|------------|------|
|
||
|
|
| `column_label` | 존재하지 않음 | ❌ 불일치 |
|
||
|
|
| `web_type` | `input_type` | ❌ 불일치 |
|
||
|
|
| `column_order` | `display_order` | ❌ 불일치 |
|
||
|
|
| `company_code` | 존재하지 않음 | ❌ 불일치 |
|
||
|
|
|
||
|
|
**실제 table_type_columns 구조**:
|
||
|
|
```sql
|
||
|
|
- id (integer, PK)
|
||
|
|
- table_name (varchar(255), NOT NULL)
|
||
|
|
- column_name (varchar(255), NOT NULL)
|
||
|
|
- input_type (varchar(50), NOT NULL, DEFAULT 'text')
|
||
|
|
- detail_settings (text)
|
||
|
|
- is_nullable (varchar(10), DEFAULT 'Y')
|
||
|
|
- display_order (integer, DEFAULT 0)
|
||
|
|
- created_date (timestamp, DEFAULT now())
|
||
|
|
- updated_date (timestamp, DEFAULT now())
|
||
|
|
```
|
||
|
|
|
||
|
|
#### company_mng 테이블
|
||
|
|
|
||
|
|
| 예상 컬럼명 | 실제 컬럼명 | 상태 |
|
||
|
|
|------------|------------|------|
|
||
|
|
| `company_code` | `company_code` | ✅ 일치 |
|
||
|
|
| `company_name` | `company_name` | ✅ 일치 |
|
||
|
|
|
||
|
|
**실제 company_mng 구조**:
|
||
|
|
```sql
|
||
|
|
- company_code (varchar(32), PK)
|
||
|
|
- company_name (varchar(64))
|
||
|
|
- writer (varchar(32))
|
||
|
|
- regdate (timestamp)
|
||
|
|
- status (varchar(32))
|
||
|
|
- business_registration_number (varchar(20))
|
||
|
|
- representative_name (varchar(100))
|
||
|
|
- representative_phone (varchar(20))
|
||
|
|
- email (varchar(255))
|
||
|
|
- website (varchar(500))
|
||
|
|
- address (varchar(500))
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 적용된 수정사항
|
||
|
|
|
||
|
|
### 1. 마이그레이션 파일 수정
|
||
|
|
|
||
|
|
**파일**: `db/migrations/036_create_table_column_category_values.sql`
|
||
|
|
|
||
|
|
**변경사항**:
|
||
|
|
```sql
|
||
|
|
-- 변경 전
|
||
|
|
CONSTRAINT fk_category_value_company FOREIGN KEY (company_code)
|
||
|
|
REFERENCES company_info(company_code),
|
||
|
|
|
||
|
|
-- 변경 후
|
||
|
|
CONSTRAINT fk_category_value_company FOREIGN KEY (company_code)
|
||
|
|
REFERENCES company_mng(company_code),
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 백엔드 서비스 수정
|
||
|
|
|
||
|
|
**파일**: `backend-node/src/services/tableCategoryValueService.ts`
|
||
|
|
|
||
|
|
**변경사항**:
|
||
|
|
```typescript
|
||
|
|
// 변경 전
|
||
|
|
const query = `
|
||
|
|
SELECT
|
||
|
|
tc.table_name AS "tableName",
|
||
|
|
tc.column_name AS "columnName",
|
||
|
|
tc.column_label AS "columnLabel",
|
||
|
|
COUNT(cv.value_id) AS "valueCount"
|
||
|
|
FROM table_columns tc
|
||
|
|
LEFT JOIN table_column_category_values cv
|
||
|
|
ON tc.table_name = cv.table_name
|
||
|
|
AND tc.column_name = cv.column_name
|
||
|
|
AND cv.is_active = true
|
||
|
|
AND (cv.company_code = $2 OR cv.company_code = '*')
|
||
|
|
WHERE tc.table_name = $1
|
||
|
|
AND tc.web_type = 'category'
|
||
|
|
AND (tc.company_code = $2 OR tc.company_code = '*')
|
||
|
|
GROUP BY tc.table_name, tc.column_name, tc.column_label, tc.column_order
|
||
|
|
ORDER BY tc.column_order, tc.column_label
|
||
|
|
`;
|
||
|
|
|
||
|
|
// 변경 후
|
||
|
|
const query = `
|
||
|
|
SELECT
|
||
|
|
tc.table_name AS "tableName",
|
||
|
|
tc.column_name AS "columnName",
|
||
|
|
tc.column_name AS "columnLabel", -- column_label이 없으므로 column_name 사용
|
||
|
|
COUNT(cv.value_id) AS "valueCount"
|
||
|
|
FROM table_type_columns tc -- table_columns → table_type_columns
|
||
|
|
LEFT JOIN table_column_category_values cv
|
||
|
|
ON tc.table_name = cv.table_name
|
||
|
|
AND tc.column_name = cv.column_name
|
||
|
|
AND cv.is_active = true
|
||
|
|
AND (cv.company_code = $2 OR cv.company_code = '*')
|
||
|
|
WHERE tc.table_name = $1
|
||
|
|
AND tc.input_type = 'category' -- web_type → input_type
|
||
|
|
GROUP BY tc.table_name, tc.column_name, tc.display_order -- column_order → display_order
|
||
|
|
ORDER BY tc.display_order, tc.column_name
|
||
|
|
`;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 주요 차이점 분석
|
||
|
|
|
||
|
|
### 1. 멀티테넌시 방식
|
||
|
|
|
||
|
|
**예상**: 모든 테이블에 `company_code` 컬럼 존재
|
||
|
|
**실제**: `table_type_columns`에는 `company_code` 컬럼이 없음
|
||
|
|
|
||
|
|
**영향**:
|
||
|
|
- 카테고리 컬럼 조회 시 회사별 필터링 불가
|
||
|
|
- 모든 회사가 동일한 테이블 구조 사용
|
||
|
|
- 카테고리 **값**만 회사별로 분리됨 (의도된 설계로 보임)
|
||
|
|
|
||
|
|
**결론**: ✅ 정상 - 테이블 구조는 공통, 데이터만 회사별 분리
|
||
|
|
|
||
|
|
### 2. 라벨 관리
|
||
|
|
|
||
|
|
**예상**: `table_columns.column_label` 컬럼에 라벨 저장
|
||
|
|
**실제**: `column_label` 컬럼 없음
|
||
|
|
|
||
|
|
**해결책**:
|
||
|
|
- 현재는 `column_name`을 그대로 라벨로 사용
|
||
|
|
- 필요 시 향후 `table_labels` 테이블과 JOIN하여 라벨 조회 가능
|
||
|
|
|
||
|
|
### 3. 타입 컬럼명
|
||
|
|
|
||
|
|
**예상**: `web_type`
|
||
|
|
**실제**: `input_type`
|
||
|
|
|
||
|
|
**결론**: ✅ 수정 완료 - `input_type` 사용으로 변경
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 테스트 계획
|
||
|
|
|
||
|
|
### 1. 마이그레이션 테스트
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 1. 마이그레이션 실행
|
||
|
|
\i db/migrations/036_create_table_column_category_values.sql
|
||
|
|
|
||
|
|
-- 2. 테이블 생성 확인
|
||
|
|
SELECT table_name
|
||
|
|
FROM information_schema.tables
|
||
|
|
WHERE table_name = 'table_column_category_values';
|
||
|
|
|
||
|
|
-- 3. 외래키 제약조건 확인
|
||
|
|
SELECT
|
||
|
|
tc.constraint_name,
|
||
|
|
tc.table_name,
|
||
|
|
kcu.column_name,
|
||
|
|
ccu.table_name AS foreign_table_name,
|
||
|
|
ccu.column_name AS foreign_column_name
|
||
|
|
FROM information_schema.table_constraints AS tc
|
||
|
|
JOIN information_schema.key_column_usage AS kcu
|
||
|
|
ON tc.constraint_name = kcu.constraint_name
|
||
|
|
JOIN information_schema.constraint_column_usage AS ccu
|
||
|
|
ON ccu.constraint_name = tc.constraint_name
|
||
|
|
WHERE tc.table_name = 'table_column_category_values'
|
||
|
|
AND tc.constraint_type = 'FOREIGN KEY';
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 카테고리 타입 컬럼 생성
|
||
|
|
|
||
|
|
먼저 테스트용 테이블에 카테고리 타입 컬럼을 추가해야 합니다:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 테스트용 projects 테이블이 없으면 생성
|
||
|
|
CREATE TABLE IF NOT EXISTS projects (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
project_name VARCHAR(200) NOT NULL,
|
||
|
|
project_type VARCHAR(50), -- 카테고리 타입
|
||
|
|
project_status VARCHAR(50), -- 카테고리 타입
|
||
|
|
priority VARCHAR(50), -- 카테고리 타입
|
||
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
|
|
);
|
||
|
|
|
||
|
|
-- table_type_columns에 카테고리 타입 등록
|
||
|
|
INSERT INTO table_type_columns (table_name, column_name, input_type, display_order)
|
||
|
|
VALUES
|
||
|
|
('projects', 'project_type', 'category', 1),
|
||
|
|
('projects', 'project_status', 'category', 2),
|
||
|
|
('projects', 'priority', 'category', 3)
|
||
|
|
ON CONFLICT DO NOTHING;
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. API 테스트
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 1. 카테고리 컬럼 목록 조회
|
||
|
|
curl -X GET http://localhost:8080/api/table-categories/projects/columns \
|
||
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
||
|
|
|
||
|
|
# 예상 응답:
|
||
|
|
# {
|
||
|
|
# "success": true,
|
||
|
|
# "data": [
|
||
|
|
# {
|
||
|
|
# "tableName": "projects",
|
||
|
|
# "columnName": "project_type",
|
||
|
|
# "columnLabel": "project_type",
|
||
|
|
# "valueCount": 4
|
||
|
|
# },
|
||
|
|
# {
|
||
|
|
# "tableName": "projects",
|
||
|
|
# "columnName": "project_status",
|
||
|
|
# "columnLabel": "project_status",
|
||
|
|
# "valueCount": 4
|
||
|
|
# },
|
||
|
|
# {
|
||
|
|
# "tableName": "projects",
|
||
|
|
# "columnName": "priority",
|
||
|
|
# "columnLabel": "priority",
|
||
|
|
# "valueCount": 4
|
||
|
|
# }
|
||
|
|
# ]
|
||
|
|
# }
|
||
|
|
|
||
|
|
# 2. 카테고리 값 목록 조회
|
||
|
|
curl -X GET http://localhost:8080/api/table-categories/projects/project_type/values \
|
||
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
||
|
|
|
||
|
|
# 예상 응답:
|
||
|
|
# {
|
||
|
|
# "success": true,
|
||
|
|
# "data": [
|
||
|
|
# {
|
||
|
|
# "valueId": 1,
|
||
|
|
# "valueCode": "DEV",
|
||
|
|
# "valueLabel": "개발",
|
||
|
|
# "color": "#3b82f6",
|
||
|
|
# ...
|
||
|
|
# }
|
||
|
|
# ]
|
||
|
|
# }
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 추가 고려사항
|
||
|
|
|
||
|
|
### 1. 라벨 표시 개선
|
||
|
|
|
||
|
|
현재는 `columnName`을 그대로 라벨로 사용하지만, 더 나은 사용자 경험을 위해 다음 개선 가능:
|
||
|
|
|
||
|
|
**옵션 A**: `table_labels` 테이블 활용
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
tc.table_name AS "tableName",
|
||
|
|
tc.column_name AS "columnName",
|
||
|
|
COALESCE(tl.table_label, tc.column_name) AS "columnLabel",
|
||
|
|
COUNT(cv.value_id) AS "valueCount"
|
||
|
|
FROM table_type_columns tc
|
||
|
|
LEFT JOIN table_labels tl
|
||
|
|
ON tc.table_name = tl.table_name
|
||
|
|
LEFT JOIN table_column_category_values cv
|
||
|
|
ON tc.table_name = cv.table_name
|
||
|
|
AND tc.column_name = cv.column_name
|
||
|
|
WHERE tc.table_name = $1
|
||
|
|
AND tc.input_type = 'category'
|
||
|
|
GROUP BY tc.table_name, tc.column_name, tl.table_label, tc.display_order
|
||
|
|
ORDER BY tc.display_order;
|
||
|
|
```
|
||
|
|
|
||
|
|
**옵션 B**: `detail_settings`에서 라벨 추출
|
||
|
|
```sql
|
||
|
|
SELECT
|
||
|
|
tc.table_name AS "tableName",
|
||
|
|
tc.column_name AS "columnName",
|
||
|
|
COALESCE(
|
||
|
|
(tc.detail_settings::jsonb->>'label')::text,
|
||
|
|
tc.column_name
|
||
|
|
) AS "columnLabel"
|
||
|
|
FROM table_type_columns tc
|
||
|
|
WHERE tc.table_name = $1
|
||
|
|
AND tc.input_type = 'category';
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. input_type = 'category' 추가
|
||
|
|
|
||
|
|
현재 `input_type`에 `'category'` 값이 있는지 확인 필요:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 현재 사용 중인 input_type 확인
|
||
|
|
SELECT DISTINCT input_type
|
||
|
|
FROM table_type_columns
|
||
|
|
ORDER BY input_type;
|
||
|
|
```
|
||
|
|
|
||
|
|
만약 `'category'` 타입이 없다면, 기존 시스템에 추가해야 합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 호환성 체크리스트
|
||
|
|
|
||
|
|
### 데이터베이스
|
||
|
|
- [x] `company_mng` 테이블 존재 확인
|
||
|
|
- [x] `table_type_columns` 테이블 구조 확인
|
||
|
|
- [x] 외래키 참조 수정 (`company_info` → `company_mng`)
|
||
|
|
- [ ] `input_type = 'category'` 추가 여부 확인
|
||
|
|
- [ ] 테스트 데이터 삽입 확인
|
||
|
|
|
||
|
|
### 백엔드
|
||
|
|
- [x] 테이블명 수정 (`table_columns` → `table_type_columns`)
|
||
|
|
- [x] 컬럼명 수정 (`web_type` → `input_type`)
|
||
|
|
- [x] 컬럼명 수정 (`column_order` → `display_order`)
|
||
|
|
- [x] 라벨 처리 수정 (`column_label` → `column_name`)
|
||
|
|
- [ ] 멀티테넌시 로직 확인
|
||
|
|
|
||
|
|
### 프론트엔드
|
||
|
|
- [ ] API 응답 구조 확인
|
||
|
|
- [ ] 라벨 표시 테스트
|
||
|
|
- [ ] UI 테스트
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 결론
|
||
|
|
|
||
|
|
✅ **호환성 문제 수정 완료**
|
||
|
|
|
||
|
|
주요 변경사항:
|
||
|
|
1. `company_info` → `company_mng` (외래키)
|
||
|
|
2. `table_columns` → `table_type_columns` (테이블명)
|
||
|
|
3. `web_type` → `input_type` (컬럼명)
|
||
|
|
4. `column_order` → `display_order` (컬럼명)
|
||
|
|
5. `column_label` → `column_name` (라벨 처리)
|
||
|
|
|
||
|
|
**다음 단계**:
|
||
|
|
1. 마이그레이션 실행
|
||
|
|
2. 테스트용 카테고리 컬럼 생성
|
||
|
|
3. API 테스트
|
||
|
|
4. 프론트엔드 테스트
|
||
|
|
|