24 KiB
24 KiB
레벨 기반 연쇄 드롭다운 시스템 설계
1. 개요
1.1 목적
다양한 계층 구조를 지원하는 범용 연쇄 드롭다운 시스템 구축
1.2 지원하는 계층 유형
| 유형 | 설명 | 예시 |
|---|---|---|
| MULTI_TABLE | 각 레벨이 다른 테이블 | 국가 → 시/도 → 구/군 → 동 |
| SELF_REFERENCE | 같은 테이블 내 자기참조 | 대분류 → 중분류 → 소분류 |
| BOM | BOM 구조 (수량 등 속성 포함) | 제품 → 어셈블리 → 부품 |
| TREE | 무한 깊이 트리 | 조직도, 메뉴 구조 |
2. 데이터베이스 설계
2.1 테이블 구조
┌─────────────────────────────────────┐
│ cascading_hierarchy_group │ ← 계층 그룹 정의
├─────────────────────────────────────┤
│ group_code (PK) │
│ group_name │
│ hierarchy_type │ ← MULTI_TABLE/SELF_REFERENCE/BOM/TREE
│ max_levels │
│ is_fixed_levels │
│ self_ref_* (자기참조 설정) │
│ bom_* (BOM 설정) │
│ company_code │
└─────────────────────────────────────┘
│
│ 1:N (MULTI_TABLE 유형만)
▼
┌─────────────────────────────────────┐
│ cascading_hierarchy_level │ ← 레벨별 테이블/컬럼 정의
├─────────────────────────────────────┤
│ group_code (FK) │
│ level_order │ ← 1, 2, 3...
│ level_name │
│ table_name │
│ value_column │
│ label_column │
│ parent_key_column │ ← 부모 테이블 참조 컬럼
│ company_code │
└─────────────────────────────────────┘
2.2 기존 시스템과의 관계
┌─────────────────────────────────────┐
│ cascading_relation │ ← 기존 2단계 관계 (유지)
│ (2단계 전용) │
└─────────────────────────────────────┘
│
│ 호환성 뷰
▼
┌─────────────────────────────────────┐
│ v_cascading_as_hierarchy │ ← 기존 관계를 계층 형태로 변환
└─────────────────────────────────────┘
3. 계층 유형별 상세 설계
3.1 MULTI_TABLE (다중 테이블 계층)
사용 사례: 국가 → 시/도 → 구/군 → 동
테이블 구조:
country_info province_info city_info district_info
├─ country_code (PK) ├─ province_code (PK) ├─ city_code (PK) ├─ district_code (PK)
├─ country_name ├─ province_name ├─ city_name ├─ district_name
├─ country_code (FK) ├─ province_code (FK) ├─ city_code (FK)
설정 예시:
-- 그룹 정의
INSERT INTO cascading_hierarchy_group (
group_code, group_name, hierarchy_type, max_levels, company_code
) VALUES (
'REGION_HIERARCHY', '지역 계층', 'MULTI_TABLE', 4, 'EMAX'
);
-- 레벨 정의
INSERT INTO cascading_hierarchy_level VALUES
(1, 'REGION_HIERARCHY', 'EMAX', 1, '국가', 'country_info', 'country_code', 'country_name', NULL),
(2, 'REGION_HIERARCHY', 'EMAX', 2, '시/도', 'province_info', 'province_code', 'province_name', 'country_code'),
(3, 'REGION_HIERARCHY', 'EMAX', 3, '구/군', 'city_info', 'city_code', 'city_name', 'province_code'),
(4, 'REGION_HIERARCHY', 'EMAX', 4, '동', 'district_info', 'district_code', 'district_name', 'city_code');
API 호출 흐름:
1. 레벨 1 (국가): GET /api/cascading-hierarchy/options/REGION_HIERARCHY/1
→ [{ value: 'KR', label: '대한민국' }, { value: 'US', label: '미국' }]
2. 레벨 2 (시/도): GET /api/cascading-hierarchy/options/REGION_HIERARCHY/2?parentValue=KR
→ [{ value: 'SEOUL', label: '서울특별시' }, { value: 'BUSAN', label: '부산광역시' }]
3. 레벨 3 (구/군): GET /api/cascading-hierarchy/options/REGION_HIERARCHY/3?parentValue=SEOUL
→ [{ value: 'GANGNAM', label: '강남구' }, { value: 'SEOCHO', label: '서초구' }]
3.2 SELF_REFERENCE (자기참조 계층)
사용 사례: 제품 카테고리 (대분류 → 중분류 → 소분류)
테이블 구조 (code_info 활용):
code_info
├─ code_category = 'PRODUCT_CATEGORY'
├─ code_value (PK) = 'ELEC', 'ELEC_TV', 'ELEC_TV_LED'
├─ code_name = '전자제품', 'TV', 'LED TV'
├─ parent_code = NULL, 'ELEC', 'ELEC_TV' ← 자기참조
├─ level = 1, 2, 3
├─ sort_order
설정 예시:
INSERT INTO cascading_hierarchy_group (
group_code, group_name, hierarchy_type, max_levels,
self_ref_table, self_ref_id_column, self_ref_parent_column,
self_ref_value_column, self_ref_label_column, self_ref_level_column,
self_ref_filter_column, self_ref_filter_value,
company_code
) VALUES (
'PRODUCT_CATEGORY', '제품 카테고리', 'SELF_REFERENCE', 3,
'code_info', 'code_value', 'parent_code',
'code_value', 'code_name', 'level',
'code_category', 'PRODUCT_CATEGORY',
'EMAX'
);
API 호출 흐름:
1. 레벨 1 (대분류): GET /api/cascading-hierarchy/options/PRODUCT_CATEGORY/1
→ WHERE parent_code IS NULL AND code_category = 'PRODUCT_CATEGORY'
→ [{ value: 'ELEC', label: '전자제품' }, { value: 'FURN', label: '가구' }]
2. 레벨 2 (중분류): GET /api/cascading-hierarchy/options/PRODUCT_CATEGORY/2?parentValue=ELEC
→ WHERE parent_code = 'ELEC' AND code_category = 'PRODUCT_CATEGORY'
→ [{ value: 'ELEC_TV', label: 'TV' }, { value: 'ELEC_REF', label: '냉장고' }]
3. 레벨 3 (소분류): GET /api/cascading-hierarchy/options/PRODUCT_CATEGORY/3?parentValue=ELEC_TV
→ WHERE parent_code = 'ELEC_TV' AND code_category = 'PRODUCT_CATEGORY'
→ [{ value: 'ELEC_TV_LED', label: 'LED TV' }, { value: 'ELEC_TV_OLED', label: 'OLED TV' }]
3.3 BOM (Bill of Materials)
사용 사례: 제품 BOM 구조
테이블 구조:
klbom_tbl (BOM 관계) item_info (품목 마스터)
├─ id (자식 품목) ├─ item_code (PK)
├─ pid (부모 품목) ├─ item_name
├─ qty (수량) ├─ item_spec
├─ aylevel (레벨) ├─ unit
├─ bom_report_objid
설정 예시:
INSERT INTO cascading_hierarchy_group (
group_code, group_name, hierarchy_type, max_levels, is_fixed_levels,
bom_table, bom_parent_column, bom_child_column,
bom_item_table, bom_item_id_column, bom_item_label_column,
bom_qty_column, bom_level_column,
company_code
) VALUES (
'PRODUCT_BOM', '제품 BOM', 'BOM', NULL, 'N',
'klbom_tbl', 'pid', 'id',
'item_info', 'item_code', 'item_name',
'qty', 'aylevel',
'EMAX'
);
API 호출 흐름:
1. 루트 품목 (레벨 1): GET /api/cascading-hierarchy/bom/PRODUCT_BOM/roots
→ WHERE pid IS NULL OR pid = ''
→ [{ value: 'PROD001', label: '완제품 A', level: 1 }]
2. 하위 품목: GET /api/cascading-hierarchy/bom/PRODUCT_BOM/children?parentValue=PROD001
→ WHERE pid = 'PROD001'
→ [
{ value: 'ASSY001', label: '어셈블리 A', qty: 1, level: 2 },
{ value: 'ASSY002', label: '어셈블리 B', qty: 2, level: 2 }
]
3. 더 하위: GET /api/cascading-hierarchy/bom/PRODUCT_BOM/children?parentValue=ASSY001
→ WHERE pid = 'ASSY001'
→ [
{ value: 'PART001', label: '부품 A', qty: 4, level: 3 },
{ value: 'PART002', label: '부품 B', qty: 2, level: 3 }
]
BOM 전용 응답 형식:
interface BomOption {
value: string; // 품목 코드
label: string; // 품목명
qty: number; // 수량
level: number; // BOM 레벨
hasChildren: boolean; // 하위 품목 존재 여부
spec?: string; // 규격 (선택)
unit?: string; // 단위 (선택)
}
3.4 TREE (무한 깊이 트리)
사용 사례: 조직도, 메뉴 구조
테이블 구조:
dept_info
├─ dept_code (PK)
├─ dept_name
├─ parent_dept_code ← 자기참조 (무한 깊이)
├─ sort_order
├─ is_active
설정 예시:
INSERT INTO cascading_hierarchy_group (
group_code, group_name, hierarchy_type, max_levels, is_fixed_levels,
self_ref_table, self_ref_id_column, self_ref_parent_column,
self_ref_value_column, self_ref_label_column, self_ref_order_column,
company_code
) VALUES (
'ORG_CHART', '조직도', 'TREE', NULL, 'N',
'dept_info', 'dept_code', 'parent_dept_code',
'dept_code', 'dept_name', 'sort_order',
'EMAX'
);
API 호출 흐름 (BOM과 유사):
1. 루트 노드: GET /api/cascading-hierarchy/tree/ORG_CHART/roots
→ WHERE parent_dept_code IS NULL
→ [{ value: 'HQ', label: '본사', hasChildren: true }]
2. 하위 노드: GET /api/cascading-hierarchy/tree/ORG_CHART/children?parentValue=HQ
→ WHERE parent_dept_code = 'HQ'
→ [
{ value: 'DIV1', label: '사업부1', hasChildren: true },
{ value: 'DIV2', label: '사업부2', hasChildren: true }
]
4. API 설계
4.1 계층 그룹 관리 API
GET /api/cascading-hierarchy/groups # 그룹 목록
POST /api/cascading-hierarchy/groups # 그룹 생성
GET /api/cascading-hierarchy/groups/:code # 그룹 상세
PUT /api/cascading-hierarchy/groups/:code # 그룹 수정
DELETE /api/cascading-hierarchy/groups/:code # 그룹 삭제
4.2 레벨 관리 API (MULTI_TABLE용)
GET /api/cascading-hierarchy/groups/:code/levels # 레벨 목록
POST /api/cascading-hierarchy/groups/:code/levels # 레벨 추가
PUT /api/cascading-hierarchy/groups/:code/levels/:order # 레벨 수정
DELETE /api/cascading-hierarchy/groups/:code/levels/:order # 레벨 삭제
4.3 옵션 조회 API
# MULTI_TABLE / SELF_REFERENCE
GET /api/cascading-hierarchy/options/:groupCode/:level
?parentValue=xxx # 부모 값 (레벨 2 이상)
&companyCode=xxx # 회사 코드 (선택)
# BOM / TREE
GET /api/cascading-hierarchy/tree/:groupCode/roots # 루트 노드
GET /api/cascading-hierarchy/tree/:groupCode/children # 자식 노드
?parentValue=xxx
GET /api/cascading-hierarchy/tree/:groupCode/path # 경로 조회
?value=xxx
GET /api/cascading-hierarchy/tree/:groupCode/search # 검색
?keyword=xxx
5. 프론트엔드 컴포넌트 설계
5.1 CascadingHierarchyDropdown
interface CascadingHierarchyDropdownProps {
groupCode: string; // 계층 그룹 코드
level: number; // 현재 레벨 (1, 2, 3...)
parentValue?: string; // 부모 값 (레벨 2 이상)
value?: string; // 선택된 값
onChange: (value: string, option: HierarchyOption) => void;
placeholder?: string;
disabled?: boolean;
required?: boolean;
}
// 사용 예시 (지역 계층)
<CascadingHierarchyDropdown groupCode="REGION_HIERARCHY" level={1} onChange={setCountry} />
<CascadingHierarchyDropdown groupCode="REGION_HIERARCHY" level={2} parentValue={country} onChange={setProvince} />
<CascadingHierarchyDropdown groupCode="REGION_HIERARCHY" level={3} parentValue={province} onChange={setCity} />
5.2 CascadingHierarchyGroup (자동 연결)
interface CascadingHierarchyGroupProps {
groupCode: string;
values: Record<number, string>; // { 1: 'KR', 2: 'SEOUL', 3: 'GANGNAM' }
onChange: (level: number, value: string) => void;
layout?: 'horizontal' | 'vertical';
}
// 사용 예시
<CascadingHierarchyGroup
groupCode="REGION_HIERARCHY"
values={regionValues}
onChange={(level, value) => {
setRegionValues(prev => ({ ...prev, [level]: value }));
}}
/>
5.3 BomTreeSelect (BOM 전용)
interface BomTreeSelectProps {
groupCode: string;
value?: string;
onChange: (value: string, path: BomOption[]) => void;
showQty?: boolean; // 수량 표시
showLevel?: boolean; // 레벨 표시
maxDepth?: number; // 최대 깊이 제한
}
// 사용 예시
<BomTreeSelect
groupCode="PRODUCT_BOM"
value={selectedPart}
onChange={(value, path) => {
setSelectedPart(value);
console.log('선택 경로:', path); // [완제품 → 어셈블리 → 부품]
}}
showQty
/>
6. 화면관리 시스템 통합
6.1 컴포넌트 설정 확장
interface SelectBasicConfig {
// 기존 설정
cascadingEnabled?: boolean;
cascadingRelationCode?: string; // 기존 2단계 관계
cascadingRole?: 'parent' | 'child';
cascadingParentField?: string;
// 🆕 레벨 기반 계층 설정
hierarchyEnabled?: boolean;
hierarchyGroupCode?: string; // 계층 그룹 코드
hierarchyLevel?: number; // 이 컴포넌트의 레벨
hierarchyParentField?: string; // 부모 레벨 필드명
}
6.2 설정 UI 확장
┌─────────────────────────────────────────┐
│ 연쇄 드롭다운 설정 │
├─────────────────────────────────────────┤
│ ○ 2단계 관계 (기존) │
│ └─ 관계 선택: [창고-위치 ▼] │
│ └─ 역할: [부모] [자식] │
│ │
│ ● 다단계 계층 (신규) │
│ └─ 계층 그룹: [지역 계층 ▼] │
│ └─ 레벨: [2 - 시/도 ▼] │
│ └─ 부모 필드: [country_code] (자동감지) │
└─────────────────────────────────────────┘
7. 구현 우선순위
Phase 1: 기반 구축
- ✅ 기존 2단계 연쇄 드롭다운 완성
- 📋 데이터베이스 마이그레이션 (066_create_cascading_hierarchy.sql)
- 📋 백엔드 API 구현 (계층 그룹 CRUD)
Phase 2: MULTI_TABLE 지원
- 📋 레벨 관리 API
- 📋 옵션 조회 API
- 📋 프론트엔드 컴포넌트
Phase 3: SELF_REFERENCE 지원
- 📋 자기참조 쿼리 로직
- 📋 code_info 기반 카테고리 계층
Phase 4: BOM/TREE 지원
- 📋 BOM 전용 API
- 📋 트리 컴포넌트
- 📋 무한 깊이 지원
Phase 5: 화면관리 통합
- 📋 설정 UI 확장
- 📋 자동 연결 기능
8. 성능 고려사항
8.1 쿼리 최적화
- 인덱스:
(group_code, company_code, level_order) - 캐싱: 자주 조회되는 옵션 목록 Redis 캐싱
- Lazy Loading: 하위 레벨은 필요 시에만 로드
8.2 BOM 재귀 쿼리
-- PostgreSQL WITH RECURSIVE 활용
WITH RECURSIVE bom_tree AS (
-- 루트 노드
SELECT id, pid, qty, 1 AS level
FROM klbom_tbl
WHERE pid IS NULL
UNION ALL
-- 하위 노드
SELECT b.id, b.pid, b.qty, t.level + 1
FROM klbom_tbl b
JOIN bom_tree t ON b.pid = t.id
WHERE t.level < 10 -- 최대 깊이 제한
)
SELECT * FROM bom_tree;
8.3 트리 최적화 전략
- Materialized Path:
/HQ/DIV1/DEPT1/TEAM1 - Nested Set: left/right 값으로 범위 쿼리
- Closure Table: 별도 관계 테이블
9. 추가 연쇄 패턴
9.1 조건부 연쇄 (Conditional Cascading)
사용 사례: 특정 조건에 따라 다른 옵션 목록 표시
입고유형: [구매입고] → 창고: [원자재창고, 부품창고] 만 표시
입고유형: [생산입고] → 창고: [완제품창고, 반제품창고] 만 표시
테이블: cascading_condition
INSERT INTO cascading_condition (
relation_code, condition_name,
condition_field, condition_operator, condition_value,
filter_column, filter_values, company_code
) VALUES
('WAREHOUSE_LOCATION', '구매입고 창고',
'inbound_type', 'EQ', 'PURCHASE',
'warehouse_type', 'RAW_MATERIAL,PARTS', 'EMAX');
9.2 다중 부모 연쇄 (Multi-Parent Cascading)
사용 사례: 여러 부모 필드의 조합으로 자식 필터링
회사: [A사] + 사업부: [영업부문] → 부서: [영업1팀, 영업2팀]
테이블: cascading_multi_parent, cascading_multi_parent_source
-- 관계 정의
INSERT INTO cascading_multi_parent (
relation_code, relation_name,
child_table, child_value_column, child_label_column, company_code
) VALUES (
'COMPANY_DIVISION_DEPT', '회사-사업부-부서',
'dept_info', 'dept_code', 'dept_name', 'EMAX'
);
-- 부모 소스 정의
INSERT INTO cascading_multi_parent_source (
relation_code, company_code, parent_order, parent_name,
parent_table, parent_value_column, child_filter_column
) VALUES
('COMPANY_DIVISION_DEPT', 'EMAX', 1, '회사', 'company_info', 'company_code', 'company_code'),
('COMPANY_DIVISION_DEPT', 'EMAX', 2, '사업부', 'division_info', 'division_code', 'division_code');
9.3 자동 입력 그룹 (Auto-Fill Group)
사용 사례: 마스터 선택 시 여러 필드 자동 입력
고객사 선택 → 담당자, 연락처, 주소, 결제조건 자동 입력
테이블: cascading_auto_fill_group, cascading_auto_fill_mapping
-- 그룹 정의
INSERT INTO cascading_auto_fill_group (
group_code, group_name,
master_table, master_value_column, master_label_column, company_code
) VALUES (
'CUSTOMER_AUTO_FILL', '고객사 정보 자동입력',
'customer_info', 'customer_code', 'customer_name', 'EMAX'
);
-- 필드 매핑
INSERT INTO cascading_auto_fill_mapping (
group_code, company_code, source_column, target_field, target_label
) VALUES
('CUSTOMER_AUTO_FILL', 'EMAX', 'contact_person', 'contact_name', '담당자'),
('CUSTOMER_AUTO_FILL', 'EMAX', 'contact_phone', 'contact_phone', '연락처'),
('CUSTOMER_AUTO_FILL', 'EMAX', 'address', 'delivery_address', '배송주소');
9.4 상호 배제 (Mutual Exclusion)
사용 사례: 같은 값 선택 불가
출발 창고: [창고A] → 도착 창고: [창고B, 창고C] (창고A 제외)
테이블: cascading_mutual_exclusion
INSERT INTO cascading_mutual_exclusion (
exclusion_code, exclusion_name, field_names,
source_table, value_column, label_column,
error_message, company_code
) VALUES (
'WAREHOUSE_TRANSFER', '창고간 이동',
'from_warehouse_code,to_warehouse_code',
'warehouse_info', 'warehouse_code', 'warehouse_name',
'출발 창고와 도착 창고는 같을 수 없습니다',
'EMAX'
);
9.5 역방향 조회 (Reverse Lookup)
사용 사례: 자식에서 부모 방향으로 조회
품목: [부품A] 선택 → 사용처 BOM: [제품X, 제품Y, 제품Z]
테이블: cascading_reverse_lookup
INSERT INTO cascading_reverse_lookup (
lookup_code, lookup_name,
source_table, source_value_column, source_label_column,
target_table, target_value_column, target_label_column, target_link_column,
company_code
) VALUES (
'ITEM_USED_IN_BOM', '품목 사용처 BOM',
'item_info', 'item_code', 'item_name',
'klbom_tbl', 'pid', 'ayupgname', 'id',
'EMAX'
);
10. 전체 테이블 구조 요약
┌─────────────────────────────────────────────────────────────────┐
│ 연쇄 드롭다운 시스템 구조 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [기존 - 2단계] │
│ cascading_relation ─────────────────────────────────────────── │
│ │
│ [신규 - 다단계 계층] │
│ cascading_hierarchy_group ──┬── cascading_hierarchy_level │
│ │ (MULTI_TABLE용) │
│ │ │
│ [신규 - 조건부] │
│ cascading_condition ────────┴── 조건에 따른 필터링 │
│ │
│ [신규 - 다중 부모] │
│ cascading_multi_parent ─────┬── cascading_multi_parent_source │
│ │ (여러 부모 조합) │
│ │
│ [신규 - 자동 입력] │
│ cascading_auto_fill_group ──┬── cascading_auto_fill_mapping │
│ │ (마스터→다중 필드) │
│ │
│ [신규 - 상호 배제] │
│ cascading_mutual_exclusion ─┴── 같은 값 선택 불가 │
│ │
│ [신규 - 역방향] │
│ cascading_reverse_lookup ───┴── 자식→부모 조회 │
│ │
└─────────────────────────────────────────────────────────────────┘
11. 마이그레이션 가이드
11.1 기존 데이터 마이그레이션
-- 기존 cascading_relation → cascading_hierarchy_group 변환
INSERT INTO cascading_hierarchy_group (
group_code, group_name, hierarchy_type, max_levels, company_code
)
SELECT
'LEGACY_' || relation_code,
relation_name,
'MULTI_TABLE',
2,
company_code
FROM cascading_relation
WHERE is_active = 'Y';
11.2 호환성 유지
- 기존
cascading_relation테이블 유지 - 기존 API 엔드포인트 유지
- 점진적 마이그레이션 지원
12. 구현 우선순위 (업데이트)
| Phase | 기능 | 복잡도 | 우선순위 |
|---|---|---|---|
| 1 | 기존 2단계 연쇄 (cascading_relation) | 완료 | 완료 |
| 2 | 다단계 계층 - MULTI_TABLE | 중 | 높음 |
| 3 | 다단계 계층 - SELF_REFERENCE | 중 | 높음 |
| 4 | 자동 입력 그룹 (Auto-Fill) | 낮음 | 높음 |
| 5 | 조건부 연쇄 | 중 | 중간 |
| 6 | 상호 배제 | 낮음 | 중간 |
| 7 | 다중 부모 연쇄 | 높음 | 낮음 |
| 8 | BOM/TREE 구조 | 높음 | 낮음 |
| 9 | 역방향 조회 | 중 | 낮음 |