File manager - Edit - /var/www/html/portalHomolog/app/Http/Services/Demandas/DemandaService.php
Back
<?php namespace App\Http\Services\Demandas; use App\Enums\StatusDemanda; use App\Helpers\FileUploadHelper; use App\Http\Services\AtivosTi\AtivosTiService; use App\Models\AtivosTi\Equipamento; use App\Models\Demandas\Demanda; use App\Models\Demandas\DemandaAnexo; use App\Models\Demandas\DemandaDestinatario; use App\Models\Demandas\DemandaSubtarefa; use App\Models\IndicadoresTi\Assunto; use App\Models\IndicadoresTi\Setor; use App\Models\IndicadoresTi\Subassunto; use App\Models\UnidadeOrganizacional; use App\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; class DemandaService { // ------------------------------------------------------------------------- // VISIBILIDADE // ------------------------------------------------------------------------- public function queryBase(User $user, array $filters = []): Builder { $permissoes = $user->permissoes->pluck('permissao'); /** @var Builder $query */ $query = Demanda::query() ->whereIn('modulo', ['geral', 'ti']) ->when(!empty($filters['busca']), fn($q) => $q->where('titulo', 'like', '%' . $filters['busca'] . '%')) ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status'])) ->when(!empty($filters['prioridade']), fn($q) => $q->where('prioridade', $filters['prioridade'])); if ($permissoes->contains('DEMANDAS_SECRETARIA')) { $structureId = $user->structure_id; $unidadeIds = $user->unidades()->pluck('unidades_organizacionais.id'); $userId = $user->id; $query->where(function ($q) use ($structureId, $unidadeIds, $userId) { if ($structureId) { $q->where('demandas.structure_id', $structureId); } $q->orWhereIn('unidade_organizacional_id', $unidadeIds) ->orWhereHas('destinatarios', fn($d) => $d->where(function ($d2) use ($unidadeIds, $userId) { $d2->whereIn('unidade_id', $unidadeIds)->orWhere('responsavel_id', $userId); })); }); } elseif ($permissoes->contains('GERENCIADOR_DEMANDAS')) { $unidadeIds = $user->unidades()->pluck('unidades_organizacionais.id'); $userId = $user->id; $query->where(function ($q) use ($unidadeIds, $userId) { $q->whereIn('unidade_organizacional_id', $unidadeIds) ->orWhereHas('destinatarios', fn($d) => $d->where(function ($d2) use ($unidadeIds, $userId) { $d2->whereIn('unidade_id', $unidadeIds)->orWhere('responsavel_id', $userId); })); }); } else { $query->whereRaw('1 = 0'); } return $query; } // ------------------------------------------------------------------------- // LISTAGEM // ------------------------------------------------------------------------- private function buildQueryMap(User $user, array $filters = []): array { $queries = [ 'unidade' => $this->queryDaUnidade($user, $filters), 'recebidas' => $this->queryRecebidas($user, $filters), 'minhas' => $this->queryMinhas($user, $filters), ]; if ($user->permissoes->pluck('permissao')->contains('DEMANDAS_SECRETARIA') && $user->structure_id) { $queries['diretoria'] = $this->queryDaDiretoria($user, $filters); } return $queries; } public function listarComAbas(User $user, array $filters = [], string $aba = 'unidade'): array { $queries = $this->buildQueryMap($user, $filters); $contagens = array_map(fn($q) => (clone $q)->count(), $queries); $aba = array_key_exists($aba, $queries) ? $aba : 'unidade'; $demandas = $queries[$aba] ->with(['unidade', 'solicitante', 'destinatarios.unidade', 'destinatarios.responsavel', 'tiDetalhe.cliente']) ->orderBy('data_abertura', 'desc') ->paginate(20) ->withQueryString(); return compact('demandas', 'contagens', 'aba'); } public function listarKanban(User $user, array $filters = [], string $aba = 'unidade'): array { $queries = $this->buildQueryMap($user, $filters); $contagens = array_map(fn($q) => (clone $q)->count(), $queries); $aba = array_key_exists($aba, $queries) ? $aba : 'unidade'; $base = $queries[$aba]; $ativos = (clone $base) ->with(['unidade', 'solicitante', 'destinatarios', 'tiDetalhe.cliente']) ->whereIn('status', ['pendente', 'em_andamento']) ->orderBy('data_abertura', 'desc') ->get(); $atendidos = (clone $base)->with(['unidade', 'destinatarios'])->where('status', 'atendido')->latest('data_abertura')->limit(5)->get(); $cancelados = (clone $base)->with(['unidade', 'destinatarios'])->where('status', 'cancelado')->latest('data_abertura')->limit(5)->get(); $totaisFinais = [ 'atendido' => (clone $base)->where('status', 'atendido')->count(), 'cancelado' => (clone $base)->where('status', 'cancelado')->count(), ]; $porStatus = $ativos->groupBy('status'); $porStatus->put('atendido', $atendidos); $porStatus->put('cancelado', $cancelados); return compact('porStatus', 'contagens', 'aba', 'totaisFinais'); } private function queryDaUnidade(User $user, array $filters = []): Builder { $unidadeIds = $user->unidades()->pluck('unidades_organizacionais.id'); return Demanda::query() ->whereIn('modulo', ['geral', 'ti']) ->whereHas('destinatarios', fn($q) => $q->where('tipo', 'unidade')->whereIn('unidade_id', $unidadeIds)) ->when(!empty($filters['busca']), fn($q) => $q->where('titulo', 'like', '%' . $filters['busca'] . '%')) ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status'])) ->when(!empty($filters['prioridade']), fn($q) => $q->where('prioridade', $filters['prioridade'])); } private function queryRecebidas(User $user, array $filters = []): Builder { return Demanda::query() ->whereIn('modulo', ['geral', 'ti']) ->whereHas('destinatarios', fn($q) => $q->where('tipo', 'usuario')->where('responsavel_id', $user->id)) ->when(!empty($filters['busca']), fn($q) => $q->where('titulo', 'like', '%' . $filters['busca'] . '%')) ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status'])) ->when(!empty($filters['prioridade']), fn($q) => $q->where('prioridade', $filters['prioridade'])); } private function queryMinhas(User $user, array $filters = []): Builder { return Demanda::query() ->whereIn('modulo', ['geral', 'ti']) ->where('user_id', $user->id) ->whereNot(fn($q) => $q->where('modulo', 'ti')->where('canal', 'portal')) ->when(!empty($filters['busca']), fn($q) => $q->where('titulo', 'like', '%' . $filters['busca'] . '%')) ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status'])) ->when(!empty($filters['prioridade']), fn($q) => $q->where('prioridade', $filters['prioridade'])); } public function calcularContagens(User $user, array $filters = []): array { return array_map(fn($q) => (clone $q)->count(), $this->buildQueryMap($user, $filters)); } public function listarDiretoria(User $user, array $filters = []): \Illuminate\Support\Collection { return $this->queryDaDiretoria($user, $filters) ->with(['unidade', 'solicitante', 'destinatarios.responsavel', 'tiDetalhe.cliente']) ->orderBy('unidade_organizacional_id') ->orderBy('data_abertura', 'desc') ->get() ->groupBy('unidade_organizacional_id'); } private function queryDaDiretoria(User $user, array $filters = []): Builder { return Demanda::query() ->whereIn('modulo', ['geral', 'ti']) ->where(function ($q) use ($user) { $q->where('demandas.structure_id', $user->structure_id) ->orWhereHas('unidade', fn($u) => $u->where('structure_id', $user->structure_id)); }) ->when(!empty($filters['busca']), fn($q) => $q->where('titulo', 'like', '%' . $filters['busca'] . '%')) ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status'])) ->when(!empty($filters['prioridade']), fn($q) => $q->where('prioridade', $filters['prioridade'])); } public function calcularResumo(User $user): array { $base = $this->queryBase($user); $total = (clone $base)->count(); $porStatus = (clone $base)->select('status', DB::raw('count(*) as total')) ->groupBy('status')->pluck('total', 'status'); $porPrioridade = (clone $base)->select('prioridade', DB::raw('count(*) as total')) ->groupBy('prioridade')->pluck('total', 'prioridade'); return [ 'total' => $total, 'por_status' => $porStatus, 'por_prioridade' => $porPrioridade, 'em_andamento' => $porStatus->get('em_andamento', 0), 'pendente' => $porStatus->get('pendente', 0), 'atendido' => $porStatus->get('atendido', 0), 'cancelado' => $porStatus->get('cancelado', 0), ]; } public function criar(array $dados, User $autor): Demanda { return DB::transaction(function () use ($dados, $autor) { if (empty($dados['structure_id']) && !empty($dados['unidade_organizacional_id'])) { $dados['structure_id'] = UnidadeOrganizacional::find($dados['unidade_organizacional_id'])?->structure_id; } $eTi = !empty($dados['setor_id']); $dados['modulo'] = $eTi ? 'ti' : ($dados['modulo'] ?? 'geral'); if (($dados['solicitante_tipo'] ?? null) === 'usuario' && !empty($dados['solicitante_id'])) { $dados['user_id'] = $dados['solicitante_id']; } elseif (($dados['solicitante_tipo'] ?? null) === 'cliente') { $dados['user_id'] = null; } $demanda = Demanda::create([ 'numero' => Demanda::gerarNumero(), 'titulo' => $dados['titulo'], 'descricao' => $dados['descricao'] ?? null, 'status' => $dados['status'] ?? 'pendente', 'prioridade' => $dados['prioridade'] ?? 'media', 'modulo' => $dados['modulo'], 'canal' => $dados['canal'] ?? null, 'structure_id' => $dados['structure_id'] ?? null, 'user_id' => $dados['user_id'] ?? null, 'unidade_organizacional_id' => $dados['unidade_organizacional_id'] ?? null, 'data_abertura' => $dados['data_abertura'] ?? now(), 'data_fechamento' => $dados['data_fechamento'] ?? null, 'prazo' => $dados['prazo'] ?? null, ]); $demanda->logs()->create([ 'user_id' => $autor->id, 'tipo' => 'abertura', 'conteudo' => __('Demanda criada.'), ]); if ($eTi) { $this->salvarTiDetalhe($demanda, $dados); if (!empty($dados['destinatario_unidade_id'])) { $demanda->destinatarios()->firstOrCreate( ['tipo' => 'unidade', 'unidade_id' => $dados['destinatario_unidade_id']], ['status' => 'pendente'] ); } else { $demanda->destinatarios()->firstOrCreate( ['tipo' => 'usuario', 'responsavel_id' => $autor->id], ['status' => 'em_andamento'] ); } } return $demanda; }); } public function atualizar(Demanda $demanda, array $dados, User $autor): Demanda { return DB::transaction(function () use ($demanda, $dados, $autor) { $statusAnterior = $demanda->status; $demanda->update([ 'titulo' => $dados['titulo'], 'descricao' => $dados['descricao'] ?? $demanda->descricao, 'numero_sei' => $dados['numero_sei'] ?? null, 'url_sei' => $dados['url_sei'] ?? null, 'status' => $dados['status'], 'prioridade' => $dados['prioridade'], 'user_id' => $dados['user_id'] ?? $demanda->user_id, 'unidade_organizacional_id' => $dados['unidade_organizacional_id'] ?? $demanda->unidade_organizacional_id, 'data_abertura' => $dados['data_abertura'] ?? $demanda->data_abertura, 'data_fechamento' => $dados['data_fechamento'] ?? $demanda->data_fechamento, 'prazo' => array_key_exists('prazo', $dados) ? ($dados['prazo'] ?: null) : $demanda->prazo, ]); $demanda->logs()->create([ 'user_id' => $autor->id, 'tipo' => 'atualizacao', 'conteudo' => $statusAnterior !== $dados['status'] ? __('Status alterado para: :s', ['s' => StatusDemanda::tryFrom($dados['status'])?->label() ?? $dados['status']]) : __('Demanda atualizada.'), ]); return $demanda->fresh(); }); } public function moverStatus(Demanda $demanda, string $novoStatus, User $autor): void { $eqService = app(AtivosTiService::class); DB::transaction(function () use ($demanda, $novoStatus, $autor, $eqService) { $terminais = ['atendido', 'cancelado']; $eReabertura = in_array($demanda->status, $terminais) && !in_array($novoStatus, $terminais); $eEncerramento = !in_array($demanda->status, $terminais) && in_array($novoStatus, $terminais); $demanda->update([ 'status' => $novoStatus, 'data_fechamento' => $eEncerramento ? now() : ($eReabertura ? null : $demanda->data_fechamento), ]); $demanda->logs()->create([ 'user_id' => $autor->id, 'tipo' => $eReabertura ? 'reabertura' : 'status', 'conteudo' => $eReabertura ? __('Demanda reaberta por :nome.', ['nome' => $autor->name]) : __('Status movido para: :s', ['s' => StatusDemanda::tryFrom($novoStatus)?->label() ?? $novoStatus]), ]); if ($novoStatus === 'atendido') { $demanda->equipamentos() ->where('status', 'em_manutencao') ->each(fn(Equipamento $eq) => $eqService->reativarSeNecessario($eq)); } }); } public function adicionarDestinatario(Demanda $demanda, string $tipo, string $id, User $autor): void { $dados = ['tipo' => $tipo, 'status' => 'pendente']; if ($tipo === 'unidade') { $dados['unidade_id'] = $id; } else { $dados['responsavel_id'] = $id; } $dest = $demanda->destinatarios()->create($dados); $demanda->logs()->create([ 'user_id' => $autor->id, 'tipo' => 'encaminhamento', 'conteudo' => __('Encaminhado para: :nome', ['nome' => $dest->nome_display]), ]); } public function removerDestinatario(Demanda $demanda, int $destinatarioId, User $autor): void { $dest = $demanda->destinatarios()->findOrFail($destinatarioId); $label = $dest->nome_display; $dest->delete(); $demanda->logs()->create([ 'user_id' => $autor->id, 'tipo' => 'encaminhamento', 'conteudo' => __('Destinatário removido: :nome', ['nome' => $label]), ]); } public function criarSubtarefa(Demanda $demanda, string $titulo, ?string $unidadeId, ?string $responsavelId): void { $demanda->subtarefas()->create([ 'titulo' => $titulo, 'unidade_id' => $unidadeId ?: null, 'responsavel_id' => $responsavelId ?: null, 'ordem' => $demanda->subtarefas()->max('ordem') + 1, ]); } public function toggleSubtarefa(DemandaSubtarefa $subtarefa, User $user): array { $concluindo = !$subtarefa->concluida; $subtarefa->update([ 'concluida' => $concluindo, 'concluida_por' => $concluindo ? $user->id : null, 'concluida_em' => $concluindo ? now() : null, ]); return [ 'concluida' => $subtarefa->concluida, 'conclusor' => $concluindo ? $user->name : null, 'concluida_em' => $concluindo ? $subtarefa->concluida_em->format('d/m/Y H:i') : null, ]; } public function removerSubtarefa(DemandaSubtarefa $subtarefa): void { $subtarefa->delete(); } public function salvarTiDetalhe(Demanda $demanda, array $dados): void { $setor = Setor::find($dados['setor_id'] ?? null); $assunto = Assunto::find($dados['assunto_id'] ?? null); $subassunto = Subassunto::find($dados['subassunto_id'] ?? null); $tiData = [ 'setor_id' => $dados['setor_id'] ?? null, 'setor_nome' => $setor?->nome, 'assunto_id' => $dados['assunto_id'] ?? null, 'assunto_nome' => $assunto?->nome, 'subassunto_id' => $dados['subassunto_id'] ?? null, 'subassunto_nome' => $subassunto?->nome, 'tipo' => $dados['tipo'] ?? null, 'canal' => $dados['canal'] ?? null, 'cliente_id' => ($dados['solicitante_tipo'] ?? null) === 'cliente' && !empty($dados['solicitante_id']) ? $dados['solicitante_id'] : null, ]; // 'local' so e enviado pelo formulario de abertura de chamado TI do portal. // Preserva o valor ja gravado quando a origem (ex: edicao no admin) nao o envia. if (array_key_exists('local', $dados)) { $tiData['local'] = $dados['local']; } $demanda->update(['modulo' => 'ti']); $demanda->tiDetalhe()->updateOrCreate(['demanda_id' => $demanda->id], $tiData); } /** * @param UploadedFile[] $arquivos * @throws ValidationException */ public function salvarAnexos(Demanda $demanda, array $arquivos, User $autor): void { foreach ($arquivos as $arquivo) { $nomeOriginal = FileUploadHelper::sanitizeOriginalFileName($arquivo); $caminho = FileUploadHelper::processSafeGeneralUploadPublicDisk( $arquivo, 'demandas', $this->gerarNomeAnexo($demanda, $nomeOriginal) ); if (!$caminho) { throw ValidationException::withMessages([ 'anexos' => __('Arquivo inválido ou não permitido: :nome', [ 'nome' => $arquivo->getClientOriginalName(), ]), ]); } $demanda->anexos()->create([ 'user_id' => $autor->id, 'nome_original' => $nomeOriginal, 'caminho' => $caminho, 'mime_type' => $arquivo->getMimeType(), 'tamanho' => $arquivo->getSize(), ]); } } private function gerarNomeAnexo(Demanda $demanda, string $nomeOriginal): string { $ext = pathinfo($nomeOriginal, PATHINFO_EXTENSION); $base = Str::slug(pathinfo($nomeOriginal, PATHINFO_FILENAME)); $base = Str::limit($base, 40, ''); return 'dem' . $demanda->numero . '_' . now()->format('Ymd_His') . '_' . $base . '.' . $ext; } public function excluirAnexo(DemandaAnexo $anexo, User $autor): void { DB::transaction(function () use ($anexo, $autor) { $demanda = $anexo->demanda; $nome = $anexo->nome_original; Storage::disk('public')->delete($anexo->caminho); $anexo->delete(); $demanda->logs()->create([ 'user_id' => $autor->id, 'tipo' => 'atualizacao', 'conteudo' => __('Anexo removido: :nome', ['nome' => $nome]), ]); }); } // ------------------------------------------------------------------------- // AUXILIARES // ------------------------------------------------------------------------- public function listarUnidades(): Collection { return UnidadeOrganizacional::orderBy('name')->get(['id', 'name', 'abbreviation']); } public function listarUnidadesDoUsuario(User $user): Collection { $permissoes = $user->permissoes->pluck('permissao'); if ($permissoes->contains('DEMANDAS_SECRETARIA') && $user->structure_id) { return UnidadeOrganizacional::where('structure_id', $user->structure_id) ->orderBy('name') ->get(['id', 'name', 'abbreviation']); } return $user->unidades()->orderBy('name')->get(['unidades_organizacionais.id', 'name', 'abbreviation']); } // ------------------------------------------------------------------------- // ANALYTICS - Comum a todos os perfis // ------------------------------------------------------------------------- /** Itens que exigem ação do usuário: demandas da unidade sem responsável + encaminhadas para ele pendentes */ public function acaoRequerida(User $user): array { $unidadeIds = $user->unidades()->pluck('unidades_organizacionais.id'); $userId = $user->id; $semResponsavel = DemandaDestinatario::where('tipo', 'unidade') ->whereIn('unidade_id', $unidadeIds) ->whereNull('responsavel_id') ->whereHas('demanda', fn($q) => $q->whereIn('status', ['pendente', 'em_andamento'])) ->count(); $minhasPendentes = DemandaDestinatario::where('tipo', 'usuario') ->where('responsavel_id', $userId) ->where('status', 'pendente') ->whereHas('demanda', fn($q) => $q->whereIn('status', ['pendente', 'em_andamento'])) ->count(); return [ 'sem_responsavel' => $semResponsavel, 'minhas_pendentes' => $minhasPendentes, 'total' => $semResponsavel + $minhasPendentes, ]; } /** Contagem das demandas ABERTAS (pendente + em_andamento) agrupadas por prioridade */ public function abertasPorPrioridade(User $user): \Illuminate\Support\Collection { return $this->queryBase($user) ->whereIn('status', ['pendente', 'em_andamento']) ->selectRaw('prioridade, COUNT(*) as total') ->groupBy('prioridade') ->pluck('total', 'prioridade'); } /** Demandas com prazo: vencidas (passou) e próximas (próximos 7 dias) */ public function proximasPrazo(User $user): array { $base = $this->queryBase($user) ->whereNotNull('prazo') ->whereNotIn('status', ['atendido', 'cancelado']); $vencidas = (clone $base) ->where('prazo', '<', now()->toDateString()) ->with(['unidade']) ->orderBy('prazo') ->limit(5) ->get(); $proximas = (clone $base) ->whereBetween('prazo', [now()->toDateString(), now()->addDays(7)->toDateString()]) ->with(['unidade']) ->orderBy('prazo') ->limit(5) ->get(); return compact('vencidas', 'proximas'); } // ------------------------------------------------------------------------- // ANALYTICS - Apenas Secretário (DEMANDAS_SECRETARIA) // ------------------------------------------------------------------------- /** Volume de demandas abertas por mês nos últimos 6 meses */ public function tendenciaMensal(User $user): array { $inicio = now()->startOfMonth()->subMonths(5); $rows = $this->queryBase($user) ->where('data_abertura', '>=', $inicio) ->selectRaw("DATE_FORMAT(data_abertura, '%Y-%m') as mes, COUNT(*) as total") ->groupBy('mes') ->orderBy('mes') ->pluck('total', 'mes'); $meses = []; for ($i = 5; $i >= 0; $i--) { $key = now()->subMonths($i)->format('Y-m'); $meses[$key] = (int) ($rows[$key] ?? 0); } $mesAtual = now()->format('Y-m'); $mesAnterior = now()->subMonth()->format('Y-m'); $qtdAtual = $meses[$mesAtual] ?? 0; $qtdAnterior = $meses[$mesAnterior] ?? 0; $variacao = $qtdAnterior > 0 ? round((($qtdAtual - $qtdAnterior) / $qtdAnterior) * 100) : null; return ['meses' => $meses, 'qtd_atual' => $qtdAtual, 'variacao' => $variacao]; } /** Top 5 responsáveis com mais demandas (inclui abertas e fechadas) */ public function porResponsavel(User $user): \Illuminate\Support\Collection { return $this->queryBase($user) ->join('demanda_destinatarios as dd', 'dd.demanda_id', '=', 'demandas.id') ->join('users as u', 'u.id', '=', 'dd.responsavel_id') ->whereNotNull('dd.responsavel_id') ->selectRaw('dd.responsavel_id, u.firstName, u.lastName, COUNT(DISTINCT demandas.id) as total') ->groupBy('dd.responsavel_id', 'u.firstName', 'u.lastName') ->orderByDesc('total') ->limit(5) ->get() ->map(fn($r) => [ 'nome' => trim($r->firstName . ' ' . $r->lastName), 'total' => (int) $r->total, ]); } /** Abertas por unidade organizacional (top 8) - para visão da secretaria */ public function porUnidade(User $user): \Illuminate\Support\Collection { $resultados = $this->queryBase($user) ->whereIn('status', ['pendente', 'em_andamento']) ->whereNotNull('unidade_organizacional_id') ->selectRaw('unidade_organizacional_id, COUNT(*) as total') ->groupBy('unidade_organizacional_id') ->orderByDesc(DB::raw('COUNT(*)')) ->limit(8) ->get(); $unidades = UnidadeOrganizacional::whereIn('id', $resultados->pluck('unidade_organizacional_id')) ->get(['id', 'name', 'abbreviation']) ->keyBy('id'); return $resultados->map(fn($r) => [ 'nome' => $unidades[$r->unidade_organizacional_id]?->abbreviation ?? $unidades[$r->unidade_organizacional_id]?->name ?? '-', 'total' => (int) $r->total, ]); } /** Distribuição pelo canal de entrada (portal, telefone, email…) */ public function porCanal(User $user): \Illuminate\Support\Collection { return $this->queryBase($user) ->whereNotNull('canal') ->selectRaw('canal, COUNT(*) as total') ->groupBy('canal') ->orderByDesc(DB::raw('COUNT(*)')) ->get() ->map(fn($r) => [ 'canal' => Demanda::CANAL[$r->canal] ?? $r->canal, 'chave' => $r->canal, 'total' => (int) $r->total, ]); } /** Tempo médio de resolução em dias por prioridade */ public function tempoMedioAtendimento(User $user): array { $base = $this->queryBase($user) ->where('status', 'atendido') ->whereNotNull('data_fechamento'); $rows = (clone $base) ->selectRaw('prioridade, AVG(DATEDIFF(data_fechamento, data_abertura)) as media') ->groupBy('prioridade') ->pluck('media', 'prioridade'); $geral = (clone $base) ->selectRaw('AVG(DATEDIFF(data_fechamento, data_abertura)) as media') ->value('media'); return [ 'baixa' => $rows->has('baixa') ? round((float) $rows['baixa'], 1) : null, 'media' => $rows->has('media') ? round((float) $rows['media'], 1) : null, 'alta' => $rows->has('alta') ? round((float) $rows['alta'], 1) : null, 'critica'=> $rows->has('critica') ? round((float) $rows['critica'],1) : null, 'geral' => $geral !== null ? round((float) $geral, 1) : null, ]; } public function buscarResponsaveis(string $termo): \Illuminate\Support\Collection { return User::where(function ($q) use ($termo) { $q->where('firstName', 'like', '%' . $termo . '%') ->orWhere('lastName', 'like', '%' . $termo . '%') ->orWhere('matriculation', 'like', '%' . $termo . '%'); }) ->orderBy('firstName') ->limit(15) ->get(['id', 'firstName', 'lastName', 'matriculation', 'email']) ->map(fn($u) => [ 'id' => $u->id, 'name' => ($u->matriculation ? $u->matriculation . ' - ' : '') . $u->name, 'email' => $u->email ]); } // ------------------------------------------------------------------------- // RELATÓRIO // ------------------------------------------------------------------------- public function queryRelatorio(User $user, array $filters = []): Builder { return $this->queryBase($user, array_intersect_key($filters, array_flip(['status', 'prioridade']))) ->when(!empty($filters['data_inicio']), fn($q) => $q->whereDate('data_abertura', '>=', $filters['data_inicio'])) ->when(!empty($filters['data_fim']), fn($q) => $q->whereDate('data_abertura', '<=', $filters['data_fim'])) ->when(!empty($filters['unidade_id']), fn($q) => $q->where('unidade_organizacional_id', $filters['unidade_id'])) ->when(!empty($filters['canal']), fn($q) => $q->where('canal', $filters['canal'])); } public function relatorioResumido(User $user, array $filters): \Illuminate\Contracts\Pagination\LengthAwarePaginator { return $this->queryRelatorio($user, $filters) ->with(['unidade', 'destinatarios.responsavel', 'destinatarios.unidade']) ->orderBy('data_abertura', 'desc') ->paginate(30) ->withQueryString(); } public function relatorioResumidoAll(User $user, array $filters): Collection { return $this->queryRelatorio($user, $filters) ->with(['unidade', 'destinatarios.responsavel', 'destinatarios.unidade']) ->orderBy('data_abertura', 'desc') ->get(); } public function relatorioDetalhadoAll(User $user, array $filters): Collection { return $this->queryRelatorio($user, $filters) ->where('modulo', 'ti') ->with(['unidade', 'destinatarios.responsavel', 'tiDetalhe']) ->orderBy('data_abertura', 'desc') ->get(); } /** * Dados agregados do relatório detalhado de TI: total por status e quebras * por setor, assunto e subassunto. */ public function montarDadosRelatorioDetalhado(User $user, array $filters): array { $demandas = $this->queryRelatorio($user, $filters) ->where('modulo', 'ti') ->with(['tiDetalhe']) ->orderBy('data_abertura') ->get(); $total = $demandas->count(); $porSetor = $demandas->groupBy(fn($d) => $d->tiDetalhe?->setor_nome ?: '-') ->map(fn($grupo, $setor) => [ 'nome' => $setor, 'total' => $grupo->count(), 'atendidos' => $grupo->where('status', 'atendido')->count(), 'pendentes' => $grupo->where('status', 'pendente')->count(), 'em_andamento' => $grupo->where('status', 'em_andamento')->count(), ]) ->sortByDesc('total') ->values(); $porAssunto = $demandas->groupBy(fn($d) => $d->tiDetalhe?->assunto_nome ?: '-') ->map(fn($grupo, $assunto) => [ 'nome' => $assunto, 'setor' => $grupo->first()->tiDetalhe?->setor_nome ?: '-', 'total' => $grupo->count(), 'atendidos' => $grupo->where('status', 'atendido')->count(), ]) ->sortByDesc('total') ->values(); $porSubassunto = $demandas->groupBy(fn($d) => ($d->tiDetalhe?->assunto_nome ?: '-') . ' › ' . ($d->tiDetalhe?->subassunto_nome ?: '-')) ->map(fn($grupo, $chave) => [ 'chave' => $chave, 'total' => $grupo->count(), 'atendidos' => $grupo->where('status', 'atendido')->count(), ]) ->sortByDesc('total') ->values(); $porStatus = $demandas->groupBy('status')->map->count(); return compact( 'total', 'filters', 'porSetor', 'porAssunto', 'porSubassunto', 'porStatus' ); } public function relatorioMatricial(User $user, array $filters): array { $inicio = !empty($filters['data_inicio']) ? \Carbon\Carbon::parse($filters['data_inicio'])->startOfMonth() : now()->startOfYear(); $fim = !empty($filters['data_fim']) ? \Carbon\Carbon::parse($filters['data_fim'])->endOfMonth() : now()->endOfMonth(); if ($inicio->diffInMonths($fim) > 23) { $inicio = $fim->copy()->subMonths(23)->startOfMonth(); } $nomesMeses = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez']; $meses = []; $cursor = $inicio->copy(); while ($cursor <= $fim) { $meses[$cursor->format('Y-m')] = $nomesMeses[(int)$cursor->format('n') - 1] . '/' . $cursor->format('y'); $cursor->addMonth(); } $dados = $this->queryRelatorio($user, $filters) ->whereBetween('data_abertura', [$inicio->toDateTimeString(), $fim->copy()->endOfMonth()->toDateTimeString()]) ->whereNotNull('unidade_organizacional_id') ->selectRaw("unidade_organizacional_id, DATE_FORMAT(data_abertura, '%Y-%m') as mes, COUNT(*) as total") ->groupBy('unidade_organizacional_id', DB::raw("DATE_FORMAT(data_abertura, '%Y-%m')")) ->get(); $unidadeIds = $dados->pluck('unidade_organizacional_id')->unique()->filter()->values(); $unidades = UnidadeOrganizacional::whereIn('id', $unidadeIds)->orderBy('name')->get(['id', 'name', 'abbreviation']); $matrix = []; $totaisPorMes = array_fill_keys(array_keys($meses), 0); foreach ($unidades as $unidade) { $row = ['nome' => $unidade->abbreviation ?? $unidade->name, 'meses' => [], 'total' => 0]; foreach (array_keys($meses) as $mesKey) { $val = (int) ($dados->where('unidade_organizacional_id', $unidade->id)->where('mes', $mesKey)->first()?->total ?? 0); $row['meses'][$mesKey] = $val; $row['total'] += $val; $totaisPorMes[$mesKey] += $val; } $matrix[] = $row; } return [ 'meses' => $meses, 'matrix' => $matrix, 'totaisPorMes' => $totaisPorMes, 'totalGeral' => array_sum($totaisPorMes), 'periodo' => ['inicio' => $inicio, 'fim' => $fim], 'filters' => $filters, ]; } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings