# 권한 그룹 관리 시스템 상세 가이드

> 작성일: 2025-01-27  
> 파일 위치: `backend-node/src/services/roleService.ts`, `backend-node/src/controllers/roleController.ts`

---

## 📋 목차

1. [권한 그룹 관리 구조](#권한-그룹-관리-구조)
2. [최고 관리자 권한](#최고-관리자-권한)
3. [회사 관리자 권한](#회사-관리자-권한)
4. [메뉴 권한 설정](#메뉴-권한-설정)
5. [멤버 관리](#멤버-관리)
6. [권한 체크 로직](#권한-체크-로직)

---

## 권한 그룹 관리 구조

### 데이터베이스 구조

```sql
-- 권한 그룹 마스터 테이블
authority_master (
  objid SERIAL PRIMARY KEY,           -- 권한 그룹 ID
  auth_name VARCHAR(200),             -- 권한 그룹명
  auth_code VARCHAR(100),             -- 권한 그룹 코드
  company_code VARCHAR(50),           -- 회사 코드 ⭐
  status VARCHAR(20),                 -- 상태 (active/inactive)
  writer VARCHAR(50),                 -- 작성자
  regdate TIMESTAMP                   -- 등록일
)

-- 권한 그룹 멤버 테이블
authority_sub_user (
  objid SERIAL PRIMARY KEY,           -- 멤버 ID
  master_objid INTEGER,               -- 권한 그룹 ID (FK)
  user_id VARCHAR(50),                -- 사용자 ID
  writer VARCHAR(50),                 -- 작성자
  regdate TIMESTAMP                   -- 등록일
)

-- 메뉴 권한 테이블
rel_menu_auth (
  objid SERIAL PRIMARY KEY,           -- 권한 ID
  menu_objid INTEGER,                 -- 메뉴 ID (FK)
  auth_objid INTEGER,                 -- 권한 그룹 ID (FK)
  create_yn VARCHAR(1),               -- 생성 권한 (Y/N)
  read_yn VARCHAR(1),                 -- 조회 권한 (Y/N)
  update_yn VARCHAR(1),               -- 수정 권한 (Y/N)
  delete_yn VARCHAR(1),               -- 삭제 권한 (Y/N)
  execute_yn VARCHAR(1),              -- 실행 권한 (Y/N) ⭐
  export_yn VARCHAR(1),               -- 내보내기 권한 (Y/N) ⭐
  writer VARCHAR(50),                 -- 작성자
  regdate TIMESTAMP                   -- 등록일
)

-- 메뉴 정보 테이블
menu_info (
  objid SERIAL PRIMARY KEY,           -- 메뉴 ID
  menu_name_kor VARCHAR(200),         -- 메뉴명 (한글)
  menu_name_eng VARCHAR(200),         -- 메뉴명 (영문)
  menu_code VARCHAR(100),             -- 메뉴 코드
  menu_url VARCHAR(500),              -- 메뉴 URL
  menu_type INTEGER,                  -- 메뉴 타입
  parent_obj_id INTEGER,              -- 부모 메뉴 ID
  seq INTEGER,                        -- 정렬 순서
  company_code VARCHAR(50),           -- 회사 코드 ⭐
  status VARCHAR(20)                  -- 상태 (active/inactive)
)
```

### 권한 계층 구조

```
최고 관리자 (SUPER_ADMIN, company_code = "*")
  ├─ 모든 회사의 권한 그룹 조회/생성/수정/삭제
  ├─ 모든 회사의 메뉴에 대해 권한 부여 가능
  └─ 다른 회사 사용자를 권한 그룹에 추가 가능

회사 관리자 (COMPANY_ADMIN, company_code = "20", "30", etc.)
  ├─ 자기 회사의 권한 그룹만 조회/생성/수정/삭제
  ├─ 자기 회사의 메뉴에 대해서만 권한 부여 가능
  └─ 자기 회사 사용자만 권한 그룹에 추가 가능

일반 사용자 (USER)
  └─ 권한 그룹 관리 불가 (읽기 전용)
```

---

## 최고 관리자 권한

### 1. 권한 그룹 목록 조회

**API**: `GET /api/roles`

**로직**:

```typescript
export const getRoleGroups = async (req, res) => {
  const companyCode = req.query.companyCode as string | undefined;

  if (isSuperAdmin(req.user)) {
    // 최고 관리자: companyCode 파라미터가 있으면 해당 회사만, 없으면 전체 조회
    targetCompanyCode = companyCode; // undefined면 전체 조회
  } else {
    // 회사 관리자: 자기 회사만 조회
    targetCompanyCode = req.user?.companyCode;
  }

  const roleGroups = await RoleService.getRoleGroups(targetCompanyCode, search);
};
```

**데이터베이스 쿼리**:

```typescript
// RoleService.getRoleGroups()
let sql = `SELECT * FROM authority_master WHERE 1=1`;

if (companyCode) {
  sql += ` AND company_code = $1`; // 특정 회사만
} else {
  // 조건 없음 -> 전체 조회
}
```

**결과**:

- ✅ `companyCode` 파라미터 없음 → **모든 회사의 권한 그룹 조회**
- ✅ `companyCode = "20"` → **회사 "20"의 권한 그룹만 조회**
- ✅ `companyCode = "*"` → **공통 권한 그룹만 조회** (있다면)

### 2. 권한 그룹 생성

**API**: `POST /api/roles`

**로직**:

```typescript
export const createRoleGroup = async (req, res) => {
  const { authName, authCode, companyCode } = req.body;

  // 최고 관리자가 아닌 경우, 자기 회사에만 생성 가능
  if (!isSuperAdmin(req.user) && companyCode !== req.user?.companyCode) {
    return res.status(403).json({
      message: "자신의 회사에만 권한 그룹을 생성할 수 있습니다",
    });
  }

  await RoleService.createRoleGroup({
    authName,
    authCode,
    companyCode,
    writer,
  });
};
```

**결과**:

- ✅ 최고 관리자는 **어떤 회사에도** 권한 그룹 생성 가능
- ✅ `companyCode = "*"` 로 공통 권한 그룹도 생성 가능
- ❌ 회사 관리자는 자기 회사에만 생성 가능

### 3. 메뉴 목록 조회 (권한 부여용)

**API**: `GET /api/roles/menus/all?companyCode=20`

**로직**:

```typescript
export const getAllMenus = async (req, res) => {
  const requestedCompanyCode = req.query.companyCode as string | undefined;

  let companyCode: string | undefined;
  if (isSuperAdmin(req.user)) {
    // 최고 관리자: 요청한 회사 코드 사용 (없으면 전체)
    companyCode = requestedCompanyCode;
  } else {
    // 회사 관리자: 자기 회사 코드만 사용
    companyCode = req.user?.companyCode;
  }

  const menus = await RoleService.getAllMenus(companyCode);
};
```

**데이터베이스 쿼리**:

```typescript
// RoleService.getAllMenus()
let whereConditions = ["status = 'active'"];

if (companyCode) {
  // 특정 회사 메뉴만 조회 (공통 메뉴 제외)
  whereConditions.push(`company_code = $1`);
} else {
  // 조건 없음 -> 전체 메뉴 조회
}

const sql = `
  SELECT * FROM menu_info
  WHERE ${whereConditions.join(" AND ")}
  ORDER BY seq, menu_name_kor
`;
```

**결과**:

- ✅ `companyCode` 없음 → **모든 회사의 모든 메뉴 조회**
- ✅ `companyCode = "20"` → **회사 "20"의 메뉴만 조회**
- ✅ `companyCode = "*"` → **공통 메뉴만 조회**

**⚠️ 프론트엔드 구현 주의사항:**

프론트엔드에서 권한 그룹 상세 화면 (메뉴 권한 설정)에서는 **최고 관리자가 모든 메뉴를 볼 수 있도록** `companyCode` 파라미터를 전달하지 않아야 합니다:

```typescript
// MenuPermissionsTable.tsx
const { user: currentUser } = useAuth();
const isSuperAdmin =
  currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";

// 최고 관리자: companyCode 없이 모든 메뉴 조회
// 회사 관리자: 자기 회사 메뉴만 조회
const targetCompanyCode = isSuperAdmin ? undefined : roleGroup.companyCode;

const response = await roleAPI.getAllMenus(targetCompanyCode);
```

**이렇게 하지 않으면:**

- 최고 관리자가 회사 "20"의 권한 그룹에 들어가도
- `roleGroup.companyCode` (= "20")를 API로 전달하면
- 회사 "20"의 메뉴만 보이게 됨 ❌

**올바른 동작:**

- 최고 관리자가 회사 "20"의 권한 그룹에 들어가면
- `undefined`를 API로 전달하여
- **모든 회사의 모든 메뉴**를 볼 수 있어야 함 ✅

### 4. 메뉴 권한 설정

**API**: `POST /api/roles/:id/menu-permissions`

**Request Body**:

```json
{
  "permissions": [
    {
      "menuObjid": 101,
      "createYn": "Y",
      "readYn": "Y",
      "updateYn": "Y",
      "deleteYn": "Y",
      "executeYn": "Y",
      "exportYn": "Y"
    },
    {
      "menuObjid": 102,
      "createYn": "N",
      "readYn": "Y",
      "updateYn": "N",
      "deleteYn": "N",
      "executeYn": "N",
      "exportYn": "N"
    }
  ]
}
```

**로직**:

```typescript
export const setMenuPermissions = async (req, res) => {
  const authObjid = parseInt(req.params.id, 10);
  const { permissions } = req.body;

  // 권한 그룹 조회
  const roleGroup = await RoleService.getRoleGroupById(authObjid);

  // 권한 체크
  if (
    !isSuperAdmin(req.user) &&
    !canAccessCompanyData(req.user, roleGroup.companyCode)
  ) {
    return res.status(403).json({ message: "메뉴 권한 설정 권한이 없습니다" });
  }

  await RoleService.setMenuPermissions(authObjid, permissions, writer);
};
```

**데이터베이스 쿼리**:

```typescript
// RoleService.setMenuPermissions()

// 1. 기존 권한 삭제
await query("DELETE FROM rel_menu_auth WHERE auth_objid = $1", [authObjid]);

// 2. 새로운 권한 삽입
const sql = `
  INSERT INTO rel_menu_auth 
  (objid, menu_objid, auth_objid, create_yn, read_yn, update_yn, delete_yn, execute_yn, export_yn, writer, regdate)
  VALUES 
  ${values} -- (nextval('seq'), $1, $2, $3, $4, $5, $6, $7, $8, $9, NOW()), ...
`;
```

**결과**:

- ✅ 최고 관리자는 **어떤 권한 그룹에도** 메뉴 권한 설정 가능
- ✅ **모든 회사의 메뉴**에 대해 권한 부여 가능
- ✅ CRUD + Execute + Export 총 6가지 권한 설정

### 5. 멤버 관리

**API**: `PUT /api/roles/:id/members`

**Request Body**:

```json
{
  "userIds": ["user001", "user002", "user003"]
}
```

**로직**:

```typescript
export const updateRoleMembers = async (req, res) => {
  const masterObjid = parseInt(req.params.id, 10);
  const { userIds } = req.body;

  // 권한 그룹 조회
  const roleGroup = await RoleService.getRoleGroupById(masterObjid);

  // 권한 체크
  if (
    !isSuperAdmin(req.user) &&
    !canAccessCompanyData(req.user, roleGroup.companyCode)
  ) {
    return res
      .status(403)
      .json({ message: "권한 그룹 멤버 수정 권한이 없습니다" });
  }

  // 기존 멤버 조회
  const existingMembers = await RoleService.getRoleMembers(masterObjid);
  const existingUserIds = existingMembers.map((m) => m.userId);

  // 추가할 멤버 (새로 추가된 것들)
  const toAdd = userIds.filter((id) => !existingUserIds.includes(id));

  // 제거할 멤버 (기존에 있었는데 없어진 것들)
  const toRemove = existingUserIds.filter((id) => !userIds.includes(id));

  // 추가
  if (toAdd.length > 0) {
    await RoleService.addRoleMembers(masterObjid, toAdd, writer);
  }

  // 제거
  if (toRemove.length > 0) {
    await RoleService.removeRoleMembers(masterObjid, toRemove, writer);
  }
};
```

**결과**:

- ✅ 최고 관리자는 **어떤 회사 사용자도** 권한 그룹에 추가 가능
- ✅ 다른 회사 권한 그룹에도 멤버 추가 가능
- ⚠️ 단, 사용자 목록 API에서 최고 관리자는 필터링되므로 추가 불가

---

## 회사 관리자 권한

### 1. 권한 그룹 목록 조회

**로직**:

```typescript
if (!isSuperAdmin(req.user)) {
  // 회사 관리자: 자기 회사만 조회
  targetCompanyCode = req.user?.companyCode; // 강제로 자기 회사
}
```

**결과**:

- ✅ 자기 회사 (`company_code = "20"`) 권한 그룹만 조회
- ❌ 다른 회사 권한 그룹은 **절대 볼 수 없음**

### 2. 권한 그룹 생성

**로직**:

```typescript
if (!isSuperAdmin(req.user) && companyCode !== req.user?.companyCode) {
  return res.status(403).json({
    message: "자신의 회사에만 권한 그룹을 생성할 수 있습니다",
  });
}
```

**결과**:

- ✅ 자기 회사에만 권한 그룹 생성 가능
- ❌ 다른 회사나 공통(`*`)에는 생성 불가

### 3. 메뉴 목록 조회

**로직**:

```typescript
if (!isSuperAdmin(req.user)) {
  // 회사 관리자: 자기 회사 코드만 사용
  companyCode = req.user?.companyCode; // 강제로 자기 회사
}
```

**데이터베이스 쿼리**:

```sql
SELECT * FROM menu_info
WHERE status = 'active'
  AND company_code = '20' -- 자기 회사만
ORDER BY seq, menu_name_kor
```

**결과**:

- ✅ 자기 회사 메뉴만 조회
- ❌ 공통 메뉴(`company_code = "*"`)는 **조회되지 않음**
- ❌ 다른 회사 메뉴는 **절대 볼 수 없음**

### 4. 메뉴 권한 설정

**로직**:

```typescript
// 권한 그룹의 회사 코드 체크
if (
  !isSuperAdmin(req.user) &&
  !canAccessCompanyData(req.user, roleGroup.companyCode)
) {
  return res.status(403).json({ message: "메뉴 권한 설정 권한이 없습니다" });
}
```

**`canAccessCompanyData` 함수**:

```typescript
export function canAccessCompanyData(
  user: UserInfo | undefined,
  targetCompanyCode: string
): boolean {
  if (!user) return false;
  if (isSuperAdmin(user)) return true; // 슈퍼관리자는 모든 회사 접근 가능
  return user.companyCode === targetCompanyCode; // 자기 회사만 접근 가능
}
```

**결과**:

- ✅ 자기 회사 권한 그룹에만 메뉴 권한 설정 가능
- ✅ 자기 회사 메뉴에 대해서만 권한 부여 가능
- ❌ 다른 회사 권한 그룹이나 메뉴는 접근 불가

### 5. 멤버 관리

**로직**:

```typescript
// 권한 그룹의 회사 코드 체크
if (
  !isSuperAdmin(req.user) &&
  !canAccessCompanyData(req.user, roleGroup.companyCode)
) {
  return res
    .status(403)
    .json({ message: "권한 그룹 멤버 수정 권한이 없습니다" });
}
```

**사용자 목록 조회 (Dual List Box)**:

```typescript
// adminController.getUserList()
if (req.user && req.user.companyCode !== "*") {
  whereConditions.push(`company_code != '*'`); // 최고 관리자 필터링
  whereConditions.push(`company_code = $1`); // 자기 회사만
}
```

**결과**:

- ✅ 자기 회사 권한 그룹에만 멤버 추가/제거 가능
- ✅ 자기 회사 사용자만 멤버로 추가 가능
- ❌ 최고 관리자는 목록에서 **절대 보이지 않음**
- ❌ 다른 회사 사용자는 **절대 볼 수 없음**

---

## 메뉴 권한 설정

### 권한 종류 (6가지)

| 권한         | 컬럼명       | 설명                      | 예시                     |
| ------------ | ------------ | ------------------------- | ------------------------ |
| **생성**     | `create_yn`  | 데이터 생성 가능 여부     | 사용자 추가, 주문 생성   |
| **조회**     | `read_yn`    | 데이터 조회 가능 여부     | 목록 보기, 상세 보기     |
| **수정**     | `update_yn`  | 데이터 수정 가능 여부     | 정보 변경, 상태 변경     |
| **삭제**     | `delete_yn`  | 데이터 삭제 가능 여부     | 항목 삭제, 데이터 제거   |
| **실행**     | `execute_yn` | 특수 기능 실행 가능 여부  | 플로우 실행, 배치 실행   |
| **내보내기** | `export_yn`  | 데이터 내보내기 가능 여부 | Excel 다운로드, PDF 출력 |

### 권한 설정 예시

#### 1. 읽기 전용 권한

```json
{
  "createYn": "N",
  "readYn": "Y",
  "updateYn": "N",
  "deleteYn": "N",
  "executeYn": "N",
  "exportYn": "N"
}
```

→ 조회만 가능, 수정/삭제 불가

#### 2. 전체 권한

```json
{
  "createYn": "Y",
  "readYn": "Y",
  "updateYn": "Y",
  "deleteYn": "Y",
  "executeYn": "Y",
  "exportYn": "Y"
}
```

→ 모든 작업 가능

#### 3. 운영자 권한

```json
{
  "createYn": "Y",
  "readYn": "Y",
  "updateYn": "Y",
  "deleteYn": "N",
  "executeYn": "Y",
  "exportYn": "Y"
}
```

→ 삭제 제외 모든 작업 가능

### 메뉴 권한 설정 프로세스

```mermaid
graph TD
    A[권한 그룹 선택] --> B[메뉴 목록 조회]
    B --> C{최고 관리자?}
    C -->|Yes| D[모든 회사 메뉴 조회]
    C -->|No| E[자기 회사 메뉴만 조회]
    D --> F[메뉴별 권한 설정]
    E --> F
    F --> G[기존 권한 삭제]
    G --> H[새 권한 일괄 삽입]
    H --> I[완료]
```

---

## 멤버 관리

### Dual List Box 구조

```
┌─────────────────────┐        ┌─────────────────────┐
│   사용 가능한 사용자   │   →   │   권한 그룹 멤버      │
│                     │   ←   │                     │
│  [ ] user001        │        │  [x] user005        │
│  [ ] user002        │        │  [x] user006        │
│  [ ] user003        │        │  [x] user007        │
│  [ ] user004        │        │                     │
└─────────────────────┘        └─────────────────────┘
```

### 멤버 추가/제거 로직

```typescript
// 프론트엔드에서 전송
PUT /api/roles/123/members
{
  "userIds": ["user005", "user006", "user008"] // 최종 멤버 목록
}

// 백엔드 처리
const existingUserIds = ["user005", "user006", "user007"]; // 기존 멤버
const newUserIds = ["user005", "user006", "user008"];     // 요청된 멤버

const toAdd = ["user008"];    // 새로 추가 (기존에 없던 것)
const toRemove = ["user007"]; // 제거 (기존에 있었는데 없어진 것)

// 추가
INSERT INTO authority_sub_user (master_objid, user_id, writer, regdate)
VALUES (123, 'user008', 'admin', NOW())

// 제거
DELETE FROM authority_sub_user
WHERE master_objid = 123 AND user_id = 'user007'
```

### 사용자 목록 필터링

**API**: `GET /api/admin/users?companyCode=20&size=1000`

**로직**:

```typescript
// 회사 코드 필터
if (companyCode && companyCode.trim()) {
  whereConditions.push(`company_code = $1`);
  queryParams.push(companyCode.trim());
}

// 최고 관리자 필터링 (중요!)
if (req.user && req.user.companyCode !== "*") {
  whereConditions.push(`company_code != '*'`); // 최고 관리자 숨김
}

// 검색 조건
if (search && search.trim()) {
  whereConditions.push(`(
    user_id ILIKE $2 OR
    user_name ILIKE $2 OR
    dept_name ILIKE $2
  )`);
  queryParams.push(`%${search.trim()}%`);
}
```

**결과**:

- ✅ 최고 관리자: 요청한 회사의 사용자 목록
- ✅ 회사 관리자: 자기 회사 사용자 목록
- ✅ 최고 관리자(`company_code = "*"`)는 **절대 목록에 표시되지 않음**

---

## 권한 체크 로직

### 1. `isSuperAdmin()`

**파일**: `backend-node/src/utils/permissionUtils.ts`

```typescript
export function isSuperAdmin(user: UserInfo | undefined): boolean {
  if (!user) return false;
  return user.companyCode === "*";
}
```

**사용**:

- 권한 그룹 목록 전체 조회 여부
- 다른 회사 데이터 접근 여부
- 모든 메뉴 조회 여부

### 2. `isCompanyAdmin()`

```typescript
export function isCompanyAdmin(user: UserInfo | undefined): boolean {
  if (!user) return false;
  return user.userType === "COMPANY_ADMIN";
}
```

**사용**:

- 권한 그룹 관리 접근 여부
- 메뉴 권한 설정 접근 여부

### 3. `canAccessCompanyData()`

```typescript
export function canAccessCompanyData(
  user: UserInfo | undefined,
  targetCompanyCode: string
): boolean {
  if (!user) return false;
  if (isSuperAdmin(user)) return true; // 슈퍼관리자는 모든 회사 접근 가능
  return user.companyCode === targetCompanyCode; // 자기 회사만 접근 가능
}
```

**사용**:

- 권한 그룹 상세 조회 접근 여부
- 권한 그룹 수정/삭제 접근 여부
- 멤버 관리 접근 여부
- 메뉴 권한 설정 접근 여부

### 권한 체크 플로우

```mermaid
graph TD
    A[API 요청] --> B{로그인 확인}
    B -->|No| C[401 Unauthorized]
    B -->|Yes| D{최고 관리자?}
    D -->|Yes| E[모든 데이터 접근 허용]
    D -->|No| F{회사 관리자?}
    F -->|No| G[403 Forbidden]
    F -->|Yes| H{자기 회사 데이터?}
    H -->|No| I[403 Forbidden]
    H -->|Yes| J[접근 허용]
```

---

## 💡 핵심 정리

### ✅ 최고 관리자가 할 수 있는 것

1. **권한 그룹 관리**

   - ✅ 모든 회사의 권한 그룹 조회
   - ✅ 어떤 회사에도 권한 그룹 생성 가능
   - ✅ 다른 회사 권한 그룹 수정/삭제 가능

2. **메뉴 권한 설정**

   - ✅ 모든 회사의 메뉴 조회 가능
   - ✅ 어떤 권한 그룹에도 메뉴 권한 부여 가능
   - ✅ 모든 메뉴에 대해 CRUD + Execute + Export 권한 설정

3. **멤버 관리**
   - ✅ 어떤 회사 권한 그룹에도 멤버 추가 가능
   - ✅ 다른 회사 사용자도 멤버로 추가 가능
   - ⚠️ 단, 최고 관리자는 사용자 목록에서 필터링되므로 추가 불가

### ✅ 회사 관리자가 할 수 있는 것

1. **권한 그룹 관리**

   - ✅ 자기 회사 권한 그룹만 조회
   - ✅ 자기 회사에만 권한 그룹 생성 가능
   - ✅ 자기 회사 권한 그룹만 수정/삭제 가능

2. **메뉴 권한 설정**

   - ✅ 자기 회사 메뉴만 조회 가능
   - ✅ 자기 회사 권한 그룹에만 메뉴 권한 부여 가능
   - ✅ 자기 회사 메뉴에 대해서만 CRUD + Execute + Export 권한 설정

3. **멤버 관리**
   - ✅ 자기 회사 권한 그룹에만 멤버 추가 가능
   - ✅ 자기 회사 사용자만 멤버로 추가 가능
   - ✅ 최고 관리자는 목록에서 절대 보이지 않음

### ❌ 제한 사항

1. **회사 관리자는 절대 할 수 없는 것**

   - ❌ 다른 회사 권한 그룹 조회/수정/삭제
   - ❌ 공통 메뉴(`company_code = "*"`) 조회
   - ❌ 최고 관리자를 멤버로 추가
   - ❌ 다른 회사 사용자를 멤버로 추가

2. **최고 관리자도 할 수 없는 것**
   - ❌ 다른 최고 관리자를 권한 그룹 멤버로 추가 (사용자 목록 필터링으로 인해)

---

## 📊 권한 매트릭스

| 작업               | 최고 관리자         | 회사 관리자              | 일반 사용자 |
| ------------------ | ------------------- | ------------------------ | ----------- |
| **권한 그룹 조회** | ✅ 모든 회사        | ✅ 자기 회사만           | ❌          |
| **권한 그룹 생성** | ✅ 모든 회사        | ✅ 자기 회사만           | ❌          |
| **권한 그룹 수정** | ✅ 모든 회사        | ✅ 자기 회사만           | ❌          |
| **권한 그룹 삭제** | ✅ 모든 회사        | ✅ 자기 회사만           | ❌          |
| **메뉴 목록 조회** | ✅ 모든 회사 메뉴   | ✅ 자기 회사 메뉴만      | ❌          |
| **메뉴 권한 설정** | ✅ 모든 권한 그룹   | ✅ 자기 회사 권한 그룹만 | ❌          |
| **멤버 추가**      | ✅ 모든 회사 사용자 | ✅ 자기 회사 사용자만    | ❌          |
| **멤버 제거**      | ✅ 모든 회사 멤버   | ✅ 자기 회사 멤버만      | ❌          |

---

## 📝 작성자

- 작성: AI Assistant (Claude Sonnet 4.5)
- 검토 필요: 백엔드 개발자, 시스템 아키텍트
- 관련 파일:
  - `backend-node/src/services/roleService.ts`
  - `backend-node/src/controllers/roleController.ts`
  - `backend-node/src/utils/permissionUtils.ts`
  - `frontend/components/admin/RoleDetailManagement.tsx`
