docs: 메뉴 동기화 시스템 상세 설계 추가
- 동기화 상태 분류 (new, up_to_date, updatable, customized, deleted) - 동기화 액션 유형 (개별, 신규 가져오기, 기존 업데이트, 선택 동기화) - 커스터마이징 메뉴 보호 정책 (force 옵션) - 동기화 전용 API 스펙 4개 엔드포인트 - MenuSyncService 메서드 설계 - MNG 동기화 센터 UI 와이어프레임
This commit is contained in:
@@ -134,9 +134,83 @@ ## 5. 메뉴 상태 플로우
|
||||
|
||||
---
|
||||
|
||||
## 6. API 엔드포인트 설계
|
||||
## 6. 동기화 시스템 설계
|
||||
|
||||
### 6.1 글로벌 메뉴 관리 (시스템 관리자용)
|
||||
### 6.1 동기화 상태 분류
|
||||
|
||||
테넌트 메뉴의 글로벌 메뉴 대비 상태를 5가지로 분류합니다:
|
||||
|
||||
| 상태 | 코드 | 설명 | 아이콘 |
|
||||
|------|------|------|--------|
|
||||
| **신규** | `new` | 글로벌에 있으나 테넌트에 없음 | 🆕 |
|
||||
| **최신** | `up_to_date` | 글로벌과 동일한 상태 | ✅ |
|
||||
| **업데이트 가능** | `updatable` | 글로벌이 변경됨, 동기화 가능 | 🔄 |
|
||||
| **커스텀** | `customized` | 테넌트가 수정함 (보호됨) | ✏️ |
|
||||
| **삭제됨** | `deleted` | 글로벌에서 삭제됨 | ⚠️ |
|
||||
|
||||
### 6.2 동기화 액션 유형
|
||||
|
||||
사용자 편의를 위해 4가지 동기화 액션을 제공합니다:
|
||||
|
||||
| 액션 | 설명 | 대상 상태 |
|
||||
|------|------|----------|
|
||||
| **개별 동기화** | 선택한 메뉴만 동기화 | 모든 상태 (force 옵션) |
|
||||
| **신규 가져오기** | 새로운 글로벌 메뉴만 일괄 복제 | `new` |
|
||||
| **기존 업데이트** | 변경된 기존 메뉴만 일괄 동기화 | `updatable` (커스텀 제외) |
|
||||
| **선택 동기화** | 필터/선택 후 일괄 동기화 | 선택된 메뉴 |
|
||||
|
||||
### 6.3 커스터마이징 메뉴 보호 정책
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 커스텀 메뉴 보호 정책 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ is_customized = 1 인 메뉴: │
|
||||
│ ├── 일괄 동기화 시 자동 제외 (보호됨) │
|
||||
│ ├── 개별 동기화 시 경고 메시지 표시 │
|
||||
│ └── force=true 옵션으로만 강제 동기화 가능 │
|
||||
│ │
|
||||
│ 동기화 시 is_customized 플래그: │
|
||||
│ ├── 동기화 완료 → is_customized = 0 (원본 상태로) │
|
||||
│ └── 테넌트 수정 → is_customized = 1 (자동 설정) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 6.4 동기화 상태 판단 로직
|
||||
|
||||
```php
|
||||
// 의사 코드
|
||||
function getSyncStatus($tenantMenu, $globalMenu) {
|
||||
if ($globalMenu === null) {
|
||||
return 'deleted'; // 글로벌 메뉴 삭제됨
|
||||
}
|
||||
|
||||
if ($tenantMenu === null) {
|
||||
return 'new'; // 테넌트에 없음
|
||||
}
|
||||
|
||||
if ($tenantMenu->is_customized) {
|
||||
return 'customized'; // 테넌트가 수정함
|
||||
}
|
||||
|
||||
// 변경 여부 비교 (name, url, icon, sort_order)
|
||||
$hasChanges = compareMenuFields($tenantMenu, $globalMenu);
|
||||
|
||||
if ($hasChanges) {
|
||||
return 'updatable'; // 동기화 가능
|
||||
}
|
||||
|
||||
return 'up_to_date'; // 최신 상태
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. API 엔드포인트 설계
|
||||
|
||||
### 7.1 글로벌 메뉴 관리 (시스템 관리자용)
|
||||
|
||||
| Method | Endpoint | 설명 |
|
||||
|--------|----------|------|
|
||||
@@ -146,7 +220,7 @@ ### 6.1 글로벌 메뉴 관리 (시스템 관리자용)
|
||||
| DELETE | `/v1/admin/global-menus/{id}` | 글로벌 메뉴 삭제 |
|
||||
| POST | `/v1/admin/global-menus/{id}/sync-to-tenants` | 특정 메뉴를 모든 테넌트에 추가 |
|
||||
|
||||
### 6.2 테넌트 메뉴 관리 (테넌트 관리자용)
|
||||
### 7.2 테넌트 메뉴 관리 (테넌트 관리자용)
|
||||
|
||||
| Method | Endpoint | 설명 |
|
||||
|--------|----------|------|
|
||||
@@ -158,14 +232,168 @@ ### 6.2 테넌트 메뉴 관리 (테넌트 관리자용)
|
||||
| DELETE | `/v1/menus/{id}` | 테넌트 메뉴 삭제 (기존) |
|
||||
| PATCH | `/v1/menus/{id}/toggle` | 상태 토글 (기존) |
|
||||
| POST | `/v1/menus/{id}/restore` | 삭제된 메뉴 복원 |
|
||||
| POST | `/v1/menus/{id}/sync-from-global` | 글로벌 원본과 동기화 |
|
||||
| POST | `/v1/menus/{id}/sync-from-global` | 글로벌 원본과 동기화 (개별) |
|
||||
| POST | `/v1/menus/reorder` | 메뉴 순서 변경 (기존) |
|
||||
|
||||
### 7.3 동기화 전용 API (테넌트 관리자용)
|
||||
|
||||
| Method | Endpoint | 설명 |
|
||||
|--------|----------|------|
|
||||
| GET | `/v1/menus/sync-status` | 동기화 상태 목록 조회 |
|
||||
| POST | `/v1/menus/sync` | 선택 동기화 (menu_ids 지정) |
|
||||
| POST | `/v1/menus/sync-new` | 신규 글로벌 메뉴 일괄 가져오기 |
|
||||
| POST | `/v1/menus/sync-updates` | 기존 메뉴 일괄 업데이트 (커스텀 제외) |
|
||||
|
||||
### 7.4 동기화 API 상세 스펙
|
||||
|
||||
#### GET `/v1/menus/sync-status`
|
||||
|
||||
동기화 상태 목록을 조회합니다.
|
||||
|
||||
**Query Parameters:**
|
||||
| 파라미터 | 타입 | 필수 | 설명 |
|
||||
|----------|------|------|------|
|
||||
| `status` | string | N | 필터: `new`, `updatable`, `up_to_date`, `customized`, `deleted` |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "message.fetched",
|
||||
"data": {
|
||||
"summary": {
|
||||
"new": 2,
|
||||
"updatable": 3,
|
||||
"up_to_date": 15,
|
||||
"customized": 1,
|
||||
"deleted": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"global_menu_id": 10,
|
||||
"global_name": "고객센터",
|
||||
"global_url": "/support",
|
||||
"global_icon": "headphones",
|
||||
"status": "new",
|
||||
"tenant_menu_id": null,
|
||||
"tenant_name": null,
|
||||
"is_customized": false,
|
||||
"changes": []
|
||||
},
|
||||
{
|
||||
"global_menu_id": 1,
|
||||
"global_name": "대시보드",
|
||||
"global_url": "/dashboard",
|
||||
"global_icon": "home",
|
||||
"status": "updatable",
|
||||
"tenant_menu_id": 100,
|
||||
"tenant_name": "대시보드",
|
||||
"is_customized": false,
|
||||
"changes": ["icon", "url"]
|
||||
},
|
||||
{
|
||||
"global_menu_id": 2,
|
||||
"global_name": "사용자 관리",
|
||||
"global_url": "/users",
|
||||
"global_icon": "users",
|
||||
"status": "customized",
|
||||
"tenant_menu_id": 101,
|
||||
"tenant_name": "직원 관리",
|
||||
"is_customized": true,
|
||||
"changes": ["name"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### POST `/v1/menus/sync`
|
||||
|
||||
선택한 메뉴를 동기화합니다.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"menu_ids": [10, 11, 12],
|
||||
"force": false
|
||||
}
|
||||
```
|
||||
|
||||
| 파라미터 | 타입 | 필수 | 설명 |
|
||||
|----------|------|------|------|
|
||||
| `menu_ids` | array | Y | 동기화할 글로벌 메뉴 ID 목록 |
|
||||
| `force` | boolean | N | 커스텀 메뉴 강제 덮어쓰기 (기본: false) |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "message.menu.synced",
|
||||
"data": {
|
||||
"synced": 2,
|
||||
"created": 1,
|
||||
"skipped": 0,
|
||||
"details": [
|
||||
{ "global_menu_id": 10, "action": "created", "tenant_menu_id": 150 },
|
||||
{ "global_menu_id": 11, "action": "updated", "tenant_menu_id": 102 },
|
||||
{ "global_menu_id": 12, "action": "updated", "tenant_menu_id": 103 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### POST `/v1/menus/sync-new`
|
||||
|
||||
신규 글로벌 메뉴만 일괄 가져옵니다.
|
||||
|
||||
**Request Body:** (없음)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "message.menu.new_imported",
|
||||
"data": {
|
||||
"imported": 3,
|
||||
"menus": [
|
||||
{ "global_menu_id": 10, "name": "고객센터", "tenant_menu_id": 150 },
|
||||
{ "global_menu_id": 11, "name": "결제내역", "tenant_menu_id": 151 },
|
||||
{ "global_menu_id": 12, "name": "구독관리", "tenant_menu_id": 152 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### POST `/v1/menus/sync-updates`
|
||||
|
||||
변경된 기존 메뉴를 일괄 업데이트합니다 (커스텀 메뉴 자동 제외).
|
||||
|
||||
**Request Body:** (없음)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "message.menu.updates_synced",
|
||||
"data": {
|
||||
"updated": 3,
|
||||
"skipped_customized": 1,
|
||||
"details": [
|
||||
{ "global_menu_id": 1, "tenant_menu_id": 100, "changes": ["icon"] },
|
||||
{ "global_menu_id": 3, "tenant_menu_id": 102, "changes": ["url", "name"] }
|
||||
],
|
||||
"skipped": [
|
||||
{ "global_menu_id": 2, "tenant_menu_id": 101, "reason": "customized" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 서비스 메서드 설계
|
||||
## 8. 서비스 메서드 설계
|
||||
|
||||
### 7.1 MenuService.php 추가 메서드
|
||||
### 8.1 MenuService.php 추가 메서드
|
||||
|
||||
```php
|
||||
/**
|
||||
@@ -190,7 +418,7 @@ ### 7.1 MenuService.php 추가 메서드
|
||||
public function restore(int $menuId): Menu
|
||||
```
|
||||
|
||||
### 7.2 GlobalMenuService.php (신규)
|
||||
### 8.2 GlobalMenuService.php (신규)
|
||||
|
||||
```php
|
||||
/**
|
||||
@@ -214,11 +442,40 @@ ### 7.2 GlobalMenuService.php (신규)
|
||||
public function syncToAllTenants(int $globalMenuId): int
|
||||
```
|
||||
|
||||
### 8.3 MenuSyncService.php (신규)
|
||||
|
||||
```php
|
||||
/**
|
||||
* 동기화 상태 목록 조회
|
||||
*/
|
||||
public function getSyncStatus(int $tenantId, ?string $statusFilter = null): array
|
||||
|
||||
/**
|
||||
* 선택 동기화 (신규 생성 또는 기존 업데이트)
|
||||
*/
|
||||
public function syncMenus(int $tenantId, array $globalMenuIds, bool $force = false): array
|
||||
|
||||
/**
|
||||
* 신규 글로벌 메뉴 일괄 가져오기
|
||||
*/
|
||||
public function importNewMenus(int $tenantId): array
|
||||
|
||||
/**
|
||||
* 기존 메뉴 일괄 업데이트 (커스텀 제외)
|
||||
*/
|
||||
public function syncUpdates(int $tenantId): array
|
||||
|
||||
/**
|
||||
* 메뉴 필드 비교 (변경 사항 감지)
|
||||
*/
|
||||
private function compareMenuFields(Menu $tenantMenu, Menu $globalMenu): array
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. MNG 화면 설계
|
||||
## 9. MNG 화면 설계
|
||||
|
||||
### 8.1 글로벌 메뉴 관리 (시스템 관리자)
|
||||
### 9.1 글로벌 메뉴 관리 (시스템 관리자)
|
||||
|
||||
```
|
||||
/mng/global-menus
|
||||
@@ -239,7 +496,7 @@ ### 8.1 글로벌 메뉴 관리 (시스템 관리자)
|
||||
└── 동기화 실행
|
||||
```
|
||||
|
||||
### 8.2 테넌트 메뉴 관리 (테넌트 관리자)
|
||||
### 9.2 테넌트 메뉴 관리 (테넌트 관리자)
|
||||
|
||||
```
|
||||
/mng/menus
|
||||
@@ -264,9 +521,93 @@ ### 8.2 테넌트 메뉴 관리 (테넌트 관리자)
|
||||
└── 동기화 실행 버튼
|
||||
```
|
||||
|
||||
### 9.3 동기화 센터 화면 (테넌트 관리자)
|
||||
|
||||
```
|
||||
/mng/menus/sync
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 📋 메뉴 동기화 센터 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [요약 카드] │
|
||||
│ ┌──────────┬──────────┬──────────┬──────────┬──────────┐ │
|
||||
│ │ 🆕 신규 │ 🔄 업데이트│ ✅ 최신 │ ✏️ 커스텀│ ⚠️ 삭제됨│ │
|
||||
│ │ 2개 │ 3개 │ 15개 │ 1개 │ 1개 │ │
|
||||
│ └──────────┴──────────┴──────────┴──────────┴──────────┘ │
|
||||
│ │
|
||||
│ [빠른 액션] │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 🆕 신규 가져오기 │ │ 🔄 업데이트 동기화│ │
|
||||
│ │ (2개) │ │ (3개) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ │
|
||||
│ │
|
||||
│ [상태 필터] │
|
||||
│ ○ 전체 ● 신규 ○ 업데이트 ○ 커스텀 ○ 삭제됨 │
|
||||
│ │
|
||||
│ [메뉴 목록 테이블] │
|
||||
│ ┌────┬────────────┬────────────┬────────┬──────────┬────────┐ │
|
||||
│ │ ☑ │ 글로벌 메뉴 │ 테넌트 메뉴 │ 상태 │ 변경 항목 │ 액션 │ │
|
||||
│ ├────┼────────────┼────────────┼────────┼──────────┼────────┤ │
|
||||
│ │ ☑ │ 고객센터 │ - │ 🆕 신규 │ - │ 가져오기│ │
|
||||
│ │ ☑ │ 대시보드 │ 대시보드 │ 🔄 업뎃 │ icon,url │ 동기화 │ │
|
||||
│ │ ☐ │ 사용자관리 │ 직원관리 │ ✏️ 커스텀│ name │ ⚠️ 강제│ │
|
||||
│ └────┴────────────┴────────────┴────────┴──────────┴────────┘ │
|
||||
│ │
|
||||
│ [선택 액션] │
|
||||
│ ☐ 전체 선택 │ [🔄 선택 동기화 (2개)] [❌ 취소] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.4 동기화 확인 모달
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ 동기화 확인 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 다음 메뉴를 동기화하시겠습니까? │
|
||||
│ │
|
||||
│ 📝 동기화 내역: │
|
||||
│ • 신규 생성: 2개 (고객센터, 결제내역) │
|
||||
│ • 업데이트: 1개 (대시보드) │
|
||||
│ │
|
||||
│ ⚠️ 경고: │
|
||||
│ • 커스텀 메뉴 1개가 포함되어 있습니다. │
|
||||
│ - "직원관리" (원본: 사용자관리) │
|
||||
│ ☐ 커스텀 메뉴도 강제 동기화 (원본으로 덮어쓰기) │
|
||||
│ │
|
||||
│ [취소] [동기화 실행] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.5 동기화 결과 모달
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ✅ 동기화 완료 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 동기화가 성공적으로 완료되었습니다. │
|
||||
│ │
|
||||
│ 📊 결과: │
|
||||
│ • 생성됨: 2개 │
|
||||
│ • 업데이트됨: 1개 │
|
||||
│ • 건너뜀 (커스텀): 1개 │
|
||||
│ │
|
||||
│ 📋 상세: │
|
||||
│ ✅ 고객센터 - 신규 생성 │
|
||||
│ ✅ 결제내역 - 신규 생성 │
|
||||
│ ✅ 대시보드 - icon, url 업데이트 │
|
||||
│ ⏭️ 직원관리 - 커스텀 메뉴로 건너뜀 │
|
||||
│ │
|
||||
│ [확인] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 변경 영향도 분석
|
||||
## 10. 변경 영향도 분석
|
||||
|
||||
| 파일 | 변경 내용 | 영향도 |
|
||||
|------|----------|--------|
|
||||
@@ -282,9 +623,9 @@ ## 9. 변경 영향도 분석
|
||||
|
||||
---
|
||||
|
||||
## 10. 구현 계획
|
||||
## 11. 구현 계획
|
||||
|
||||
### Phase 1: DB 및 모델 (1일)
|
||||
### Phase 1: DB 및 모델
|
||||
- [ ] 마이그레이션 생성 (`global_menu_id`, `is_customized`)
|
||||
- [ ] Menu 모델 수정 (fillable, 관계 메서드)
|
||||
- [ ] MenuBootstrapService 수정 (복사 시 global_menu_id 저장)
|
||||
@@ -316,7 +657,7 @@ ### Phase 4: 테스트 (1일)
|
||||
|
||||
---
|
||||
|
||||
## 11. 관련 파일 위치
|
||||
## 12. 관련 파일 위치
|
||||
|
||||
```
|
||||
API 프로젝트: /Users/hskwon/Works/@KD_SAM/SAM/api/
|
||||
@@ -352,14 +693,14 @@ ## 11. 관련 파일 위치
|
||||
|
||||
---
|
||||
|
||||
## 12. 메뉴 추가 SQL (참고)
|
||||
## 13. 메뉴 추가 SQL (참고)
|
||||
|
||||
PDF 문서에서 추출한 신규 메뉴 SQL은 별도 파일 참조:
|
||||
- `claudedocs/MENU_INSERT_QUERIES.sql`
|
||||
|
||||
---
|
||||
|
||||
## 13. 다음 작업
|
||||
## 14. 다음 작업
|
||||
|
||||
1. Phase 1 시작: 마이그레이션 생성
|
||||
2. Menu 모델 수정
|
||||
@@ -371,4 +712,5 @@ ## 변경 이력
|
||||
|
||||
| 날짜 | 작성자 | 내용 |
|
||||
|------|--------|------|
|
||||
| 2025-12-01 | Claude | 초안 작성 |
|
||||
| 2025-12-01 | Claude | 초안 작성 |
|
||||
| 2025-12-02 | Claude | 동기화 시스템 상세 설계 추가 (상태 분류, API 스펙, MNG UI) |
|
||||
Reference in New Issue
Block a user