Files
sam-manage/docs/MIGRATION_PLAN.md
hskwon 575e9df431 feat: Phase 4-1 테넌트 관리 백엔드 구현
- TenantService 생성 (CRUD, 통계, 복원/영구삭제)
- API Controller 구현 (HTMX 요청 감지, HTML/JSON 이중 응답)
- FormRequest 검증 (StoreTenantRequest, UpdateTenantRequest)
- Tenant 모델 확장 (17개 필드, 관계 설정, accessor)
- Department, Menu, Role 모델 복사 (admin → mng)
- Web Controller 수정 (index/create/edit 화면)
- MIGRATION_PLAN.md 작성 (HTMX + API 아키텍처)
2025-11-21 14:46:13 +09:00

557 lines
16 KiB
Markdown

# 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 테넌트 관리 구현 시작