with(['project', 'assignee', 'issues']) ->withCount('issues') ->withTrashed(); // 프로젝트 필터 if (! empty($filters['project_id'])) { $query->where('project_id', $filters['project_id']); } // 검색 필터 if (! empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('title', 'like', "%{$search}%") ->orWhere('description', 'like', "%{$search}%"); }); } // 상태 필터 if (! empty($filters['status'])) { $query->where('status', $filters['status']); } // 우선순위 필터 if (! empty($filters['priority'])) { $query->where('priority', $filters['priority']); } // 담당자 필터 if (! empty($filters['assignee_id'])) { $query->where('assignee_id', $filters['assignee_id']); } // 마감 상태 필터 if (! empty($filters['due_status'])) { if ($filters['due_status'] === 'overdue') { $query->overdue(); } elseif ($filters['due_status'] === 'due_soon') { $query->dueSoon(); } } // Soft Delete 필터 if (isset($filters['trashed'])) { if ($filters['trashed'] === 'only') { $query->onlyTrashed(); } elseif ($filters['trashed'] === 'with') { $query->withTrashed(); } } // 정렬 $sortBy = $filters['sort_by'] ?? 'sort_order'; $sortDirection = $filters['sort_direction'] ?? 'asc'; $query->orderBy($sortBy, $sortDirection)->orderBy('id'); return $query->paginate($perPage); } /** * 프로젝트별 작업 목록 (칸반보드용) */ public function getTasksByProject(int $projectId): Collection { return AdminPmTask::query() ->where('project_id', $projectId) ->with(['assignee', 'issues']) ->withCount([ 'issues', 'issues as resolved_issues_count' => function ($query) { $query->whereIn('status', ['resolved', 'closed']); }, ]) ->orderBy('sort_order') ->orderBy('id') ->get(); } /** * 특정 작업 조회 */ public function getTaskById(int $id, bool $withTrashed = false): ?AdminPmTask { $query = AdminPmTask::query() ->with(['project', 'assignee', 'issues', 'creator', 'updater']) ->withCount('issues'); if ($withTrashed) { $query->withTrashed(); } return $query->find($id); } /** * 작업 생성 */ public function createTask(array $data): AdminPmTask { // 정렬 순서 자동 설정 if (! isset($data['sort_order'])) { $maxOrder = AdminPmTask::where('project_id', $data['project_id'])->max('sort_order') ?? 0; $data['sort_order'] = $maxOrder + 1; } $data['created_by'] = auth()->id(); return AdminPmTask::create($data); } /** * 작업 수정 */ public function updateTask(int $id, array $data): bool { $task = AdminPmTask::findOrFail($id); $data['updated_by'] = auth()->id(); return $task->update($data); } /** * 작업 삭제 (Soft Delete) */ public function deleteTask(int $id): bool { $task = AdminPmTask::findOrFail($id); $task->deleted_by = auth()->id(); $task->save(); return $task->delete(); } /** * 작업 복원 */ public function restoreTask(int $id): bool { $task = AdminPmTask::onlyTrashed()->findOrFail($id); $task->deleted_by = null; return $task->restore(); } /** * 작업 영구 삭제 */ public function forceDeleteTask(int $id): bool { return AdminPmTask::withTrashed()->findOrFail($id)->forceDelete(); } /** * 작업 상태 변경 */ public function changeStatus(int $id, string $status): AdminPmTask { $task = AdminPmTask::findOrFail($id); $task->status = $status; $task->updated_by = auth()->id(); $task->save(); return $task; } /** * 작업 긴급 토글 */ public function toggleUrgent(int $id): AdminPmTask { $task = AdminPmTask::findOrFail($id); $task->is_urgent = ! $task->is_urgent; $task->updated_by = auth()->id(); $task->save(); return $task; } /** * 작업 순서 변경 (드래그앤드롭) */ public function reorderTasks(int $projectId, array $taskIds): bool { return DB::transaction(function () use ($projectId, $taskIds) { foreach ($taskIds as $order => $taskId) { AdminPmTask::where('id', $taskId) ->where('project_id', $projectId) ->update(['sort_order' => $order + 1]); } return true; }); } /** * 다중 작업 상태 일괄 변경 */ public function bulkChangeStatus(array $taskIds, string $status): int { return AdminPmTask::whereIn('id', $taskIds) ->update([ 'status' => $status, 'updated_by' => auth()->id(), 'updated_at' => now(), ]); } /** * 다중 작업 담당자 일괄 변경 */ public function bulkChangeAssignee(array $taskIds, ?int $assigneeId): int { return AdminPmTask::whereIn('id', $taskIds) ->update([ 'assignee_id' => $assigneeId, 'updated_by' => auth()->id(), 'updated_at' => now(), ]); } /** * 다중 작업 우선순위 일괄 변경 */ public function bulkChangePriority(array $taskIds, string $priority): int { return AdminPmTask::whereIn('id', $taskIds) ->update([ 'priority' => $priority, 'updated_by' => auth()->id(), 'updated_at' => now(), ]); } /** * 다중 작업 일괄 삭제 */ public function bulkDelete(array $taskIds): int { AdminPmTask::whereIn('id', $taskIds) ->update([ 'deleted_by' => auth()->id(), ]); return AdminPmTask::whereIn('id', $taskIds)->delete(); } /** * 다중 작업 일괄 복원 */ public function bulkRestore(array $taskIds): int { return AdminPmTask::onlyTrashed() ->whereIn('id', $taskIds) ->update([ 'deleted_by' => null, 'deleted_at' => null, ]); } /** * 작업 통계 (프로젝트별) */ public function getTaskStatsByProject(int $projectId): array { return [ 'total' => AdminPmTask::where('project_id', $projectId)->count(), 'todo' => AdminPmTask::where('project_id', $projectId) ->status(AdminPmTask::STATUS_TODO)->count(), 'in_progress' => AdminPmTask::where('project_id', $projectId) ->status(AdminPmTask::STATUS_IN_PROGRESS)->count(), 'done' => AdminPmTask::where('project_id', $projectId) ->status(AdminPmTask::STATUS_DONE)->count(), 'overdue' => AdminPmTask::where('project_id', $projectId)->overdue()->count(), 'due_soon' => AdminPmTask::where('project_id', $projectId)->dueSoon()->count(), ]; } /** * 마감 임박/지연 작업 목록 */ public function getUrgentTasks(?int $projectId = null): array { $overdueQuery = AdminPmTask::overdue()->with('project', 'assignee'); $dueSoonQuery = AdminPmTask::dueSoon()->with('project', 'assignee'); if ($projectId) { $overdueQuery->where('project_id', $projectId); $dueSoonQuery->where('project_id', $projectId); } return [ 'overdue' => $overdueQuery->orderBy('due_date')->get(), 'due_soon' => $dueSoonQuery->orderBy('due_date')->get(), ]; } }