From 0ad1cafcb972ce2cc2bd46ee80af7f681c2854f5 Mon Sep 17 00:00:00 2001 From: Toornaa Date: Mon, 31 Aug 2026 15:58:14 +0330 Subject: [PATCH] fix-dashboard-monitoring-blocker-query --- .../Http/Controllers/Api/SprintController.php | 3 +- backend/app/Models/Task.php | 14 +++++++++ backend/app/Services/DashboardService.php | 8 ++--- .../tests/Feature/SecurityHardeningTest.php | 29 +++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/backend/app/Http/Controllers/Api/SprintController.php b/backend/app/Http/Controllers/Api/SprintController.php index 2350173..80bce7c 100644 --- a/backend/app/Http/Controllers/Api/SprintController.php +++ b/backend/app/Http/Controllers/Api/SprintController.php @@ -465,7 +465,8 @@ class SprintController extends Controller $tasks = $sprint->tasks; $total = $tasks->count(); $completed = $tasks->where('status', 'done')->count(); - $blocked = $tasks->where('is_blocked', true)->count() + $sprint->blockers->whereNotIn('status', ['resolved', 'closed'])->count(); + $blocked = $tasks->filter(fn (Task $task) => $task->is_blocked)->count() + + $sprint->blockers->whereNotIn('status', ['resolved', 'closed'])->count(); $overdue = $tasks->filter(fn (Task $task) => $task->due_date?->isPast() && $task->status !== 'done')->count(); $progress = $total > 0 ? round(($completed / $total) * 100) : 0; $today = Carbon::today(); diff --git a/backend/app/Models/Task.php b/backend/app/Models/Task.php index 61a87f6..d772162 100644 --- a/backend/app/Models/Task.php +++ b/backend/app/Models/Task.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; @@ -23,6 +24,19 @@ class Task extends Model 'actual_time' => 'decimal:2', ]; + public function scopeBlocked(Builder $query): Builder + { + return $query->where(function (Builder $blocked) { + $blocked->whereNotNull('blocker_type') + ->orWhereNotNull('blocker_note'); + }); + } + + public function getIsBlockedAttribute(): bool + { + return filled($this->blocker_type) || filled($this->blocker_note); + } + public function project(): BelongsTo { return $this->belongsTo(Project::class); diff --git a/backend/app/Services/DashboardService.php b/backend/app/Services/DashboardService.php index d9e9f77..7e3a94d 100644 --- a/backend/app/Services/DashboardService.php +++ b/backend/app/Services/DashboardService.php @@ -176,7 +176,7 @@ class DashboardService 'tasks as overdue_tasks_count' => fn ($query) => $query ->whereDate('due_date', '<', $today) ->whereNotIn('status', ['done', 'canceled']), - 'tasks as blocked_tasks_count' => fn ($query) => $query->where('is_blocked', true), + 'tasks as blocked_tasks_count' => fn ($query) => $query->blocked(), ]) ->orderByRaw("CASE WHEN risk_level = 'critical' THEN 0 WHEN risk_level = 'high' THEN 1 ELSE 2 END") ->limit(8) @@ -207,7 +207,7 @@ class DashboardService ->withCount([ 'tasks as total_tasks_count', 'tasks as completed_tasks_count' => fn ($query) => $query->where('status', 'done'), - 'tasks as blocked_tasks_count' => fn ($query) => $query->where('is_blocked', true), + 'tasks as blocked_tasks_count' => fn ($query) => $query->blocked(), ]) ->where('status', 'active') ->orderBy('end_date') @@ -260,7 +260,7 @@ class DashboardService Task::with('project:id,title') ->whereNotIn('status', ['done', 'canceled']) ->where(function ($query) use ($today, $weekEnd) { - $query->where('is_blocked', true) + $query->blocked() ->orWhereDate('due_date', '<', $today) ->orWhereBetween('due_date', [$today, $weekEnd]); }) @@ -305,7 +305,7 @@ class DashboardService 'projects_at_risk' => $projects->whereIn('health', ['at_risk', 'off_track'])->count(), 'open_tasks' => Task::whereNotIn('status', ['done', 'canceled'])->count(), 'overdue_tasks' => Task::whereDate('due_date', '<', $today)->whereNotIn('status', ['done', 'canceled'])->count(), - 'blocked_tasks' => Task::where('is_blocked', true)->whereNotIn('status', ['done', 'canceled'])->count(), + 'blocked_tasks' => Task::blocked()->whereNotIn('status', ['done', 'canceled'])->count(), 'active_sprints' => Sprint::where('status', 'active')->count(), 'meetings_today' => Meeting::whereDate('date', $today)->where('status', '!=', 'cancelled')->count(), 'overdue_action_items' => ActionItem::whereDate('due_date', '<', $today)->whereNotIn('status', ['completed', 'cancelled'])->count(), diff --git a/backend/tests/Feature/SecurityHardeningTest.php b/backend/tests/Feature/SecurityHardeningTest.php index 1785e4a..bea4c15 100644 --- a/backend/tests/Feature/SecurityHardeningTest.php +++ b/backend/tests/Feature/SecurityHardeningTest.php @@ -8,6 +8,7 @@ use App\Models\MeetingActionItem; use App\Models\Permission; use App\Models\Project; use App\Models\Role; +use App\Models\Task; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\UploadedFile; @@ -202,4 +203,32 @@ class SecurityHardeningTest extends TestCase $this->assertSame('Private action', $item->fresh()->title); } + + public function test_dashboard_monitoring_uses_task_blocker_fields(): void + { + $user = User::factory()->create(['status' => 'active']); + $this->grantAdminRole($user); + $project = Project::create([ + 'title' => 'Monitoring project', + 'project_manager_id' => $user->id, + 'created_by' => $user->id, + ]); + Task::create([ + 'title' => 'Blocked task', + 'project_id' => $project->id, + 'reporter_id' => $user->id, + 'created_by' => $user->id, + 'status' => 'in_progress', + 'blocker_type' => 'dependency', + ]); + + $token = $user->createToken('api-token')->plainTextToken; + + $this->withToken($token) + ->getJson('/api/dashboard/monitoring') + ->assertOk() + ->assertJsonPath('success', true) + ->assertJsonPath('data.summary.blocked_tasks', 1) + ->assertJsonPath('data.projects.0.blocked_tasks', 1); + } }