# Admin → MNG 마이그레이션 계획

> 📌 **Admin 시스템 관리 메뉴 11개를 MNG로 마이그레이션**

**작성일**: 2025-11-21
**상태**: Phase 4 준비 중

---

## 📋 마이그레이션 개요

### 목표
Admin(Filament v4)의 시스템 관리 메뉴 11개를 MNG(Plain Laravel)로 이식하여 수정 용이한 관리자 패널 구축

### 핵심 전략

```
┌─────────────────────────────────────────┐
│  Blade 화면 (Web Routes)               │
│  - 화면만 담당, 데이터 처리 없음         │
│  ↓ HTMX 호출 (hx-get, hx-post 등)      │
│  ↓                                      │
│  API Routes (/api/admin/*)             │
│  - 실제 데이터 CRUD 처리                │
│  - HTMX 요청 시 HTML 반환              │
│  - 일반 요청 시 JSON 반환              │
│  ↓                                      │
│  API Controller → Service → Model      │
│  ↓                                      │
│  MySQL (admin/api와 DB 공유)            │
└─────────────────────────────────────────┘
```

### 기술 스택
- **프론트엔드**: Blade + HTMX + DaisyUI + Tailwind CSS
- **백엔드**: Laravel 12 + PHP 8.4 + Sanctum
- **인터랙션**: HTMX (Alpine.js 제거)

---

## 🎯 마이그레이션 대상 (11개 메뉴)

### Admin 시스템 관리 메뉴 분석

| # | 메뉴명 | Resource 파일 | 모델 | 복잡도 | 우선순위 |
|---|--------|--------------|------|--------|---------|
| 1 | 테넌트 | `TenantResource.php` | `Tenant` | ⭐⭐ | 1 |
| 2 | 사용자 | `UserResource.php` | `User` | ⭐⭐⭐ | 2 |
| 3 | 메뉴 | `MenuResource.php` | `Menu` | ⭐⭐ | 3 |
| 4 | 역할 | `RoleResource.php` | `Role` | ⭐⭐ | 4 |
| 5 | 부서 | `DepartmentResource.php` | `Department` | ⭐⭐ | 5 |
| 6 | 권한 | `PermissionResource.php` | `Permission` | ⭐⭐ | 6 |
| 7 | 역할 권한 관리 | `RolePermissionsResource.php` | - | ⭐⭐⭐ | 7 |
| 8 | 부서 권한 관리 | `DepartmentPermissionsResource.php` | - | ⭐⭐⭐ | 8 |
| 9 | 개인 권한 관리 | `UserPermissionsResource.php` | - | ⭐⭐⭐ | 9 |
| 10 | 권한 분석 | `PermissionAnalysisResource.php` | - | ⭐⭐⭐⭐ | 10 |
| 11 | 삭제된 데이터 백업 | `ArchivedRecordResource.php` | `ArchivedRecord` | ⭐⭐⭐ | 11 |

**복잡도:**
- ⭐ 단순 CRUD
- ⭐⭐ CRUD + 관계
- ⭐⭐⭐ CRUD + 복잡한 관계 + 커스텀 UI
- ⭐⭐⭐⭐ 읽기 전용 + 복잡한 쿼리 + 매트릭스 UI

---

## 🏗️ 라우트 구조

### Web Routes (Blade 화면만)

```php
// routes/web.php
Route::middleware(['auth:sanctum'])->group(function () {
    // 테넌트 관리
    Route::get('/tenants', [TenantController::class, 'index'])->name('tenants.index');
    Route::get('/tenants/create', [TenantController::class, 'create'])->name('tenants.create');
    Route::get('/tenants/{tenant}/edit', [TenantController::class, 'edit'])->name('tenants.edit');

    // 사용자 관리
    Route::get('/users', [UserController::class, 'index'])->name('users.index');
    Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
    Route::get('/users/{user}/edit', [UserController::class, 'edit'])->name('users.edit');

    // ... 나머지 메뉴
});
```

### API Routes (실제 데이터 처리)

```php
// routes/api.php
Route::middleware(['auth:sanctum'])->prefix('admin')->group(function () {
    // 테넌트 API
    Route::apiResource('tenants', Api\Admin\TenantController::class);

    // 사용자 API
    Route::apiResource('users', Api\Admin\UserController::class);

    // 권한 관리 API (Custom)
    Route::prefix('permissions')->group(function () {
        Route::get('/role/{role}', [Api\Admin\RolePermissionController::class, 'show']);
        Route::post('/role/{role}', [Api\Admin\RolePermissionController::class, 'update']);

        Route::get('/department/{department}', [Api\Admin\DepartmentPermissionController::class, 'show']);
        Route::post('/department/{department}', [Api\Admin\DepartmentPermissionController::class, 'update']);

        Route::get('/user/{user}', [Api\Admin\UserPermissionController::class, 'show']);
        Route::post('/user/{user}', [Api\Admin\UserPermissionController::class, 'update']);

        Route::get('/analysis', [Api\Admin\PermissionAnalysisController::class, 'index']);
    });

    // ... 나머지 메뉴
});
```

---

## 📐 표준 개발 프로세스

### 1. 모델 복사 (Admin → MNG)

```bash
# 1. 모델 복사
cp admin/app/Models/Tenants/Tenant.php mng/app/Models/Tenant.php
cp admin/app/Models/Members/User.php mng/app/Models/User.php
# ...

# 2. Filament 코드 제거
# - form(), table(), getNavigationLabel() 등 제거
# - 순수 Eloquent 관계만 유지

# 3. Traits 복사
cp admin/app/Traits/BelongsToTenant.php mng/app/Traits/
cp admin/app/Traits/ModelTrait.php mng/app/Traits/
```

### 2. Service Layer 생성

```php
// mng/app/Services/TenantService.php
namespace App\Services;

use App\Models\Tenant;
use Illuminate\Pagination\LengthAwarePaginator;

class TenantService
{
    /**
     * 테넌트 목록 조회 (검색, 필터, 페이징)
     */
    public function getTenants(array $filters = []): LengthAwarePaginator
    {
        $query = Tenant::query();

        // 검색
        if (!empty($filters['search'])) {
            $query->where('company_name', 'like', "%{$filters['search']}%");
        }

        // 상태 필터
        if (!empty($filters['status'])) {
            $query->where('tenant_st_code', $filters['status']);
        }

        return $query->paginate(20);
    }

    /**
     * 테넌트 생성
     */
    public function createTenant(array $data): Tenant
    {
        return Tenant::create($data);
    }

    // update(), delete() ...
}
```

### 3. API Controller 생성

```php
// mng/app/Http/Controllers/Api/Admin/TenantController.php
namespace App\Http\Controllers\Api\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\Tenant\StoreTenantRequest;
use App\Services\TenantService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class TenantController extends Controller
{
    public function __construct(
        private TenantService $tenantService
    ) {}

    /**
     * 테넌트 목록 (API)
     * GET /api/admin/tenants
     */
    public function index(Request $request): JsonResponse
    {
        $tenants = $this->tenantService->getTenants($request->all());

        // HTMX 요청 시 HTML 반환
        if ($request->header('HX-Request')) {
            return response()->view('tenants.partials.table', compact('tenants'));
        }

        // 일반 요청 시 JSON 반환
        return response()->json([
            'success' => true,
            'data' => $tenants->items(),
            'meta' => [
                'current_page' => $tenants->currentPage(),
                'total' => $tenants->total(),
            ],
        ]);
    }

    /**
     * 테넌트 생성 (API)
     * POST /api/admin/tenants
     */
    public function store(StoreTenantRequest $request): JsonResponse
    {
        $tenant = $this->tenantService->createTenant($request->validated());

        return response()->json([
            'success' => true,
            'data' => $tenant,
            'message' => 'tenants.created',
        ], 201);
    }

    // update(), destroy() ...
}
```

### 4. Web Controller 생성 (Blade 화면만)

```php
// mng/app/Http/Controllers/TenantController.php
namespace App\Http\Controllers;

class TenantController extends Controller
{
    /**
     * 테넌트 목록 화면
     * GET /tenants
     */
    public function index()
    {
        return view('tenants.index');
    }

    /**
     * 테넌트 생성 화면
     * GET /tenants/create
     */
    public function create()
    {
        return view('tenants.create');
    }

    /**
     * 테넌트 수정 화면
     * GET /tenants/{tenant}/edit
     */
    public function edit($id)
    {
        return view('tenants.edit', compact('id'));
    }
}
```

### 5. Blade 뷰 생성 (HTMX 호출)

```blade
{{-- resources/views/tenants/index.blade.php --}}
@extends('layouts.app')

@section('content')
<div class="space-y-4">
    {{-- 헤더 --}}
    <div class="flex justify-between items-center">
        <h1 class="text-2xl font-bold">테넌트 관리</h1>
        <a href="{{ route('tenants.create') }}" class="btn btn-primary">테넌트 추가</a>
    </div>

    {{-- 검색/필터 --}}
    <div class="card bg-white shadow-xl">
        <div class="card-body">
            <form hx-get="/api/admin/tenants"
                  hx-target="#tenant-table"
                  hx-trigger="submit">
                <div class="grid grid-cols-3 gap-4">
                    <input type="text" name="search"
                           placeholder="회사명 검색"
                           class="input input-bordered" />
                    <select name="status" class="select select-bordered">
                        <option value="">전체 상태</option>
                        <option value="trial">트라이얼</option>
                        <option value="active">활성</option>
                    </select>
                    <button type="submit" class="btn btn-primary">검색</button>
                </div>
            </form>
        </div>
    </div>

    {{-- 테이블 영역 (HTMX로 로드) --}}
    <div id="tenant-table"
         hx-get="/api/admin/tenants"
         hx-trigger="load">
        <div class="flex justify-center p-8">
            <span class="loading loading-spinner loading-lg"></span>
        </div>
    </div>
</div>
@endsection
```

### 6. 부분 템플릿 생성 (HTMX 응답)

```blade
{{-- resources/views/tenants/partials/table.blade.php --}}
<div class="card bg-white shadow-xl">
    <div class="card-body">
        <div class="overflow-x-auto">
            <table class="table w-full">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>회사명</th>
                        <th>이메일</th>
                        <th>상태</th>
                        <th>작업</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach($tenants as $tenant)
                    <tr>
                        <td>{{ $tenant->id }}</td>
                        <td>{{ $tenant->company_name }}</td>
                        <td>{{ $tenant->email }}</td>
                        <td>
                            <span class="badge badge-success">
                                {{ $tenant->tenant_st_code }}
                            </span>
                        </td>
                        <td>
                            <a href="{{ route('tenants.edit', $tenant->id) }}"
                               class="btn btn-sm">수정</a>
                            <button hx-delete="/api/admin/tenants/{{ $tenant->id }}"
                                    hx-confirm="정말 삭제하시겠습니까?"
                                    hx-target="closest tr"
                                    hx-swap="outerHTML swap:1s"
                                    class="btn btn-sm btn-error">
                                삭제
                            </button>
                        </td>
                    </tr>
                    @endforeach
                </tbody>
            </table>
        </div>

        {{-- 페이징 (HTMX) --}}
        <div class="flex justify-center mt-4">
            @if($tenants->hasPages())
                <div class="btn-group">
                    @for($page = 1; $page <= $tenants->lastPage(); $page++)
                        <button hx-get="/api/admin/tenants?page={{ $page }}"
                                hx-target="#tenant-table"
                                class="btn btn-sm {{ $page == $tenants->currentPage() ? 'btn-active' : '' }}">
                            {{ $page }}
                        </button>
                    @endfor
                </div>
            @endif
        </div>
    </div>
</div>
```

---

## 📋 Phase 4 세부 계획

### Phase 4-1: 테넌트 관리 (우선순위 1)

**예상 기간**: 2-3일

**구현 범위:**
- [x] 모델 복사 (Tenant.php)
- [ ] TenantService 생성
- [ ] API Controller (Api\Admin\TenantController)
- [ ] Web Controller (TenantController)
- [ ] FormRequest (StoreTenantRequest, UpdateTenantRequest)
- [ ] Blade 뷰 (index, create, edit)
- [ ] 부분 템플릿 (partials/table, partials/form)
- [ ] Feature Test

### Phase 4-2: 사용자 관리 (우선순위 2)

**예상 기간**: 3-4일

**구현 범위:**
- [ ] 모델 복사 (User.php)
- [ ] UserService 생성
- [ ] API Controller
- [ ] Web Controller
- [ ] FormRequest
- [ ] Blade 뷰
- [ ] **탭 UI** (HTMX로 구현)
  - Tenants 탭
  - Departments 탭
  - Roles 탭
  - Permissions 탭
- [ ] Feature Test

### Phase 4-3: 메뉴/역할/부서 관리 (우선순위 3-5)

**예상 기간**: 5-6일

**구현 범위:**
- [ ] Menu CRUD + 트리 구조 UI
- [ ] Role CRUD
- [ ] Department CRUD + 트리 구조 UI

### Phase 4-4: 권한 관리 (우선순위 6-9)

**예상 기간**: 7-8일

**구현 범위:**
- [ ] Permission CRUD
- [ ] 역할 권한 관리 (체크박스 매트릭스)
- [ ] 부서 권한 관리 (체크박스 매트릭스)
- [ ] 개인 권한 관리 (체크박스 매트릭스)

### Phase 4-5: 권한 분석/백업 (우선순위 10-11)

**예상 기간**: 3-4일

**구현 범위:**
- [ ] 권한 분석 (읽기 전용 매트릭스)
- [ ] 삭제 데이터 백업 조회/복원

---

## 🔧 HTMX 패턴 가이드

### 1. 목록 조회 (Load)
```blade
<div hx-get="/api/admin/users"
     hx-trigger="load"
     hx-target="this">
    <span class="loading loading-spinner"></span>
</div>
```

### 2. 검색/필터 (Submit)
```blade
<form hx-get="/api/admin/users"
      hx-target="#results"
      hx-trigger="submit">
    <input name="search" class="input input-bordered" />
    <button class="btn btn-primary">검색</button>
</form>
```

### 3. 생성 (POST)
```blade
<form hx-post="/api/admin/users"
      hx-target="#user-list"
      hx-swap="beforeend">
    <!-- 폼 필드 -->
    <button class="btn btn-primary">저장</button>
</form>
```

### 4. 수정 (PUT)
```blade
<form hx-put="/api/admin/users/{{ $user->id }}"
      hx-target="closest tr"
      hx-swap="outerHTML">
    <!-- 폼 필드 -->
    <button class="btn btn-primary">수정</button>
</form>
```

### 5. 삭제 (DELETE)
```blade
<button hx-delete="/api/admin/users/{{ $user->id }}"
        hx-confirm="정말 삭제하시겠습니까?"
        hx-target="closest tr"
        hx-swap="outerHTML swap:1s"
        class="btn btn-error">
    삭제
</button>
```

### 6. 탭 전환
```blade
<div class="tabs tabs-boxed">
    <button class="tab tab-active"
            hx-get="/api/admin/users/{{ $user->id }}/tenants"
            hx-target="#tab-content">
        테넌트
    </button>
    <button class="tab"
            hx-get="/api/admin/users/{{ $user->id }}/roles"
            hx-target="#tab-content">
        역할
    </button>
</div>

<div id="tab-content" class="mt-4">
    <!-- HTMX로 로드된 탭 내용 -->
</div>
```

---

## 📊 진행 상황 체크리스트

### 전체 진행률
- [ ] Phase 4-1: 테넌트 관리 (0%)
- [ ] Phase 4-2: 사용자 관리 (0%)
- [ ] Phase 4-3: 메뉴/역할/부서 (0%)
- [ ] Phase 4-4: 권한 관리 (0%)
- [ ] Phase 4-5: 권한 분석/백업 (0%)

### 공통 작업
- [x] Admin 모델 분석 완료
- [x] 마이그레이션 계획 수립
- [ ] HTMX 환경 구축
- [ ] DaisyUI 컴포넌트 확정
- [ ] API 응답 형식 표준화

---

## 🔗 관련 문서

- **[MNG_PROJECT_PLAN.md](../../claudedocs/mng/MNG_PROJECT_PLAN.md)** - 전체 프로젝트 계획
- **[DEV_PROCESS.md](../../claudedocs/mng/DEV_PROCESS.md)** - 개발 프로세스 (HTMX 패턴)
- **[mng/docs/INDEX.md](./INDEX.md)** - MNG 프로젝트 문서
- **[CLAUDE.md](../../CLAUDE.md)** - SAM 프로젝트 가이드

---

**최종 업데이트**: 2025-11-21
**버전**: 1.0
**다음 단계**: Phase 4-1 테넌트 관리 구현 시작