select([ DB::raw("COALESCE(batch_id, CONCAT('legacy_', id)) as batch_id"), DB::raw("COALESCE(batch_description, CONCAT(record_type, ' 삭제 (ID: ', original_id, ')')) as batch_description"), DB::raw('MIN(id) as id'), DB::raw('MIN(tenant_id) as tenant_id'), DB::raw('GROUP_CONCAT(DISTINCT record_type) as record_types'), DB::raw('COUNT(*) as record_count'), DB::raw('MIN(deleted_by) as deleted_by'), DB::raw('MIN(deleted_at) as deleted_at'), // 대상 정보 추출 (테넌트명, 사용자명 등) DB::raw("MAX(JSON_UNQUOTE(JSON_EXTRACT(main_data, '$.company_name'))) as target_company_name"), DB::raw("MAX(JSON_UNQUOTE(JSON_EXTRACT(main_data, '$.name'))) as target_name"), DB::raw("MAX(JSON_UNQUOTE(JSON_EXTRACT(main_data, '$.email'))) as target_email"), DB::raw("MAX(JSON_UNQUOTE(JSON_EXTRACT(main_data, '$.code'))) as target_code"), ]); // 테넌트 필터링 (서브쿼리에서 적용) if (! empty($filters['tenant_id'])) { $subQuery->where('tenant_id', $filters['tenant_id']); } $subQuery->groupBy( DB::raw("COALESCE(batch_id, CONCAT('legacy_', id))"), DB::raw("COALESCE(batch_description, CONCAT(record_type, ' 삭제 (ID: ', original_id, ')'))") ); // 2. 외부 쿼리: 서브쿼리를 감싸서 정확한 count 계산 $query = DB::table(DB::raw("({$subQuery->toSql()}) as grouped")) ->mergeBindings($subQuery->getQuery()) ->select('*'); // 레코드 타입 필터 if (! empty($filters['record_type'])) { $query->where('record_types', 'like', "%{$filters['record_type']}%"); } // 삭제자 필터 if (! empty($filters['deleted_by'])) { $query->where('deleted_by', $filters['deleted_by']); } // 검색 if (! empty($filters['search'])) { $query->where('batch_description', 'like', "%{$filters['search']}%"); } // 정렬 (기본: 삭제일시 내림차순) $sortBy = $filters['sort_by'] ?? 'deleted_at'; $sortDirection = $filters['sort_direction'] ?? 'desc'; $query->orderBy($sortBy, $sortDirection); // NOTE: DB::table() 사용 시 request의 page 파라미터를 자동으로 읽지 못함 // filters에서 page를 명시적으로 전달 $page = $filters['page'] ?? null; return $query->paginate($perPage, ['*'], 'page', $page); } /** * 특정 batch의 모든 레코드 조회 * legacy_ 접두사가 붙은 경우 단일 레코드 ID로 조회 */ public function getRecordsByBatchId(string $batchId): Collection { // legacy_ 접두사가 붙은 경우 (기존 데이터) if (str_starts_with($batchId, 'legacy_')) { $id = (int) str_replace('legacy_', '', $batchId); return ArchivedRecord::with(['deletedByUser', 'relations']) ->where('id', $id) ->get(); } return ArchivedRecord::with(['deletedByUser', 'relations']) ->where('batch_id', $batchId) ->orderBy('id') ->get(); } /** * 특정 아카이브 레코드 조회 */ public function getArchivedRecordById(int $id): ?ArchivedRecord { return ArchivedRecord::with(['deletedByUser', 'relations']) ->find($id); } /** * batch_id로 첫 번째 레코드 조회 (상세 페이지 진입용) */ public function getFirstRecordByBatchId(string $batchId): ?ArchivedRecord { return ArchivedRecord::with(['deletedByUser', 'relations']) ->where('batch_id', $batchId) ->orderBy('id') ->first(); } /** * 삭제자 목록 조회 (필터용) */ public function getDeletedByUsers(): array { return ArchivedRecord::query() ->whereNotNull('deleted_by') ->with('deletedByUser:id,name') ->get() ->pluck('deletedByUser') ->filter() ->unique('id') ->values() ->toArray(); } /** * 아카이브 통계 */ public function getStats(): array { return [ 'total_batches' => ArchivedRecord::distinct('batch_id')->count('batch_id'), 'total_records' => ArchivedRecord::count(), 'tenants' => ArchivedRecord::tenant()->count(), 'users' => ArchivedRecord::user()->count(), ]; } }