diff --git a/claudedocs/MENU_INSERT_QUERIES.sql b/claudedocs/MENU_INSERT_QUERIES.sql new file mode 100644 index 0000000..b774c47 --- /dev/null +++ b/claudedocs/MENU_INSERT_QUERIES.sql @@ -0,0 +1,158 @@ +-- ===================================================== +-- SAM ERP 메뉴 추가 쿼리 +-- 생성일: 2025-12-01 +-- 대상: 인사관리, 전자결재, 기준정보 확장 등 +-- 참고문서: SAM_ERP_인사관리전자결재_Storyboard_D0.6_251201.pdf +-- ===================================================== + +-- 총 추가 메뉴: 23개 +-- - 최상위 9개: 인사관리, 전자결재, 게시판, 보고서 및 분석, 계정정보, 회사정보, 구독관리, 결제내역, 고객센터 +-- - 인사관리 하위 3개: 근태관리, 휴가관리, 급여관리 +-- - 전자결재 하위 3개: 기안함, 결재함, 참조함 +-- - 기준정보 관리 하위 8개: 직급관리, 직책관리, 근무관리, 휴가설정, 팝업관리, 게시판관리, 일반설정, 알림설정 + +-- ===================================================== +-- 1. 인사관리 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '인사관리', '/hr', 'users', 19, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '인사관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- 인사관리 하위 메뉴 +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, (SELECT id FROM menus WHERE name = '인사관리' AND tenant_id IS NULL AND deleted_at IS NULL LIMIT 1), + '근태관리', '/hr/attendance', 'clock', 1, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '근태관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, (SELECT id FROM menus WHERE name = '인사관리' AND tenant_id IS NULL AND deleted_at IS NULL LIMIT 1), + '휴가관리', '/hr/leave', 'calendar', 2, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '휴가관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, (SELECT id FROM menus WHERE name = '인사관리' AND tenant_id IS NULL AND deleted_at IS NULL LIMIT 1), + '급여관리', '/hr/payroll', 'dollar-sign', 3, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '급여관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 2. 전자결재 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '전자결재', '/approval', 'file-signature', 20, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '전자결재' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- 전자결재 하위 메뉴 +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, (SELECT id FROM menus WHERE name = '전자결재' AND tenant_id IS NULL AND deleted_at IS NULL LIMIT 1), + '기안함', '/approval/drafts', 'edit', 1, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '기안함' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, (SELECT id FROM menus WHERE name = '전자결재' AND tenant_id IS NULL AND deleted_at IS NULL LIMIT 1), + '결재함', '/approval/pending', 'check-square', 2, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '결재함' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, (SELECT id FROM menus WHERE name = '전자결재' AND tenant_id IS NULL AND deleted_at IS NULL LIMIT 1), + '참조함', '/approval/references', 'eye', 3, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '참조함' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 3. 게시판 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '게시판', '/boards', 'layout', 21, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '게시판' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 4. 보고서 및 분석 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '보고서 및 분석', '/reports', 'bar-chart-2', 22, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '보고서 및 분석' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 5. 계정정보 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '계정정보', '/account', 'user-circle', 23, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '계정정보' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 6. 회사정보 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '회사정보', '/company', 'building', 24, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '회사정보' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 7. 구독관리 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '구독관리', '/subscription', 'credit-card', 25, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '구독관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 8. 결제내역 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '결제내역', '/payments', 'receipt', 26, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '결제내역' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 9. 고객센터 (최상위 메뉴) +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, NULL, '고객센터', '/support', 'headphones', 27, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '고객센터' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 10. 기준정보 관리 하위 메뉴 추가 (parent_id = 167) +-- 주의: parent_id는 실제 DB의 기준정보 관리 메뉴 ID로 변경 필요 +-- ===================================================== +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '직급관리', '/master-data/positions', 'award', 12, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '직급관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '직책관리', '/master-data/job-titles', 'briefcase', 13, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '직책관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '근무관리', '/master-data/work-schedules', 'clock', 14, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '근무관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '휴가설정', '/master-data/leave-settings', 'calendar-check', 15, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '휴가설정' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '팝업관리', '/master-data/popups', 'message-square', 16, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '팝업관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '게시판관리', '/master-data/board-settings', 'clipboard', 17, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '게시판관리' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '일반설정', '/master-data/general-settings', 'settings', 18, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '일반설정' AND tenant_id IS NULL AND deleted_at IS NULL); + +INSERT INTO menus (tenant_id, parent_id, name, url, icon, sort_order, is_active, hidden, is_external, external_url, created_at, updated_at) +SELECT NULL, 167, '알림설정', '/master-data/notification-settings', 'bell', 19, 1, 0, 0, NULL, NOW(), NOW() +WHERE NOT EXISTS (SELECT 1 FROM menus WHERE name = '알림설정' AND tenant_id IS NULL AND deleted_at IS NULL); + +-- ===================================================== +-- 실행 전 확인 사항 +-- ===================================================== +-- 1. 기준정보 관리 메뉴의 실제 ID 확인 (위에서 167로 가정) +-- SELECT id, name FROM menus WHERE name = '기준정보 관리' AND tenant_id IS NULL; +-- +-- 2. 기존 메뉴 확인 +-- SELECT id, name, url, sort_order FROM menus WHERE tenant_id IS NULL ORDER BY sort_order; +-- +-- 3. 실행 후 확인 +-- SELECT id, parent_id, name, url, sort_order, is_active +-- FROM menus +-- WHERE tenant_id IS NULL +-- ORDER BY COALESCE(parent_id, 0), sort_order; \ No newline at end of file diff --git a/claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md b/claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md new file mode 100644 index 0000000..cadba34 --- /dev/null +++ b/claudedocs/MENU_INTEGRATION_SYSTEM_DESIGN.md @@ -0,0 +1,374 @@ +# 메뉴 통합관리 시스템 설계서 + +> 작성일: 2025-12-01 +> 상태: 설계 완료, 개발 대기 +> 관련 문서: SAM_ERP_인사관리전자결재_Storyboard_D0.6_251201.pdf + +--- + +## 1. 개요 + +### 1.1 배경 +현재 테넌트 생성 시 글로벌 메뉴(tenant_id = NULL)를 완전히 복사하여 독립적인 테넌트 메뉴를 생성합니다. 이 방식은 원본과의 연결이 없어 관리가 어렵습니다. + +### 1.2 목표 +- 글로벌 메뉴와 테넌트 메뉴 간의 연결(링크) 시스템 구축 +- 템플릿 복사 시에도 관계 유지 +- 관리자에서 메뉴 추가/삭제/동기화 관리 용이하게 + +### 1.3 현재 구조 vs 목표 구조 + +**현재:** +``` +글로벌 메뉴 (tenant_id = NULL) + ↓ 완전 복사 (독립) +테넌트 메뉴 (tenant_id = X) + ❌ 원본과의 연결 없음 +``` + +**목표:** +``` +글로벌 메뉴 (id=10) + ↓ 복사 + 링크 유지 +테넌트 메뉴 (id=100, global_menu_id=10) + ✅ 원본 추적 가능 +``` + +--- + +## 2. 정책 결정 사항 + +| 항목 | 결정 내용 | +|------|----------| +| **글로벌 메뉴 삭제 시** | 테넌트 메뉴 유지 (`global_menu_id = NULL`로 변경) | +| **활성 메뉴 (is_active=1)** | 새 테넌트 생성 시 자동 복사 | +| **비활성 메뉴 (is_active=0)** | 테넌트가 수동으로 복제 가능 | +| **숨김 메뉴 (hidden=1)** | 복사되지만 테넌트에서 메뉴 안 보임 | +| **기존 데이터** | 신규 테넌트부터 적용 (기존 데이터 변경 없음) | + +--- + +## 3. 테넌트 메뉴 기능 매트릭스 + +| 기능 | 설명 | API | MNG | +|------|------|-----|-----| +| **복제** | 글로벌 메뉴 → 테넌트 메뉴로 복제 | ✅ | ✅ | +| **추가** | 테넌트 자체 메뉴 생성 (global_menu_id = NULL) | ✅ | ✅ | +| **수정** | 메뉴명, URL, 아이콘, 정렬 등 수정 | ✅ | ✅ | +| **삭제** | 테넌트 메뉴 삭제 (소프트) | ✅ | ✅ | +| **숨김** | hidden 토글 | ✅ | ✅ | +| **복원** | 삭제된 메뉴 복원 | ✅ | ✅ | +| **동기화** | 글로벌 메뉴 변경사항 반영 | ✅ | ✅ | + +--- + +## 4. DB 스키마 변경 + +### 4.1 마이그레이션 + +```sql +-- 1. global_menu_id 컬럼 추가 +ALTER TABLE menus +ADD COLUMN global_menu_id BIGINT UNSIGNED NULL +COMMENT '원본 글로벌 메뉴 ID (복제된 메뉴인 경우)' +AFTER parent_id; + +-- 2. is_customized 플래그 추가 (원본 대비 수정 여부) +ALTER TABLE menus +ADD COLUMN is_customized TINYINT(1) NOT NULL DEFAULT 0 +COMMENT '테넌트가 커스터마이징 했는지 여부' +AFTER hidden; + +-- 3. 인덱스 추가 +ALTER TABLE menus ADD INDEX menus_global_menu_id_idx (global_menu_id); +ALTER TABLE menus ADD INDEX menus_tenant_global_idx (tenant_id, global_menu_id); +``` + +### 4.2 변경된 테이블 구조 + +``` +menus +├── id (PK) +├── tenant_id (FK, NULL = 글로벌 메뉴) +├── parent_id (FK, 상위 메뉴) +├── global_menu_id (FK, 원본 글로벌 메뉴) ← 신규 +├── name +├── url +├── icon +├── sort_order +├── is_active +├── hidden +├── is_customized ← 신규 +├── is_external +├── external_url +├── created_by +├── updated_by +├── deleted_by +├── created_at +├── updated_at +└── deleted_at +``` + +--- + +## 5. 메뉴 상태 플로우 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 글로벌 메뉴 (tenant_id = NULL) │ +├─────────────────────────────────────────────────────────────────┤ +│ is_active=1, hidden=0 → 새 테넌트에 자동 복사 │ +│ is_active=1, hidden=1 → 복사되지만 테넌트에서 안 보임 │ +│ is_active=0 → 복사 안됨, 테넌트가 수동 복제 가능 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 테넌트 메뉴 (tenant_id = X) │ +├─────────────────────────────────────────────────────────────────┤ +│ global_menu_id = 10 → 글로벌 메뉴 #10에서 복제됨 │ +│ global_menu_id = NULL → 테넌트가 직접 생성한 메뉴 │ +│ is_customized = 1 → 테넌트가 내용 수정함 │ +│ is_customized = 0 → 원본 그대로 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 6. API 엔드포인트 설계 + +### 6.1 글로벌 메뉴 관리 (시스템 관리자용) + +| Method | Endpoint | 설명 | +|--------|----------|------| +| GET | `/v1/admin/global-menus` | 글로벌 메뉴 목록 | +| POST | `/v1/admin/global-menus` | 글로벌 메뉴 생성 | +| PUT | `/v1/admin/global-menus/{id}` | 글로벌 메뉴 수정 | +| DELETE | `/v1/admin/global-menus/{id}` | 글로벌 메뉴 삭제 | +| POST | `/v1/admin/global-menus/{id}/sync-to-tenants` | 특정 메뉴를 모든 테넌트에 추가 | + +### 6.2 테넌트 메뉴 관리 (테넌트 관리자용) + +| Method | Endpoint | 설명 | +|--------|----------|------| +| GET | `/v1/menus` | 테넌트 메뉴 목록 (기존) | +| GET | `/v1/menus/available-global` | 복제 가능한 글로벌 메뉴 목록 | +| POST | `/v1/menus` | 테넌트 메뉴 생성 (기존) | +| POST | `/v1/menus/clone-global/{globalMenuId}` | 글로벌 메뉴 복제 | +| PUT | `/v1/menus/{id}` | 테넌트 메뉴 수정 (기존) | +| DELETE | `/v1/menus/{id}` | 테넌트 메뉴 삭제 (기존) | +| PATCH | `/v1/menus/{id}/toggle` | 상태 토글 (기존) | +| POST | `/v1/menus/{id}/restore` | 삭제된 메뉴 복원 | +| POST | `/v1/menus/{id}/sync-from-global` | 글로벌 원본과 동기화 | +| POST | `/v1/menus/reorder` | 메뉴 순서 변경 (기존) | + +--- + +## 7. 서비스 메서드 설계 + +### 7.1 MenuService.php 추가 메서드 + +```php +/** + * 복제 가능한 글로벌 메뉴 목록 + * - 테넌트가 아직 복제하지 않은 글로벌 메뉴 + */ +public function getAvailableGlobalMenus(int $tenantId): Collection + +/** + * 글로벌 메뉴를 테넌트로 복제 + */ +public function cloneFromGlobal(int $globalMenuId, int $tenantId): Menu + +/** + * 글로벌 원본과 동기화 (이름, URL, 아이콘 등 업데이트) + */ +public function syncFromGlobal(int $menuId): Menu + +/** + * 삭제된 메뉴 복원 + */ +public function restore(int $menuId): Menu +``` + +### 7.2 GlobalMenuService.php (신규) + +```php +/** + * 글로벌 메뉴 생성 + */ +public function store(array $data): Menu + +/** + * 글로벌 메뉴 수정 + */ +public function update(int $id, array $data): Menu + +/** + * 글로벌 메뉴 삭제 (연결된 테넌트 메뉴의 global_menu_id를 NULL로) + */ +public function destroy(int $id): bool + +/** + * 특정 글로벌 메뉴를 모든 테넌트에 추가 + */ +public function syncToAllTenants(int $globalMenuId): int +``` + +--- + +## 8. MNG 화면 설계 + +### 8.1 글로벌 메뉴 관리 (시스템 관리자) + +``` +/mng/global-menus +├── 목록 화면 +│ ├── 트리 구조로 표시 +│ ├── 활성/비활성/숨김 상태 표시 +│ ├── 연결된 테넌트 수 표시 +│ └── 추가/수정/삭제 버튼 +│ +├── 생성/수정 모달 +│ ├── 메뉴명, URL, 아이콘 +│ ├── 상위 메뉴 선택 +│ ├── 활성 여부, 숨김 여부 +│ └── 정렬 순서 +│ +└── 테넌트 동기화 모달 + ├── 대상 테넌트 선택 (전체/선택) + └── 동기화 실행 +``` + +### 8.2 테넌트 메뉴 관리 (테넌트 관리자) + +``` +/mng/menus +├── 목록 화면 +│ ├── 트리 구조로 표시 +│ ├── 글로벌 연결 여부 표시 (🔗 아이콘) +│ ├── 커스터마이징 여부 표시 (✏️ 아이콘) +│ └── 추가/수정/삭제/숨김 버튼 +│ +├── 글로벌 메뉴 복제 모달 +│ ├── 복제 가능한 글로벌 메뉴 목록 +│ ├── 체크박스 선택 +│ └── 일괄 복제 버튼 +│ +├── 생성/수정 모달 +│ ├── 메뉴명, URL, 아이콘 +│ ├── 상위 메뉴 선택 +│ └── 정렬 순서 +│ +└── 동기화 모달 + ├── 원본과 비교 (diff 표시) + └── 동기화 실행 버튼 +``` + +--- + +## 9. 변경 영향도 분석 + +| 파일 | 변경 내용 | 영향도 | +|------|----------|--------| +| `database/migrations/` | global_menu_id, is_customized 추가 | 🔴 높음 | +| `app/Models/Commons/Menu.php` | fillable, 관계 메서드 추가 | 🟡 중간 | +| `app/Services/MenuService.php` | 복제, 동기화, 복원 메서드 추가 | 🟡 중간 | +| `app/Services/MenuBootstrapService.php` | global_menu_id 저장 로직 추가 | 🟡 중간 | +| `app/Services/GlobalMenuService.php` | 신규 생성 | 🟢 낮음 | +| `app/Http/Controllers/Api/V1/MenuController.php` | 엔드포인트 추가 | 🟡 중간 | +| `app/Http/Controllers/Api/Admin/GlobalMenuController.php` | 신규 생성 | 🟢 낮음 | +| `routes/api.php` | 라우트 추가 | 🟡 중간 | +| `mng/` | 뷰, 컨트롤러 생성 | 🟢 낮음 | + +--- + +## 10. 구현 계획 + +### Phase 1: DB 및 모델 (1일) +- [ ] 마이그레이션 생성 (`global_menu_id`, `is_customized`) +- [ ] Menu 모델 수정 (fillable, 관계 메서드) +- [ ] MenuBootstrapService 수정 (복사 시 global_menu_id 저장) + +### Phase 2: API 서비스 (1-2일) +- [ ] GlobalMenuService 생성 +- [ ] MenuService 메서드 추가 + - [ ] `getAvailableGlobalMenus()` + - [ ] `cloneFromGlobal()` + - [ ] `syncFromGlobal()` + - [ ] `restore()` +- [ ] GlobalMenuController 생성 +- [ ] MenuController 엔드포인트 추가 +- [ ] 라우트 등록 + +### Phase 3: MNG 화면 (2-3일) +- [ ] 글로벌 메뉴 관리 화면 + - [ ] 목록 (트리 구조) + - [ ] 생성/수정/삭제 + - [ ] 테넌트 동기화 +- [ ] 테넌트 메뉴 관리 화면 개선 + - [ ] 글로벌 연결 표시 + - [ ] 복제 기능 + - [ ] 동기화 기능 + +### Phase 4: 테스트 (1일) +- [ ] 유닛 테스트 +- [ ] 통합 테스트 + +--- + +## 11. 관련 파일 위치 + +``` +API 프로젝트: /Users/hskwon/Works/@KD_SAM/SAM/api/ + +├─ app/Models/Commons/ +│ └─ Menu.php # Menu 모델 +│ +├─ app/Models/Scopes/ +│ └─ TenantScope.php # 테넌트 격리 스코프 +│ +├─ app/Traits/ +│ └─ BelongsToTenant.php # 테넌트 자동 격리 트레이트 +│ +├─ app/Services/ +│ ├─ MenuService.php # 메뉴 CRUD 서비스 +│ ├─ MenuBootstrapService.php # 메뉴 복사(부트스트랩) 서비스 +│ └─ RegisterService.php # 회원가입 서비스 (메뉴 복사 호출) +│ +├─ app/Http/Controllers/Api/V1/ +│ └─ MenuController.php # 메뉴 API 컨트롤러 +│ +├─ app/Observers/ +│ └─ MenuObserver.php # 메뉴 이벤트 옵저버 (권한 자동 생성) +│ +└─ database/migrations/ + ├─ 2025_08_15_000000_create_authz_structures.php + ├─ 2025_08_15_000200_drop_slug_from_menus_table.php + └─ 2025_11_12_160656_add_performance_indexes_to_menus_table.php + +MNG 프로젝트: /Users/hskwon/Works/@KD_SAM/SAM/mng/ +(구현 예정) +``` + +--- + +## 12. 메뉴 추가 SQL (참고) + +PDF 문서에서 추출한 신규 메뉴 SQL은 별도 파일 참조: +- `claudedocs/MENU_INSERT_QUERIES.sql` + +--- + +## 13. 다음 작업 + +1. Phase 1 시작: 마이그레이션 생성 +2. Menu 모델 수정 +3. MenuBootstrapService 수정 + +--- + +## 변경 이력 + +| 날짜 | 작성자 | 내용 | +|------|--------|------| +| 2025-12-01 | Claude | 초안 작성 | \ No newline at end of file