$pages * @return array|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 $pages * @return array */ 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]; } }