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,475 @@
<?php
namespace App\Services\Mobile\ConectaCidadao\Farmacia;
use App\Enums\Geral\MedidaTempo;
use App\Enums\Mobile\ConectaCidadao\Farmacia\MonitoramentoPosologia;
use App\Models\Farmacia\AppCidadaoLog;
use App\Models\Farmacia\ParametrizacaoAppCidadao;
use App\Models\Farmacia\ReceitaItem;
use App\Models\Farmacia\ReceitaMonitoramentoPosologia;
use App\Models\Farmacia\ReceitaTratamentoInicio;
use App\Services\Service;
use Carbon\Carbon;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class MonitoramentoPosologiaService extends Service
{
public static function medicamentos(): array
{
self::verificarParametrizacao();
$pacienteId = auth('mobile')->user()->id;
$rows = DB::table('farmacia_receita_item as fri')
->select(
'fri.id as item_id',
'fr.id as receita_id',
'fr.data_receita',
'cm.descricao as material_descricao',
'um.nome as unidade_medida_nome',
'fri.quantidade',
'fri.intervalo_tempo',
'fri.intervalo_medida_tempo_id',
'mt.nome as intervalo_medida_tempo_nome',
'fri.durante_tempo',
'ti.inicio_em',
'ti.interrompido_em',
DB::raw("(SELECT COUNT(*) FROM farmacia_receita_monitoramento_posologia WHERE receita_item_id = fri.id AND paciente_id = {$pacienteId}) as total_confirmadas")
)
->join('farmacia_receita as fr', 'fr.id', 'fri.receita_id')
->join('cadastro_material as cm', 'cm.id', 'fri.material_id')
->leftJoin('cadastro_unidade_medida as um', 'um.id', 'cm.apresentacao_unidade_medida_id')
->leftJoin('geral_medida_tempo as mt', 'mt.id', 'fri.intervalo_medida_tempo_id')
->leftJoin('farmacia_receita_tratamento_inicio as ti', function ($join) use ($pacienteId) {
$join->on('ti.receita_item_id', '=', 'fri.id')
->where('ti.paciente_id', '=', $pacienteId);
})
->where('fr.paciente_id', $pacienteId)
->where(function ($q) {
$q->whereDate('fri.proxima_retirada', '>=', now()->toDateString())
->orWhereNotNull('ti.inicio_em');
})
->orderBy('fr.data_receita', 'desc')
->orderBy('fri.id', 'asc')
->get();
$receitas = [];
foreach ($rows as $row) {
$receitaId = $row->receita_id;
if (!isset($receitas[$receitaId])) {
$receitas[$receitaId] = [
'id' => $receitaId,
'data_receita' => $row->data_receita,
'itens' => [],
];
}
$inicioEm = $row->inicio_em ? Carbon::parse($row->inicio_em) : null;
$interrompidoEm = $row->interrompido_em ? Carbon::parse($row->interrompido_em) : null;
$receitas[$receitaId]['itens'][] = [
'id' => $row->item_id,
'medicamento' => $row->material_descricao,
'posologia_texto' => self::montarPosologiaTexto($row),
'posologia_incompleta' => is_null($row->intervalo_tempo) || is_null($row->durante_tempo),
'status' => self::calcularStatus(
$row->durante_tempo,
$row->intervalo_tempo,
$row->intervalo_medida_tempo_id,
$inicioEm,
$interrompidoEm,
(int) $row->total_confirmadas
),
];
}
return [
'habilitado' => true,
'data' => array_values($receitas),
];
}
public static function iniciar(int $receitaItemId): array
{
self::verificarParametrizacao();
$pacienteId = auth('mobile')->user()->id;
$item = ReceitaItem::findOrFail($receitaItemId);
abort_if($item->receita->paciente_id !== $pacienteId, Response::HTTP_FORBIDDEN);
request()->validate([
'inicio_em' => ['required', 'date'],
'aderiu_tratamento' => ['nullable', 'boolean'],
]);
$inicioEm = Carbon::parse(request()->input('inicio_em'));
ReceitaTratamentoInicio::updateOrCreate(
['receita_item_id' => $receitaItemId, 'paciente_id' => $pacienteId],
[
'inicio_em' => $inicioEm,
'interrompido_em' => null,
'aderiu_tratamento' => request()->boolean('aderiu_tratamento'),
]
);
if ($inicioEm->isPast()) {
ReceitaMonitoramentoPosologia::firstOrCreate(
['receita_item_id' => $receitaItemId, 'paciente_id' => $pacienteId, 'dose_numero' => 1],
['confirmado_em' => $inicioEm, 'observacao' => null]
);
}
self::log($pacienteId, 'INICIO_TRATAMENTO', $item->receita_id, $receitaItemId);
return ['registrado' => true];
}
public static function index(int $receitaItemId): array
{
self::verificarParametrizacao();
$pacienteId = auth('mobile')->user()->id;
$item = ReceitaItem::findOrFail($receitaItemId);
abort_if($item->receita->paciente_id !== $pacienteId, Response::HTTP_FORBIDDEN);
$inicio = ReceitaTratamentoInicio::where('receita_item_id', $receitaItemId)
->where('paciente_id', $pacienteId)
->first();
$confirmacoes = ReceitaMonitoramentoPosologia::where('receita_item_id', $receitaItemId)
->where('paciente_id', $pacienteId)
->orderBy('dose_numero')
->get();
$inicioEm = $inicio ? $inicio->inicio_em : null;
$interrompidoEm = $inicio ? $inicio->interrompido_em : null;
$status = self::calcularStatus(
$item->durante_tempo,
$item->intervalo_tempo,
$item->intervalo_medida_tempo_id,
$inicioEm,
$interrompidoEm,
$confirmacoes->count()
);
$cronograma = self::calcularCronograma($item, $inicio, $confirmacoes);
$adesao = self::calcularAdesao($item, $inicioEm, $confirmacoes->count());
self::log($pacienteId, 'CONSULTA_MONITORAMENTO', $item->receita_id, $receitaItemId);
return [
'receita_item_id' => $receitaItemId,
'medicamento' => $item->material->descricao,
'posologia_incompleta' => is_null($item->intervalo_tempo) || is_null($item->durante_tempo),
'status' => $status,
'inicio_em' => $inicioEm,
'interrompido_em' => $interrompidoEm,
'adesao' => $adesao,
'cronograma' => $cronograma,
];
}
public static function store(int $receitaItemId): array
{
self::verificarParametrizacao();
$pacienteId = auth('mobile')->user()->id;
$item = ReceitaItem::findOrFail($receitaItemId);
abort_if($item->receita->paciente_id !== $pacienteId, Response::HTTP_FORBIDDEN);
$inicio = ReceitaTratamentoInicio::where('receita_item_id', $receitaItemId)
->where('paciente_id', $pacienteId)
->first();
abort_if(is_null($inicio), Response::HTTP_UNPROCESSABLE_ENTITY);
abort_if(!is_null($inicio->interrompido_em), Response::HTTP_FORBIDDEN);
request()->validate([
'confirmado_em' => ['required', 'date'],
'dose_numero' => ['required', 'integer', 'min:1'],
'observacao' => ['nullable', 'string', 'max:500'],
]);
$monitoramento = ReceitaMonitoramentoPosologia::create([
'receita_item_id' => $receitaItemId,
'paciente_id' => $pacienteId,
'confirmado_em' => request()->input('confirmado_em'),
'dose_numero' => request()->input('dose_numero'),
'observacao' => request()->input('observacao'),
]);
self::log($pacienteId, 'CONFIRMAR_DOSE', $item->receita_id, $receitaItemId);
return ['id' => $monitoramento->id];
}
public static function destroy(int $id): array
{
self::verificarParametrizacao();
$pacienteId = auth('mobile')->user()->id;
$monitoramento = ReceitaMonitoramentoPosologia::findOrFail($id);
abort_if($monitoramento->paciente_id !== $pacienteId, Response::HTTP_FORBIDDEN);
$receitaId = $monitoramento->receitaItem->receita_id;
$receitaItemId = $monitoramento->receita_item_id;
$monitoramento->delete();
self::log($pacienteId, 'CANCELAR_DOSE', $receitaId, $receitaItemId);
return ['removido' => true];
}
public static function interromper(int $receitaItemId): array
{
self::verificarParametrizacao();
$pacienteId = auth('mobile')->user()->id;
$item = ReceitaItem::findOrFail($receitaItemId);
abort_if($item->receita->paciente_id !== $pacienteId, Response::HTTP_FORBIDDEN);
$inicio = ReceitaTratamentoInicio::where('receita_item_id', $receitaItemId)
->where('paciente_id', $pacienteId)
->whereNull('interrompido_em')
->firstOrFail();
$inicio->update(['interrompido_em' => Carbon::now()]);
self::log($pacienteId, 'INTERROMPER_MONITORAMENTO', $item->receita_id, $receitaItemId);
return ['interrompido' => true];
}
private static function calcularStatus(
?int $duranteTempo,
?int $intervaloTempo,
?int $intervaloMedidaTempoId,
?Carbon $inicioEm,
?Carbon $interrompidoEm,
int $totalConfirmadas
): string {
if (is_null($inicioEm)) {
return MonitoramentoPosologia::STATUS_NAO_INICIADO;
}
if (!is_null($interrompidoEm)) {
return MonitoramentoPosologia::STATUS_INTERROMPIDO;
}
if (is_null($duranteTempo) || is_null($intervaloTempo)) {
return MonitoramentoPosologia::STATUS_EM_ANDAMENTO;
}
$intervaloMinutos = self::calcularIntervaloEmMinutos($intervaloTempo, $intervaloMedidaTempoId);
if ($intervaloMinutos > 0) {
$totalDosesPrevistas = (int) ceil(($duranteTempo * 24 * 60) / $intervaloMinutos);
if ($totalConfirmadas >= $totalDosesPrevistas) {
return MonitoramentoPosologia::STATUS_FINALIZADO;
}
}
$fimTratamento = $inicioEm->copy()->addDays($duranteTempo);
if (Carbon::now()->lessThanOrEqualTo($fimTratamento)) {
return MonitoramentoPosologia::STATUS_EM_ANDAMENTO;
}
return $totalConfirmadas > 0 ? MonitoramentoPosologia::STATUS_FINALIZADO : MonitoramentoPosologia::STATUS_EXPIRADO;
}
private static function calcularCronograma(ReceitaItem $item, ?ReceitaTratamentoInicio $inicio, Collection $confirmacoes): array
{
if (is_null($inicio) || is_null($item->intervalo_tempo) || is_null($item->durante_tempo)) {
return [];
}
$intervaloMinutos = self::calcularIntervaloEmMinutos($item->intervalo_tempo, $item->intervalo_medida_tempo_id);
if ($intervaloMinutos <= 0) {
return [];
}
$duracaoMinutos = $item->durante_tempo * 24 * 60;
$totalDoses = (int) ceil($duracaoMinutos / $intervaloMinutos);
$confirmacoesPorNumero = $confirmacoes->keyBy('dose_numero');
$cronograma = [];
for ($i = 0; $i < $totalDoses; $i++) {
$doseNumero = $i + 1;
$esperadoEm = $inicio->inicio_em->copy()->addMinutes($i * $intervaloMinutos);
$confirmacao = $confirmacoesPorNumero->get($doseNumero);
if ($confirmacao) {
$statusDose = MonitoramentoPosologia::STATUS_DOSE_CONFIRMADA;
} elseif ($esperadoEm->copy()->addMinutes(MonitoramentoPosologia::TOLERANCIA_ATRASO_MINUTOS)->isPast()) {
$statusDose = MonitoramentoPosologia::STATUS_DOSE_ATRASADA;
} else {
$statusDose = MonitoramentoPosologia::STATUS_DOSE_PENDENTE;
}
$cronograma[] = [
'id' => $confirmacao ? $confirmacao->id : null,
'numero' => $doseNumero,
'esperado_em' => $esperadoEm->toDateTimeString(),
'confirmado_em' => $confirmacao ? $confirmacao->confirmado_em : null,
'status' => $statusDose,
'observacao' => $confirmacao ? $confirmacao->observacao : null,
];
}
return $cronograma;
}
private static function calcularIntervaloEmMinutos(int $intervalo, ?int $medidaTempoId): int
{
if ($medidaTempoId === MedidaTempo::MINUTOS) {
return $intervalo;
}
if ($medidaTempoId === MedidaTempo::DIAS) {
return $intervalo * 24 * 60;
}
if ($medidaTempoId === MedidaTempo::SEMANAS) {
return $intervalo * 7 * 24 * 60;
}
return $intervalo * 60;
}
private static function calcularAdesao(ReceitaItem $item, ?Carbon $inicioEm, int $confirmadas): array
{
if (is_null($inicioEm) || is_null($item->intervalo_tempo) || is_null($item->durante_tempo)) {
return [
'doses_previstas' => null,
'doses_confirmadas' => $confirmadas,
'percentual' => null,
];
}
$intervaloMinutos = self::calcularIntervaloEmMinutos($item->intervalo_tempo, $item->intervalo_medida_tempo_id);
$duracaoMinutos = $item->durante_tempo * 24 * 60;
$decorridoMinutos = min($inicioEm->diffInMinutes(Carbon::now()), $duracaoMinutos);
$previstas = $intervaloMinutos > 0 ? (int) ceil($decorridoMinutos / $intervaloMinutos) : 0;
$percentual = $previstas > 0 ? round(($confirmadas / $previstas) * 100, 1) : null;
return [
'doses_previstas' => $previstas,
'doses_confirmadas' => $confirmadas,
'percentual' => $percentual,
];
}
private static function montarPosologiaTexto(object $row): ?string
{
$texto = '';
if (!is_null($row->quantidade)) {
$quantidade = (float) $row->quantidade;
$texto .= fmod($quantidade, 1) == 0 ? (int) $quantidade : number_format($quantidade, 2, ',', '.');
if ($row->unidade_medida_nome) {
$texto .= ' ' . strtolower($row->unidade_medida_nome);
}
}
if (!is_null($row->intervalo_tempo)) {
$intervalo = (int) $row->intervalo_tempo;
if ($row->intervalo_medida_tempo_nome) {
$unidade = strtolower(self::pluralizar($row->intervalo_medida_tempo_nome, $intervalo));
$texto .= $intervalo === 1
? " a cada {$unidade}"
: " a cada {$intervalo} {$unidade}";
} else {
$texto .= " a cada {$intervalo}";
}
}
if (!is_null($row->durante_tempo)) {
$dias = (int) $row->durante_tempo;
$texto .= $dias === 1 ? ' durante 1 dia' : " durante {$dias} dias";
}
return empty($texto) ? null : $texto;
}
private static function pluralizar(string $nome, int $valor): string
{
if (strpos($nome, '(es)') !== false) {
return $valor === 1
? str_replace('(es)', '', $nome)
: str_replace('(es)', 'es', $nome);
}
if (strpos($nome, '(s)') !== false) {
return $valor === 1
? str_replace('(s)', '', $nome)
: str_replace('(s)', 's', $nome);
}
return $nome;
}
private static function verificarParametrizacao(): void
{
$parametrizacao = ParametrizacaoAppCidadao::first();
abort_if(!($parametrizacao->permite_envio_receitas_medicas ?? false), Response::HTTP_FORBIDDEN);
}
private static function log(int $pacienteId, string $tipoAcao, ?int $receitaId = null, ?int $receitaItemId = null): void
{
AppCidadaoLog::create([
'paciente_id' => $pacienteId,
'receita_id' => $receitaId,
'receita_item_id' => $receitaItemId,
'tipo_acao' => $tipoAcao,
]);
}
}