fix-dashboard-monitoring-blocker-query
مخزن کامیت contained در
والد
c94bb44d9f
کامیت
0ad1cafcb9
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
بارگذاری…
مرجع در شماره جدید