where('agendamento_id', $meta['agendamento_id']) ->firstOrFail(); $jornada = Jornada::query() ->where('id', $meta['jornada_id']) ->firstOrFail(); $soapId = self::ensureSoap($atendimento->id, $jornada->id); if (!empty($data['antropometria']) && is_array($data['antropometria'])) { self::upsertAntropometria($atendimento->id, $soapId, $data['antropometria']); } if (!empty($data['sinais_vitais']) && is_array($data['sinais_vitais'])) { self::upsertSinaisVitais($atendimento->id, $soapId, $data['sinais_vitais']); } if (!empty($data['feridas'])) { if (isset($data['feridas'][0]) && is_array($data['feridas'][0])) { foreach ($data['feridas'] as $indice => $ferida) { if (is_array($ferida)) { self::upsertFeridas($soapId, $ferida, $indice); } } } elseif (is_array($data['feridas'])) { self::upsertFeridas($soapId, $data['feridas'], 0); } } $response = [ 'updated' => true, 'atendimento_id' => $atendimento->id, 'soap_id' => $soapId, 'idempotency_key' => $idempotencyKey, ]; self::salvarRespostaIdempotencia($idempotencyKey, $customer, $response); return $response; }); } private static function ensureSoap(int $atendimentoId, int $jornadaId): int { $dado = DB::table('ambulatorio_atendimento_soap') ->where('atendimento_id', $atendimentoId) ->first(); if ($dado) { return (int) $dado->id; } $soapId = DB::table('ambulatorio_atendimento_soap')->insertGetId([ 'atendimento_id' => $atendimentoId, 'created_at' => now(), 'updated_at' => now(), 'created_centro_custo_id' => self::getCentroCusto($jornadaId), 'created_lotacao_id' => self::getLotacao($jornadaId) ]); return (int) $soapId; } private static function getCentroCusto(int $jornadaId): int { return DB::table('ambulatorio_jornada') ->where('id', $jornadaId) ->value('created_centro_custo_id'); } private static function getLotacao(int $jornadaId): int { return DB::table('ambulatorio_jornada') ->where('id', $jornadaId) ->value('created_lotacao_id'); } private static function upsertAntropometria(int $atendimentoId, int $soapId, array $ant): void { $update = ['updated_at' => now()]; if (array_key_exists('peso', $ant)) { $update['peso'] = $ant['peso']; } if (array_key_exists('altura', $ant)) { $update['altura'] = $ant['altura']; } if (array_key_exists('perimetro_cefalico', $ant)) { $update['perimetro_cefalico'] = $ant['perimetro_cefalico']; } if (array_key_exists('circunferencia_abdominal', $ant)) { $update['circunferencia_abdominal'] = $ant['circunferencia_abdominal']; } if (array_key_exists('perimetro_panturrilha', $ant)) { $update['perimetro_panturrilha'] = $ant['perimetro_panturrilha']; } if (array_key_exists('comprimento_pe_direito', $ant)) { $update['comprimento_pe_direito'] = $ant['comprimento_pe_direito']; } if (array_key_exists('largura_pe_direito', $ant)) { $update['largura_pe_direito'] = $ant['largura_pe_direito']; } if (array_key_exists('circunferencia_pe_direito', $ant)) { $update['circunferencia_pe_direito'] = $ant['circunferencia_pe_direito']; } if (array_key_exists('comprimento_pe_esquerdo', $ant)) { $update['comprimento_pe_esquerdo'] = $ant['comprimento_pe_esquerdo']; } if (array_key_exists('largura_pe_esquerdo', $ant)) { $update['largura_pe_esquerdo'] = $ant['largura_pe_esquerdo']; } if (array_key_exists('circunferencia_pe_esquerdo', $ant)) { $update['circunferencia_pe_esquerdo'] = $ant['circunferencia_pe_esquerdo']; } if (count($update) === 1) { return; } $exists = DB::table('ambulatorio_atendimento_antropometria') ->where('atendimento_id', $atendimentoId) ->where('soap_id', $soapId) ->exists(); if ($exists) { DB::table('ambulatorio_atendimento_antropometria') ->where('atendimento_id', $atendimentoId) ->where('soap_id', $soapId) ->update($update); return; } $insert = array_merge($update, [ 'atendimento_id' => $atendimentoId, 'soap_id' => $soapId, 'created_at' => now(), ]); DB::table('ambulatorio_atendimento_antropometria') ->insert($insert); } private static function upsertSinaisVitais(int $atendimentoId, int $soapId, array $sv): void { $update = ['updated_at' => now()]; if (array_key_exists('pressao_arterial', $sv)) { $update['pressao_arterial'] = $sv['pressao_arterial']; } if (array_key_exists('frequencia_respiratoria', $sv)) { $update['frequencia_respiratoria'] = $sv['frequencia_respiratoria']; } if (array_key_exists('frequencia_cardiaca', $sv)) { $update['frequencia_cardiaca'] = $sv['frequencia_cardiaca']; } if (array_key_exists('temperatura', $sv)) { $update['temperatura'] = $sv['temperatura']; } if (array_key_exists('saturacao_o2', $sv)) { $update['saturacao_o2'] = $sv['saturacao_o2']; } if (count($update) === 1) { return; } $exists = DB::table('ambulatorio_atendimento_sinal_vital') ->where('atendimento_id', $atendimentoId) ->where('soap_id', $soapId) ->exists(); if ($exists) { DB::table('ambulatorio_atendimento_sinal_vital') ->where('atendimento_id', $atendimentoId) ->where('soap_id', $soapId) ->update($update); return; } $insert = array_merge($update, [ 'atendimento_id' => $atendimentoId, 'soap_id' => $soapId, 'created_at' => now(), ]); DB::table('ambulatorio_atendimento_sinal_vital') ->insert($insert); } private static function upsertFeridas(int $soapId, array $fe, int $indice = 0): void { $update = ['updated_at' => now()]; $feridaId = null; $identificadorExterno = (string) ($fe['identificador_externo'] ?? ''); if (!empty($identificadorExterno)) { $feridaId = self::resolveFeridaId($identificadorExterno); } if (!$feridaId) { throw new FeridaNotFoundException($identificadorExterno ?: 'vazio', $indice); } $update['ferida_id'] = $feridaId; $paiDescricao = trim((string) ($fe['local_anatomico_descricao'] ?? '')); $filhoDescricao = trim((string) ($fe['local_regiao_anatomico_descricao'] ?? '')); $temPai = $paiDescricao !== ''; $temFilho = $filhoDescricao !== ''; if (!$temPai && !$temFilho) { throw new LocalAnatomicoAusenteException($indice); } if ($temFilho) { $localIdPreResolve = $temPai ? self::resolveLocalAnatomicoId($paiDescricao) : null; $regiaoRow = self::resolveLocalAnatomicoRegiaoCompleto($filhoDescricao, $localIdPreResolve); if (!$regiaoRow) { throw new LocalAnatomicoRegiaoNotFoundException($filhoDescricao, $indice); } if (isset($regiaoRow->ativo) && !$regiaoRow->ativo) { throw new LocalAnatomicoRegiaoInativoException($filhoDescricao, $indice); } $regiaoId = (int) $regiaoRow->id; $localIdFromRegiao = (int) $regiaoRow->local_anatomico_id; if ($temPai) { $localIdEnviado = $localIdPreResolve; if ($localIdEnviado !== null && $localIdEnviado !== $localIdFromRegiao) { throw new LocalAnatomicoInconsistenteException($paiDescricao, $filhoDescricao, $indice); } } $update['local_anatomico_id'] = $localIdFromRegiao; $update['local_anatomico_regiao_id'] = $regiaoId; } else { $localId = self::resolveLocalAnatomicoId($paiDescricao); if (!$localId) { throw new LocalAnatomicoNotFoundException($paiDescricao, $indice); } $update['local_anatomico_id'] = $localId; $naoInformadoRow = DB::table('cadastro_local_anatomico_regiao') ->where('local_anatomico_id', $localId) ->where('key', 'nao_informado') ->first(); $update['local_anatomico_regiao_id'] = $naoInformadoRow ? (int) $naoInformadoRow->id : null; } if (array_key_exists('data_inicio_tratamento', $fe)) { $update['data'] = $fe['data_inicio_tratamento']; } if (array_key_exists('ferida_necrose_porcentagem', $fe)) { $necroseId = self::resolveFeridaNecroseId((string) $fe['ferida_necrose_porcentagem']); $update['ferida_necrose'] = $necroseId; } if (array_key_exists('ferida_exsudato_descricao', $fe)) { $exsudatoId = self::resolveExsudatoId((string) $fe['ferida_exsudato_descricao']); $update['ferida_exsudato_id'] = $exsudatoId; } if (array_key_exists('status_ferida', $fe)) { $status = mb_strtolower(trim((string) $fe['status_ferida'])); $update['cicatrizada'] = in_array($status, ['cicatrizada', 'cicatriz', '1', 'true'], true); } if (array_key_exists('dor', $fe)) { $update['dor'] = (int) $fe['dor']; } if (array_key_exists('comprimento_ferida', $fe)) { $update['comprimento'] = $fe['comprimento_ferida']; } if (array_key_exists('largura_ferida', $fe)) { $update['largura'] = $fe['largura_ferida']; } if (array_key_exists('profundidade_ferida', $fe)) { $update['profundidade'] = $fe['profundidade_ferida']; } $query = DB::table('ambulatorio_atendimento_soap_ferida') ->where('soap_id', $soapId); if (isset($update['ferida_id'])) { $query->where('ferida_id', $update['ferida_id']); } if (isset($update['local_anatomico_id'])) { $query->where('local_anatomico_id', $update['local_anatomico_id']); } if (isset($update['local_anatomico_regiao_id'])) { $query->where('local_anatomico_regiao_id', $update['local_anatomico_regiao_id']); } $exists = $query->exists(); if ($exists) { $query->update($update); return; } $insert = array_merge($update, [ 'soap_id' => $soapId, 'created_at' => now(), ]); $soapFeridaId = (int) DB::table('ambulatorio_atendimento_soap_ferida')->insertGetId($insert); self::persistFeridaImages($soapFeridaId, [ $fe['foto_ferida'] ?? null, $fe['foto_ferida_limpa'] ?? null, $fe['foto_curativo'] ?? null, $fe['foto_ferida_evolucao'] ?? null, ]); $procedimentos = self::resolveProcedimentosFromFerida($feridaId); if (!empty($procedimentos)) { $soap = AtendimentoSoap::find($soapId); $atendimentoId = data_get($soap, 'atendimento_id'); if ($atendimentoId === null) { $atendimentoId = (int) DB::table('ambulatorio_atendimento_soap') ->where('id', $soapId) ->value('atendimento_id'); } $ocupacaoId = data_get($soap, 'atendimento.ocupacao.id') ?? data_get($soap, 'createdLotacao.ocupacao_id'); $especialidadeId = data_get($soap, 'atendimento.especialidade.id'); $usuarioId = data_get($soap, 'atendimento.usuario.id') ?? data_get($soap, 'createdLotacao.usuario_id'); foreach ($procedimentos as $proc) { $procedimentoId = $proc['procedimento_id']; $cidId = $proc['cid_id']; $atendimentoProcedimentoId = DB::table('ambulatorio_atendimento_procedimento') ->where('atendimento_id', $atendimentoId) ->where('procedimento_id', $procedimentoId) ->where('soap_id', $soapId) ->orderByDesc('id') ->value('id'); if (!$atendimentoProcedimentoId) { $atendimentoProcedimentoId = DB::table('ambulatorio_atendimento_procedimento')->insertGetId([ 'atendimento_id' => $atendimentoId, 'procedimento_id' => $procedimentoId, 'soap_id' => $soapId, 'cid_id' => $cidId, 'quantidade' => 1, 'automatico' => false, 'ocupacao_id' => $ocupacaoId, 'especialidade_id' => $especialidadeId, 'usuario_id' => $usuarioId, 'created_lotacao_id' => data_get($soap, 'created_lotacao_id'), 'created_centro_custo_id' => data_get($soap, 'created_centro_custo_id'), 'updated_lotacao_id' => data_get($soap, 'updated_lotacao_id') ?? data_get($soap, 'created_lotacao_id'), 'updated_centro_custo_id' => data_get($soap, 'updated_centro_custo_id') ?? data_get($soap, 'created_centro_custo_id'), 'created_at' => now(), 'updated_at' => now(), ]); DB::table('ambulatorio_atendimento_soap_procedimento_clinico')->insert([ 'soap_id' => $soapId, 'atendimento_procedimento_id' => $atendimentoProcedimentoId, 'created_lotacao_id' => data_get($soap, 'created_lotacao_id'), 'created_centro_custo_id' => data_get($soap, 'created_centro_custo_id'), 'updated_lotacao_id' => data_get($soap, 'updated_lotacao_id') ?? data_get($soap, 'created_lotacao_id'), 'updated_centro_custo_id' => data_get($soap, 'updated_centro_custo_id') ?? data_get($soap, 'created_centro_custo_id'), 'created_at' => now(), 'updated_at' => now(), ]); } $existsLink = DB::table('ambulatorio_atendimento_soap_ferida_procedimento') ->where('soap_ferida_id', $soapFeridaId) ->where('atendimento_procedimento_id', $atendimentoProcedimentoId) ->exists(); if (!$existsLink) { DB::table('ambulatorio_atendimento_soap_ferida_procedimento')->insert([ 'soap_id' => $soapId, 'soap_ferida_id' => $soapFeridaId, 'atendimento_procedimento_id' => $atendimentoProcedimentoId, 'created_at' => now(), 'updated_at' => now(), ]); } } } } private static function persistFeridaImages(int $soapFeridaId, array $arquivos): void { foreach ($arquivos as $arquivo) { if (!is_string($arquivo)) { continue; } $arquivo = trim($arquivo); if ($arquivo === '') { continue; } if (self::isHttpUrl($arquivo)) { $saved = self::downloadAndStoreImage($arquivo); if ($saved === null) { continue; } $arquivo = $saved; } $exists = DB::table('ambulatorio_atendimento_soap_ferida_imagem') ->where('soap_ferida_id', $soapFeridaId) ->where('arquivo', $arquivo) ->exists(); if ($exists) { continue; } DB::table('ambulatorio_atendimento_soap_ferida_imagem')->insert([ 'soap_ferida_id' => $soapFeridaId, 'arquivo' => $arquivo, 'created_at' => now(), 'updated_at' => now(), ]); } } private static function isHttpUrl(?string $string): bool { if (!is_string($string)) { return false; } $string = trim($string); if ($string === '') { return false; } return (bool) filter_var($string, FILTER_VALIDATE_URL) && preg_match('#^https?://#i', $string) === 1; } private static function guessExtensionFromUrl(string $url): ?string { $path = parse_url($url, PHP_URL_PATH) ?: ''; $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); if (in_array($ext, ['jpg', 'jpeg', 'png', 'webp', 'gif'], true)) { return $ext === 'jpeg' ? 'jpg' : $ext; } return null; } private static function getExtensionFromContentType(?string $contentType): ?string { $map = [ 'image/jpeg' => 'jpg', 'image/jpg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif', ]; $ct = $contentType ? strtolower(trim($contentType)) : null; return $ct && isset($map[$ct]) ? $map[$ct] : null; } private static function buildFeridasBasePath(): string { $db = app('config')->get('database.default'); return 'ambulatorio' . DIRECTORY_SEPARATOR . 'feridas' . DIRECTORY_SEPARATOR . $db; } private static function downloadAndStoreImage(string $url, int $timeoutSeconds = 15, int $maxBytes = 10_000_000): ?string { $permitidos = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']; try { $cliente = new Client([ 'timeout' => $timeoutSeconds, 'allow_redirects' => true, 'http_errors' => false, 'headers' => [ 'User-Agent' => 'SafeCareImageFetcher/1.0', ], ]); $response = $cliente->get($url, ['stream' => true]); $code = $response->getStatusCode(); if ($code !== 200) { return null; } $contentType = strtolower(trim($response->getHeaderLine('content-type'))); $extensao = self::getExtensionFromContentType($contentType); if (!$extensao) { $extensao = self::guessExtensionFromUrl($url); if (!$extensao) { $extensao = 'jpg'; } } if ($contentType !== '' && !in_array($contentType, $permitidos, true)) { if (!in_array($extensao, ['jpg', 'png', 'webp', 'gif'], true)) { return null; } } $body = $response->getBody(); $buffer = ''; $read = 0; $chunkSize = 131072; while (!$body->eof()) { $chunk = $body->read($chunkSize); if ($chunk === '') { break; } $read += strlen($chunk); if ($read > $maxBytes) { return null; } $buffer .= $chunk; } if ($buffer === '') { return null; } $nomeArquivo = Str::uuid()->toString() . '.' . $extensao; $caminhoBase = self::buildFeridasBasePath(); $caminhoCompleto = $caminhoBase . DIRECTORY_SEPARATOR . $nomeArquivo; $ok = Storage::put($caminhoCompleto, $buffer); if (!$ok) { return null; } return $nomeArquivo; } catch (\Throwable $e) { return null; } } private static function resolveFeridaId(?string $identificadorExterno): ?int { if (empty($identificadorExterno)) { return null; } $row = DB::table('cadastro_ferida') ->where('identificador_externo', $identificadorExterno) ->first(); return $row ? (int) $row->id : null; } private static function resolveLocalAnatomicoId(?string $descricao): ?int { if ($descricao === null) { return null; } $id = LocalAnatomico::fromDescricao($descricao); if ($id !== null) { return $id; } $localNormalizado = mb_strtolower(trim($descricao)); $row = DB::table('cadastro_local_anatomico') ->whereRaw('LOWER(descricao) = ?', [$localNormalizado]) ->first(); if ($row) { return (int) $row->id; } $row = DB::table('cadastro_local_anatomico') ->where('descricao', 'like', '%' . $descricao . '%') ->first(); return $row ? (int) $row->id : null; } private static function resolveLocalAnatomicoRegiaoId(?string $descricaoOuKey, ?int $localAnatomicoId = null): ?int { return self::attemptResolveRegion($descricaoOuKey, $localAnatomicoId); } private static function attemptResolveRegion(?string $descricaoOuKey, ?int $localAnatomicoId): ?int { if ($descricaoOuKey === null) { return null; } if ($descricaoOuKey) { $id = LocalAnatomicoRegiao::fromKey($descricaoOuKey, $localAnatomicoId); if ($id !== null) { return $id; } } $id = LocalAnatomicoRegiao::fromDescricao($descricaoOuKey, $localAnatomicoId); if ($id !== null) { return $id; } $norm = LocalAnatomicoRegiao::normalize($descricaoOuKey); $query = DB::table('cadastro_local_anatomico_regiao') ->whereRaw('LOWER(descricao) = ?', [$norm]); if ($localAnatomicoId !== null) { $query->where('local_anatomico_id', $localAnatomicoId); } $row = $query->first(['id']); if ($row) { return (int) $row->id; } $like = DB::table('cadastro_local_anatomico_regiao') ->where('descricao', 'like', '%' . $descricaoOuKey . '%'); if ($localAnatomicoId !== null) { $like->where('local_anatomico_id', $localAnatomicoId); } $rowLike = $like->first(['id']); return $rowLike ? (int) $rowLike->id : null; } private static function resolveLocalAnatomicoRegiaoCompleto(?string $descricaoOuKey, ?int $localAnatomicoId = null): ?object { if ($descricaoOuKey === null) { return null; } $cols = ['id', 'local_anatomico_id', 'ativo']; $id = LocalAnatomicoRegiao::fromKey($descricaoOuKey, $localAnatomicoId); if ($id !== null) { return DB::table('cadastro_local_anatomico_regiao') ->where('id', $id) ->first($cols); } $id = LocalAnatomicoRegiao::fromDescricao($descricaoOuKey, $localAnatomicoId); if ($id !== null) { return DB::table('cadastro_local_anatomico_regiao') ->where('id', $id) ->first($cols); } $norm = LocalAnatomicoRegiao::normalize($descricaoOuKey); $exactQuery = DB::table('cadastro_local_anatomico_regiao') ->whereRaw('LOWER(descricao) = ?', [$norm]); if ($localAnatomicoId !== null) { $exactQuery->where('local_anatomico_id', $localAnatomicoId); } $row = $exactQuery->first($cols); if ($row) { return $row; } $likeQuery = DB::table('cadastro_local_anatomico_regiao') ->where('descricao', 'like', '%' . $descricaoOuKey . '%'); if ($localAnatomicoId !== null) { $likeQuery->where('local_anatomico_id', $localAnatomicoId); } return $likeQuery->first($cols); } private static function resolveFeridaNecroseId(?string $valor): ?int { if ($valor === null) { return null; } $raw = trim(mb_strtolower($valor)); $necroseNormalizada = str_replace(' ', '', $raw); if (preg_match('/^\d{1,3}$/', $necroseNormalizada) === 1) { $necroseNormalizada = $necroseNormalizada . '%'; } $permitidos = ['0%', '20%', '40%', '60%', '80%', '100%']; if (!in_array($necroseNormalizada, $permitidos, true)) { return null; } $id = DB::table('cadastro_ferida_necrose') ->whereRaw('LOWER(descricao) = ?', [$necroseNormalizada]) ->value('id'); return $id ? (int) $id : null; } private static function resolveExsudatoId(?string $descricao): ?int { if ($descricao === null) { return null; } $id = FeridaExsudato::fromDescricao($descricao); if ($id !== null) { return $id; } $exsudatoNormalizado = mb_strtolower(trim($descricao)); $row = DB::table('cadastro_ferida_exsudato') ->whereRaw('LOWER(descricao) = ?', [$exsudatoNormalizado]) ->first(); if ($row) { return (int) $row->id; } $row = DB::table('cadastro_ferida_exsudato') ->where('descricao', 'like', '%' . $descricao . '%') ->first(); return $row ? (int) $row->id : null; } private static function resolveProcedimentosFromFerida(?int $feridaId): array { $items = []; if ($feridaId) { $associacoes = DB::table('cadastro_ferida_procedimento') ->where('ferida_id', $feridaId) ->get(); foreach ($associacoes as $assoc) { if (!empty($assoc->procedimento_id)) { $items[] = [ 'procedimento_id' => (int) $assoc->procedimento_id, 'cid_id' => !empty($assoc->cid_id) ? (int) $assoc->cid_id : null, ]; } } } return $items; } private static function chaveDeterministica(array $data, string $customer): string { $meta = $data['metaDataConectaSus'] ?? []; $payloadHash = hash('sha256', json_encode($data)); $seed = json_encode([ 'customer' => $customer, 'agendamento' => $meta['agendamento_id'] ?? null, 'payload_hash' => $payloadHash, ]); return hash('sha256', $seed); } private static function garantirIdempotencia(string $key, string $customer, array $payload): array { $existing = DB::table('idempotencies_safecare') ->where('key', $key) ->where('customer', $customer) ->first(); if ($existing && !empty($existing->response_json)) { return [ 'replayed' => true, 'response' => json_decode($existing->response_json, true), ]; } if (!$existing) { DB::table('idempotencies_safecare')->insert([ 'key' => $key, 'customer' => $customer, 'request_hash' => hash('sha256', json_encode($payload)), 'created_at' => now(), 'updated_at' => now(), ]); } return ['replayed' => false]; } private static function salvarRespostaIdempotencia(string $key, string $customer, array $response): void { DB::table('idempotencies_safecare') ->where('key', $key) ->where('customer', $customer) ->update([ 'response_json' => json_encode($response), 'updated_at' => now(), ]); } }