fix-dashboard-monitoring-blocker-query

مخزن کامیت contained در
مسعود نیک زاد 2026-08-31 15:58:14 +03:30
والد c94bb44d9f
کامیت 0ad1cafcb9
4فایلهای تغییر یافته به همراه49 افزوده شده و 5 حذف شده

مشاهده پرونده

@ -465,7 +465,8 @@ class SprintController extends Controller
$tasks = $sprint->tasks; $tasks = $sprint->tasks;
$total = $tasks->count(); $total = $tasks->count();
$completed = $tasks->where('status', 'done')->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(); $overdue = $tasks->filter(fn (Task $task) => $task->due_date?->isPast() && $task->status !== 'done')->count();
$progress = $total > 0 ? round(($completed / $total) * 100) : 0; $progress = $total > 0 ? round(($completed / $total) * 100) : 0;
$today = Carbon::today(); $today = Carbon::today();

مشاهده پرونده

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
@ -23,6 +24,19 @@ class Task extends Model
'actual_time' => 'decimal:2', '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 public function project(): BelongsTo
{ {
return $this->belongsTo(Project::class); return $this->belongsTo(Project::class);

مشاهده پرونده

@ -176,7 +176,7 @@ class DashboardService
'tasks as overdue_tasks_count' => fn ($query) => $query 'tasks as overdue_tasks_count' => fn ($query) => $query
->whereDate('due_date', '<', $today) ->whereDate('due_date', '<', $today)
->whereNotIn('status', ['done', 'canceled']), ->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") ->orderByRaw("CASE WHEN risk_level = 'critical' THEN 0 WHEN risk_level = 'high' THEN 1 ELSE 2 END")
->limit(8) ->limit(8)
@ -207,7 +207,7 @@ class DashboardService
->withCount([ ->withCount([
'tasks as total_tasks_count', 'tasks as total_tasks_count',
'tasks as completed_tasks_count' => fn ($query) => $query->where('status', 'done'), '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') ->where('status', 'active')
->orderBy('end_date') ->orderBy('end_date')
@ -260,7 +260,7 @@ class DashboardService
Task::with('project:id,title') Task::with('project:id,title')
->whereNotIn('status', ['done', 'canceled']) ->whereNotIn('status', ['done', 'canceled'])
->where(function ($query) use ($today, $weekEnd) { ->where(function ($query) use ($today, $weekEnd) {
$query->where('is_blocked', true) $query->blocked()
->orWhereDate('due_date', '<', $today) ->orWhereDate('due_date', '<', $today)
->orWhereBetween('due_date', [$today, $weekEnd]); ->orWhereBetween('due_date', [$today, $weekEnd]);
}) })
@ -305,7 +305,7 @@ class DashboardService
'projects_at_risk' => $projects->whereIn('health', ['at_risk', 'off_track'])->count(), 'projects_at_risk' => $projects->whereIn('health', ['at_risk', 'off_track'])->count(),
'open_tasks' => Task::whereNotIn('status', ['done', 'canceled'])->count(), 'open_tasks' => Task::whereNotIn('status', ['done', 'canceled'])->count(),
'overdue_tasks' => Task::whereDate('due_date', '<', $today)->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(), 'active_sprints' => Sprint::where('status', 'active')->count(),
'meetings_today' => Meeting::whereDate('date', $today)->where('status', '!=', 'cancelled')->count(), 'meetings_today' => Meeting::whereDate('date', $today)->where('status', '!=', 'cancelled')->count(),
'overdue_action_items' => ActionItem::whereDate('due_date', '<', $today)->whereNotIn('status', ['completed', 'cancelled'])->count(), 'overdue_action_items' => ActionItem::whereDate('due_date', '<', $today)->whereNotIn('status', ['completed', 'cancelled'])->count(),

مشاهده پرونده

@ -8,6 +8,7 @@ use App\Models\MeetingActionItem;
use App\Models\Permission; use App\Models\Permission;
use App\Models\Project; use App\Models\Project;
use App\Models\Role; use App\Models\Role;
use App\Models\Task;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
@ -202,4 +203,32 @@ class SecurityHardeningTest extends TestCase
$this->assertSame('Private action', $item->fresh()->title); $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);
}
} }