252 خطوط
8.6 KiB
PHP
252 خطوط
8.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\ActivityLog;
|
|
use App\Models\Project;
|
|
use App\Models\Sprint;
|
|
use App\Models\Task;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
|
|
class ReportService
|
|
{
|
|
protected function applyDateFilters($query, $filters, $dateField = 'created_at')
|
|
{
|
|
if (! empty($filters['date_from'])) {
|
|
$query->where($dateField, '>=', $filters['date_from']);
|
|
}
|
|
if (! empty($filters['date_to'])) {
|
|
$query->where($dateField, '<=', $filters['date_to']);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
public function projectStatus($filters = [])
|
|
{
|
|
$query = Project::query();
|
|
|
|
if (! empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
if (! empty($filters['project_id'])) {
|
|
$query->where('id', $filters['project_id']);
|
|
}
|
|
|
|
$projects = $query->get();
|
|
$grouped = $projects->groupBy('status');
|
|
|
|
return $grouped->map(fn ($items, $status) => [
|
|
'status' => $status === 'planning' ? 'برنامهریزی' : ($status === 'in_progress' ? 'در حال انجام' : ($status === 'done' ? 'تکمیل شده' : ($status === 'on_hold' ? 'متوقف' : $status))),
|
|
'count' => $items->count(),
|
|
'label' => $status,
|
|
'value' => $items->count(),
|
|
])->values();
|
|
}
|
|
|
|
public function delayedTasks($filters = [])
|
|
{
|
|
$query = Task::with(['project:id,title', 'assignee:id,name'])
|
|
->whereDate('due_date', '<', Carbon::today())
|
|
->where('status', '!=', 'done');
|
|
|
|
$query = $this->applyDateFilters($query, $filters, 'due_date');
|
|
|
|
if (! empty($filters['project_id'])) {
|
|
$query->where('project_id', $filters['project_id']);
|
|
}
|
|
if (! empty($filters['user_id'])) {
|
|
$query->where('assignee_id', $filters['user_id']);
|
|
}
|
|
if (! empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
|
|
return $query->orderBy('due_date')->get()->map(fn ($t) => [
|
|
'id' => $t->id,
|
|
'title' => $t->title,
|
|
'project' => ['title' => $t->project?->title],
|
|
'project_name' => $t->project?->title,
|
|
'assignee' => ['name' => $t->assignee?->name],
|
|
'assignee_name' => $t->assignee?->name,
|
|
'due_date' => $t->due_date?->format('Y-m-d'),
|
|
'delay_days' => abs(Carbon::today()->diffInDays($t->due_date, false)),
|
|
'days_delayed' => abs(Carbon::today()->diffInDays($t->due_date, false)),
|
|
'priority' => $t->priority,
|
|
]);
|
|
}
|
|
|
|
public function teamPerformance($filters = [])
|
|
{
|
|
$query = User::with(['tasks' => function ($q) use ($filters) {
|
|
if (! empty($filters['date_from'])) {
|
|
$q->where('created_at', '>=', $filters['date_from']);
|
|
}
|
|
if (! empty($filters['date_to'])) {
|
|
$q->where('created_at', '<=', $filters['date_to']);
|
|
}
|
|
}]);
|
|
|
|
if (! empty($filters['user_id'])) {
|
|
$query->where('id', $filters['user_id']);
|
|
}
|
|
|
|
$users = $query->get();
|
|
|
|
return $users->map(function ($user) {
|
|
$totalTasks = $user->tasks->count();
|
|
$completedTasks = $user->tasks->where('status', 'done')->count();
|
|
$delayedTasks = $user->tasks->where('due_date', '<', Carbon::now())->where('status', '!=', 'done')->count();
|
|
$inProgress = $user->tasks->whereNotIn('status', ['done', 'canceled'])->count();
|
|
|
|
return [
|
|
'name' => $user->name,
|
|
'completed' => $completedTasks,
|
|
'in_progress' => $inProgress,
|
|
'delayed' => $delayedTasks,
|
|
'total' => $totalTasks,
|
|
];
|
|
});
|
|
}
|
|
|
|
public function workload($filters = [])
|
|
{
|
|
$query = User::query();
|
|
|
|
if (! empty($filters['user_id'])) {
|
|
$query->where('id', $filters['user_id']);
|
|
}
|
|
|
|
$users = $query->get();
|
|
|
|
$allActiveTasks = Task::where('status', '!=', 'done')->count();
|
|
$maxTasks = max($allActiveTasks, 1);
|
|
|
|
return $users->map(function ($user) use ($maxTasks) {
|
|
$activeTasks = Task::where('assignee_id', $user->id)->where('status', '!=', 'done')->count();
|
|
$loadPercent = min(round(($activeTasks / $maxTasks) * 100), 100);
|
|
|
|
return [
|
|
'user_id' => $user->id,
|
|
'name' => $user->name,
|
|
'tasks_count' => $activeTasks,
|
|
'total' => $activeTasks,
|
|
'load_percent' => $loadPercent,
|
|
'load' => $loadPercent,
|
|
];
|
|
});
|
|
}
|
|
|
|
public function sprintProgress($filters = [])
|
|
{
|
|
$query = Sprint::with(['project:id,title', 'tasks']);
|
|
|
|
if (! empty($filters['project_id'])) {
|
|
$query->where('project_id', $filters['project_id']);
|
|
}
|
|
if (! empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
|
|
$sprints = $query->get();
|
|
|
|
return $sprints->map(function ($sprint) {
|
|
$totalTasks = $sprint->tasks->count();
|
|
$completedTasks = $sprint->tasks->where('status', 'done')->count();
|
|
|
|
return [
|
|
'id' => $sprint->id,
|
|
'title' => $sprint->title,
|
|
'project' => $sprint->project?->title,
|
|
'start_date' => $sprint->start_date,
|
|
'end_date' => $sprint->end_date,
|
|
'status' => $sprint->status,
|
|
'total_tasks' => $totalTasks,
|
|
'completed_tasks' => $completedTasks,
|
|
'progress' => $totalTasks > 0 ? round(($completedTasks / $totalTasks) * 100, 2) : 0,
|
|
];
|
|
});
|
|
}
|
|
|
|
public function timeEstimate($filters = [])
|
|
{
|
|
$query = Task::with('project');
|
|
$query = $this->applyDateFilters($query, $filters);
|
|
|
|
if (! empty($filters['project_id'])) {
|
|
$query->where('project_id', $filters['project_id']);
|
|
}
|
|
if (! empty($filters['user_id'])) {
|
|
$query->where('assignee_id', $filters['user_id']);
|
|
}
|
|
if (! empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
|
|
$tasks = $query->get();
|
|
|
|
return $tasks->map(function ($task) {
|
|
$diff = ($task->estimated_time ?? 0) - ($task->actual_time ?? 0);
|
|
|
|
return [
|
|
'id' => $task->id,
|
|
'title' => $task->title,
|
|
'project' => ['title' => $task->project?->title ?? '—'],
|
|
'estimated' => $task->estimated_time,
|
|
'estimated_time' => $task->estimated_time,
|
|
'actual' => $task->actual_time,
|
|
'actual_time' => $task->actual_time,
|
|
'diff' => $diff,
|
|
'difference' => $diff,
|
|
'status' => $task->status,
|
|
];
|
|
});
|
|
}
|
|
|
|
public function recentActivities($filters = [])
|
|
{
|
|
$query = ActivityLog::with('user');
|
|
$query = $this->applyDateFilters($query, $filters);
|
|
|
|
if (! empty($filters['project_id'])) {
|
|
$query->where('project_id', $filters['project_id']);
|
|
}
|
|
if (! empty($filters['user_id'])) {
|
|
$query->where('user_id', $filters['user_id']);
|
|
}
|
|
if (! empty($filters['action'])) {
|
|
$query->where('action', $filters['action']);
|
|
}
|
|
|
|
return $query->orderBy('created_at', 'desc')->limit(50)->get()->map(fn ($log) => [
|
|
'id' => $log->id,
|
|
'description' => $log->description,
|
|
'user' => $log->user?->name ?? '?',
|
|
'user_name' => $log->user?->name ?? '?',
|
|
'created_at' => $log->created_at?->toDateTimeString(),
|
|
'time_ago' => $log->created_at?->diffForHumans(),
|
|
]);
|
|
}
|
|
|
|
public function riskyProjects($filters = [])
|
|
{
|
|
$query = Project::whereIn('risk_level', ['high', 'critical'])->where('is_archived', false);
|
|
|
|
if (! empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
if (! empty($filters['project_id'])) {
|
|
$query->where('id', $filters['project_id']);
|
|
}
|
|
|
|
return $query->with('projectManager:id,name')->get()->map(fn ($p) => [
|
|
'id' => $p->id,
|
|
'title' => $p->title,
|
|
'risk_level' => $p->risk_level,
|
|
'progress' => $p->progress,
|
|
'delayed_tasks' => Task::where('project_id', $p->id)->where('due_date', '<', Carbon::now())->where('status', '!=', 'done')->count(),
|
|
]);
|
|
}
|
|
}
|