Files
vexplor/docs/레벨기반_연쇄드롭다운_설계.md
2025-12-10 15:59:04 +09:00

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: 기반 구축

  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 재귀 쿼리

-- 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 역방향 조회 낮음