From aabd7913361f0f12879a062c9fb19dcf0f5887a3 Mon Sep 17 00:00:00 2001 From: hskwon Date: Tue, 2 Dec 2025 09:38:15 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EB=A9=94=EB=89=B4=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 동기화 상태 분류 (new, up_to_date, updatable, customized, deleted) - 동기화 액션 유형 (개별, 신규 가져오기, 기존 업데이트, 선택 동기화) - 커스터마이징 메뉴 보호 정책 (force 옵션) - 동기화 전용 API 스펙 4개 엔드포인트 - MenuSyncService 메서드 설계 - MNG 동기화 센터 UI 와이어프레임 --- claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md | 376 ++++++++++++++++++- 1 file changed, 359 insertions(+), 17 deletions(-) diff --git a/claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md b/claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md index cadba34..598ec2e 100644 --- a/claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md +++ b/claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md @@ -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 | 초안 작성 | \ No newline at end of file +| 2025-12-01 | Claude | 초안 작성 | +| 2025-12-02 | Claude | 동기화 시스템 상세 설계 추가 (상태 분류, API 스펙, MNG UI) | \ No newline at end of file