# 메뉴 기반 권한 시스템 가이드 (동적 화면 대응)

## 개요

**기존 메뉴 기반 권한 시스템을 유지**하면서 **동적으로 생성되는 화면에도 대응**하는 개선된 시스템입니다.

### 핵심 아이디어 💡

```
사용자가 화면 생성
    ↓
자동으로 메뉴 추가 (menu_info)
    ↓
권한 관리자가 메뉴 권한 설정 (rel_menu_auth)
    ↓
사용자는 "메뉴"로만 권한 확인 (직관적!)
```

---

## 시스템 구조

### 1. `menu_info` (메뉴 정보)

| 컬럼             | 타입         | 설명                                                               |
| ---------------- | ------------ | ------------------------------------------------------------------ |
| objid            | INTEGER      | 메뉴 ID (PK)                                                       |
| menu_name        | VARCHAR(100) | 메뉴 이름                                                          |
| menu_code        | VARCHAR(50)  | 메뉴 코드                                                          |
| menu_url         | VARCHAR(255) | 메뉴 URL                                                           |
| **menu_type**    | VARCHAR(20)  | **'static'**(고정 메뉴) 또는 **'dynamic'**(화면 생성 시 자동 추가) |
| **screen_code**  | VARCHAR(50)  | 동적 메뉴인 경우 `screen_definitions.screen_code`                  |
| **company_code** | VARCHAR(20)  | 회사 코드 (회사별 메뉴 격리)                                       |
| parent_objid     | INTEGER      | 부모 메뉴 ID (계층 구조)                                           |
| is_active        | BOOLEAN      | 활성/비활성                                                        |

### 2. `rel_menu_auth` (메뉴별 권한)

| 컬럼           | 타입    | 설명                                      |
| -------------- | ------- | ----------------------------------------- |
| menu_objid     | INTEGER | 메뉴 ID (FK)                              |
| auth_objid     | INTEGER | 권한 그룹 ID (FK)                         |
| **create_yn**  | CHAR(1) | 생성 권한 ('Y'/'N')                       |
| **read_yn**    | CHAR(1) | 읽기 권한 ('Y'/'N')                       |
| **update_yn**  | CHAR(1) | 수정 권한 ('Y'/'N')                       |
| **delete_yn**  | CHAR(1) | 삭제 권한 ('Y'/'N')                       |
| **execute_yn** | CHAR(1) | 실행 권한 ('Y'/'N') - 플로우 실행, DDL 등 |
| **export_yn**  | CHAR(1) | 내보내기 권한 ('Y'/'N')                   |

---

## 자동화 기능 🤖

### 1. 화면 생성 시 자동 메뉴 추가

```sql
-- 사용자가 화면 생성
INSERT INTO screen_definitions (screen_name, screen_code, company_code, ...)
VALUES ('계약 관리', 'SCR_CONTRACT', 'ILSHIN', ...);

-- ↓ 트리거가 자동 실행 ↓

-- menu_info에 자동 추가됨!
-- menu_name = '계약 관리'
-- menu_code = 'SCR_CONTRACT'
-- menu_url = '/screen/SCR_CONTRACT'
-- menu_type = 'dynamic'
-- company_code = 'ILSHIN'
```

### 2. 화면 삭제 시 자동 메뉴 비활성화

```sql
-- 화면 삭제
UPDATE screen_definitions
SET is_active = 'D'
WHERE screen_code = 'SCR_CONTRACT';

-- ↓ 트리거가 자동 실행 ↓

-- 해당 메뉴도 비활성화됨!
UPDATE menu_info
SET is_active = FALSE
WHERE screen_code = 'SCR_CONTRACT';
```

---

## 사용 예시

### 예시 1: 영업팀에게 계약 관리 화면 읽기 권한 부여

```sql
-- 1. 계약 관리 메뉴 ID 조회 (화면 생성 시 자동으로 추가됨)
SELECT objid FROM menu_info
WHERE menu_code = 'SCR_CONTRACT';
-- 결과: objid = 1005

-- 2. 영업팀 권한 그룹 ID 조회
SELECT objid FROM authority_master
WHERE auth_code = 'SALES_TEAM' AND company_code = 'ILSHIN';
-- 결과: objid = 1001

-- 3. 읽기 권한 부여
INSERT INTO rel_menu_auth (menu_objid, auth_objid, create_yn, read_yn, update_yn, delete_yn, writer)
VALUES (1005, 1001, 'N', 'Y', 'N', 'N', 'admin');
```

### 예시 2: 개발팀에게 플로우 관리 전체 권한 부여

```sql
-- 플로우 관리 메뉴에 CRUD + 실행 권한
INSERT INTO rel_menu_auth (menu_objid, auth_objid, create_yn, read_yn, update_yn, delete_yn, execute_yn, writer)
VALUES (
    (SELECT objid FROM menu_info WHERE menu_code = 'MENU_FLOW_MGMT'),
    (SELECT objid FROM authority_master WHERE auth_code = 'DEV_TEAM'),
    'Y', 'Y', 'Y', 'Y', 'Y', 'admin'
);
```

### 예시 3: 권한 확인

```sql
-- 'john.doe' 사용자가 계약 관리 메뉴를 읽을 수 있는지 확인
SELECT check_user_menu_permission('john.doe', 1005, 'read');
-- 결과: TRUE 또는 FALSE

-- 'john.doe' 사용자가 접근 가능한 모든 메뉴 조회
SELECT * FROM get_user_accessible_menus('john.doe', 'ILSHIN');
```

---

## 프론트엔드 통합

### React Hook

```typescript
// hooks/useMenuPermission.ts
import { useState, useEffect } from "react";
import { checkMenuPermission } from "@/lib/api/menu";

export function useMenuPermission(
  menuObjid: number,
  permissionType: "create" | "read" | "update" | "delete" | "execute" | "export"
) {
  const [hasPermission, setHasPermission] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const checkPermission = async () => {
      try {
        const response = await checkMenuPermission(menuObjid, permissionType);
        setHasPermission(response.success && response.data?.hasPermission);
      } catch (error) {
        console.error("권한 확인 오류:", error);
        setHasPermission(false);
      } finally {
        setIsLoading(false);
      }
    };

    checkPermission();
  }, [menuObjid, permissionType]);

  return { hasPermission, isLoading };
}
```

### 사용자 메뉴 렌더링

```tsx
// components/Navigation.tsx
import { useEffect, useState } from "react";
import { getUserAccessibleMenus } from "@/lib/api/menu";
import { useAuth } from "@/hooks/useAuth";

export function Navigation() {
  const { user } = useAuth();
  const [menus, setMenus] = useState([]);

  useEffect(() => {
    const loadMenus = async () => {
      if (!user) return;

      const response = await getUserAccessibleMenus(
        user.userId,
        user.companyCode
      );
      if (response.success) {
        setMenus(response.data);
      }
    };

    loadMenus();
  }, [user]);

  return (
    <nav>
      {menus.map((menu) => (
        <NavItem key={menu.menuObjid} menu={menu} />
      ))}
    </nav>
  );
}
```

### 버튼 권한 제어

```tsx
// components/ContractDetail.tsx
import { useMenuPermission } from "@/hooks/useMenuPermission";

export function ContractDetail({ menuObjid }: { menuObjid: number }) {
  const { hasPermission: canUpdate } = useMenuPermission(menuObjid, "update");
  const { hasPermission: canDelete } = useMenuPermission(menuObjid, "delete");

  return (
    <div>
      <h1>계약 상세</h1>
      {canUpdate && <Button>수정</Button>}
      {canDelete && <Button variant="destructive">삭제</Button>}
    </div>
  );
}
```

---

## 권한 관리 UI 설계

### 권한 그룹 상세 페이지에서 메뉴 권한 설정

```tsx
// 체크박스 그리드 형태
┌─────────────────┬────────┬────────┬────────┬────────┬────────┬────────┐
│ 메뉴            │ 생성   │ 읽기   │ 수정   │ 삭제   │ 실행   │ 내보내기│
├─────────────────┼────────┼────────┼────────┼────────┼────────┼────────┤
│ 대시보드        │   ☐    │   ☑    │   ☐    │   ☐    │   ☐    │   ☐    │
│ 계약 관리       │   ☑    │   ☑    │   ☑    │   ☐    │   ☐    │   ☑    │
│ 사용자 관리     │   ☐    │   ☑    │   ☐    │   ☐    │   ☐    │   ☐    │
│ 플로우 관리     │   ☐    │   ☑    │   ☐    │   ☐    │   ☑    │   ☐    │
└─────────────────┴────────┴────────┴────────┴────────┴────────┴────────┘
```

---

## 실전 시나리오

### 시나리오: 사용자가 "배송 현황" 화면 생성 → 권한 설정

```sql
-- 1단계: 사용자가 화면 생성
INSERT INTO screen_definitions (screen_name, screen_code, company_code, created_by)
VALUES ('배송 현황', 'SCR_DELIVERY', 'ILSHIN', 'admin');

-- 2단계: 트리거가 자동으로 메뉴 추가 (자동!)
-- menu_info에 'SCR_DELIVERY' 메뉴가 자동 생성됨

-- 3단계: 권한 관리자가 영업팀에게 읽기 권한 부여
INSERT INTO rel_menu_auth (
    menu_objid,
    auth_objid,
    read_yn,
    export_yn,
    writer
)
VALUES (
    (SELECT objid FROM menu_info WHERE menu_code = 'SCR_DELIVERY'),
    (SELECT objid FROM authority_master WHERE auth_code = 'SALES_TEAM'),
    'Y',
    'Y',
    'admin'
);

-- 4단계: 영업팀 사용자가 로그인하면 "배송 현황" 메뉴가 보임!
SELECT * FROM get_user_accessible_menus('sales_user', 'ILSHIN');
```

---

## 장점

### ✅ 사용자 친화적

- **"메뉴" 개념으로 권한 관리** (직관적)
- 기존 시스템과 동일한 UI/UX

### ✅ 자동화

- 화면 생성 시 **자동으로 메뉴 추가**
- 화면 삭제 시 **자동으로 메뉴 비활성화**

### ✅ 세밀한 권한

- 메뉴별 **6가지 권한** (Create, Read, Update, Delete, Execute, Export)
- 권한 그룹 단위 관리

### ✅ 회사별 격리

- `menu_info.company_code`로 회사별 메뉴 분리
- 슈퍼관리자는 모든 회사 메뉴 관리

---

## 마이그레이션 실행

```bash
# 1. 권한 그룹 시스템 개선
docker exec -i <DB_CONTAINER_NAME> psql -U postgres -d ilshin < db/migrations/028_add_company_code_to_authority_master.sql

# 2. 메뉴 기반 권한 시스템 개선
docker exec -i <DB_CONTAINER_NAME> psql -U postgres -d ilshin < db/migrations/030_improve_menu_auth_system.sql

# 검증
docker exec -it <DB_CONTAINER_NAME> psql -U postgres -d ilshin -c "SELECT * FROM menu_info WHERE menu_type = 'dynamic';"
docker exec -it <DB_CONTAINER_NAME> psql -U postgres -d ilshin -c "SELECT * FROM v_menu_auth_summary;"
```

---

## FAQ

### Q1: 동적 메뉴와 정적 메뉴의 차이는?

**A**:

- **정적 메뉴** (`menu_type='static'`): 수동으로 추가한 고정 메뉴 (예: 대시보드, 사용자 관리)
- **동적 메뉴** (`menu_type='dynamic'`): 화면 생성 시 자동 추가된 메뉴

### Q2: 화면을 삭제하면 메뉴도 삭제되나요?

**A**: 메뉴는 **삭제되지 않고 비활성화**(`is_active=FALSE`)됩니다. 나중에 복구 가능합니다.

### Q3: 같은 화면에 대해 회사마다 다른 권한을 설정할 수 있나요?

**A**: 네! `menu_info.company_code`와 `authority_master.company_code`로 회사별 격리됩니다.

### Q4: 기존 메뉴 시스템과 호환되나요?

**A**: 완전히 호환됩니다. 기존 `menu_info`와 `rel_menu_auth`를 그대로 사용하며, 새로운 컬럼만 추가됩니다.

---

## 다음 단계

1. ✅ 마이그레이션 실행 (028, 030)
2. 🔄 백엔드 API 구현 (권한 체크 미들웨어)
3. 🔄 프론트엔드 UI 개발 (메뉴 권한 설정 그리드)
4. 🔄 테스트 (영업팀 시나리오)

---

## 관련 파일

- **마이그레이션**: `db/migrations/028_add_company_code_to_authority_master.sql`
- **마이그레이션**: `db/migrations/030_improve_menu_auth_system.sql`
- **백엔드 서비스**: `backend-node/src/services/RoleService.ts`
- **프론트엔드 API**: `frontend/lib/api/role.ts`
