refactor: [docs] 팀별 폴더 구조 재편 (공유/개발/프론트/기획)
- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동) - 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/) - 기획팀 폴더 requests/ 생성 - plans/ → dev/dev_plans/ 이름 변경 - README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용) - resources.md 신규 (노션 링크용, assets/brochure 이관 예정) - CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동 - 전체 참조 경로 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
262
dev/guides/archive-restore-feature-analysis.md
Normal file
262
dev/guides/archive-restore-feature-analysis.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Archive & Restore Feature Analysis
|
||||
|
||||
**날짜:** 2025-11-30
|
||||
**작업자:** Claude Code
|
||||
**요청:** 영구 삭제 데이터 복원 기능 구현
|
||||
|
||||
---
|
||||
|
||||
## 1. 요청 내용
|
||||
|
||||
- `https://mng.sam.kr/archived-records` 에서 영구 삭제된 데이터를 복원할 수 있는 기능
|
||||
- 삭제/복구 프로세스 정립:
|
||||
- 일반 관리자/테넌트: Soft Delete
|
||||
- 슈퍼관리자: 영구 삭제 가능 → archived_records에 저장
|
||||
- 영구 삭제 데이터: 복원 가능해야 함
|
||||
- UI 개선: 작업 설명 컬럼 개선
|
||||
|
||||
---
|
||||
|
||||
## 2. 현재 상태 분석
|
||||
|
||||
### 2.1 forceDelete 사용 서비스 (8개)
|
||||
|
||||
모든 서비스가 **아카이브 없이** 바로 영구 삭제:
|
||||
|
||||
| 서비스 | 메서드 | 삭제 대상 | 파일 위치 |
|
||||
|--------|--------|----------|-----------|
|
||||
| `TenantService` | `forceDeleteTenant()` | 테넌트 + 부서/메뉴/역할 | `app/Services/TenantService.php:115` |
|
||||
| `UserService` | `forceDeleteUser()` | 사용자 | `app/Services/UserService.php:232` |
|
||||
| `DepartmentService` | `forceDeleteDepartment()` | 부서 | `app/Services/DepartmentService.php:171` |
|
||||
| `MenuService` | `forceDeleteMenu()` | 메뉴 | `app/Services/MenuService.php:281` |
|
||||
| `BoardService` | `forceDeleteBoard()` | 게시판 | `app/Services/BoardService.php:141` |
|
||||
| `ProjectService` | `forceDeleteProject()` | 프로젝트 | `app/Services/ProjectManagement/ProjectService.php:134` |
|
||||
| `IssueService` | `forceDeleteIssue()` | 이슈 | `app/Services/ProjectManagement/IssueService.php:160` |
|
||||
| `TaskService` | `forceDeleteTask()` | 작업 | `app/Services/ProjectManagement/TaskService.php:168` |
|
||||
|
||||
### 2.2 현재 DB 스키마
|
||||
|
||||
**archived_records 테이블:**
|
||||
```
|
||||
id bigint PK
|
||||
batch_id char(36) -- UUID, 그룹핑용
|
||||
batch_description varchar(255) -- 배치 설명
|
||||
record_type varchar(50) -- ✅ varchar로 변경됨 (기존 enum)
|
||||
original_id bigint -- 원본 레코드 ID
|
||||
main_data json -- 원본 데이터 (JSON)
|
||||
schema_version varchar(50) -- 스키마 버전
|
||||
deleted_by bigint FK -- 삭제자
|
||||
deleted_at timestamp -- 삭제 시간
|
||||
notes text -- 메모
|
||||
created_at, updated_at, created_by, updated_by
|
||||
```
|
||||
|
||||
**archived_record_relations 테이블:**
|
||||
```
|
||||
id bigint PK
|
||||
archived_record_id bigint FK -- archived_records.id
|
||||
table_name varchar(100) -- 관련 테이블명
|
||||
data json -- 관련 데이터 (JSON)
|
||||
record_count int -- 레코드 수
|
||||
created_at, updated_at, created_by, updated_by
|
||||
```
|
||||
|
||||
### 2.3 문제점
|
||||
|
||||
1. **아카이브 생성 코드 없음**: `ArchivedRecord::create()` 호출하는 곳이 없음
|
||||
2. ✅ ~~**record_type enum 제한**~~: varchar로 변경 완료
|
||||
3. **복원 기능 없음**: RestoreService 미존재
|
||||
4. **데이터 유실**: forceDelete 시 데이터가 완전히 삭제됨
|
||||
|
||||
---
|
||||
|
||||
## 3. 구현 계획
|
||||
|
||||
### Phase 1: 인프라 구축 (이번 작업)
|
||||
|
||||
#### 3.1 마이그레이션 ✅ 완료
|
||||
- `record_type` enum → varchar(50) 변경 완료
|
||||
|
||||
#### 3.2 ArchiveService 생성
|
||||
```php
|
||||
class ArchiveService {
|
||||
// 단일 모델 아카이브
|
||||
public function archiveModel(Model $model, array $relations = [], ?string $batchId = null): ArchivedRecord
|
||||
|
||||
// 배치 아카이브 (여러 모델)
|
||||
public function archiveBatch(Collection $models, string $description, array $relations = []): string
|
||||
|
||||
// 모델별 record_type 매핑
|
||||
private function getRecordType(Model $model): string
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 RestoreService 생성
|
||||
```php
|
||||
class RestoreService {
|
||||
// 단일 레코드 복원
|
||||
public function restoreRecord(ArchivedRecord $record): Model
|
||||
|
||||
// 배치 전체 복원
|
||||
public function restoreBatch(string $batchId): Collection
|
||||
|
||||
// 관계 데이터 복원
|
||||
private function restoreRelations(ArchivedRecord $record): void
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4 기존 서비스 수정 (TenantService, UserService 먼저)
|
||||
|
||||
#### 3.5 UI 개선
|
||||
- 복원 버튼 추가
|
||||
- 라우트 추가
|
||||
- 컨트롤러 메서드 추가
|
||||
|
||||
---
|
||||
|
||||
## 4. 수정 대상 파일
|
||||
|
||||
| # | 파일 | 작업 | 상태 |
|
||||
|---|------|------|------|
|
||||
| 1 | `database/migrations/2025_11_30_*_modify_archived_records_record_type_to_varchar.php` | 신규 | ✅ 완료 |
|
||||
| 2 | `app/Services/ArchiveService.php` | 신규 | 🔄 진행 중 |
|
||||
| 3 | `app/Services/RestoreService.php` | 신규 | ⏳ 대기 |
|
||||
| 4 | `app/Services/TenantService.php` | 수정 | ⏳ 대기 |
|
||||
| 5 | `app/Services/UserService.php` | 수정 | ⏳ 대기 |
|
||||
| 6 | `app/Http/Controllers/ArchivedRecordController.php` | 수정 | ⏳ 대기 |
|
||||
| 7 | `routes/web.php` | 수정 | ⏳ 대기 |
|
||||
| 8 | `resources/views/archived-records/show.blade.php` | 수정 | ⏳ 대기 |
|
||||
|
||||
---
|
||||
|
||||
## 5. record_type 매핑
|
||||
|
||||
```php
|
||||
$recordTypeMap = [
|
||||
Tenant::class => 'tenant',
|
||||
User::class => 'user',
|
||||
Department::class => 'department',
|
||||
Menu::class => 'menu',
|
||||
Role::class => 'role',
|
||||
Board::class => 'board',
|
||||
Project::class => 'project',
|
||||
Issue::class => 'issue',
|
||||
Task::class => 'task',
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 복원 로직 흐름
|
||||
|
||||
```
|
||||
1. ArchivedRecord 조회 (batch_id 또는 id)
|
||||
2. main_data에서 원본 데이터 추출
|
||||
3. 원본 테이블에 INSERT (새 ID 할당)
|
||||
4. relations 복원 (ArchivedRecordRelation)
|
||||
5. ArchivedRecord 삭제
|
||||
6. 트랜잭션 커밋
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 주의 사항
|
||||
|
||||
- **FK 제약**: 복원 시 관계 테이블 순서 중요 (부모 먼저)
|
||||
- **ID 할당**: 복원 시 새 ID 할당 (original_id는 참조용)
|
||||
- **tenant_id 무결성**: Multi-tenant 데이터 복원 시 tenant_id 검증
|
||||
- **트랜잭션**: 복원 실패 시 롤백 필수
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase 2: 테넌트 필터링 기능 추가 (신규 요청)
|
||||
|
||||
### 8.1 요청 내용
|
||||
|
||||
1. **대상 테넌트 필드 추가**:
|
||||
- 테넌트 삭제 시: 어떤 테넌트인지 표시
|
||||
- 사용자 삭제 시: 어떤 테넌트 소속인지 표시
|
||||
2. **상단 테넌트 선택 필터링**: 현재 선택된 테넌트의 아카이브만 표시
|
||||
|
||||
### 8.2 현재 문제점
|
||||
|
||||
- `archived_records` 테이블에 `tenant_id` 컬럼 없음
|
||||
- 사용자 삭제 시 소속 테넌트 정보 저장 안 됨
|
||||
- 테넌트 선택 필터링 불가
|
||||
|
||||
### 8.3 해결 방안
|
||||
|
||||
#### 방안 A: tenant_id 컬럼 추가 (권장)
|
||||
```
|
||||
장점:
|
||||
- 직접 필터링 가능 (성능 좋음)
|
||||
- 명확한 테넌트 소속 관계
|
||||
- 인덱스 활용 가능
|
||||
|
||||
단점:
|
||||
- 마이그레이션 필요
|
||||
- 기존 데이터 처리 필요 (main_data에서 추출)
|
||||
```
|
||||
|
||||
#### 방안 B: main_data에서 JSON 추출 (현재 방식)
|
||||
```
|
||||
장점:
|
||||
- DB 스키마 변경 없음
|
||||
|
||||
단점:
|
||||
- JSON 추출 쿼리 복잡
|
||||
- 성능 저하 (인덱스 불가)
|
||||
- 사용자의 경우 tenant_id가 main_data에 없을 수 있음
|
||||
```
|
||||
|
||||
### 8.4 권장 방안: A (tenant_id 컬럼 추가)
|
||||
|
||||
#### 수정 대상 파일
|
||||
|
||||
| # | 저장소 | 파일 | 작업 |
|
||||
|---|--------|------|------|
|
||||
| 1 | **api/** | `database/migrations/2025_12_01_*_add_tenant_id_to_archived_records.php` | 신규 - DB 마이그레이션 |
|
||||
| 2 | mng/ | `app/Services/ArchiveService.php` | 수정 - tenant_id 저장 로직 |
|
||||
| 3 | mng/ | `app/Services/ArchivedRecordService.php` | 수정 - 테넌트 필터링 |
|
||||
| 4 | mng/ | `app/Models/Archives/ArchivedRecord.php` | 수정 - fillable, 관계 추가 |
|
||||
| 5 | mng/ | `resources/views/archived-records/partials/table.blade.php` | 수정 - 대상 테넌트 표시 |
|
||||
|
||||
> **NOTE**: DB 마이그레이션은 `api/` 저장소에서 관리됨. mng/에서는 모델과 서비스만 수정.
|
||||
|
||||
#### 마이그레이션 내용 (api/)
|
||||
```php
|
||||
// api/database/migrations/2025_12_01_*_add_tenant_id_to_archived_records.php
|
||||
Schema::table('archived_records', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('tenant_id')->nullable()->after('record_type');
|
||||
$table->foreign('tenant_id')->references('id')->on('tenants')->nullOnDelete();
|
||||
$table->index('tenant_id');
|
||||
});
|
||||
```
|
||||
|
||||
#### tenant_id 결정 로직
|
||||
```
|
||||
- 테넌트 삭제: tenant_id = 삭제되는 테넌트의 ID (자기 자신)
|
||||
- 사용자 삭제: tenant_id = session('selected_tenant_id') (현재 선택된 테넌트)
|
||||
- 부서/메뉴/역할 삭제: tenant_id = 해당 레코드의 tenant_id
|
||||
```
|
||||
|
||||
#### 기존 데이터 처리
|
||||
```sql
|
||||
-- 테넌트 타입: main_data에서 id 추출
|
||||
UPDATE archived_records
|
||||
SET tenant_id = JSON_UNQUOTE(JSON_EXTRACT(main_data, '$.id'))
|
||||
WHERE record_type = 'tenant' AND tenant_id IS NULL;
|
||||
|
||||
-- 사용자 타입: main_data에 tenant_id가 없으므로 NULL 유지
|
||||
-- (또는 user_tenants 관계에서 추출 - 복잡)
|
||||
```
|
||||
|
||||
### 8.5 UI 변경
|
||||
|
||||
#### 목록 테이블 컬럼
|
||||
| 작업 설명 | 대상 테넌트 | 대상 정보 | 레코드 타입 | ... |
|
||||
|
||||
#### 필터링
|
||||
- 상단 테넌트 선택 시 `session('selected_tenant_id')` 기준 필터링
|
||||
- 슈퍼관리자: 전체 보기 가능
|
||||
- 일반 관리자: 소속 테넌트만 보기
|
||||
Reference in New Issue
Block a user