init commit
All checks were successful
build-api-image / build (push) Successful in 51s

This commit is contained in:
Antonio Lopes dos Santos
2026-08-04 16:52:02 -03:00
commit afec302c3e
11594 changed files with 1636728 additions and 0 deletions

View File

@@ -0,0 +1,630 @@
<?php
namespace App\Services\Mobile\ConectaCidadao\ExameImagem;
use App\Enums\ExameImagem\Fechamento\Status;
use App\Enums\ExameImagem\Parametrizacao\ControleJornada\Tipo as ControleJornadaTipo;
use App\Enums\ExameImagem\Tipo;
use App\Enums\Parametrizacao\Exame\Tipo as ExameTipo;
use App\Models\Ambulatorio\AtendimentoSoapSolicitacaoExameItem;
use App\Models\Cadastro\Feriado\Feriado;
use App\Models\ExameImagem\Agendamento\Agendamento;
use App\Models\ExameImagem\Agendamento\AgendamentoExame;
use App\Services\Service;
use Carbon\Carbon;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
class AgendamentoImagemService extends Service
{
public static function elegibilidade()
{
$habilitado = DB::table('exame_imagem_parametrizacao_integracao_aplicativo')
->value('habilita_agendamento_conectasus_cidadao');
return ['habilitado' => (bool) $habilitado];
}
public static function prestadores()
{
self::validarParametrizacaoHabilitada();
$agora = now();
return DB::table('exame_imagem_parametrizacao_unidade as parametrizacao')
->leftJoin('cadastro_unidade as unidade', 'unidade.id', 'parametrizacao.unidade_id')
->leftJoin('cadastro_infraestrutura as infraestrutura', 'infraestrutura.id', 'parametrizacao.infraestrutura_id')
->where(function ($q) {
$q->where('parametrizacao.executante', true)->orWhere('parametrizacao.prestador', true);
})
->whereNull('parametrizacao.deleted_at')
->whereExists(function ($q) use ($agora) {
$q->select(DB::raw(1))
->from('exame_imagem_jornada as jornada')
->whereColumn('jornada.infraestrutura_id', 'parametrizacao.infraestrutura_id')
->whereNotNull('jornada.vagas_app')
->where('jornada.vagas_app', '>', 0)
->where(function ($q2) use ($agora) {
$q2->whereDate('jornada.data', '>', $agora->toDateString())
->orWhere(function ($q3) use ($agora) {
$q3->whereDate('jornada.data', $agora->toDateString())
->whereTime('jornada.hora_fim', '>', $agora->format('H:i:s'));
});
})
->whereNull('jornada.deleted_at');
})
->get([
'parametrizacao.unidade_id',
'unidade.nome_fantasia',
'parametrizacao.infraestrutura_id',
'infraestrutura.nome as infraestrutura_nome',
])
->map(
fn($param) =>
[
'unidade_id' => $param->unidade_id,
'nome_fantasia' => $param->nome_fantasia,
'infraestrutura_id' => $param->infraestrutura_id,
'infraestrutura_nome' => $param->infraestrutura_nome,
]
)
->unique(fn($item) => $item['unidade_id'] . '-' . $item['infraestrutura_id'])
->sortBy('nome_fantasia')
->values();
}
public static function exames(): array
{
self::validarParametrizacaoHabilitada();
$pacienteId = auth('mobile')->user()->id;
$statusRealizados = [
Status::REALIZADO,
Status::RESULTADO,
];
$exameIdsBloqueadosPorValidade = DB::table('parametrizacao_exame as exame')
->join('exame_imagem_agendamento_exame as ae', 'ae.exame_id', 'exame.id')
->join('exame_imagem_agendamento as ag', 'ag.id', 'ae.agendamento_id')
->where('ae.paciente_id', $pacienteId)
->whereIn('ae.status_id', $statusRealizados)
->whereNotNull('exame.validade')
->where('exame.validade', '>', 0)
->where(fn($q) => $q->where('exame.permite_repeticao', false)->orWhereNull('exame.permite_repeticao'))
->whereRaw('ag.data >= DATE_SUB(CURDATE(), INTERVAL exame.validade DAY)')
->pluck('exame.id')
->unique()
->toArray();
$rows = DB::table('ambulatorio_atendimento_soap_solicitacao_exame_item')
->join('ambulatorio_atendimento_soap_solicitacao_exame', 'ambulatorio_atendimento_soap_solicitacao_exame.id', '=', 'ambulatorio_atendimento_soap_solicitacao_exame_item.solicitacao_exame_id')
->join('parametrizacao_exame', 'parametrizacao_exame.id', '=', 'ambulatorio_atendimento_soap_solicitacao_exame_item.exame_id')
->join('ambulatorio_atendimento_soap', 'ambulatorio_atendimento_soap.id', '=', 'ambulatorio_atendimento_soap_solicitacao_exame.soap_id')
->join('ambulatorio_atendimento', 'ambulatorio_atendimento.id', '=', 'ambulatorio_atendimento_soap.atendimento_id')
->join('painel_senha_fila', 'painel_senha_fila.id', '=', 'ambulatorio_atendimento.fila_id')
->join('cadastro_lotacao', 'cadastro_lotacao.id', '=', 'ambulatorio_atendimento_soap_solicitacao_exame.created_lotacao_id')
->join('cadastro_usuario as profissional', 'profissional.id', '=', 'cadastro_lotacao.usuario_id')
->join('cadastro_cidadao', 'cadastro_cidadao.id', '=', 'profissional.cidadao_id')
->leftJoin('cadastro_grupo', 'cadastro_grupo.id', 'parametrizacao_exame.grupo_tipo_id')
->where('parametrizacao_exame.tipo_id', ExameTipo::IMAGEM)
->whereNull('parametrizacao_exame.regulacao_tipo_id')
->whereNull('parametrizacao_exame.deleted_at')
->where('ambulatorio_atendimento_soap_solicitacao_exame_item.descartado_exame_imagem', '!=', true)
->where('ambulatorio_atendimento_soap_solicitacao_exame_item.agendado_exame_imagem', '!=', true)
->whereNotIn('parametrizacao_exame.id', $exameIdsBloqueadosPorValidade)
->where('painel_senha_fila.paciente_id', $pacienteId)
->where('ambulatorio_atendimento_soap.is_rascunho', false)
->orderBy('ambulatorio_atendimento_soap_solicitacao_exame.id')
->orderBy('parametrizacao_exame.nome')
->get(
[
'ambulatorio_atendimento_soap_solicitacao_exame.id as solicitacao_id',
'ambulatorio_atendimento_soap_solicitacao_exame.created_at as data_solicitacao',
'ambulatorio_atendimento_soap_solicitacao_exame_item.id as exame_item_id',
'parametrizacao_exame.id',
'parametrizacao_exame.codigo',
'parametrizacao_exame.nome',
'parametrizacao_exame.observacao',
'parametrizacao_exame.sexo_id',
'cadastro_grupo.id as grupo_id',
'cadastro_cidadao.nome as profissional_nome',
'profissional.id as profissional_id',
]
);
return $rows
->groupBy('solicitacao_id')
->map(fn($group) => [
'solicitacao_id' => $group->first()->solicitacao_id,
'data_solicitacao' => Carbon::parse($group->first()->data_solicitacao)->format('Y-m-d'),
'profissional_id' => $group->first()->profissional_id,
'profissional_nome' => $group->first()->profissional_nome,
'exames' => $group->map(fn($exame) => [
'exame_item_id' => $exame->exame_item_id,
'id' => $exame->id,
'codigo' => $exame->codigo,
'nome' => $exame->nome,
'observacao' => $exame->observacao,
'sexo_id' => $exame->sexo_id,
'grupo_id' => $exame->grupo_id,
])->values()->toArray(),
])
->values()
->toArray();
}
public static function jornadas(): array
{
self::validarParametrizacaoHabilitada();
request()->validate(
[
'infraestrutura_id' => 'required|integer|exists:cadastro_infraestrutura,id',
'exame_id' => 'required|integer|exists:parametrizacao_exame,id',
]
);
$infraestruturaId = (int) request()->infraestrutura_id;
$exameId = (int) request()->exame_id;
$grupoId = DB::table('parametrizacao_exame')
->where('id', $exameId)
->value('grupo_tipo_id');
$hoje = Carbon::today();
$fim = Carbon::today()->addMonths(3);
$agora = now();
$jornadasRaw = DB::table('exame_imagem_jornada as jornada')
->where('jornada.infraestrutura_id', $infraestruturaId)
->when($grupoId, fn($q) => $q->where('jornada.exame_id', $grupoId))
->whereNotNull('jornada.vagas_app')
->where('jornada.vagas_app', '>', 0)
->where(function ($q) use ($agora) {
$q->whereDate('jornada.data', '>', $agora->toDateString())
->orWhere(function ($q2) use ($agora) {
$q2->whereDate('jornada.data', $agora->toDateString())
->whereTime('jornada.hora_fim', '>', $agora->format('H:i:s'));
});
})
->whereDate('jornada.data', '<=', $fim->toDateString())
->whereNull('jornada.deleted_at')
->orderBy('jornada.data')
->orderBy('jornada.hora_inicial')
->get(
[
'jornada.id',
'jornada.data',
'jornada.hora_inicial',
'jornada.hora_fim',
'jornada.vagas_app',
'jornada.exame_id as grupo_id',
'jornada.controle_jornada_id',
'jornada.tempo_atendimento',
]
);
$jornadaIds = $jornadasRaw->pluck('id')->toArray();
$statusExcluidos = [
Status::CANCELADO,
Status::FALTOU,
Status::FALTA_MEDICA,
];
$consumoPorJornada = DB::table('exame_imagem_agendamento as ag')
->join('exame_imagem_agendamento_exame as ag_exame', 'ag_exame.agendamento_id', '=', 'ag.id')
->whereIn('ag.jornada_id', $jornadaIds)
->where('ag.app_conectasus_cidadao', true)
->whereNotIn('ag_exame.status_id', $statusExcluidos)
->groupBy('ag.jornada_id')
->select(
'ag.jornada_id',
DB::raw('COUNT(DISTINCT ag.id) as visitas'),
DB::raw('COUNT(ag_exame.id) as exames')
)
->get()
->keyBy('jornada_id');
$horariosOcupadosPorJornada = DB::table('exame_imagem_agendamento as ag')
->join('exame_imagem_agendamento_exame as ag_exame', 'ag_exame.agendamento_id', '=', 'ag.id')
->whereIn('ag.jornada_id', $jornadaIds)
->whereNotIn('ag_exame.status_id', $statusExcluidos)
->select('ag.jornada_id', DB::raw("DATE_FORMAT(ag.hora_exame, '%H:%i') as horario"))
->distinct()
->get()
->groupBy('jornada_id')
->map(fn($rows) => $rows->pluck('horario')->toArray());
$jornadas = $jornadasRaw->map(function ($jornada) use ($consumoPorJornada, $horariosOcupadosPorJornada) {
$consumo = $consumoPorJornada[$jornada->id] ?? null;
$consumido = $jornada->controle_jornada_id == ControleJornadaTipo::EXAME
? ($consumo->exames ?? 0)
: ($consumo->visitas ?? 0);
$disponivel = max(0, $jornada->vagas_app - $consumido);
return [
'id' => $jornada->id,
'data' => Carbon::parse($jornada->data)->format('Y-m-d'),
'hora_inicial' => $jornada->hora_inicial,
'hora_fim' => $jornada->hora_fim,
'tempo_atendimento' => $jornada->tempo_atendimento,
'vagas_app' => $jornada->vagas_app,
'consumido' => $consumido,
'disponivel' => $disponivel,
'controle_jornada_id' => $jornada->controle_jornada_id,
'horarios' => self::gerarHorariosDisponiveis(
$jornada->data,
$jornada->hora_inicial,
$jornada->hora_fim,
$jornada->tempo_atendimento,
$horariosOcupadosPorJornada[$jornada->id] ?? []
),
];
})->values()->toArray();
$feriados = Feriado::whereDate('data', '>=', $hoje->toDateString())
->whereDate('data', '<=', $fim->toDateString())
->whereNull('deleted_at')
->orderBy('data')
->get(['data', 'observacao'])
->map(
fn($f) =>
[
'data' => Carbon::parse($f->data)->format('Y-m-d'),
'observacao' => $f->observacao,
]
)
->values()
->toArray();
return [
'jornadas' => $jornadas,
'feriados' => $feriados,
'cota' => self::computarCotaApp($infraestruturaId),
];
}
private static function gerarHorariosDisponiveis(
string $data,
string $horaInicial,
string $horaFim,
?int $tempoAtendimento,
array $horariosOcupados
): array {
if (!$tempoAtendimento || $tempoAtendimento <= 0) {
return [];
}
$dataStr = Carbon::parse($data)->format('Y-m-d');
$horarioAtual = Carbon::parse($dataStr . ' ' . $horaInicial);
$fim = Carbon::parse($dataStr . ' ' . $horaFim);
$horarios = [];
while ($horarioAtual->lt($fim)) {
if ($horarioAtual->isPast()) {
$horarioAtual->addMinutes($tempoAtendimento);
continue;
}
$horarioStr = $horarioAtual->format('H:i');
if (!in_array($horarioStr, $horariosOcupados)) {
$horarios[] = [
'horario' => $horarioStr,
'data_hora' => $dataStr . 'T' . $horarioStr . ':00',
];
}
$horarioAtual->addMinutes($tempoAtendimento);
}
return $horarios;
}
public static function agendar(): array
{
self::validarParametrizacaoHabilitada();
request()->validate(
[
'jornada_id' => 'required|integer|exists:exame_imagem_jornada,id',
'infraestrutura_id' => 'required|integer|exists:cadastro_infraestrutura,id',
'horario' => 'required|date_format:H:i',
'exames' => 'required|array|min:1',
'exames.*.exame_item_id' => 'required|integer|exists:ambulatorio_atendimento_soap_solicitacao_exame_item,id',
'exames.*.exame_id' => 'required|integer|exists:parametrizacao_exame,id',
'exames.*.profissional_id' => 'required|integer|exists:cadastro_usuario,id',
'observacao' => 'nullable|string',
'gestante' => 'nullable|boolean',
'nome_responsavel' => 'nullable|string',
]
);
$paciente = auth('mobile')->user();
$jornada = DB::table('exame_imagem_jornada')->where('id', request()->jornada_id)->first();
if (!$jornada) {
abort(Response::HTTP_NOT_FOUND, 'Jornada não encontrada.');
}
$dataJornada = Carbon::parse($jornada->data)->format('Y-m-d');
$horaInicialJornada = Carbon::parse($dataJornada . ' ' . $jornada->hora_inicial);
$horaFimJornada = Carbon::parse($dataJornada . ' ' . $jornada->hora_fim);
$dataHoraExame = Carbon::parse($dataJornada . ' ' . request()->horario);
if ($dataHoraExame->isPast()) {
abort(Response::HTTP_UNPROCESSABLE_ENTITY, 'Não é possível agendar em horário retroativo.');
}
if ($dataHoraExame->lt($horaInicialJornada) || $dataHoraExame->gte($horaFimJornada)) {
abort(Response::HTTP_UNPROCESSABLE_ENTITY, 'O horário informado está fora do intervalo da jornada.');
}
self::validarHorarioDisponivel($jornada, request()->horario);
$exameIds = collect(request()->exames)->pluck('exame_id')->toArray();
$examesAtivos = DB::table('exame_imagem_agendamento_exame as ae')
->leftJoin('parametrizacao_exame as exame', 'exame.id', 'ae.exame_id')
->where('ae.paciente_id', $paciente->id)
->whereIn('ae.exame_id', $exameIds)
->where('ae.status_id', Status::AGENDADO)
->get(['ae.exame_id', 'exame.nome as exame_nome']);
if ($examesAtivos->isNotEmpty()) {
$nomes = $examesAtivos->map(fn($ae) => $ae->exame_nome ?? "Exame #{$ae->exame_id}")->unique()->values();
abort(response()->json([
'message' => 'Um ou mais exames do pedido já possuem agendamento ativo.',
'exames' => $nomes,
], Response::HTTP_UNPROCESSABLE_ENTITY));
}
self::verificarVagasApp($jornada, count(request()->exames));
self::verificarCotaApp((int) request()->infraestrutura_id);
$solicitanteInfraId = DB::table('cadastro_infraestrutura')
->where('unidade_id', $paciente->unidade_id)
->where('principal', true)
->value('id');
$solicitanteUnidadeId = $paciente->unidade_id;
$ultimoNumero = DB::table('exame_imagem_agendamento')
->orderByDesc('agendamento_numero')
->value('agendamento_numero');
$agendamentoNumero = $ultimoNumero ? $ultimoNumero + 1 : 1;
$agendamento = new Agendamento();
$agendamento->agendamento_numero = $agendamentoNumero;
$agendamento->paciente_id = $paciente->id;
$agendamento->jornada_id = $jornada->id;
$agendamento->medico_id = request()->exames[0]['profissional_id'];
$agendamento->profissional_id = $jornada->usuario_id;
$agendamento->data = $jornada->data;
$agendamento->exame_id = $jornada->exame_id;
$agendamento->hora_exame = request()->horario;
$agendamento->unidade_id = $jornada->unidade_id;
$agendamento->infraestrutura_id = $jornada->infraestrutura_id;
$agendamento->unidade_solicitante_id = $solicitanteUnidadeId;
$agendamento->infraestrutura_solicitante_id = $solicitanteInfraId;
$agendamento->observacao = request()->observacao;
$agendamento->gestante = request()->gestante ?? false;
$agendamento->urgente = false;
$agendamento->nome_responsavel = request()->nome_responsavel;
$agendamento->retroativo = false;
$agendamento->app_conectasus_cidadao = true;
$agendamento->save();
foreach (request()->exames as $exameData) {
$agendamentoExame = new AgendamentoExame();
$agendamentoExame->agendamento_id = $agendamento->id;
$agendamentoExame->paciente_id = $paciente->id;
$agendamentoExame->exame_id = $exameData['exame_id'];
$agendamentoExame->status_id = Status::AGENDADO;
$agendamentoExame->soap_solicitacao_exame_item_id = $exameData['exame_item_id'];
$agendamentoExame->save();
AtendimentoSoapSolicitacaoExameItem::findOrFail($exameData['exame_item_id'])
->update(['agendado_exame_imagem' => true]);
}
return [
'agendamento_id' => $agendamento->id,
'agendamento_numero' => $agendamento->agendamento_numero,
];
}
private static function validarHorarioDisponivel(object $jornada, string $horario): void
{
$statusExcluidos = [
Status::CANCELADO,
Status::FALTOU,
Status::FALTA_MEDICA,
];
$ocupado = DB::table('exame_imagem_agendamento as ag')
->join('exame_imagem_agendamento_exame as ag_exame', 'ag_exame.agendamento_id', '=', 'ag.id')
->where('ag.jornada_id', $jornada->id)
->whereTime('ag.hora_exame', $horario)
->whereNotIn('ag_exame.status_id', $statusExcluidos)
->exists();
if ($ocupado) {
abort(Response::HTTP_UNPROCESSABLE_ENTITY, 'Este horário não está mais disponível. Selecione outro horário.');
}
}
private static function verificarVagasApp(object $jornada, int $quantidadeExames): void
{
$statusExcluidos = [
Status::CANCELADO,
Status::FALTOU,
Status::FALTA_MEDICA,
];
$query = DB::table('exame_imagem_agendamento as ag')
->join('exame_imagem_agendamento_exame as ag_exame', 'ag_exame.agendamento_id', '=', 'ag.id')
->where('ag.jornada_id', $jornada->id)
->where('ag.app_conectasus_cidadao', true)
->whereNotIn('ag_exame.status_id', $statusExcluidos);
if ($jornada->controle_jornada_id == ControleJornadaTipo::EXAME) {
$consumido = $query->count('ag_exame.id');
$necessario = $quantidadeExames;
} else {
$consumido = $query->distinct()->count('ag.id');
$necessario = 1;
}
if ($consumido + $necessario > $jornada->vagas_app) {
abort(Response::HTTP_UNPROCESSABLE_ENTITY, 'Não há vagas disponíveis para o aplicativo nesta jornada.');
}
}
private static function verificarCotaApp(int $infraestruturaId): void
{
$cota = self::computarCotaApp($infraestruturaId);
if ($cota && $cota['bloqueado']) {
abort(Response::HTTP_UNPROCESSABLE_ENTITY, 'O limite de agendamentos pelo aplicativo foi atingido para este período.');
}
}
private static function computarCotaApp(int $infraestruturaId): ?array
{
$appConfig = DB::table('exame_imagem_parametrizacao_app_conectasus_cidadao_prestador')
->where('infraestrutura_id', $infraestruturaId)
->whereNull('deleted_at')
->first();
if (!$appConfig) {
return null;
}
$hoje = now()->toDateString();
$statusExcluidos = [Status::CANCELADO, Status::FALTOU, Status::FALTA_MEDICA];
$cotaQuantidade = null;
$cotaValor = null;
if ($appConfig->percentual_quantitativo) {
$prestadorQtd = DB::table('exame_imagem_parametrizacao_cota_prestador')
->where('infraestrutura_id', $infraestruturaId)
->where('cota_tipo_id', Tipo::QUANTIDADE)
->where('inicio', '<=', $hoje)
->where('fim', '>=', $hoje)
->whereNull('deleted_at')
->first();
if ($prestadorQtd) {
$limite = (int) floor($prestadorQtd->cota_unidade * $appConfig->percentual_quantitativo / 100);
$consumido = DB::table('exame_imagem_agendamento as ag')
->join('exame_imagem_agendamento_exame as ag_exame', 'ag_exame.agendamento_id', '=', 'ag.id')
->where('ag.infraestrutura_id', $infraestruturaId)
->where('ag.app_conectasus_cidadao', true)
->whereDate('ag.data', '>=', $prestadorQtd->inicio)
->whereDate('ag.data', '<=', $prestadorQtd->fim)
->whereNotIn('ag_exame.status_id', $statusExcluidos)
->distinct()
->count('ag.id');
$cotaQuantidade = [
'percentual_limite' => (float) $appConfig->percentual_quantitativo,
'limite' => $limite,
'consumido' => $consumido,
'percentual_consumido' => $limite > 0 ? round($consumido / $limite * 100, 2) : 0,
'bloqueado' => $consumido >= $limite,
'periodo_inicio' => $prestadorQtd->inicio,
'periodo_fim' => $prestadorQtd->fim,
];
}
}
if ($appConfig->percentual_financeiro) {
$prestadorValor = DB::table('exame_imagem_parametrizacao_cota_prestador')
->where('infraestrutura_id', $infraestruturaId)
->where('cota_tipo_id', Tipo::VALOR)
->where('inicio', '<=', $hoje)
->where('fim', '>=', $hoje)
->whereNull('deleted_at')
->first();
if ($prestadorValor) {
$limite = round($prestadorValor->cota_valor * $appConfig->percentual_financeiro / 100, 2);
$consumido = DB::table('exame_imagem_agendamento as ag')
->join('exame_imagem_agendamento_exame as ag_exame', 'ag_exame.agendamento_id', '=', 'ag.id')
->join('parametrizacao_exame as exame', 'exame.id', '=', 'ag_exame.exame_id')
->where('ag.infraestrutura_id', $infraestruturaId)
->where('ag.app_conectasus_cidadao', true)
->whereDate('ag.data', '>=', $prestadorValor->inicio)
->whereDate('ag.data', '<=', $prestadorValor->fim)
->whereNotIn('ag_exame.status_id', $statusExcluidos)
->sum('exame.valor');
$consumido = round((float) $consumido, 2);
$cotaValor = [
'percentual_limite' => (float) $appConfig->percentual_financeiro,
'limite' => $limite,
'consumido' => $consumido,
'percentual_consumido' => $limite > 0 ? round($consumido / $limite * 100, 2) : 0,
'bloqueado' => $consumido >= $limite,
'periodo_inicio' => $prestadorValor->inicio,
'periodo_fim' => $prestadorValor->fim,
];
}
}
if (!$cotaQuantidade && !$cotaValor) {
return null;
}
$bloqueado = ($cotaQuantidade['bloqueado'] ?? false) || ($cotaValor['bloqueado'] ?? false);
return [
'bloqueado' => $bloqueado,
'quantidade' => $cotaQuantidade,
'valor' => $cotaValor,
];
}
private static function validarParametrizacaoHabilitada(): void
{
$habilitado = DB::table('exame_imagem_parametrizacao_integracao_aplicativo')
->value('habilita_agendamento_conectasus_cidadao');
if (!$habilitado) {
abort(Response::HTTP_FORBIDDEN, 'O agendamento de exame de imagem pelo ConectaSUS Cidadão não está habilitado.');
}
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace App\Services\Mobile\ConectaCidadao\ExameImagem;
use App\Enums\ExameImagem\Fechamento\Status;
use App\Models\ExameImagem\Parametrizacao\IntegracaoAplicativo;
use App\Services\Service;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class LaudoService extends Service
{
public static function laudos(): array
{
self::validarVisualizacaoHabilitada();
$paciente = auth('mobile')->user();
return DB::table('exame_imagem_agendamento as agendamento')
->join('exame_imagem_jornada as jornada', 'jornada.id', 'agendamento.jornada_id')
->join('cadastro_infraestrutura as infraestrutura', 'infraestrutura.id', 'agendamento.infraestrutura_id')
->join('cadastro_unidade as unidade', 'unidade.id', 'infraestrutura.unidade_id')
->leftJoin('cadastro_infraestrutura as infra_sol', 'infra_sol.id', 'agendamento.infraestrutura_solicitante_id')
->leftJoin('cadastro_unidade as unidade_sol', 'unidade_sol.id', 'infra_sol.unidade_id')
->leftJoin('cadastro_usuario as medico_usuario', 'medico_usuario.id', 'agendamento.profissional_id')
->leftJoin('cadastro_cidadao as medico_cidadao', 'medico_cidadao.id', 'medico_usuario.cidadao_id')
->where('agendamento.paciente_id', $paciente->id)
->whereExists(function ($sub) {
$sub->select(DB::raw(1))
->from('exame_imagem_agendamento_exame as exame')
->whereColumn('exame.agendamento_id', 'agendamento.id')
->where('exame.status_id', Status::REALIZADO);
})
->orderByDesc('jornada.data')
->get([
'agendamento.id',
'jornada.data',
'agendamento.hora_exame',
'unidade.nome_fantasia as unidade_nome',
'unidade_sol.nome_fantasia as unidade_solicitante',
'medico_cidadao.nome as medico_nome',
])
->map(
fn($item) =>
[
'id' => $item->id,
'data' => $item->data,
'hora_exame' => $item->hora_exame,
'unidade' => $item->unidade_nome,
'unidade_solicitante' => $item->unidade_solicitante,
'medico' => $item->medico_nome,
'exames' => self::getExamesComResultado($item->id),
]
)
->values()
->toArray();
}
private static function getExamesComResultado(int $agendamentoId): array
{
return DB::table('exame_imagem_agendamento_exame as exame')
->join('parametrizacao_exame as param_exame', 'param_exame.id', 'exame.exame_id')
->leftJoin('cadastro_usuario as usuario', 'usuario.id', 'exame.usuario_id')
->leftJoin('cadastro_cidadao as cidadao_usuario', 'cidadao_usuario.id', 'usuario.cidadao_id')
->where('exame.agendamento_id', $agendamentoId)
->where('exame.status_id', Status::REALIZADO)
->get(
[
'exame.id',
'param_exame.nome',
'param_exame.codigo',
'exame.arquivo',
'exame.resultado',
'exame.laudo_nome',
'cidadao_usuario.nome as lancado_por',
]
)
->map(
fn($exame) =>
[
'id' => $exame->id,
'nome' => $exame->nome,
'codigo' => $exame->codigo,
'resultado' => $exame->resultado,
'laudo_nome' => $exame->laudo_nome,
'lancado_por' => $exame->lancado_por,
'url_download' => $exame->arquivo
? url("api/mobile/conecta-cidadao/exame-imagem/laudos/{$exame->id}/download")
: null,
]
)
->toArray();
}
public static function download(int $exameId): array
{
$paciente = auth('mobile')->user();
$exame = DB::table('exame_imagem_agendamento_exame as exame')
->join('exame_imagem_agendamento as agendamento', 'agendamento.id', 'exame.agendamento_id')
->where('exame.id', $exameId)
->where('agendamento.paciente_id', $paciente->id)
->whereNotNull('exame.arquivo')
->first(['exame.arquivo']);
abort_if(is_null($exame), Response::HTTP_NOT_FOUND, 'Laudo não encontrado.');
$caminho = 'exame-imagem' . DIRECTORY_SEPARATOR . 'laudos' . DIRECTORY_SEPARATOR . app('config')->get('database.default') . DIRECTORY_SEPARATOR . $exame->arquivo;
abort_if(!Storage::exists($caminho), Response::HTTP_NOT_FOUND, 'Arquivo do laudo não encontrado.');
return [
'nome' => $exame->arquivo,
'caminho' => Storage::path($caminho),
];
}
private static function validarVisualizacaoHabilitada(): void
{
$integracao = IntegracaoAplicativo::first();
if (!($integracao->habilita_consulta_imagem_conectasus_cidadao ?? false)) {
abort(Response::HTTP_FORBIDDEN, 'A consulta de exames de imagem pelo ConectaSUS Cidadão não está habilitada.');
}
}
}