Tenant::class, 'user' => User::class, 'department' => Department::class, 'menu' => Menu::class, 'role' => Role::class, ]; /** * record_type → 테이블명 매핑 */ private const TABLE_NAME_MAP = [ 'tenant' => 'tenants', 'user' => 'users', 'department' => 'departments', 'menu' => 'menus', 'role' => 'roles', ]; /** * 단일 아카이브 레코드 복원 * * @return Model 복원된 모델 * * @throws \Exception 복원 실패 시 */ public function restoreRecord(ArchivedRecord $record): Model { $modelClass = $this->getModelClass($record->record_type); if (! $modelClass) { throw new \Exception("지원하지 않는 레코드 타입: {$record->record_type}"); } return DB::transaction(function () use ($record, $modelClass) { // 1. 메인 데이터 복원 $mainData = $record->main_data; // ID, timestamps 제거 (새로 생성되도록) unset($mainData['id'], $mainData['created_at'], $mainData['updated_at'], $mainData['deleted_at']); // soft delete 관련 필드 초기화 $mainData['deleted_by'] = null; // 복원자 정보 추가 $mainData['created_by'] = auth()->id(); $mainData['updated_by'] = auth()->id(); // 모델 생성 $model = $modelClass::create($mainData); // 2. 아카이브 레코드 삭제 $record->relations()->delete(); $record->delete(); return $model; }); } /** * 배치 전체 복원 (테넌트 + 관련 데이터) * * @return Collection 복원된 모델들 */ public function restoreBatch(string $batchId): Collection { $records = ArchivedRecord::where('batch_id', $batchId) ->with('relations') ->get(); if ($records->isEmpty()) { throw new \Exception("배치를 찾을 수 없습니다: {$batchId}"); } $restoredModels = collect(); DB::transaction(function () use ($records, &$restoredModels) { foreach ($records as $record) { // 테넌트 복원의 경우 특별 처리 if ($record->record_type === 'tenant') { $restoredModels->push($this->restoreTenantWithRelations($record)); } elseif ($record->record_type === 'user') { $restoredModels->push($this->restoreUserWithRelations($record)); } else { $restoredModels->push($this->restoreRecord($record)); } } }); return $restoredModels; } /** * 테넌트와 관련 데이터 복원 */ public function restoreTenantWithRelations(ArchivedRecord $record): Tenant { if ($record->record_type !== 'tenant') { throw new \Exception('테넌트 타입의 레코드가 아닙니다.'); } return DB::transaction(function () use ($record) { // 1. 테넌트 복원 $tenantData = $record->main_data; unset($tenantData['id'], $tenantData['created_at'], $tenantData['updated_at'], $tenantData['deleted_at']); $tenantData['deleted_by'] = null; $tenantData['created_by'] = auth()->id(); $tenant = Tenant::create($tenantData); // 2. 관련 데이터 복원 foreach ($record->relations as $relation) { $this->restoreRelationData($tenant, $relation); } // 3. 아카이브 삭제 $record->relations()->delete(); $record->delete(); return $tenant; }); } /** * 사용자와 관련 데이터 복원 */ public function restoreUserWithRelations(ArchivedRecord $record): User { if ($record->record_type !== 'user') { throw new \Exception('사용자 타입의 레코드가 아닙니다.'); } return DB::transaction(function () use ($record) { // 1. 사용자 복원 $userData = $record->main_data; unset($userData['id'], $userData['created_at'], $userData['updated_at'], $userData['deleted_at']); $userData['deleted_by'] = null; $userData['created_by'] = auth()->id(); $user = User::create($userData); // 2. 테넌트 관계 복원 foreach ($record->relations as $relation) { if ($relation->table_name === 'user_tenants') { foreach ($relation->data as $tenantData) { // 테넌트가 존재하는지 확인 $tenantId = $tenantData['tenant_id'] ?? null; if ($tenantId && Tenant::find($tenantId)) { $pivotData = $tenantData['pivot'] ?? []; unset($pivotData['user_id'], $pivotData['tenant_id']); $user->tenants()->attach($tenantId, $pivotData); } } } } // 3. 아카이브 삭제 $record->relations()->delete(); $record->delete(); return $user; }); } /** * 관계 데이터 복원 */ private function restoreRelationData(Tenant $tenant, ArchivedRecordRelation $relation): void { $tableName = $relation->table_name; $data = $relation->data; switch ($tableName) { case 'users': // 사용자 복원은 별도 처리 (user_tenants 피벗 포함) foreach ($data as $item) { $userData = $item['user'] ?? $item; $pivotData = $item['pivot'] ?? []; // 이미 존재하는 사용자인지 확인 $existingUser = User::where('email', $userData['email'])->first(); if ($existingUser) { // 기존 사용자에 테넌트 관계만 추가 if (! $existingUser->tenants()->where('tenants.id', $tenant->id)->exists()) { unset($pivotData['user_id'], $pivotData['tenant_id']); $existingUser->tenants()->attach($tenant->id, $pivotData); } } // 새 사용자 생성은 보안상 하지 않음 (비밀번호 문제) } break; case 'departments': foreach ($data as $deptData) { unset($deptData['id'], $deptData['created_at'], $deptData['updated_at'], $deptData['deleted_at']); $deptData['tenant_id'] = $tenant->id; $deptData['created_by'] = auth()->id(); Department::create($deptData); } break; case 'menus': // 메뉴는 parent_id 순서 고려 필요 (부모 먼저) $sortedMenus = collect($data)->sortBy('parent_id')->values(); $menuIdMap = []; // 원본 ID → 새 ID 매핑 foreach ($sortedMenus as $menuData) { $originalId = $menuData['id']; unset($menuData['id'], $menuData['created_at'], $menuData['updated_at'], $menuData['deleted_at']); $menuData['tenant_id'] = $tenant->id; $menuData['created_by'] = auth()->id(); // parent_id 매핑 if (! empty($menuData['parent_id']) && isset($menuIdMap[$menuData['parent_id']])) { $menuData['parent_id'] = $menuIdMap[$menuData['parent_id']]; } else { $menuData['parent_id'] = null; } $newMenu = Menu::create($menuData); $menuIdMap[$originalId] = $newMenu->id; } break; case 'roles': foreach ($data as $roleData) { unset($roleData['id'], $roleData['created_at'], $roleData['updated_at'], $roleData['deleted_at']); $roleData['tenant_id'] = $tenant->id; $roleData['created_by'] = auth()->id(); Role::create($roleData); } break; } } /** * record_type에서 모델 클래스 가져오기 */ private function getModelClass(string $recordType): ?string { return self::MODEL_CLASS_MAP[$recordType] ?? null; } /** * 복원 가능 여부 확인 */ public function canRestore(ArchivedRecord $record): array { $issues = []; // 1. 모델 클래스 지원 여부 if (! $this->getModelClass($record->record_type)) { $issues[] = "지원하지 않는 레코드 타입입니다: {$record->record_type}"; } // 2. 테넌트 복원 시, 동일 코드 존재 여부 확인 if ($record->record_type === 'tenant') { $code = $record->main_data['code'] ?? null; if ($code && Tenant::where('code', $code)->exists()) { $issues[] = "동일한 테넌트 코드가 이미 존재합니다: {$code}"; } } // 3. 사용자 복원 시, 동일 이메일 존재 여부 확인 if ($record->record_type === 'user') { $email = $record->main_data['email'] ?? null; if ($email && User::where('email', $email)->exists()) { $issues[] = "동일한 이메일의 사용자가 이미 존재합니다: {$email}"; } } return [ 'can_restore' => empty($issues), 'issues' => $issues, ]; } /** * 배치 내 모든 레코드의 복원 가능 여부 확인 */ public function canRestoreBatch(string $batchId): array { $records = ArchivedRecord::where('batch_id', $batchId)->get(); $allIssues = []; foreach ($records as $record) { $check = $this->canRestore($record); if (! $check['can_restore']) { $allIssues = array_merge($allIssues, $check['issues']); } } return [ 'can_restore' => empty($allIssues), 'issues' => array_unique($allIssues), 'record_count' => $records->count(), ]; } }