# Organization Chart Architecture & Implementation Policy (ADR) ## 1. Overview (개요) 본 문서는 Baron SSO 내 `adminfront`에서 사용될 **조직도(Organization Chart) 및 다중 테넌시(Multi-Tenancy) 대응 기능**에 대한 아키텍처 결정 사항(Architecture Decision Record)과 세부 구현 방향을 정의합니다. 이 정책은 기존 B2B 테넌트 모델(`Tenant`)과 사내 사용자 그룹 모델(`UserGroup`), 그리고 Ory Keto 기반의 권한 제어(ReBAC) 시스템 간의 일관성을 유지하면서, 복잡하고 다양한 형태의 고객사별 조직 구조(N-Depth)를 지원하기 위해 작성되었습니다. --- ## 2. Core Architectural Decisions (핵심 아키텍처 결정) ### 2.1 B2B Tenant vs. Internal UserGroup Hierarchy (테넌트 vs. 유저그룹 계층화) 조직의 계층(Hierarchy)을 표현하기 위해 `Tenant` 자체를 중첩(Nested)시킬 것인지, 아니면 단일 `Tenant` 내의 `UserGroup`을 중첩시킬 것인지에 대한 결정입니다. * **Decision (결정):** 조직도는 **`UserGroup` 내부의 자기 참조(`parent_id`)를 통해 계층화**합니다. * **Rationale (이유):** * **관심사 분리 (Separation of Concerns):** `Tenant` 모델은 결제, 도메인 매핑, B2B 고객사(Company) 격리 등 무거운 비즈니스 로직을 담고 있습니다. "개발팀", "인사부"와 같은 단순한 사내 조직 단위까지 `Tenant` 테이블에 저장하면 시스템 복잡도가 기하급수적으로 증가합니다. * **조회 성능 (Performance):** 특정 고객사(Company)의 전체 조직도를 그릴 때 `SELECT * FROM user_groups WHERE tenant_id = ?` 단일 쿼리로 모든 노드를 가져와 애플리케이션 메모리에서 트리를 구성할 수 있어 성능상 매우 유리합니다. * **단일 진실 공급원 (SoT):** 회사(Company) 단위의 물리적 격리는 `Tenant`가, 논리적인 사내 부서/팀 구조는 `UserGroup`이 담당하도록 역할을 명확히 분리합니다. ### 2.2 Flexible N-Depth Organizational Structure (유연한 N-Depth 조직 구조) 고객사마다 조직 단계(부, 국, 실, 본부, 파트, 반, 셀 등)의 명칭과 깊이(Depth)가 다릅니다. 이를 하드코딩된 Enum으로 제한해서는 안 됩니다. * **Decision (결정):** 조직의 단계나 명칭을 시스템(DB 스키마)에서 강제하지 않으며, **N-Depth 인접 목록(Adjacency List) 모델을 사용**합니다. * **Implementation (구현):** * `UserGroup` 모델에 `parent_id` (UUID, Nullable) 컬럼을 추가하여 부모-자식 관계를 형성합니다. * 조직 타입(`unit_type`) 필드는 고정된 Enum(예: `TEAM`, `GROUP`) 대신, 고객사가 자유롭게 입력할 수 있는 **동적 문자열(`String`)**로 관리하거나, 계층의 상대적 깊이(Depth)만을 의미 단위로 사용합니다. * 프론트엔드의 Checkbox Tree 컴포넌트는 재귀적(Recursive)으로 설계되어 데이터의 깊이에 상관없이 무한한 N-Depth를 렌더링할 수 있어야 합니다. --- ## 3. Data Structure & Schema Updates (데이터 구조 및 스키마 업데이트) 새로운 테이블을 추가하는 대신, 기존 모델을 확장하여 중복을 방지합니다. ### 3.1 `user_groups` 테이블 확장 조직 계층 및 부서 단위 표현을 위해 필드를 추가합니다. * `id`, `tenant_id`, `name`, `description` (기존 유지) * **`parent_id` (UUID, Nullable FK):** 상위 `UserGroup` 참조 (조직 트리 구성). * **`unit_type` (String, Optional):** 조직 단위 명칭 (예: "본부", "실", "팀"). 시스템이 강제하지 않으며 프론트엔드 라벨링 용도로 사용됩니다. ### 3.2 `users` 테이블 확장 (직급 및 직무) CSV에서 업로드되는 사용자의 인사 정보(직급, 직무 등)는 `User` 모델에 직접 저장합니다. * **`position` (String):** 직급 (예: "수석", "책임", "사원"). * **`job_title` (String):** 직무 (예: "프론트엔드 개발", "기획"). * *(또는 기존에 존재하는 `Metadata` (JSONB) 필드를 활용하여 스키마 변경 없이 동적 속성으로 관리할 수도 있습니다.)* --- ## 4. ReBAC Integration Policy (Ory Keto 연동 정책) DB의 `user_groups` 계층 트리는 Ory Keto의 관계 튜플(Tuple)과 동기화되어 권한 제어에 사용됩니다. (기존 통합 권한 정책 `tenant-usergroup-policy.md` 준수) 1. **조직 계층 동기화 (Hierarchy):** * DB에서 A팀(`UserGroup`)이 B본부(`UserGroup`)의 하위로 설정되면, Keto에는 `UserGroup:#parent@UserGroup:` 튜플이 생성됩니다. 2. **소속원 매핑 (Membership):** * 유저가 A팀에 속하면 `UserGroup:#members@User:<유저_ID>` 튜플이 생성됩니다. 3. **조직장 및 어드민 승격 (Leadership):** * CSV 데이터 분석 또는 수동 지정을 통해 특정 유저가 A팀의 '조직장'으로 식별되면, `UserGroup:#owners@User:<유저_ID>` 튜플이 생성됩니다. * 정책에 따라 `owners` 관계를 가진 유저는 해당 조직(UserGroup)과 그 하위 조직에 대한 `admins` 권한을 자동으로 상속받습니다. --- ## 5. Data Loading & CSV Upload Strategy (데이터 로딩 및 CSV 업로드 전략) 고정된 컬럼 구조는 다양한 회사의 조직도를 수용할 수 없으므로 유연한 파싱 로직이 필요합니다. ### 5.1 Flexible CSV Format (유연한 CSV 포맷) * **경로 기반 방식 (Path-based):** 조직 계층을 슬래시(`/`) 등으로 구분하여 하나의 문자열로 전달받습니다. * *예시 컬럼:* `[조직_경로, 직급, 이름, 직무, 이메일]` * *데이터 예시:* `"개발본부/클라우드실/플랫폼팀", "수석", "홍길동", "백엔드 개발", "hong@example.com"` * **동적 뎁스 방식 (Dynamic Depth):** 뒤에서부터 고정된 사용자 속성 열(직급, 직무, 이름, 이메일 등)을 식별하고, 그 앞의 모든 열을 동적인 계층 구조로 해석합니다. ### 5.2 Processing Flow (처리 흐름) 1. **Parsing & Validation:** 프론트엔드/백엔드에서 유연한 CSV 포맷을 파싱하고, `UserGroup` 계층 경로를 분석합니다. 2. **Tree Resolution:** 백엔드는 "개발본부 > 클라우드실 > 플랫폼팀" 경로를 DB에서 조회하거나 없으면 순차적으로 생성(`parent_id` 매핑)하여 `UserGroup` ID 트리를 완성합니다. 3. **User Upsert:** `User` 정보를 생성하거나 업데이트(`position`, `job_title` 갱신)합니다. 4. **Keto Synchronization:** DB 트랜잭션 완료 후, Background Worker가 변경된 조직 계층과 멤버십 정보를 기반으로 Ory Keto 튜플을 생성/삭제(Reconciliation)합니다. --- ## 6. Frontend Multi-Tenancy UI (프론트엔드 다중 테넌트 UI) 관리자가 여러 테넌트(Company)에 접근 권한이 있을 경우, 조직도를 명확히 구분하여 보여주어야 합니다. * **Tabs Interface:** 화면 상단 또는 측면에 사용자가 접근 가능한 최상위 `Tenant` 목록을 탭(Tabs) 형태로 제공합니다. * **Scoped Fetching:** 특정 탭(Tenant)을 선택할 때마다 해당 `tenant_id`를 파라미터로 백엔드 API를 호출하여, 격리된 해당 회사만의 `UserGroup` 트리를 렌더링합니다. * **Checkbox Tree Component:** Radix UI와 TailwindCSS를 기반으로 개발되며, N-Depth 중첩을 지원하고 부모-자식 간의 반선택(Indeterminate) 상태를 재귀적으로 계산하는 독립적인(Reusable) 컴포넌트로 구현됩니다.