Files
Investidor-V2/app/Services/DashboardWorkQueue.php
T

89 lines
4.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\CrmTask;
use App\Models\User;
use Carbon\CarbonImmutable;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/** Personal follow-up only: assignment never grants authority beyond the destination. */
final class DashboardWorkQueue
{
/**
* @param array<string, int> $pages
* @return array<string, mixed>|null
*/
public function forActor(User $actor, array $pages): ?array
{
$now = CarbonImmutable::now()->startOfSecond();
$access = app(PortalProfileAccess::class);
$manager = $actor->can('manage-portal') && ! $access->has($actor, 'auditor');
$groups = [];
if (app(ScopedPortalAdministration::class)->interestOperator($actor)) {
$query = DB::table('interest_requests')->where('assigned_to', $actor->id)->whereNull('deleted_at')
->whereNotIn('status', ['closed', 'cancelled', 'refused', 'converted'])
->select(['id', 'reference', 'next_action as title', 'status', 'next_action_due_at as due_at']);
$groups['interests'] = $this->group($query, 'interests', $pages, $now);
}
if (app(VirtualDesk::class)->installed()) {
$assigned = DB::table('desk_requests')->where('analyst_id', $actor->id);
// Direct request consultation already permits the assigned analyst, independently of decision authority.
if ($manager || $assigned->exists()) {
$query = DB::table('desk_requests as r')->join('desk_cases as c', 'c.id', '=', 'r.case_id')
->where('r.analyst_id', $actor->id)->whereNull('c.closed_at')
->whereNotIn('r.status', ['draft', 'cancelled', 'closed'])
->select(['r.id', 'r.protocol as reference', 'c.title', 'r.status', 'r.due_at']);
$groups['desk'] = $this->group($query, 'desk', $pages, $now);
}
}
if ($manager && Schema::hasTable('tasks') && Schema::hasTable('leads')) {
$query = CrmTask::availableForWork()->join('leads as l', 'l.id', '=', 'tasks.lead_id')
->where('tasks.assignee_user_id', $actor->id)->whereNull('l.deleted_at')
->select(['tasks.id', 'tasks.title', 'tasks.status', 'tasks.due_at', 'l.id as parent_id', 'l.title as reference']);
$groups['crm_tasks'] = $this->group($query->toBase(), 'crm_tasks', $pages, $now);
}
return $groups === [] ? null : ['as_of' => $now->toISOString(), 'groups' => $groups, 'desk_states' => config('virtual-desk.request_states')];
}
/**
* @param 'interests'|'desk'|'crm_tasks' $kind
* @param array<string, int> $pages
* @return array<string, mixed>
*/
private function group(Builder $query, string $kind, array $pages, CarbonImmutable $now): array
{
[$deadline, $order, $idColumn] = match ($kind) {
'interests' => ['next_action_due_at', 'next_action_due_at ASC NULLS LAST', 'id'],
'desk' => ['r.due_at', 'r.due_at ASC NULLS LAST', 'r.id'],
'crm_tasks' => ['tasks.due_at', 'tasks.due_at ASC NULLS LAST', 'tasks.id'],
};
$pageName = $kind.'_page';
$page = $pages[$pageName] ?? 1;
$total = (clone $query)->count();
$overdue = (clone $query)->where($deadline, '<', $now)->count();
$lastPage = max(1, (int) ceil($total / 5));
// An assignment may disappear between visits. Recover to an existing page instead of trapping a stale URL.
$page = min($page, $lastPage);
$items = (clone $query)->orderByRaw($order)->orderBy($idColumn)
->offset(($page - 1) * 5)->limit(5)->get()->map(function (\stdClass $row) use ($kind, $now): array {
$due = $row->due_at === null ? null : CarbonImmutable::parse($row->due_at);
return ['id' => (string) $row->id, 'title' => $row->title ?: 'Acompanhar manifestação', 'reference' => $row->reference,
'status' => $row->status, 'due_at' => $due?->toISOString(), 'overdue' => $due?->lessThan($now) ?? false,
'href' => match ($kind) {
'interests' => '/management/interests/'.$row->id,
'desk' => '/balcao-virtual/pedidos/'.$row->id,
'crm_tasks' => '/management/crm/leads/'.$row->parent_id.'#crm-task-'.$row->id,
}];
})->all();
return ['total' => $total, 'overdue' => $overdue, 'items' => $items, 'page' => $page, 'last_page' => $lastPage,
'previous' => $page > 1 ? route('dashboard', [...$pages, $pageName => $page - 1], absolute: false) : null,
'next' => $page < $lastPage ? route('dashboard', [...$pages, $pageName => $page + 1], absolute: false) : null];
}
}