# 레벨 기반 연쇄 드롭다운 시스템 설계 ## 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) ``` **설정 예시**: ```sql -- 그룹 정의 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 ``` **설정 예시**: ```sql 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 ``` **설정 예시**: ```sql 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 전용 응답 형식**: ```typescript 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 ``` **설정 예시**: ```sql 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 ```typescript 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; } // 사용 예시 (지역 계층) ``` ### 5.2 CascadingHierarchyGroup (자동 연결) ```typescript interface CascadingHierarchyGroupProps { groupCode: string; values: Record; // { 1: 'KR', 2: 'SEOUL', 3: 'GANGNAM' } onChange: (level: number, value: string) => void; layout?: 'horizontal' | 'vertical'; } // 사용 예시 { setRegionValues(prev => ({ ...prev, [level]: value })); }} /> ``` ### 5.3 BomTreeSelect (BOM 전용) ```typescript interface BomTreeSelectProps { groupCode: string; value?: string; onChange: (value: string, path: BomOption[]) => void; showQty?: boolean; // 수량 표시 showLevel?: boolean; // 레벨 표시 maxDepth?: number; // 최대 깊이 제한 } // 사용 예시 { setSelectedPart(value); console.log('선택 경로:', path); // [완제품 → 어셈블리 → 부품] }} showQty /> ``` --- ## 6. 화면관리 시스템 통합 ### 6.1 컴포넌트 설정 확장 ```typescript 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: 기반 구축 1. ✅ 기존 2단계 연쇄 드롭다운 완성 2. 📋 데이터베이스 마이그레이션 (066_create_cascading_hierarchy.sql) 3. 📋 백엔드 API 구현 (계층 그룹 CRUD) ### Phase 2: MULTI_TABLE 지원 1. 📋 레벨 관리 API 2. 📋 옵션 조회 API 3. 📋 프론트엔드 컴포넌트 ### Phase 3: SELF_REFERENCE 지원 1. 📋 자기참조 쿼리 로직 2. 📋 code_info 기반 카테고리 계층 ### Phase 4: BOM/TREE 지원 1. 📋 BOM 전용 API 2. 📋 트리 컴포넌트 3. 📋 무한 깊이 지원 ### Phase 5: 화면관리 통합 1. 📋 설정 UI 확장 2. 📋 자동 연결 기능 --- ## 8. 성능 고려사항 ### 8.1 쿼리 최적화 - 인덱스: `(group_code, company_code, level_order)` - 캐싱: 자주 조회되는 옵션 목록 Redis 캐싱 - Lazy Loading: 하위 레벨은 필요 시에만 로드 ### 8.2 BOM 재귀 쿼리 ```sql -- 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` ```sql 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` ```sql -- 관계 정의 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` ```sql -- 그룹 정의 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` ```sql 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` ```sql 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 기존 데이터 마이그레이션 ```sql -- 기존 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 | 역방향 조회 | 중 | 낮음 |