'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', ]; /** * record_type → 모델 클래스 역매핑 */ private const MODEL_CLASS_MAP = [ 'tenant' => Tenant::class, 'user' => User::class, 'department' => Department::class, 'menu' => Menu::class, 'role' => Role::class, ]; /** * 단일 모델 아카이브 * * @param Model $model 아카이브할 모델 * @param array $relationNames 함께 아카이브할 관계명 (예: ['users', 'departments']) * @param string|null $batchId 배치 ID (동일 배치로 묶을 경우) * @param string|null $description 배치 설명 * @param int|null $tenantId 대상 테넌트 ID (명시적 지정) */ public function archiveModel( Model $model, array $relationNames = [], ?string $batchId = null, ?string $description = null, ?int $tenantId = null ): ArchivedRecord { $batchId = $batchId ?? (string) Str::uuid(); $recordType = $this->getRecordType($model); // 기본 설명 생성 if (! $description) { $description = $this->generateDescription($model, $recordType); } // tenant_id 결정 $tenantId = $tenantId ?? $this->determineTenantId($model, $recordType); return DB::transaction(function () use ($model, $relationNames, $batchId, $description, $recordType, $tenantId) { // 1. 메인 레코드 아카이브 $archivedRecord = ArchivedRecord::create([ 'batch_id' => $batchId, 'batch_description' => $description, 'record_type' => $recordType, 'tenant_id' => $tenantId, 'original_id' => $model->getKey(), 'main_data' => $model->toArray(), 'schema_version' => 'v1.0', 'deleted_by' => auth()->id(), 'deleted_at' => now(), 'created_by' => auth()->id(), ]); // 2. 관계 데이터 아카이브 foreach ($relationNames as $relationName) { $this->archiveRelation($archivedRecord, $model, $relationName); } return $archivedRecord; }); } /** * 배치 아카이브 (여러 모델을 하나의 배치로) * * @param Collection $models 아카이브할 모델 컬렉션 * @param string $description 배치 설명 * @param array $relationNames 각 모델에서 아카이브할 관계명 * @return string 배치 ID */ public function archiveBatch( Collection $models, string $description, array $relationNames = [] ): string { $batchId = (string) Str::uuid(); DB::transaction(function () use ($models, $description, $relationNames, $batchId) { foreach ($models as $model) { $this->archiveModel($model, $relationNames, $batchId, $description); } }); return $batchId; } /** * 테넌트와 관련 데이터 전체 아카이브 (테넌트 삭제 전용) */ public function archiveTenantWithRelations(Tenant $tenant): string { $batchId = (string) Str::uuid(); $description = "테넌트 삭제: {$tenant->company_name} (ID: {$tenant->id})"; DB::transaction(function () use ($tenant, $batchId, $description) { // 1. 메인 테넌트 아카이브 (tenant_id = 자기 자신) $archivedRecord = ArchivedRecord::create([ 'batch_id' => $batchId, 'batch_description' => $description, 'record_type' => 'tenant', 'tenant_id' => $tenant->id, 'original_id' => $tenant->id, 'main_data' => $tenant->toArray(), 'schema_version' => 'v1.0', 'deleted_by' => auth()->id(), 'deleted_at' => now(), 'created_by' => auth()->id(), ]); // 2. 관련 사용자 아카이브 (user_tenants 피벗 포함) $users = $tenant->users()->get(); if ($users->isNotEmpty()) { ArchivedRecordRelation::create([ 'archived_record_id' => $archivedRecord->id, 'table_name' => 'users', 'data' => $users->map(fn ($user) => [ 'user' => $user->toArray(), 'pivot' => $user->pivot?->toArray(), ])->toArray(), 'record_count' => $users->count(), 'created_by' => auth()->id(), ]); } // 3. 부서 아카이브 $departments = $tenant->departments()->withTrashed()->get(); if ($departments->isNotEmpty()) { ArchivedRecordRelation::create([ 'archived_record_id' => $archivedRecord->id, 'table_name' => 'departments', 'data' => $departments->toArray(), 'record_count' => $departments->count(), 'created_by' => auth()->id(), ]); } // 4. 메뉴 아카이브 $menus = $tenant->menus()->withTrashed()->get(); if ($menus->isNotEmpty()) { ArchivedRecordRelation::create([ 'archived_record_id' => $archivedRecord->id, 'table_name' => 'menus', 'data' => $menus->toArray(), 'record_count' => $menus->count(), 'created_by' => auth()->id(), ]); } // 5. 역할 아카이브 (Role 모델에는 SoftDeletes 없음) $roles = $tenant->roles()->get(); if ($roles->isNotEmpty()) { ArchivedRecordRelation::create([ 'archived_record_id' => $archivedRecord->id, 'table_name' => 'roles', 'data' => $roles->toArray(), 'record_count' => $roles->count(), 'created_by' => auth()->id(), ]); } }); return $batchId; } /** * 사용자 아카이브 (사용자 삭제 전용) */ public function archiveUser(User $user): string { $batchId = (string) Str::uuid(); $description = "사용자 삭제: {$user->name} ({$user->email})"; // tenant_id = 현재 선택된 테넌트 $tenantId = session('selected_tenant_id'); DB::transaction(function () use ($user, $batchId, $description, $tenantId) { // 1. 메인 사용자 아카이브 $archivedRecord = ArchivedRecord::create([ 'batch_id' => $batchId, 'batch_description' => $description, 'record_type' => 'user', 'tenant_id' => $tenantId, 'original_id' => $user->id, 'main_data' => $user->toArray(), 'schema_version' => 'v1.0', 'deleted_by' => auth()->id(), 'deleted_at' => now(), 'created_by' => auth()->id(), ]); // 2. 테넌트 관계 아카이브 $tenants = $user->tenants()->get(); if ($tenants->isNotEmpty()) { ArchivedRecordRelation::create([ 'archived_record_id' => $archivedRecord->id, 'table_name' => 'user_tenants', 'data' => $tenants->map(fn ($tenant) => [ 'tenant_id' => $tenant->id, 'tenant_name' => $tenant->company_name, 'pivot' => $tenant->pivot?->toArray(), ])->toArray(), 'record_count' => $tenants->count(), 'created_by' => auth()->id(), ]); } }); return $batchId; } /** * 모델에서 record_type 추출 */ public function getRecordType(Model $model): string { $class = get_class($model); return self::RECORD_TYPE_MAP[$class] ?? Str::snake(class_basename($class)); } /** * record_type에서 모델 클래스 추출 */ public function getModelClass(string $recordType): ?string { return self::MODEL_CLASS_MAP[$recordType] ?? null; } /** * 관계 데이터 아카이브 */ private function archiveRelation(ArchivedRecord $archivedRecord, Model $model, string $relationName): void { if (! method_exists($model, $relationName)) { return; } $relation = $model->{$relationName}(); // withTrashed가 있으면 soft deleted 포함 if (method_exists($relation, 'withTrashed')) { $relatedData = $relation->withTrashed()->get(); } else { $relatedData = $relation->get(); } if ($relatedData->isEmpty()) { return; } ArchivedRecordRelation::create([ 'archived_record_id' => $archivedRecord->id, 'table_name' => $relationName, 'data' => $relatedData->toArray(), 'record_count' => $relatedData->count(), 'created_by' => auth()->id(), ]); } /** * 기본 설명 생성 */ private function generateDescription(Model $model, string $recordType): string { $name = $model->name ?? $model->company_name ?? $model->title ?? "ID: {$model->getKey()}"; return match ($recordType) { 'tenant' => "테넌트 삭제: {$name}", 'user' => "사용자 삭제: {$name}", 'department' => "부서 삭제: {$name}", 'menu' => "메뉴 삭제: {$name}", 'role' => "역할 삭제: {$name}", default => "{$recordType} 삭제: {$name}", }; } /** * tenant_id 결정 로직 * - tenant 삭제: 삭제되는 테넌트의 ID (자기 자신) * - user 삭제: session('selected_tenant_id') (현재 선택된 테넌트) * - department/menu/role 삭제: 해당 레코드의 tenant_id */ private function determineTenantId(Model $model, string $recordType): ?int { return match ($recordType) { 'tenant' => $model->getKey(), 'user' => session('selected_tenant_id'), 'department', 'menu', 'role' => $model->tenant_id ?? session('selected_tenant_id'), default => session('selected_tenant_id'), }; } }