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

57
.dockerignore Normal file
View File

@@ -0,0 +1,57 @@
# Contexto do build e a raiz do repositorio (api/ + frontend/).
# Credenciais — os .example precisam entrar (o build gera caches com eles).
**/.env
**/.env.*
!api/.env.example
api/connections.json
!api/connections.json.example
api/storage/*.key
# Reconstruidos dentro da imagem.
api/vendor/
frontend/node_modules/
frontend/dist/
# Dados de runtime.
api/storage/logs/*
api/storage/framework/cache/*
api/storage/framework/sessions/*
api/storage/framework/views/*
api/storage/framework/testing/*
api/storage/app/public/*
api/storage/app/documento/*
api/storage/app/intranet/*
api/storage/pronto-atendimento/*
api/bootstrap/cache/*
api/public/storage
api/public/hot
# Nao entram na imagem: deploy antigo (bare metal) e ferramentas de repo.
config/
scripts/
README.md
.git
.gitea
.github
.idea
.vscode
api/docker-compose.yml
api/webpack.mix.js
api/package.json
api/package-lock.json
api/tests/
api/phpunit.xml
api/.phpunit.result.cache
api/.styleci.yml
api/.editorconfig
*.swp
*.log
routes.txt
TODO*
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log

View File

@@ -0,0 +1,8 @@
GITEA_INSTANCE_URL=http://192.168.0.41:3001
GITEA_RUNNER_REGISTRATION_TOKEN=
RUNNER_NAME=conectasus-runner-01
RUNNER_LABELS=ubuntu-latest:docker://gitea/runner-images:ubuntu-latest
ACT_RUNNER_IMAGE=gitea/act_runner:0.6.1
TZ=America/Sao_Paulo

106
.gitea/runner/README.md Normal file
View File

@@ -0,0 +1,106 @@
# Runner do Gitea (act_runner)
Runner que executa a esteira [`build-api-image.yml`](../workflows/build-api-image.yml).
Sobe em um servidor **separado** do Gitea — o Gitea fica em `192.168.0.41:3001`
e este compose roda na máquina que vai construir as imagens.
## Instalação
```bash
cp .env.example .env
```
Pegue o token de registro no Gitea e cole em `GITEA_RUNNER_REGISTRATION_TOKEN`:
| Escopo do runner | Onde pegar o token |
| --- | --- |
| Só o repositório conectasus | Repo › Settings › Actions › Runners › Create new runner |
| Toda a organização `rkm` | Org › Settings › Actions › Runners |
| Instância inteira | Site Administration › Actions › Runners |
Depois:
```bash
docker compose up -d
docker compose logs -f
```
O runner aparece como online em Settings › Actions › Runners do Gitea. O token de
registro é usado uma única vez; a credencial permanente fica em `./data/.runner`.
## O que o compose faz
| Item | Motivo |
| --- | --- |
| `/var/run/docker.sock` montado | O `act_runner` usa o daemon do host para criar os containers de job **e** repassa o socket para dentro deles. É isso que faz o `docker build` da esteira funcionar. |
| `./data` | Guarda o registro do runner. Apagar essa pasta força novo registro. |
| Label `ubuntu-latest:docker://gitea/runner-images:ubuntu-latest` | Casa com o `runs-on: ubuntu-latest` do workflow. Essa imagem (0,57 GB) já traz `docker`, `git`, `node`, `bash` e `curl` — tudo que a esteira usa. A variante `-full` tem 17,6 GB e não é necessária. |
Como o Gitea está em outro servidor, tudo que aponta para ele é configuração:
`GITEA_INSTANCE_URL` e o token vêm do `.env`, nada fixo no compose. Se as
variáveis obrigatórias faltarem, o `docker compose up` falha na hora com a
mensagem em vez de subir um runner quebrado.
## Pré-requisitos no servidor do runner
**1. Alcançar o Gitea.** O runner e os containers de job precisam falar com
`192.168.0.41:3001` (API do Actions e registry de imagens):
```bash
curl -s http://192.168.0.41:3001/api/v1/version
```
**2. Confiar no registry via HTTP.** O registry não tem TLS, e o `docker push` da
esteira roda no daemon do host (por causa do socket montado). Então a
configuração é do host, não do compose — em `/etc/docker/daemon.json`:
```json
{ "insecure-registries": ["192.168.0.41:3001"] }
```
Se o arquivo já existir, **acrescente ao array** em vez de sobrescrever. Depois:
```bash
systemctl reload docker
docker info | grep -A5 'Insecure Registries'
```
Use `reload`, não `restart`. `insecure-registries` é recarregável por SIGHUP, e
`restart` derruba todos os containers do host quando `live-restore` está
desligado (confira com `docker info --format '{{.LiveRestoreEnabled}}'`).
Sem isso o push falha com `http: server gave HTTP response to HTTPS client`.
**3. Espaço em disco.** Cada build gera uma imagem da API com o `vendor/`
completo. Convém uma limpeza periódica:
```bash
docker image prune -af --filter "until=168h"
```
## Mudando o runner de máquina
O compose não tem nada preso à máquina: o Gitea é alcançado por
`GITEA_INSTANCE_URL`, que vem do `.env`. Para mover o runner basta subir o
compose no servidor novo e registrar de novo.
Na fase inicial de desenvolvimento é natural rodar na própria máquina de dev.
Duas ressalvas quando esse host também roda outros containers:
- Um `restart` do daemon Docker (necessário se você usar `restart` em vez de
`reload` para o `insecure-registries`) derruba tudo que estiver no host.
- Os builds competem por disco e CPU com os containers já existentes.
Ao migrar para o servidor do Gitea, o registro anterior deve ser removido em
Settings › Actions › Runners, e a pasta `./data` do host antigo descartada.
`GITEA_INSTANCE_URL` pode continuar apontando para o IP — não precisa virar
`localhost`.
## Notas
- Se o Gitea for acessado por hostname em vez de IP e o servidor do runner não
resolver esse nome, acrescente `extra_hosts` ao serviço no compose.
- Por padrão o runner executa **um job por vez**. Para paralelismo, monte um
`config.yaml` com `runner.capacity` e aponte `CONFIG_FILE` para ele.
- O socket do Docker montado dá ao runner controle total sobre o daemon do host.
Use um servidor dedicado a CI, não um que rode containers de produção.

View File

@@ -0,0 +1,22 @@
services:
act-runner:
image: ${ACT_RUNNER_IMAGE:-gitea/act_runner:0.6.1}
container_name: ${RUNNER_NAME:-conectasus-runner-01}
restart: unless-stopped
environment:
GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL:?informe a URL do Gitea}
GITEA_RUNNER_REGISTRATION_TOKEN: NJtScAqvccbNQJfdFnS8udtLsjSITXJq5bzmi41d
GITEA_RUNNER_NAME: ${RUNNER_NAME:-conectasus-runner-01}
GITEA_RUNNER_LABELS: ${RUNNER_LABELS:-ubuntu-latest:docker://gitea/runner-images:ubuntu-latest}
TZ: ${TZ:-America/Sao_Paulo}
volumes:
- runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
runner-data:

21
.gitea/runner/compose.yml Normal file
View File

@@ -0,0 +1,21 @@
name: gitea-runner
services:
act-runner:
image: ${ACT_RUNNER_IMAGE:-gitea/act_runner:0.6.1}
container_name: ${RUNNER_NAME:-conectasus-runner-01}
restart: unless-stopped
environment:
GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL:?defina GITEA_INSTANCE_URL no .env}
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN:?defina GITEA_RUNNER_REGISTRATION_TOKEN no .env}
GITEA_RUNNER_NAME: ${RUNNER_NAME:-conectasus-runner-01}
GITEA_RUNNER_LABELS: ${RUNNER_LABELS:-ubuntu-latest:docker://gitea/runner-images:ubuntu-latest}
TZ: ${TZ:-America/Sao_Paulo}
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

View File

@@ -0,0 +1,453 @@
name: build-api-image
on:
push:
branches:
# Branch = ambiente. Um merge aceito aqui constroi e implanta no ambiente
# correspondente (mapeamento no step de metadados).
- develop # -> teste
- 'release-*' # -> homologacao
- feat_CI_CD_TEST # -> teste (branch de validacao da propria esteira)
tags:
- '2.*'
jobs:
build:
runs-on: ubuntu-latest
env:
REGISTRY: ${{ vars.REGISTRY_HOST || '192.168.0.41:3001' }}
IMAGE_NAME: ${{ vars.API_IMAGE_NAME || 'rkm/conectasus-api' }}
BASE_IMAGE: ${{ vars.API_BASE_IMAGE || 'tirkm/v2-application:6' }}
# O contexto e a raiz do application: o build precisa enxergar api/ e
# frontend/ ao mesmo tempo, entao Dockerfile e docker/ vivem na raiz.
CONTEXT_DIR: ${{ vars.CONTEXT_DIR || '.' }}
DOCKERFILE: ${{ vars.DOCKERFILE || 'Dockerfile' }}
# Subdiretorio da API, usado nas checagens que leem arquivos do repo
# (composer.json, migrations) — nao confundir com o contexto do build.
API_DIR: ${{ vars.API_DIR || 'api' }}
PUSH_LATEST: ${{ vars.PUSH_LATEST || 'true' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Definir metadados do build
id: meta
run: |
set -euo pipefail
if [ "${GITHUB_REF}" != "${GITHUB_REF#refs/tags/}" ]; then
IS_TAG=true
else
IS_TAG=false
fi
VERSION=$(echo "${GITHUB_REF_NAME}" | tr '/' '-')
IMAGE="${REGISTRY}/${IMAGE_NAME}"
# Branch -> ambiente. A tag de release constroi a imagem, mas nao
# implanta nem aplica migrations: ambos exigem acao manual.
case "${GITHUB_REF_NAME}" in
develop|feat_CI_CD_TEST) AMBIENTE=teste ;;
release-*) AMBIENTE=homolog ;;
*) AMBIENTE="" ;;
esac
[ "${IS_TAG}" = "true" ] && AMBIENTE=""
# Uma imagem serve todos os ambientes: a config do frontend e resolvida
# em runtime (config.js montado no deploy), nao gravada no bundle.
# Toda imagem tambem recebe uma tag pelo commit, para rastreabilidade
# e retencao. Tags de release sempre constroem uma imagem nova a
# partir do commit marcado: a tag e o marco formal do artefato.
SHA_TAG="sha-$(echo "${GITHUB_SHA}" | cut -c1-8)"
echo "ambiente=${AMBIENTE}" >> "$GITHUB_OUTPUT"
echo "sha_tag=${SHA_TAG}" >> "$GITHUB_OUTPUT"
echo "tag_sha=${IMAGE}:${SHA_TAG}" >> "$GITHUB_OUTPUT"
echo "is_tag=${IS_TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
echo "tag_version=${IMAGE}:${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag_latest=${IMAGE}:latest" >> "$GITHUB_OUTPUT"
echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
echo "Evento : $([ "${IS_TAG}" = "true" ] && echo tag || echo branch)"
echo "Versao : ${VERSION}"
echo "Imagem : ${IMAGE}:${VERSION}"
echo "Base : ${BASE_IMAGE}"
- name: Conferir versao do composer.json
run: |
set -euo pipefail
if [ "${{ steps.meta.outputs.is_tag }}" != "true" ]; then
echo "Build de branch; comparacao com composer.json nao se aplica."
exit 0
fi
DECLARED=$(grep -m1 '"version"' "${API_DIR}/composer.json" \
| sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')
TAG="${{ steps.meta.outputs.version }}"
if [ "${DECLARED}" != "${TAG}" ]; then
echo "::warning::composer.json declara versao '${DECLARED}' mas a tag e '${TAG}'. Build segue com a tag."
else
echo "composer.json e tag batem: ${TAG}"
fi
- name: Avaliar reversibilidade das migrations
id: rollback
run: |
set -euo pipefail
PREV=$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)
if [ -z "${PREV}" ]; then
echo "Nenhuma tag anterior encontrada; analise de rollback ignorada."
echo "safe=unknown" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Comparando ${PREV} -> ${GITHUB_REF_NAME}"
NOVAS=$(git diff --name-only --diff-filter=A "${PREV}" "${GITHUB_REF_NAME}" \
-- "${API_DIR}/database/migrations" || true)
if [ -z "${NOVAS}" ]; then
echo "Nenhuma migration nova neste release."
echo "safe=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Migrations novas:"
echo "${NOVAS}" | sed 's/^/ /'
DESTRUTIVAS=""
while IFS= read -r f; do
[ -f "${f}" ] || continue
UP=$(sed '/function[[:space:]]*down[[:space:]]*(/,$d' "${f}")
if echo "${UP}" | grep -qE 'dropColumn|renameColumn|dropIfExists|DROP TABLE|DROP COLUMN'; then
DESTRUTIVAS="${DESTRUTIVAS}${f}"$'\n'
fi
done <<< "${NOVAS}"
{
echo "### Reversibilidade"
echo ""
echo "Comparado com \`${PREV}\`."
} >> "$GITHUB_STEP_SUMMARY"
if [ -z "${DESTRUTIVAS}" ]; then
echo "Todas as migrations novas sao aditivas."
echo "safe=true" >> "$GITHUB_OUTPUT"
{
echo ""
echo "Migrations novas sao **aditivas**. Rollback da imagem para \`${PREV}\` e seguro mesmo com o schema ja migrado."
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
echo "safe=false" >> "$GITHUB_OUTPUT"
echo "${DESTRUTIVAS}" | while IFS= read -r f; do
[ -n "${f}" ] && echo "::warning file=${f}::Migration destrutiva no up(): rollback para ${PREV} nao sera seguro apos aplicar."
done
{
echo ""
echo "**Atencao: este release contem migrations destrutivas.**"
echo ""
echo "Depois de aplicar a migration, voltar a imagem para \`${PREV}\` NAO restaura o funcionamento:"
echo "o codigo antigo vai referenciar colunas/tabelas que deixaram de existir. Faca backup do banco"
echo "antes de migrar, ou planeje a correcao como hotfix para frente."
echo ""
echo "| Migration |"
echo "| --- |"
echo "${DESTRUTIVAS}" | while IFS= read -r f; do
[ -n "${f}" ] && echo "| \`${f}\` |"
done
} >> "$GITHUB_STEP_SUMMARY"
- name: Login no Docker Hub (imagem base)
env:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
set -euo pipefail
if [ -z "${DOCKERHUB_TOKEN:-}" ]; then
echo "DOCKERHUB_TOKEN nao definido; assumindo imagem base publica ou ja em cache."
exit 0
fi
echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USER}" --password-stdin
- name: Login no registry do Gitea
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
USER="${REGISTRY_USER:-$ACTOR}"
TOKEN="${REGISTRY_TOKEN:-}"
if [ -z "${TOKEN}" ]; then
echo "REGISTRY_TOKEN ausente; usando o GITEA_TOKEN do job."
TOKEN="${GITEA_TOKEN}"
USER="${ACTOR}"
fi
echo "${TOKEN}" | docker login "${REGISTRY}" -u "${USER}" --password-stdin
- name: Build da imagem
run: |
set -euo pipefail
docker build \
--file "${DOCKERFILE}" \
--build-arg BASE_IMAGE="${BASE_IMAGE}" \
--build-arg APP_VERSION="${{ steps.meta.outputs.version }}" \
--build-arg VCS_REF="${GITHUB_SHA}" \
--build-arg BUILD_DATE="${{ steps.meta.outputs.build_date }}" \
--build-arg SOURCE_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \
--tag "${{ steps.meta.outputs.tag_version }}" \
--tag "${{ steps.meta.outputs.tag_sha }}" \
"${CONTEXT_DIR}"
- name: Smoke test da imagem
run: |
set -euo pipefail
IMG="${{ steps.meta.outputs.tag_version }}"
echo "--> aplicacao deve bootar com config presente"
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
cd /var/www/api
cp .env.example .env
cp connections.json.example connections.json
php artisan --version
test -f bootstrap/cache/routes-v7.php || test -f bootstrap/cache/routes.php
test -f bootstrap/cache/packages.php
'
echo "--> imagem nao pode carregar credenciais"
if docker run --rm --entrypoint bash "${IMG}" -c 'test -f /var/www/api/.env || test -f /var/www/api/connections.json'; then
echo "::error::.env ou connections.json foram parar dentro da imagem"
exit 1
fi
echo "--> entrypoint deve recusar subir sem config"
if docker run --rm "${IMG}" true 2>/dev/null; then
echo "::error::entrypoint subiu sem .env/connections.json"
exit 1
fi
echo "--> frontend deve estar publicado no dist"
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
test -f /var/www/frontend/dist/index.html
test -d /var/www/frontend/dist/js
test -d /var/www/frontend/dist/css
'
echo "--> imagem NAO pode ter URL de ambiente gravada no bundle"
# O ponto da config em runtime e a imagem ser neutra. Se alguem voltar
# a ler process.env.VUE_APP_* num componente, o webpack grava o valor
# no bundle e a imagem silenciosamente vira especifica de um ambiente —
# o que quebra a promessa de "o que foi homologado e o que vai a prod".
# O index.html precisa carregar o config.js antes do bundle.
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
test -f /var/www/frontend/dist/config.js
grep -q "__APP_CONFIG__" /var/www/frontend/dist/config.js
grep -q "config.js" /var/www/frontend/dist/index.html
'
echo "--> apache deve servir api na :80 e frontend na :8080"
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
apache2ctl configtest 2>&1 | grep -q "Syntax OK"
apache2ctl -S 2>/dev/null | grep -q "^\*:80 "
apache2ctl -S 2>/dev/null | grep -q "^\*:8080 "
'
# As checagens acima usam --entrypoint bash, que nunca executa o CMD.
# As de baixo sobem o container de verdade: sao as unicas que pegam
# defeito de supervisord/apache que so aparece no boot real.
#
# O job roda com o docker.sock do host, entao bind mount de arquivo
# criado aqui dentro nao chegaria ao daemon (o caminho nao existe no
# host). Por isso a config entra via 'docker cp' num container criado
# e ainda nao iniciado.
TMP=$(mktemp -d)
docker run --rm --entrypoint bash "${IMG}" -c 'cat /var/www/api/.env.example' > "${TMP}/.env"
docker run --rm --entrypoint bash "${IMG}" -c 'cat /var/www/api/connections.json.example' > "${TMP}/connections.json"
sed -i "s|^APP_KEY=.*|APP_KEY=base64:$(head -c 32 /dev/urandom | base64)|" "${TMP}/.env"
printf 'window.__APP_CONFIG__ = { ROOT_API: "http://smoke-test", DNS: {}, ENV: "" }\n' > "${TMP}/config.js"
chmod 644 "${TMP}/.env" "${TMP}/connections.json" "${TMP}/config.js"
echo "--> entrypoint deve recusar subir sem a config de runtime do frontend"
# Sem o mount, o app subiria apontando para lugar nenhum em vez de
# falhar — o silencio e que e perigoso aqui.
CID_SEM=$(docker create "${IMG}")
docker cp "${TMP}/.env" "${CID_SEM}:/var/www/api/.env"
docker cp "${TMP}/connections.json" "${CID_SEM}:/var/www/api/connections.json"
docker start "${CID_SEM}" >/dev/null 2>&1 || true
sleep 3
if [ "$(docker inspect -f '{{.State.Running}}' "${CID_SEM}")" = "true" ]; then
echo "::error::container subiu sem /var/www/frontend/config.js montado"
docker rm -f "${CID_SEM}" >/dev/null 2>&1 || true
exit 1
fi
docker rm -f "${CID_SEM}" >/dev/null 2>&1 || true
echo "--> container deve subir com config e permanecer de pe"
CID=$(docker create "${IMG}")
cleanup() { docker rm -f "${CID}" >/dev/null 2>&1 || true; rm -rf "${TMP}"; }
trap cleanup EXIT
docker cp "${TMP}/.env" "${CID}:/var/www/api/.env"
docker cp "${TMP}/connections.json" "${CID}:/var/www/api/connections.json"
docker cp "${TMP}/config.js" "${CID}:/var/www/frontend/config.js"
docker start "${CID}" >/dev/null
for _ in $(seq 1 15); do
docker exec "${CID}" curl -s -o /dev/null http://localhost:8080/ 2>/dev/null && break
sleep 2
done
if [ "$(docker inspect -f '{{.State.Running}}' "${CID}")" != "true" ]; then
echo "::error::container nao permaneceu de pe (exit=$(docker inspect -f '{{.State.ExitCode}}' "${CID}"))"
docker logs "${CID}" 2>&1 | tail -20
exit 1
fi
CODE_API=$(docker exec "${CID}" curl -s -o /dev/null -w '%{http_code}' http://localhost:80/)
CODE_SPA=$(docker exec "${CID}" curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/)
CODE_ROTA=$(docker exec "${CID}" curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/rota/interna)
echo " api :80 -> ${CODE_API} | spa :8080 -> ${CODE_SPA} | spa /rota -> ${CODE_ROTA}"
# A API nao tem rota "/", entao 404 e resposta valida; o que nao pode e
# 000 (sem resposta). Ja o SPA precisa de 200 na raiz e tambem na rota
# interna — este segundo cobre o FallbackResource do mode: 'history'.
[ "${CODE_API}" != "000" ] || { echo "::error::apache nao respondeu na :80"; exit 1; }
[ "${CODE_SPA}" = "200" ] || { echo "::error::frontend nao respondeu 200 na :8080"; exit 1; }
[ "${CODE_ROTA}" = "200" ] || { echo "::error::FallbackResource do SPA nao esta ativo"; exit 1; }
echo "--> config.js servido deve ser o montado, e sem cache"
# Fecha o ciclo: nao basta o entrypoint copiar, o Apache precisa servir
# o arquivo instalado. E o no-store evita que o browser continue com a
# URL do ambiente anterior depois de uma troca.
SERVIDO=$(docker exec "${CID}" curl -s http://localhost:8080/config.js)
echo "${SERVIDO}" | grep -q 'smoke-test' \
|| { echo "::error::config.js servido nao e o montado: ${SERVIDO}"; exit 1; }
docker exec "${CID}" curl -sI http://localhost:8080/config.js | grep -qi 'cache-control: *no-store' \
|| { echo "::error::config.js servido sem Cache-Control no-store (mod_headers habilitado?)"; exit 1; }
echo "Smoke test OK"
- name: Push da imagem
run: |
set -euo pipefail
docker push "${{ steps.meta.outputs.tag_version }}"
docker push "${{ steps.meta.outputs.tag_sha }}"
if [ "${{ steps.meta.outputs.is_tag }}" != "true" ]; then
echo "Build de branch: tag 'latest' preservada."
exit 0
fi
if [ "${PUSH_LATEST}" = "true" ]; then
docker tag "${{ steps.meta.outputs.tag_version }}" "${{ steps.meta.outputs.tag_latest }}"
docker push "${{ steps.meta.outputs.tag_latest }}"
else
echo "PUSH_LATEST=${PUSH_LATEST}: tag 'latest' nao publicada."
fi
- name: Deploy no ambiente da branch
# Branch = ambiente: develop -> teste e release-* -> homolog.
# Tags e master nao fazem deploy por este workflow.
#
# Fica aqui, e nao num workflow proprio disparado por push, porque dois
# workflows no mesmo evento sao enfileirados em ordem arbitraria — o
# deploy poderia rodar antes de a imagem existir. Aqui a ordem e
# garantida e a imagem acabou de ser publicada.
#
# Precede a Limpeza de proposito: ela faz docker logout, e o deploy
# ainda precisa do login para o pull.
if: ${{ steps.meta.outputs.ambiente != '' && vars.AUTO_DEPLOY != 'false' }}
env:
AMBIENTE: ${{ steps.meta.outputs.ambiente }}
SSH_HOST: ${{ vars.TEST_SSH_HOST || '192.168.0.44' }}
SSH_PORT: ${{ vars.TEST_SSH_PORT || '22' }}
SSH_USER: ${{ vars.SSH_USER_TEST || secrets.SSH_USER_TEST }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY_TEST }}
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS_TEST }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
# O Gitea publica a tag em minusculas; a branch pode ter maiusculas.
TAG=$(echo "${{ steps.meta.outputs.version }}" | tr '[:upper:]' '[:lower:]')
if [ "${AMBIENTE}" = "teste" ]; then
echo "Deploy de teste usa SSH para ${SSH_HOST}:${SSH_PORT}."
chmod +x docker/deploy/deploy-over-ssh.sh
./docker/deploy/deploy-over-ssh.sh "${AMBIENTE}" "${TAG}"
else
chmod +x docker/deploy/deploy.sh
./docker/deploy/deploy.sh "${AMBIENTE}" "${TAG}"
fi
- name: Migrations automaticas do ambiente de teste
# Migrations nunca acompanham homolog/prod automaticamente. Em teste,
# a lista continua explicita para nao atingir conexoes tecnicas do
# connections.json. As duas variables sao opt-in deliberado.
if: ${{ steps.meta.outputs.ambiente == 'teste' && vars.AUTO_DEPLOY != 'false' && vars.AUTO_MIGRATE_TEST == 'true' }}
env:
TAG_VERSION: ${{ steps.meta.outputs.version }}
MUNICIPIOS: ${{ vars.MIGRATION_CONNECTIONS_TEST }}
run: |
set -euo pipefail
if [ -z "${MUNICIPIOS:-}" ]; then
echo "::error::AUTO_MIGRATE_TEST=true exige a variable MIGRATION_CONNECTIONS_TEST com a lista explicita de municipios."
exit 1
fi
# O Gitea normaliza a tag de branch para minusculas no registry.
TAG=$(echo "${TAG_VERSION}" | tr '[:upper:]' '[:lower:]')
chmod +x docker/deploy/migrate.sh
./docker/deploy/migrate.sh teste "${TAG}" "${MUNICIPIOS}"
- name: Resumo
if: always()
run: |
{
echo "### Imagem publicada"
echo ""
echo "| Campo | Valor |"
echo "| --- | --- |"
echo "| Versao | \`${{ steps.meta.outputs.version }}\` |"
echo "| Imagem | \`${{ steps.meta.outputs.tag_version }}\` |"
echo "| Base | \`${BASE_IMAGE}\` |"
echo "| Commit | \`${GITHUB_SHA}\` |"
echo ""
echo "**Uma imagem serve todos os ambientes.** A config do frontend e"
echo "resolvida em runtime: monte o \`config.js\` do ambiente em"
echo "\`/var/www/frontend/config.js\` (modelo em \`docker/config.js.example\`)."
echo "Tags de release constroem a imagem e a publicam no registry."
echo "Deploy e migrations de producao continuam sendo passos manuais."
echo ""
echo "Migrations municipais so executam automaticamente em teste quando"
echo "\`AUTO_MIGRATE_TEST=true\` e \`MIGRATION_CONNECTIONS_TEST\` estao definidos."
} >> "$GITHUB_STEP_SUMMARY"
- name: Limpeza
if: always()
run: |
docker rmi "${{ steps.meta.outputs.tag_version }}" \
"${{ steps.meta.outputs.tag_latest }}" \
"${{ steps.meta.outputs.tag_sha }}" 2>/dev/null || true
docker logout "${REGISTRY}" 2>/dev/null || true

102
.gitea/workflows/deploy.yml Normal file
View File

@@ -0,0 +1,102 @@
name: deploy
# Implanta uma imagem ja publicada. Nao constroi nada: a tag de release e o
# artefato formal, construido quando ela e criada e escolhido explicitamente
# para cada deploy.
#
# Producao nunca sobe sozinha: exige acionamento manual, com a versao escolhida.
on:
workflow_dispatch:
inputs:
ambiente:
description: 'Ambiente de destino'
required: true
type: choice
options:
- teste
- homolog
- prod
versao:
description: 'Tag da imagem (ex.: 2.59.3, feat_ci_cd_test)'
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
env:
REGISTRY: ${{ vars.REGISTRY_HOST || '192.168.0.41:3001' }}
IMAGE_NAME: ${{ vars.API_IMAGE_NAME || 'rkm/conectasus-api' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login no registry do Gitea
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
if [ -z "${REGISTRY_TOKEN:-}" ]; then
echo "::error::REGISTRY_TOKEN nao definido. O pull da imagem privada depende dele."
exit 1
fi
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u "${REGISTRY_USER:-$ACTOR}" --password-stdin
- name: Confirmar que a imagem existe antes de mexer no ambiente
run: |
set -euo pipefail
IMG="${REGISTRY}/${IMAGE_NAME}:${{ inputs.versao }}"
if ! docker manifest inspect "${IMG}" >/dev/null 2>&1; then
echo "::error::${IMG} nao existe no registry. Confira a tag (o Gitea publica em minusculas)."
exit 1
fi
echo "imagem encontrada: ${IMG}"
- name: Deploy
env:
AMBIENTE: ${{ inputs.ambiente }}
VERSAO: ${{ inputs.versao }}
SSH_HOST: ${{ vars.TEST_SSH_HOST || '192.168.0.44' }}
SSH_PORT: ${{ vars.TEST_SSH_PORT || '22' }}
SSH_USER: ${{ vars.SSH_USER_TEST || secrets.SSH_USER_TEST }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY_TEST }}
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS_TEST || secrets.SSH_KNOWN_HOST_TEST }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
if [ "${AMBIENTE}" = "teste" ]; then
echo "Deploy de teste usa SSH para ${SSH_HOST}:${SSH_PORT}."
chmod +x docker/deploy/deploy-over-ssh.sh
./docker/deploy/deploy-over-ssh.sh "${AMBIENTE}" "${VERSAO}"
else
chmod +x docker/deploy/deploy.sh
./docker/deploy/deploy.sh "${AMBIENTE}" "${VERSAO}"
fi
- name: Resumo
if: always()
run: |
{
echo "### Deploy"
echo ""
echo "| Campo | Valor |"
echo "| --- | --- |"
echo "| Ambiente | \`${{ inputs.ambiente }}\` |"
echo "| Versao | \`${{ inputs.versao }}\` |"
echo "| Acionado por | \`${{ github.actor }}\` |"
echo "| Resultado | \`${{ job.status }}\` |"
echo ""
echo "O deploy so troca a imagem. Migrations continuam sendo passo"
echo "separado e deliberado — ver \`docker/README.md\`."
} >> "$GITHUB_STEP_SUMMARY"
- name: Limpeza
if: always()
run: docker logout "${REGISTRY}" 2>/dev/null || true

View File

@@ -0,0 +1,99 @@
name: migrate
# Migrations alteram dados persistentes e nao acompanham o deploy automatico.
# Este workflow manual recebe uma imagem ja publicada e uma lista EXPLICITA de
# municipios; ele nunca tenta inferir ou migrar todas as conexoes.
on:
workflow_dispatch:
inputs:
ambiente:
description: 'Ambiente de destino'
required: true
type: choice
options:
- teste
- homolog
- prod
versao:
description: 'Tag da imagem ja publicada (ex.: 2.59.3)'
required: true
type: string
municipios:
description: 'Conexoes municipais separadas por virgula (ex.: anhembi,aparecida)'
required: true
type: string
confirmacao:
description: 'Digite MIGRAR para confirmar a alteracao dos bancos'
required: true
type: string
jobs:
migrate:
runs-on: ubuntu-latest
env:
REGISTRY: ${{ vars.REGISTRY_HOST || '192.168.0.41:3001' }}
IMAGE_NAME: ${{ vars.API_IMAGE_NAME || 'rkm/conectasus-api' }}
steps:
- name: Confirmar operacao destrutiva
env:
CONFIRMACAO: ${{ inputs.confirmacao }}
run: |
set -euo pipefail
if [ "${CONFIRMACAO}" != "MIGRAR" ]; then
echo "::error::Para executar migrations, informe confirmacao=MIGRAR."
exit 1
fi
- name: Checkout
uses: actions/checkout@v4
- name: Login no registry do Gitea
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
if [ -z "${REGISTRY_TOKEN:-}" ]; then
echo "::error::REGISTRY_TOKEN nao definido. A imagem privada nao pode ser usada."
exit 1
fi
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u "${REGISTRY_USER:-$ACTOR}" --password-stdin
- name: Aplicar migrations municipais
env:
AMBIENTE: ${{ inputs.ambiente }}
VERSAO: ${{ inputs.versao }}
MUNICIPIOS: ${{ inputs.municipios }}
run: |
set -euo pipefail
chmod +x docker/deploy/migrate.sh
./docker/deploy/migrate.sh \
"${AMBIENTE}" \
"${VERSAO}" \
"${MUNICIPIOS}"
- name: Resumo
if: always()
run: |
{
echo "### Migrations"
echo ""
echo "| Campo | Valor |"
echo "| --- | --- |"
echo "| Ambiente | \`${{ inputs.ambiente }}\` |"
echo "| Versao | \`${{ inputs.versao }}\` |"
echo "| Municipios | \`${{ inputs.municipios }}\` |"
echo "| Acionado por | \`${{ github.actor }}\` |"
echo "| Resultado | \`${{ job.status }}\` |"
echo ""
echo "A execucao para no primeiro municipio com falha. Bancos anteriores"
echo "podem ja ter recebido migrations e exigem verificacao antes de repetir."
} >> "$GITHUB_STEP_SUMMARY"
- name: Limpeza
if: always()
run: docker logout "${REGISTRY}" 2>/dev/null || true

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
*.code-workspace
.github
.exec
.github
.integracao
.qodo
.vscode
scripts/*.sh
vetur.config.js
.claude

124
Dockerfile Normal file
View File

@@ -0,0 +1,124 @@
ARG BASE_IMAGE=tirkm/v1-automation-ci
# Node 10 / npm 6 — o mesmo toolchain com que este projeto (vue 2.6 /
# @vue/cli-service 4.5) e construido hoje. Subir de versao aqui e uma mudanca
# a parte: muda o bundle gerado, e merece ser validada por si.
ARG NODE_IMAGE=node:10-alpine
# ---------------------------------------------------------------------------
# Frontend: npm so existe neste stage, e numa imagem dedicada — a base de
# runtime nao carrega toolchain de frontend. Para o runtime atravessa apenas o
# dist/.
# ---------------------------------------------------------------------------
FROM ${NODE_IMAGE} AS frontend-build
WORKDIR /build
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY frontend/ ./
# Nenhum .env entra aqui de proposito. A config do frontend e resolvida em
# RUNTIME, via public/config.js -> window.__APP_CONFIG__ (ver src/config.js),
# com o arquivo real montado no deploy. E o que mantem uma unica imagem servindo
# teste, homolog e producao: o binario homologado e o mesmo que vai para prod.
#
# Node 10 limita o heap a ~1.5GB por padrao e o webpack deste projeto estoura
# esse teto ("JavaScript heap out of memory"). O build so passa com o limite
# elevado.
RUN NODE_OPTIONS=--max-old-space-size=4096 npm run build
# ---------------------------------------------------------------------------
# API
# ---------------------------------------------------------------------------
FROM ${BASE_IMAGE} AS build
WORKDIR /build
COPY api/composer.json api/composer.lock ./
RUN composer install \
--no-dev \
--no-scripts \
--no-autoloader \
--no-interaction \
--prefer-dist \
--no-progress
COPY api/ ./
RUN set -eux; \
mkdir -p bootstrap/cache storage/framework/cache storage/framework/views storage/framework/sessions storage/logs; \
cp .env.example .env; \
cp connections.json.example connections.json; \
composer dump-autoload --no-dev --optimize --no-interaction; \
php artisan key:generate --no-interaction; \
php artisan route:cache; \
rm -f .env connections.json; \
rm -rf tests .git .idea .vscode; \
rm -f docker-compose.yml phpunit.xml .styleci.yml .editorconfig \
package.json package-lock.json webpack.mix.js .phpunit.result.cache; \
find storage/logs -type f -delete
# ---------------------------------------------------------------------------
# Runtime
# ---------------------------------------------------------------------------
FROM ${BASE_IMAGE} AS runtime
ARG APP_VERSION=dev
ARG VCS_REF=unknown
ARG BUILD_DATE=unknown
ARG SOURCE_URL=http://192.168.0.41:3001/rkm/conectasus
LABEL org.opencontainers.image.title="ConectaSUS V2" \
org.opencontainers.image.description="API Laravel + frontend Vue do ConectaSUS V2, encapsulados" \
org.opencontainers.image.version="${APP_VERSION}" \
org.opencontainers.image.revision="${VCS_REF}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.source="${SOURCE_URL}" \
org.opencontainers.image.vendor="RKM Sistemas"
ENV APP_VERSION=${APP_VERSION}
COPY docker/apache/ports.conf /etc/apache2/ports.conf
COPY docker/apache/000-default.conf /etc/apache2/sites-enabled/000-default.conf
COPY docker/apache/frontend.conf /etc/apache2/sites-enabled/frontend.conf
COPY docker/supervisor/conf.d/ /etc/supervisor/conf.d/
COPY docker/entrypoint.sh /usr/local/bin/entrypoint
# O frontend.conf usa 'Header set Cache-Control' no config.js, e mod_headers vem
# desabilitado na base :6. A base enxuta ja o habilita, mas manter aqui e de
# proposito: a2enmod e idempotente, e assim a imagem funciona sobre qualquer uma
# das duas bases em vez de depender de um detalhe interno dela.
RUN a2enmod headers
RUN chmod +x /usr/local/bin/entrypoint
COPY --from=build --chown=www-data:www-data /build /var/www/api
RUN set -eux; \
mkdir -p /var/www/api/storage/framework/cache \
/var/www/api/storage/framework/views \
/var/www/api/storage/framework/sessions \
/var/www/api/storage/logs \
/var/www/api/storage/app/public \
/var/www/api/storage/app/documento \
/var/www/api/storage/app/intranet \
/var/www/api/bootstrap/cache; \
chown -R www-data:www-data /var/www/api/storage /var/www/api/bootstrap/cache; \
chmod -R ug+rwX /var/www/api/storage /var/www/api/bootstrap/cache
# Por ultimo de proposito: e a camada que mais muda. Assim uma alteracao so de
# frontend nao invalida as camadas pesadas da API (vendor/, caches de boot).
COPY --from=frontend-build --chown=www-data:www-data /build/dist /var/www/frontend/dist
WORKDIR /var/www
EXPOSE 80 8080 6001
ENTRYPOINT ["/usr/local/bin/entrypoint"]
# -n (nodaemon) e obrigatorio: o supervisord.conf da imagem base nao define
# nodaemon=true, entao sem esta flag o supervisord daemoniza, o processo em
# primeiro plano encerra e o container sai com exit 0 logo apos subir.
CMD ["supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]

134
README.md Normal file
View File

@@ -0,0 +1,134 @@
# Antes de começar
- Reestaure os bancos de dados da INTRANET e do SIGTAP. (Esses arquivos podem ser encontrados em [192.168.0.5\backups\DATACENTER\BANCOS])
# Instalação
01 - Clone o repositório no seu ambiente de desenvolvimento:
```bash
git clone http://192.168.0.41:3001/rkm/conectasus.git .
```
02 - Entre no diretório da api:
```bash
cd api
```
03 - Copie os arquivos de configuração:
```bash
cp .env.example .env
cp connections.json.example connections.json
```
04 - Instale as dependências da api utilizando o `composer`:
```bash
composer install
```
05 - Gere as chaves de configuração:
```bash
php artisan key:generate
php artisan jwt:secret
```
06 - Configure o arquivo `.env`:
```bash
APP_NAME="ConectaSUS V2"
APP_URL=http://[URL_DO_PROJETO]
APP_URL_FRONTEND=http://[URL_DO_FRONTEND_DO_PROJETO]
MAIL_MAILER=smtp
MAIL_HOST=mail.rkmsistemas.com.br
MAIL_PORT=587
MAIL_USERNAME=socialnet@rkmsistemas.com.br
MAIL_PASSWORD=$ocial@2021RKM
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=socialnet@rkmsistemas.com.br
PUSHER_APP_ID=1
PUSHER_APP_KEY=1
PUSHER_APP_SECRET=1
PUSHER_APP_CLUSTER=mt1
# Remover a diretiva do .env.
WKHTML_PDF_BINARY=
```
07 - Configure o arquivo `connections.json`:
```json
{
"intranet": {
"driver": "mysql",
"host": "[IP_DO_SERVIDOR_DE_BANCO_DE_DADOS_AQUI]",
"port": "[PORTA_DO_SERVIDOR_DE_BANCO_DE_DADOS_AQUI]",
"database": "intranet",
"username": "[USUARIO_DO_BANCO_DE_DADOS_AQUI]",
"password": "[SENHA_DO_BANCO_DE_DADOS_AQUI]",
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci"
},
"sigtap": {
"driver": "mysql",
"host": "[IP_DO_SERVIDOR_DE_BANCO_DE_DADOS_AQUI]",
"port": "[PORTA_DO_SERVIDOR_DE_BANCO_DE_DADOS_AQUI]",
"database": "sigtap",
"username": "[USUARIO_DO_BANCO_DE_DADOS_AQUI]",
"password": "[SENHA_DO_BANCO_DE_DADOS_AQUI]",
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci"
},
"[PREFIXO_DO_CLIENTE_AQUI]": {
"driver": "mysql",
"host": "[IP_DO_SERVIDOR_DE_BANCO_DE_DADOS_AQUI]",
"port": "[PORTA_DO_SERVIDOR_DE_BANCO_DE_DADOS_AQUI]",
"database": "[NOME_DO_BANCO_DE_DADOS_DO_CLIENTE_AQUI]",
"username": "[USUARIO_DO_BANCO_DE_DADOS_AQUI]",
"password": "[SENHA_DO_BANCO_DE_DADOS_AQUI]",
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci"
}
}
```
08 - Crie o banco de dados com o nome do cliente `NOME_DO_BANCO_DE_DADOS_DO_CLIENTE_AQUI`.
09 - Execute as migrations e seeders:
```bash
php artisan migrate --seed
```
10 - Para criar as configurações iniciais, execute o seeder (Necessário executar apenas na primeira vez após instalação):
```bash
php artisan db:seed --class=InicialSeeder
```
11 - Inicie o servidor de websocket:
```bash
php artisan websockets:serve
```
12 - Entre no diretório do frontend:
```bash
cd frontend
```
13 - Copie o arquivo de configuração:
```bash
cp .env.development.local.example .env.development.local
```
14 - Configure o arquivo `.env.development.local`:
```bash
VUE_APP_ROOT_API=http://[URL_DA_API_DO_PROJETO]
VUE_APP_WS_APP_KEY=1
VUE_APP_WS_HOST=[IP_DO_SERVIDOR_APACHE]
VUE_APP_WS_PORT=[PORTA_DO_WEBSOCKET]
```
15 - Instale as dependências da api utilizando o `npm`:
```bash
npm ci
```
16 - Acesse a URL do projeto no navegador:
```bash
http://[URL_DO_FRONTEND_DO_PROJETO]:[PORTA_DO_FRONTEND_DO_PROJETO]/login/[PREFIXO_DO_CLIENTE]
```

21
api/.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.blade.php]
insert_final_newline = false
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[TODO*]
indent_size = 2

97
api/.env.example Normal file
View File

@@ -0,0 +1,97 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_URL_FRONTEND=http://localhost
LOG_CHANNEL=stack
LOG_LEVEL=debug
QUEUE_CONNECTION=database
QUEUE_NAME=default
QUEUE_DB_HOST=localhost
QUEUE_DB_PORT=3306
QUEUE_DB_USERNAME=root
QUEUE_DB_PASSWORD=
BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
MEMCACHED_HOST=memcached
REDIS_HOST=127.0.0.1
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_HOST=
PUSHER_PORT=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
# Dados para configuração do JWT.
JWT_SECRET=
JWT_SECRET_EXTERNAL=
JWT_TTL=480
# Dados para configuração do WKHTMLTOPDF para geração de PDF.
WKHTML_PDF_BINARY=
# Dados para acesso à API de assinatura eletrônica.
PKI_ENDPOINT_URL=
PKI_ACCESS_TOKEN=
# Dados para acesso à API de integração.
INTEGRACAO_CIDADAO_URL=http://localhost
INTEGRACAO_CIDADAO_SENHA=rkm2021
# Dados para acesso ao histórico do ConectaSUS V1.
CONECTASUS_V1_URL=
CONECTASUS_V1_URL_PEDERNEIRAS=
# Dados para envio de notificações via Telegram.
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
# Dados para acesso à integrações externas.
BNAFAR_HOMOLOGACAO_URL_API_SERVICO=https://servicoshm.saude.gov.br/bnafar
BNAFAR_HOMOLOGACAO_URL_API_TOKEN=http://servicoshm.saude.gov.br/jwtauth/auth
BNAFAR_PRODUCAO_URL_API_SERVICO=https://servicos.saude.gov.br/bnafar
BNAFAR_PRODUCAO_URL_API_TOKEN=https://servicos.saude.gov.br/jwtauth/auth
CNS_HOMOLOGACAO_URL_API_SERVICO=https://servicoshm.saude.gov.br/cadsus/v2/PDQSupplierJWT
CNS_HOMOLOGACAO_URL_API_TOKEN=https://ehr-auth-hmg.saude.gov.br/api/osb/token
CNS_PRODUCAO_URL_API_SERVICO=https://servicos.saude.gov.br/cadsus/v2/PDQSupplierJWT
CNS_PRODUCAO_URL_API_TOKEN=https://ehr-auth.saude.gov.br/api/osb/token
RIA_HOMOLOGACAO_URL_API_SERVICO=ehr-services-hmg.saude.gov.br/api
RIA_HOMOLOGACAO_URL_API_TOKEN=ehr-auth-hmg.saude.gov.br/api/token
RIA_PRODUCAO_URL_API_SERVICO=ehr-services.saude.gov.br/api
RIA_PRODUCAO_URL_API_TOKEN=ehr-auth.saude.gov.br/api/token
RIRA_HOMOLOGACAO_URL_API_SERVICO=https://ehr-services-hmg.saude.gov.br/api
RIRA_HOMOLOGACAO_URL_API_TOKEN=https://ehr-auth-hmg.saude.gov.br/api/token
RIRA_PRODUCAO_URL_API_SERVICO=https://uf-ehr-services.saude.gov.br/api
RIRA_PRODUCAO_URL_API_TOKEN=https://ehr-auth.saude.gov.br/api/token
RUN_SCHEDULE=

BIN
api/.env.swp Normal file

Binary file not shown.

5
api/.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore

18
api/.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
/node_modules
/public/hot
/public/storage
/storage/*.key
/storage/logs/mobile
/storage/pronto-atendimento/*
/vendor
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
connections.json
routes.txt
.idea/
TODO*

13
api/.styleci.yml Normal file
View File

@@ -0,0 +1,13 @@
php:
preset: laravel
disabled:
- no_unused_imports
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Console\Commands\Farmacia;
use App\Enums\Administracao\Modulo;
use App\Models\PainelSenha\Fila;
use App\Models\PainelSenha\OpcaoEntrada;
use App\Services\Administracao\ModuloService;
use App\Services\Telegram\TelegramService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class LimparFilaAtendimentoCommand extends Command
{
protected $signature = 'farmacia:limpar-fila-atendimento';
protected $description = 'Comando para limpeza da fila de atendimento no módulo farmácia.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
$this->getClientes()->each(function($cliente) {
try {
DB::beginTransaction();
Config::set('database.default', $cliente);
Fila::where('finalizado', false)
->where('evadido', false)
->whereIn('opcao_entrada_id', $this->getOpcoesEntrada())
->update([
'finalizado' => true,
'evadido' => true,
]);
Log::channel('farmacia-fila-atendimento')->info('Comando executado com sucesso.', [
'cliente' => $cliente,
'origem' => 'LimparFilaAtendimentoCommand',
]);
DB::commit();
} catch (Exception $error) {
DB::rollBack();
Log::channel('farmacia-fila-atendimento')->error('Erro ao executar o comando', [
'cliente' => $cliente,
'origem' => 'LimparFilaAtendimentoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Farmácia (' . ($cliente) . '):</strong> (Erro ao executar o comando: LimparFilaAtendimentoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('farmacia-fila-atendimento')->error('Erro ao executar o comando', [
'origem' => 'LimparFilaAtendimentoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Farmácia:</strong> (Erro ao executar o comando: LimparFilaAtendimentoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
private function getOpcoesEntrada()
{
return OpcaoEntrada::where('modulo_id', ModuloService::getId(Modulo::FARMACIA))
->pluck('id');
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace App\Console\Commands\Integracao\Bnafar;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\BnafarService;
use App\Services\Integracao\Bnafar\PosicaoEstoqueService;
use App\Services\Telegram\TelegramService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarPosicaoEstoqueCommand extends Command
{
protected $signature = 'integracao:bnafar-enviar-posicao-estoque';
protected $description = 'Comando para envio de posição de estoque ao BNAFAR.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
DB::beginTransaction();
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
$ibge = substr($cliente->municipio->ibge, 0, 6);
Config::set('database.default', $cliente->link);
$parametrizacao = BnafarService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = BnafarService::getToken($parametrizacao->usuario, $parametrizacao->senha);
if (!is_null($token)) {
$token = $token['access_token'];
PosicaoEstoqueService::store();
PosicaoEstoqueService::post($cliente->link, $token, $ibge);
Log::channel('integracao-bnafar')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarPosicaoEstoqueCommand',
]);
}
}
DB::commit();
} catch (Exception $error) {
DB::rollBack();
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarPosicaoEstoqueCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarPosicaoEstoqueCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'origem' => 'EnviarPosicaoEstoqueCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR:</strong> (Erro ao executar o comando: EnviarPosicaoEstoqueCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace App\Console\Commands\Integracao\Bnafar;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\Bnafar\SaidaService;
use App\Services\Integracao\BnafarService;
use App\Services\Telegram\TelegramService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarSaidaCommand extends Command
{
protected $signature = 'integracao:bnafar-enviar-saida';
protected $description = 'Comando para envio de saida ao BNAFAR.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
DB::beginTransaction();
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
$ibge = substr($cliente->municipio->ibge, 0, 6);
Config::set('database.default', $cliente->link);
$parametrizacao = BnafarService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = BnafarService::getToken($parametrizacao->usuario, $parametrizacao->senha);
if (!is_null($token)) {
$token = $token['access_token'];
SaidaService::store();
SaidaService::post($cliente->link, $token, $ibge);
Log::channel('integracao-bnafar')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarSaidaCommand',
]);
}
}
DB::commit();
} catch (Exception $error) {
DB::rollBack();
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarSaidaCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarSaidaCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'origem' => 'EnviarSaidaCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR:</strong> (Erro ao executar o comando: EnviarSaidaCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace App\Console\Commands\Integracao\Bnafar;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\Bnafar\PosicaoEstoqueService;
use App\Services\Integracao\BnafarService;
use App\Services\Telegram\TelegramService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class VerificarPosicaoEstoqueCommand extends Command
{
protected $signature = 'integracao:bnafar-verificar-posicao-estoque';
protected $description = 'Comando para verificação de posição de estoque enviada ao BNAFAR.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
DB::beginTransaction();
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
$ibge = substr($cliente->municipio->ibge, 0, 6);
Config::set('database.default', $cliente->link);
$parametrizacao = BnafarService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = BnafarService::getToken($parametrizacao->usuario, $parametrizacao->senha);
if (!is_null($token)) {
$token = $token['access_token'];
PosicaoEstoqueService::check($cliente->link, $token, $ibge);
Log::channel('integracao-bnafar')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'VerificarPosicaoEstoqueCommand',
]);
}
}
DB::commit();
} catch (Exception $error) {
DB::rollBack();
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'VerificarPosicaoEstoqueCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: VerificarPosicaoEstoqueCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'origem' => 'VerificarPosicaoEstoqueCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR:</strong> (Erro ao executar o comando: VerificarPosicaoEstoqueCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace App\Console\Commands\Integracao\Bnafar;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\Bnafar\SaidaService;
use App\Services\Integracao\BnafarService;
use App\Services\Telegram\TelegramService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class VerificarSaidaCommand extends Command
{
protected $signature = 'integracao:bnafar-verificar-saida';
protected $description = 'Comando para verificação de saida enviada ao BNAFAR.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
DB::beginTransaction();
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
$ibge = substr($cliente->municipio->ibge, 0, 6);
Config::set('database.default', $cliente->link);
$parametrizacao = BnafarService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = BnafarService::getToken($parametrizacao->usuario, $parametrizacao->senha);
if (!is_null($token)) {
$token = $token['access_token'];
SaidaService::check($cliente->link, $token, $ibge);
Log::channel('integracao-bnafar')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'VerificarSaidaCommand',
]);
}
}
DB::commit();
} catch (Exception $error) {
DB::rollBack();
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'VerificarSaidaCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: VerificarSaidaCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-bnafar')->error('Erro ao executar o comando', [
'origem' => 'VerificarSaidaCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração BNAFAR:</strong> (Erro ao executar o comando: VerificarSaidaCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,203 @@
<?php
namespace App\Console\Commands\Integracao\Erpsaude;
use App\Enums\Intranet\Sistema;
use App\Models\Integracao\Erpsaude\Atendimento as ErpsaudeAtendimento;
use App\Models\Integracao\Erpsaude\Exame as ErpsaudeExame;
use App\Models\Integracao\Erpsaude\Medicamento;
use App\Models\Integracao\Erpsaude\Paciente as ErpsaudePaciente;
use App\Models\Integracao\Erpsaude\Reavaliacao;
use App\Models\Integracao\Erpsaude\TipoAlta;
use App\Models\Integracao\Erpsaude\TipoAtendimento as ErpsaudeTipoAtendimento;
use App\Models\Integracao\Erpsaude\UnidadeAtendimento;
use App\Models\Integracao\Erpsaude\UnidadeEncaminhadora;
use App\Models\Intranet\Cliente;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class BuscarAtendimentosHospitalSantaCasaCommand extends Command
{
protected $signature = 'integracao:erpsaude-buscar-atendimentos-hospital-santa-casa';
protected $description = 'Comando para buscar os atendimentos realizados no hospital santa casa.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$cliente = Cliente::where('link', 'santarita')
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$token = env('ERPSAUDE_ACCESS_TOKEN');
$url = env('ERPSAUDE_ENDPOINT_URL') . "DadosPaciente";
$response = Http::withHeaders([
'Authorization' => "Bearer $token"
])->get($url);
$dados = $response->json();
if (!is_array($dados)) {
Log::channel('integracao-erpsaude')->error('A resposta da API não retornou um array válido', [
'cliente' => $cliente->link,
'origem' => 'BuscarAtendimentosHospitalSantaCasaCommand',
'dados' => $dados,
]);
return;
}
if ($response->successful()) {
foreach ($dados as $item) {
$cpf = isset($item['cpf']) ? substr(preg_replace('/\D/', '', $item['cpf']), 0, 11) : null;
$dataNascimento = Carbon::createFromFormat('d/m/Y', $item['dataNascimento'])->format('Y-m-d');
$cns = $item['cartaoNacionalSaude'];
$nome = $item['paciente'];
$paciente = null;
if ($cpf) {
$paciente = ErpsaudePaciente::where('cpf', $cpf)
->where('nome', $nome)
->where('data_nascimento', $dataNascimento)
->first();
}
if (!$paciente) {
$paciente = ErpsaudePaciente::where('cns', $cns)
->where('nome', $nome)
->where('data_nascimento', $dataNascimento)
->first();
}
if (!$paciente) {
$paciente = ErpsaudePaciente::create([
'nome' => $nome,
'cpf' => $cpf,
'cns' => $cns,
'data_nascimento' => $dataNascimento,
'sexo' => $item['sexo'],
'cep' => isset($item['cep']) ? preg_replace('/\D/', '', $item['cep']) : null,
]);
} else {
$paciente->update([
'nome' => $nome,
'cpf' => $cpf,
'cns' => $cns,
'data_nascimento' => $dataNascimento,
'sexo' => $item['sexo'],
'cep' => isset($item['cep']) ? preg_replace('/\D/', '', $item['cep']) : null,
]);
}
$tipoAlta = TipoAlta::where('codigo', $item['codTipoAlta'])->first();
$tipoAtendimento = ErpsaudeTipoAtendimento::where('codigo', (int) $item['codTipoAtendimento'])->first();
$unidadeEncaminhadora = UnidadeEncaminhadora::where('codigo', (int) $item['codUnEncaminhadora'])->first();
$unidadeAtendimento = UnidadeAtendimento::where('codigo', (int) $item['codUnAtendimento'])->first();
$atendimento = ErpsaudeAtendimento::create([
'paciente_id' => $paciente->id,
'registro_paciente' => $item['registroPaciente'],
'data' => Carbon::createFromFormat('d/m/Y', $item['data'])->format('Y-m-d'),
'hora' => $item['hora'],
'suspeita_maus_tratos' => $item['suspeitaMausTratos'],
'hipotese_diagnostica' => $item['hipoteseDiagnostica'],
'cid10' => $item['cid10'],
'descricao_procedimento' => $item['descricaoProcedimento'],
'descricao_exame_clinico' => $item['descricaoExameClinico'],
'conduta_medica' => $item['condutaMedica'],
'codigo_conta_prof' => $item['codigoContaProf'],
'profissional' => $item['profissional'],
'conselho' => $item['conselho'],
'uf_conselho' => $item['ufConselho'],
'convenio' => $item['convenio'],
'registro_ans' => $item['registroAns'],
'cod_convenio' => $item['codConvenio'],
'nome_setor' => $item['nomeSetor'],
'codigo_setor' => $item['codigoSetor'],
'cod_tipo_encaminhamento' => $item['codTipoEncaminhamento'],
'tipo_atendimento_id' => $tipoAtendimento ? $tipoAtendimento->id : null,
'cod_grupo_atendimento' => $item['codGrupoAtendimento'],
'unidade_encaminhadora_id' => $unidadeEncaminhadora ? $unidadeEncaminhadora->id : null,
'unidade_atendimento_id' => $unidadeAtendimento ? $unidadeAtendimento->id : null,
'data_atendimento' => Carbon::createFromFormat('d/m/Y', $item['dataAtendimento'])->format('Y-m-d'),
'data_alta' => !empty($item['dataAlta']) && preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $item['dataAlta'])
? Carbon::createFromFormat('d/m/Y', $item['dataAlta'])->format('Y-m-d')
: null,
'horario_atendimento' => $item['horarioAtendimento'],
'hora_alta' => isset($item['horaAlta']) && trim($item['horaAlta']) !== '' ? $item['horaAlta'] : null,
'tipo_alta_id' => $tipoAlta ? $tipoAlta->id : null,
'unidade_atendimento' => $item['unAtendimento'],
'cnes_un_atendimento' => $item['cnesUnAtendimento'],
]);
if (!empty($item['reavaliacoes'])) {
foreach ($item['reavaliacoes'] as $reavaliacao) {
Reavaliacao::create([
'atendimento_id' => $atendimento->id,
'reavaliacao' => $reavaliacao['reavalicao'],
'profissional' => $reavaliacao['profissional'],
'numero_conselho' => $reavaliacao['numConselho'],
'uf_conselho' => $reavaliacao['ufConselho'],
'data' => Carbon::createFromFormat('d/m/Y', $reavaliacao['dataReavaliacao'])->format('Y-m-d'),
'hora' => $reavaliacao['horaReavaliacao'],
]);
}
}
if (!empty($item['medicamentos'])) {
foreach ($item['medicamentos'] as $med) {
Medicamento::create([
'atendimento_id' => $atendimento->id,
'medicamento' => $med['medicamento'],
'quantidade' => str_replace(',', '.', $med['qtdePresc']),
'unidade_medida' => $med['uniMedPresc'],
'medicamento_id' => $med['idMedicamento'],
'tipo' => $med['tipo'],
]);
}
}
if (!empty($item['exames'])) {
foreach ($item['exames'] as $exame) {
ErpsaudeExame::create([
'atendimento_id' => $atendimento->id,
'exame' => $exame['descricao'] ?? '',
'tipo' => $exame['tipo'] ?? '',
]);
}
}
}
Log::channel('integracao-erpsaude')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'BuscarAtendimentosHospitalSantaCasaCommand'
]);
}
} catch (Exception $error) {
Log::channel('integracao-erpsaude')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'BuscarAtendimentosHospitalSantaCasaCommand',
'erro' => $error->getMessage(),
]);
return;
}
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace App\Console\Commands\Integracao\Esus;
use App\Models\Integracao\Esus\Parametrizacao;
use App\Services\AtencaoBasica\Exportacao\ExportacaoService;
use App\Services\Integracao\EsusService;
use App\Services\Telegram\TelegramService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarCommand extends Command
{
protected $signature = 'integracao:esus-enviar';
protected $description = 'Comando para envio de fichas ao e-SUS.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$currentDatabase = Config::get('database.default');
$mesAnoInicial = '072025';
try {
self::getClientes()->each(function($cliente) use($mesAnoInicial) {
try {
Config::set('database.default', $cliente);
$parametrizacao = Parametrizacao::first();
if (!($parametrizacao->ativo ?? false)) {
return;
}
$parametrizacao->url_instalacao = trim($parametrizacao->url_instalacao, '/');
try {
EsusService::getAuthorizationCookie($parametrizacao);
} catch (Exception $e) {
Log::channel('integracao-esus')->error('Falha na conexão.', [
'cliente' => $cliente,
'origem' => 'EnviarCommand',
'erro' => 'Exportação não realizada (falha no cookie inicial)',
]);
return;
}
foreach (ExportacaoService::gerarExportacoes($mesAnoInicial) as $nomeFicha => $fichas) {
$parametrizacao->cookie = EsusService::getAuthorizationCookie($parametrizacao);
try {
EsusService::deleteInconsistencias($cliente, $nomeFicha);
foreach ($fichas as $ficha) {
try {
EsusService::sendFicha($cliente, $parametrizacao, $nomeFicha, $ficha);
}
catch (Exception $error) {
Log::channel('integracao-esus')->error('Erro ao executar o comando', [
'cliente' => $cliente,
'origem' => 'EnviarCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração ESUS (' . $cliente . '):</strong> (Erro ao executar o comando: EnviarCommand).');
}
}
}
}
catch (Exception $error) {
Log::channel('integracao-esus')->error('Erro ao executar o comando', [
'cliente' => $cliente,
'origem' => 'EnviarCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração ESUS (' . $cliente . '):</strong> (Erro ao executar o comando: EnviarCommand).');
}
}
}
Log::channel('integracao-esus')->info('Comando executado com sucesso.', [
'cliente' => $cliente,
'origem' => 'EnviarCommand'
]);
}
catch (Exception $error) {
Log::channel('integracao-esus')->error('Erro ao executar o comando', [
'cliente' => $cliente,
'origem' => 'EnviarCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração ESUS (' . $cliente . '):</strong> (Erro ao executar o comando: EnviarCommand).');
}
return;
}
});
}
catch (Exception $error) {
Log::channel('integracao-esus')->error('Erro ao executar o comando', [
'origem' => 'EnviarCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração ESUS:</strong> (Erro ao executar o comando: EnviarCommand).');
}
}
Config::set('database.default', $currentDatabase);
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')
->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])
))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Integracao\Ria\DocumentoImunologicoRotina;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Integracao\RiaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarDocumentoAlteradoCommand extends Command
{
protected $signature = 'integracao:ria-enviar-documento-alterado';
protected $description = 'Comando para envio de documentos alterados ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($cliente, $parametrizacao, $parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getDocumentosAlterados()
->each(function($documentoAlterado) use ($cliente, $parametrizacao, &$token, $expiresAt) {
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$documentoAlterado->identificador_solicitante = $parametrizacao->identificador_solicitante;
$documentoAlterado->cns_solicitante = $parametrizacao->cns_solicitante;
$documentoAlterado->data_hora_envio = Carbon::now();
$response = RiaService::sendDocumentoImunologicoRotina($headers, $documentoAlterado, $cliente->municipio->estado->uf);
if ($response->status() === 201) {
$documentoAlterado->identificador_rnds = $response->header('Location') ? $response->header('Location') : null;
$documentoAlterado->response = $response->json();
$documentoAlterado->alterar = false;
$documentoAlterado->atualizado = true;
DocumentoImunologicoRotinaService::update($documentoAlterado->documento_imunologico_rotina_id, $documentoAlterado);
}
else {
DocumentoImunologicoRotina::where('id', $documentoAlterado->documento_imunologico_rotina_id)->update([
'tentou_substituir' => true,
]);
DocumentoImunologicoRotinaService::updateInconsistencias($documentoAlterado->documento_imunologico_rotina_id, $response->json());
}
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarDocumentoAlteradoCommand'
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarDocumentoAlteradoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarDocumentoAlteradoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarDocumentoAlteradoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarDocumentoAlteradoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\RiaService;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarDocumentoComInconsistenciaCommand extends Command
{
protected $signature = 'integracao:ria-enviar-documento-com-inconsistencia';
protected $description = 'Comando para envio de documentos com inconsistencia ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($cliente, $parametrizacao, $parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getDocumentosComInconsistencia()
->each(function($documentoComInconsistencia) use ($cliente, $parametrizacao, &$token, $expiresAt) {
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$documentoComInconsistencia->identificador_solicitante = $parametrizacao->identificador_solicitante;
$documentoComInconsistencia->cns_solicitante = $parametrizacao->cns_solicitante;
$documentoComInconsistencia->data_hora_envio = Carbon::now();
$response = RiaService::sendDocumentoImunologicoRotina($headers, $documentoComInconsistencia, $cliente->municipio->estado->uf);
$documentoComInconsistencia->identificador_rnds = $response->header('Location') ? $response->header('Location') : null;
$documentoComInconsistencia->response = $response->json();
DocumentoImunologicoRotinaService::update($documentoComInconsistencia->documento_imunologico_rotina_id, $documentoComInconsistencia);
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarDocumentoComInconsistenciaCommand'
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarDocumentoComInconsistenciaCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarDocumentoComInconsistenciaCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarDocumentoComInconsistenciaCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarDocumentoComInconsistenciaCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\RiaService;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarDocumentoComInconsistenciaRetroativoCommand extends Command
{
protected $signature = 'integracao:ria-enviar-documento-com-inconsistencia-retroativo';
protected $description = 'Comando para envio de documentos com inconsistencia retroativos ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getDocumentosComInconsistenciaRetroativos()
->each(function($documentoComInconsistenciaRetroativo) use ($cliente, $parametrizacao, &$token, $expiresAt) {
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$documentoComInconsistenciaRetroativo->identificador_solicitante = $parametrizacao->identificador_solicitante;
$documentoComInconsistenciaRetroativo->cns_solicitante = $parametrizacao->cns_solicitante;
$documentoComInconsistenciaRetroativo->data_hora_envio = Carbon::now();
$response = RiaService::sendDocumentoImunologicoRotina($headers, $documentoComInconsistenciaRetroativo, $cliente->municipio->estado->uf);
$documentoComInconsistenciaRetroativo->identificador_rnds = $response->header('Location') ? $response->header('Location') : null;
$documentoComInconsistenciaRetroativo->response = $response->json();
DocumentoImunologicoRotinaService::update($documentoComInconsistenciaRetroativo->documento_imunologico_rotina_id, $documentoComInconsistenciaRetroativo);
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarDocumentoComInconsistenciaRetroativoCommand'
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarDocumentoComInconsistenciaRetroativoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarDocumentoComInconsistenciaRetroativoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarDocumentoComInconsistenciaRetroativoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarDocumentoComInconsistenciaRetroativoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Integracao\Ria\DocumentoImunologicoRotina;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\RiaService;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarExclusaoDocumentoCommand extends Command
{
protected $signature = 'integracao:ria-enviar-exclusao-documento';
protected $description = 'Comando para envio de exclusão de documentos ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getDocumentosParaExclusao()
->each(function($documento) use ($parametrizacao, &$token, $expiresAt) {
if (!app()->environment('production') && !str_contains($documento->identificador_rnds, 'ehr-services-hmg.saude.gov.br')) {
return;
}
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$documento->identificador_solicitante = $parametrizacao->identificador_solicitante;
$documento->cns_solicitante = $parametrizacao->cns_solicitante;
$documento->data_hora_envio = Carbon::now();
$response = RiaService::destroy($headers, $documento->identificador_rnds);
if ($response->status() === 204) {
DocumentoImunologicoRotina::destroy($documento->id);
}
else {
$documento->response = $response->json();
DocumentoImunologicoRotinaService::update($documento->id, $documento);
}
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarExclusaoDocumentoCommand',
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarExclusaoDocumentoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarExclusaoDocumentoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarExclusaoDocumentoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarExclusaoDocumentoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\RiaService;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarRegistroAplicacaoCommand extends Command
{
protected $signature = 'integracao:ria-enviar-registro-aplicacao';
protected $description = 'Comando para envio de novos registros de aplicação ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getRegistrosAplicacao()
->each(function($registroAplicacao) use ($cliente, $parametrizacao, &$token, $expiresAt) {
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$registroAplicacao->identificador_solicitante = $parametrizacao->identificador_solicitante;
$registroAplicacao->cns_solicitante = $parametrizacao->cns_solicitante;
$registroAplicacao->data_hora_envio = Carbon::now();
$response = RiaService::sendDocumentoImunologicoRotina($headers, $registroAplicacao, $cliente->municipio->estado->uf);
$registroAplicacao->identificador_rnds = $response->header('Location') ? $response->header('Location') : null;
$registroAplicacao->response = $response->json();
DocumentoImunologicoRotinaService::store($registroAplicacao);
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarRegistroAplicacaoCommand',
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarRegistroAplicacaoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarRegistroAplicacaoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarRegistroAplicacaoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarRegistroAplicacaoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\RiaService;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarRegistroAplicacaoRetroativoCommand extends Command
{
protected $signature = 'integracao:ria-enviar-registro-aplicacao-retroativo';
protected $description = 'Comando para envio de registros de aplicação retroativos ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getRegistrosAplicacaoRetroativos()
->each(function($registroAplicacaoRetroativo) use ($cliente, $parametrizacao, &$token, $expiresAt) {
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$registroAplicacaoRetroativo->identificador_solicitante = $parametrizacao->identificador_solicitante;
$registroAplicacaoRetroativo->cns_solicitante = $parametrizacao->cns_solicitante;
$registroAplicacaoRetroativo->data_hora_envio = Carbon::now();
$response = RiaService::sendDocumentoImunologicoRotina($headers, $registroAplicacaoRetroativo, $cliente->municipio->estado->uf);
$registroAplicacaoRetroativo->identificador_rnds = $response->header('Location') ? $response->header('Location') : null;
$registroAplicacaoRetroativo->response = $response->json();
DocumentoImunologicoRotinaService::store($registroAplicacaoRetroativo);
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarRegistroAplicacaoRetroativoCommand'
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarRegistroAplicacaoRetroativoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarRegistroAplicacaoRetroativoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarRegistroAplicacaoRetroativoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarRegistroAplicacaoRetroativoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\RiaService;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarRegistroAtualizacaoCommand extends Command
{
protected $signature = 'integracao:ria-enviar-registro-atualizacao';
protected $description = 'Comando para envio de novos registros de atulaização ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getRegistrosAtualizacao()
->each(function($registroAtualizacao) use ($cliente, $parametrizacao, &$token, $expiresAt) {
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$registroAtualizacao->identificador_solicitante = $parametrizacao->identificador_solicitante;
$registroAtualizacao->cns_solicitante = $parametrizacao->cns_solicitante;
$registroAtualizacao->data_hora_envio = Carbon::now();
$response = RiaService::sendDocumentoImunologicoRotina($headers, $registroAtualizacao, $cliente->municipio->estado->uf);
$registroAtualizacao->identificador_rnds = $response->header('Location') ? $response->header('Location') : null;
$registroAtualizacao->response = $response->json();
DocumentoImunologicoRotinaService::store($registroAtualizacao);
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarRegistroAtualizacaoCommand',
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarRegistroAtualizacaoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarRegistroAtualizacaoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarRegistroAtualizacaoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarRegistroAtualizacaoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands\Integracao\Ria;
use App\Enums\Intranet\Sistema;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\RiaService;
use App\Services\Integracao\Ria\DocumentoImunologicoRotinaService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarRegistroAtualizacaoRetroativoCommand extends Command
{
protected $signature = 'integracao:ria-enviar-registro-atualizacao-retroativo';
protected $description = 'Comando para envio de registros de atualização retroativos ao RIA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
try {
$cliente = Cliente::where('link', $cliente)
->where('sistema_id', Sistema::CONECTASUS)
->firstOrFail();
Config::set('database.default', $cliente->link);
$parametrizacao = RiaService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiaService::getToken($parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoImunologicoRotinaService::getRegistrosAtualizacaoRetroativos()
->each(function($registroAtualizacaoRetroativo) use ($cliente, $parametrizacao, &$token, $expiresAt) {
if (Carbon::now() >= $expiresAt) {
$token = RiaService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$registroAtualizacaoRetroativo->identificador_solicitante = $parametrizacao->identificador_solicitante;
$registroAtualizacaoRetroativo->cns_solicitante = $parametrizacao->cns_solicitante;
$registroAtualizacaoRetroativo->data_hora_envio = Carbon::now();
$response = RiaService::sendDocumentoImunologicoRotina($headers, $registroAtualizacaoRetroativo, $cliente->municipio->estado->uf);
$registroAtualizacaoRetroativo->identificador_rnds = $response->header('Location') ? $response->header('Location') : null;
$registroAtualizacaoRetroativo->response = $response->json();
DocumentoImunologicoRotinaService::store($registroAtualizacaoRetroativo);
});
Log::channel('integracao-ria')->info('Comando executado com sucesso.', [
'cliente' => $cliente->link,
'origem' => 'EnviarRegistroAtualizacaoRetroativoCommand'
]);
}
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'cliente' => $cliente->link ?? $cliente,
'origem' => 'EnviarRegistroAtualizacaoRetroativoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA (' . ($cliente->link ?? $cliente) . '):</strong> (Erro ao executar o comando: EnviarRegistroAtualizacaoRetroativoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('integracao-ria')->error('Erro ao executar o comando', [
'origem' => 'EnviarRegistroAtualizacaoRetroativoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIA:</strong> (Erro ao executar o comando: EnviarRegistroAtualizacaoRetroativoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace App\Console\Commands\Integracao\Rira;
use App\Models\Intranet\Cliente;
use App\Services\Integracao\Rira\DocumentoService;
use App\Services\Integracao\RiraService;
use App\Services\Telegram\TelegramService;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class EnviarCommand extends Command
{
protected $signature = 'integracao:rira-enviar';
protected $description = 'Comando para envio de solicitações ao RIRA.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$currentDatabase = Config::get('database.default');
try {
self::getClientes()->each(function($cliente) {
Config::set('database.default', $cliente);
$clienteModel = Cliente::where('link', $cliente)->where('sistema_id', 1)->firstOrFail();
$parametrizacao = RiraService::getParametrizacao();
if (!is_null($parametrizacao)) {
$token = RiraService::getToken($parametrizacao, $parametrizacao->senha);
$expiresAt = Carbon::now()->addMilliseconds($token['expires_in'])->subMinutes(2);
DocumentoService::getSolicitacoes()
->each(function($solicitacao) use ($parametrizacao, &$token, $expiresAt, $clienteModel) {
if (Carbon::now() >= $expiresAt) {
$token = RiraService::getToken($parametrizacao->senha);
}
$headers = [
'Authorization' => $parametrizacao->cns_solicitante,
'Content-Type' => 'application/json',
'X-Authorization-Server' => "Bearer " . $token['access_token'],
];
$solicitacao->identificador_solicitante = $parametrizacao->identificador_solicitante;
$solicitacao->cns_solicitante = $parametrizacao->cns_solicitante;
$solicitacao->data_hora_envio = Carbon::now();
$response = RiraService::send($clienteModel, $headers, $solicitacao);
$urlRnds = null;
$identificadorRnds = null;
if (!empty($response->header('Location'))) {
$urlRnds = $response->header('Location');
$urlRndsExploded = explode('/', $urlRnds);
$identificadorRnds = $urlRndsExploded[count($urlRndsExploded) - 1];
}
$solicitacao->url_rnds = $urlRnds;
$solicitacao->identificador_rnds = $identificadorRnds;
$solicitacao->response = $response->json();
DocumentoService::save($solicitacao);
});
Log::channel('integracao-rira')->info('Comando executado com sucesso.', [
'cliente' => $cliente,
'origem' => 'EnviarCommand'
]);
}
});
} catch (Exception $error) {
Log::channel('integracao-rira')->error('Erro ao executar o comando', [
'origem' => 'EnviarCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Integração RIRA:</strong> (Erro ao executar o comando: EnviarCommand).');
}
}
Config::set('database.default', $currentDatabase);
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')
->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])
))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Console\Commands\PainelSenha;
use App\Events\PainelSenha\AvisoEvent;
use App\Models\PainelSenha\AvisoAgendamento;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
class EnvioAvisoAgendadoCommand extends Command
{
protected $signature = 'painel-senha:envio-aviso-agendado';
protected $description = 'Comando para enviar os avisos que estão agendados';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
self::getClientes()->each(function($cliente) {
Config::set('database.default', $cliente);
if(!Schema::hasTable('painel_senha_aviso')) {
return;
}
$agora = Carbon::now();
$toleranciaInicio = $agora->copy()->subSeconds(30);
$toleranciaFim = $agora->copy()->addSeconds(30);
$agendamentos = DB::table('painel_senha_aviso_agendamento')
->whereDate('data_hora_agendada', $agora->toDateString())
->whereBetween('data_hora_agendada', [$toleranciaInicio, $toleranciaFim])
->whereNull('data_hora_executada')
->where('painel_senha_aviso.is_ativo', true)
->join('painel_senha_aviso', 'painel_senha_aviso.id', 'painel_senha_aviso_agendamento.aviso_id')
->get();
if($agendamentos->isEmpty()) {
return;
}
$idsAgendamentos = $agendamentos->pluck("id");
$idsAvisos = $agendamentos->pluck("aviso_id");
AvisoAgendamento::whereIn('id', $idsAgendamentos)
->update(['data_hora_executada' => $agora]);
$avisos = DB::table('painel_senha_aviso')
->select(
'painel_senha_aviso.id',
'painel_senha_aviso.mensagem',
DB::raw('GROUP_CONCAT(cadastro_unidade.nome_fantasia) as unidades'),
DB::raw('GROUP_CONCAT(cadastro_unidade.id) as ids_unidade'),
'painel_senha_aviso.is_ativo',
)
->join('painel_senha_aviso_unidade', 'painel_senha_aviso_unidade.aviso_id', 'painel_senha_aviso.id')
->leftJoin('cadastro_unidade', 'cadastro_unidade.id', 'painel_senha_aviso_unidade.unidade_id')
->groupBy('painel_senha_aviso.id', 'painel_senha_aviso.mensagem', 'painel_senha_aviso.is_ativo')
->whereIn('painel_senha_aviso.id', $idsAvisos)
->get();
$avisos->map(function($aviso) {
AvisoEvent::dispatch($aviso->id, $aviso);
});
});
Config::set('database.default', $currentDatabase);
} catch (\Throwable $th) {
Log::info("Não foi possível enviar aviso {$th}");
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Console\Commands\PainelSenha;
use App\Enums\Administracao\Modulo;
use App\Models\PainelSenha\Fila;
use App\Models\PainelSenha\OpcaoEntrada;
use App\Services\Administracao\ModuloService;
use App\Services\Telegram\TelegramService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class LimparFilaAtendimentoCommand extends Command
{
protected $signature = 'painel-senha:limpar-fila-atendimento';
protected $description = 'Comando para limpesa da fila de atendimento no módulo painel de senha.';
public function __construct()
{
parent::__construct();
}
public function handle()
{
try {
$currentDatabase = Config::get('database.default');
$this->getClientes()->each(function($cliente) {
try {
DB::beginTransaction();
Config::set('database.default', $cliente);
Fila::where('finalizado', false)
->where('evadido', false)
->whereIn('opcao_entrada_id', $this->getOpcoesEntrada())
->update([
'finalizado' => true,
'evadido' => true,
]);
Log::channel('painel-senha-fila-atendimento')->info('Comando executado com sucesso.', [
'cliente' => $cliente,
'origem' => 'LimparFilaAtendimentoCommand',
]);
} catch (Exception $error) {
DB::rollBack();
Log::channel('painel-senha-fila-atendimento')->error('Erro ao executar o comando', [
'cliente' => $cliente,
'origem' => 'LimparFilaAtendimentoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Farmácia (' . ($cliente) . '):</strong> (Erro ao executar o comando: LimparFilaAtendimentoCommand).');
}
return;
}
});
Config::set('database.default', $currentDatabase);
} catch (Exception $error) {
Log::channel('painel-senha-fila-atendimento')->error('Erro ao executar o comando', [
'origem' => 'LimparFilaAtendimentoCommand',
'erro' => $error->getMessage(),
]);
if (env('APP_ENV') === 'production') {
TelegramService::send('<strong>Painel de senha:</strong> (Erro ao executar o comando: LimparFilaAtendimentoCommand).');
}
}
}
private function getClientes()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => empty(DB::connection('schema')->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", [$config['database']])))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
private function getOpcoesEntrada()
{
return OpcaoEntrada::where('modulo_id', ModuloService::getId(Modulo::PAINEL_SENHA))
->pluck('id');
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class PublishCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'publish';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run migrations and seeders';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->getCustomers()->each(function ($customer, $i) {
if ($i > 0) {
$this->getOutput()->writeln("\n");
}
$this->getOutput()->writeln("<question>{$customer}</question>");
$this->call('migrate', ['--database' => $customer, '--force' => true]);
$this->call('db:seed', ['--database' => $customer, '--force' => true]);
});
return 0;
}
private function getCustomers()
{
return collect(json_decode(file_get_contents(base_path('connections.json')), true))
->filter(fn ($config) => isset($config['database']) && Str::startsWith($config['database'], 'conectasus'))
->reject(fn ($config) => !$this->databaseExists($config['database']))
->map(fn ($config, $customer) => $customer)
->sort()
->values();
}
private function databaseExists($databaseName)
{
return !empty(DB::connection('schema')->select(
"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?",
[$databaseName]
));
}
}

View File

@@ -0,0 +1,280 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Symfony\Component\Console\Input\InputOption;
class SeedCommand extends Command
{
use ConfirmableTrait;
/**
* The console command name.
*
* @var string
*/
protected $name = 'db:seed';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Seed the database with records';
/**
* The connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* Create a new database seed command instance.
*
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @return void
*/
public function __construct(Resolver $resolver)
{
parent::__construct();
$this->resolver = $resolver;
$this->files = new Filesystem();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
$previousConnection = $this->resolver->getDefaultConnection();
$this->resolver->setDefaultConnection($this->getDatabase());
Schema::disableForeignKeyConstraints();
self::getSeedersClasses()->each(function ($class) {
Model::unguarded(function () use ($class) {
$this->getOutput()->writeln("<comment>Seeding:</comment> {$class['file_name']}");
$startTime = microtime(true);
$this->getSeeder(class_basename($class['instance']))->__invoke();
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
$this->getOutput()->writeln("<info>Seeded:</info> {$class['file_name']} ({$runTime}ms)");
DB::connection($this->getDatabase())->table('seeders')->insert(['seeder' => $class['file_name']]);
});
});
Schema::enableForeignKeyConstraints();
if ($previousConnection) {
$this->resolver->setDefaultConnection($previousConnection);
}
$this->info('Database seeding completed successfully.');
return 0;
}
private function getSeedersClasses()
{
$ran = DB::connection($this->getDatabase())->table('seeders')->select('seeder')->pluck('seeder')->all();
$files = $this->getSeederFiles();
$this->requireFiles($seeders = $this->pendingSeeders(
$files, $ran
));
$classes = collect();
foreach ($seeders as $file) {
$fileName = $this->getSeederName($file);
$classes->push([
'file_name' => $fileName,
'instance' => $this->resolve($fileName)
]);
}
return $classes;
}
/**
* Get all of the seeder files.
*
* @return array
*/
public function getSeederFiles()
{
return Collection::make(self::getSeederPaths())->flatMap(function ($path) {
return Str::endsWith($path, '.php') ? [$path] : $this->files->glob($path.'/*_*.php');
})->filter()->values()->keyBy(function ($file) {
return $this->getSeederName($file);
})->sortBy(function ($file, $key) {
return $key;
})->all();
}
/**
* Get seeder path.
*
* @return string
*/
protected function getSeederPaths()
{
$invalidContents = ['.', '..', '.gitkeep', 'base'];
$seedersDir = database_path() . '/seeders/';
return $this->getSeederFilesPaths(
$invalidContents,
$seedersDir
);
}
private function getSeederFilesPaths($invalidContents, $currentDirectory, $paths = [])
{
$contents = opendir($currentDirectory);
while ($content = readdir($contents)) {
if (!in_array($content, $invalidContents)) {
$fullPath = $currentDirectory . $content;
if (is_dir($fullPath)) {
$paths = $this->getSeederFilesPaths(
$invalidContents,
$fullPath . '/',
$paths
);
continue;
}
$paths[] = $fullPath;
}
}
return $paths;
}
/**
* Require in all the seeder files in a given path.
*
* @param array $files
* @return void
*/
public function requireFiles(array $files)
{
foreach ($files as $file) {
$this->files->requireOnce($file);
}
}
/**
* Resolve a seeder instance from a file.
*
* @param string $file
* @return object
*/
public function resolve($file)
{
$fileArr = explode('_', $file);
$class = Str::studly(implode('_', array_slice($fileArr, 4))).'_'.implode(array_slice($fileArr, 0, 4));
return new $class;
}
/**
* Get the name of the seeder.
*
* @param string $path
* @return string
*/
public function getSeederName($path)
{
return str_replace('.php', '', basename($path));
}
/**
* Get the seeder files that have not yet run.
*
* @param array $files
* @param array $ran
* @return array
*/
protected function pendingSeeders($files, $ran)
{
return Collection::make($files)
->reject(function ($file) use ($ran) {
return in_array($this->getSeederName($file), $ran);
})->values()->all();
}
/**
* Get a seeder instance from the container.
* @param string $class
* @return \Illuminate\Database\Seeder
*/
protected function getSeeder($class)
{
return $this->laravel->make($class)
->setContainer($this->laravel)
->setCommand($this);
}
/**
* Get the name of the database connection to use.
*
* @return string
*/
protected function getDatabase()
{
$database = $this->input->getOption('database');
return $database ?: $this->laravel['config']['database.default'];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'],
['force', null, InputOption::VALUE_NONE, 'Force the operation to run when in production'],
];
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Console\Commands;
use Database\Seeders\Base\Funcionalidade;
use Database\Seeders\Base\FuncionalidadeSub;
use Database\Seeders\Base\Menu;
use Database\Seeders\Base\Modulo;
use Database\Seeders\Base\Permissao;
use Illuminate\Console\Command;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use Symfony\Component\Console\Input\InputOption;
class SeedPermissionsCommand extends Command
{
/**
* The console command name.
*
* @var string
*/
protected $name = 'dev:seed-permissions';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Seed the database with permissions records';
/**
* The connection resolver instance.
*
* @var \Illuminate\Database\ConnectionResolverInterface
*/
protected $resolver;
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* Create a new database seed command instance.
*
* @param \Illuminate\Database\ConnectionResolverInterface $resolver
* @return void
*/
public function __construct(Resolver $resolver)
{
parent::__construct();
$this->resolver = $resolver;
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$previousConnection = $this->resolver->getDefaultConnection();
$this->resolver->setDefaultConnection($this->getDatabase());
collect([
Modulo::class,
Funcionalidade::class,
FuncionalidadeSub::class,
Menu::class,
Permissao::class,
])->each(function ($model) {
$this->getOutput()->writeln("<comment>Seeding:</comment> {$model}");
$startTime = microtime(true);
$model::run();
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
$this->getOutput()->writeln("<info>Seeded:</info> {$model} ({$runTime}ms)");
});
if ($previousConnection) {
$this->resolver->setDefaultConnection($previousConnection);
}
$this->info('Permissions seeding completed successfully.');
return 0;
}
/**
* Get the name of the database connection to use.
*
* @return string
*/
protected function getDatabase()
{
$database = $this->input->getOption('database');
return $database ?: $this->laravel['config']['database.default'];
}
/**
* Get the console command options.
*
* @return array
*/
protected function getOptions()
{
return [
['database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'],
];
}
}

View File

@@ -0,0 +1,211 @@
<?php
namespace App\Console\Commands;
use DateTime;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Composer;
use Illuminate\Support\Str;
use InvalidArgumentException;
class SeederMakeCommand extends Command
{
/**
* The console command signature.
*
* @var string
*/
protected $signature = 'make:seeder {name : The name of the seeder}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new seeder file';
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The Composer instance.
*
* @var \Illuminate\Support\Composer
*/
protected $composer;
/**
* Create a new seeder make command instance.
*
* @param \Illuminate\Filesystem\Filesystem $files
* @param \Illuminate\Support\Composer $composer
* @return void
*/
public function __construct(Filesystem $files, Composer $composer)
{
parent::__construct();
$this->files = $files;
$this->composer = $composer;
}
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$name = Str::snake(trim($this->input->getArgument('name')));
$this->writeSeeder($name);
$this->composer->dumpAutoloads();
}
/**
* Write the seeder file to disk.
*
* @param string $name
* @return string
*/
protected function writeSeeder($name)
{
$file = $this->create(
$name, $this->getSeederPath()
);
$file = pathinfo($file, PATHINFO_FILENAME);
$this->line("<info>Created Seeder:</info> {$file}");
}
/**
* Get seeder path.
*
* @return string
*/
protected function getSeederPath()
{
return $this->laravel->databasePath().DIRECTORY_SEPARATOR.'seeders';
}
/**
* Create a new seeder at the given path.
*
* @param string $name
* @param string $path
* @return string
*
* @throws \Exception
*/
public function create($name, $path)
{
$dateTime = new DateTime();
$className = $this->getClassName($dateTime, $name);
$this->ensureSeederDoesntAlreadyExist($className, $path);
$stub = $this->getStub();
$path = $this->getPath($dateTime, $name, $path);
$this->files->ensureDirectoryExists(dirname($path));
$this->files->put(
$path, $this->populateStub($className, $stub)
);
return $path;
}
/**
* Ensure that a seeder with the given name doesn't already exist.
*
* @param string $name
* @param string $seederPath
* @return void
*
* @throws \InvalidArgumentException
*/
protected function ensureSeederDoesntAlreadyExist($className, $seederPath = null)
{
if (! empty($seederPath)) {
$seederFiles = $this->files->glob($seederPath.'/*.php');
foreach ($seederFiles as $seederFile) {
$this->files->requireOnce($seederFile);
}
}
if (class_exists($className)) {
throw new InvalidArgumentException("A {$className} class already exists.");
}
}
/**
* Get the seeder stub file.
*
* @return string
*/
protected function getStub()
{
$stub = $this->stubPath().'/seeder.stub';
return $this->files->get($stub);
}
/**
* Populate the place-holders in the seeder stub.
*
* @param string $className
* @param string $stub
* @return string
*/
protected function populateStub($className, $stub)
{
$stub = str_replace(
['DummyClass', '{{ class }}', '{{class}}'],
$className, $stub
);
return $stub;
}
/**
* Get the class name of a seeder name.
*
* @param string $name
* @return string
*/
protected function getClassName($dateTime, $name)
{
return Str::studly($name).'_'.$dateTime->format('YmdHis');
}
/**
* Get the full path to the seeder.
*
* @param string $name
* @param string $path
* @return string
*/
protected function getPath($dateTime, $name, $path)
{
return $path.'/'.$dateTime->format('Y_m_d_His').'_'.$name.'.php';
}
/**
* Get the path to the stubs.
*
* @return string
*/
public function stubPath()
{
return $this->laravel->basePath().'/stubs';
}
}

152
api/app/Console/Kernel.php Normal file
View File

@@ -0,0 +1,152 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
if (!config('app.run_schedule')) {
return;
}
// BNAFAR.
$schedule->command('integracao:bnafar-enviar-posicao-estoque')
->name('integracao:bnafar-enviar-posicao-estoque')
->withoutOverlapping()
->daily()
->at('22:00');
$schedule->command('integracao:bnafar-enviar-saida')
->name('integracao:bnafar-enviar-saida')
->withoutOverlapping()
->daily()
->at('22:00');
$schedule->command('integracao:bnafar-verificar-posicao-estoque')
->name('integracao:bnafar-verificar-posicao-estoque')
->withoutOverlapping()
->daily()
->at('02:30');
$schedule->command('integracao:bnafar-verificar-saida')
->name('integracao:bnafar-verificar-saida')
->withoutOverlapping()
->daily()
->at('02:30');
// Fila de atendimento.
$schedule->command('farmacia:limpar-fila-atendimento')
->name('farmacia-limpar-fila-atendimento')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('00:25');
$schedule->command('painel-senha:limpar-fila-atendimento')
->name('painel-senha-limpar-fila-atendimento')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('00:30');
// Avisos do painel de senha
$schedule->command('painel-senha:envio-aviso-agendado')->everyMinute();
// RIA.
$schedule->command('integracao:ria-enviar-registro-aplicacao-retroativo')
->name('integracao:ria-enviar-registro-aplicacao-retroativo')
->withoutOverlapping()
->days([1,2,3,4,5])
->everyTwoHours();
$schedule->command('integracao:ria-enviar-registro-atualizacao-retroativo')
->name('integracao:ria-enviar-registro-atualizacao-retroativo')
->withoutOverlapping()
->days([1,2,3,4,5])
->everyTwoHours();
$schedule->command('integracao:ria-enviar-registro-aplicacao')
->name('integracao:ria-enviar-registro-aplicacao')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('01:00');
$schedule->command('integracao:ria-enviar-registro-atualizacao')
->name('integracao:ria-enviar-registro-atualizacao')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('01:00');
$schedule->command('integracao:ria-enviar-documento-com-inconsistencia')
->name('integracao:ria-enviar-documento-com-inconsistencia')
->withoutOverlapping()
->weeklyOn(6, '01:00');
$schedule->command('integracao:ria-enviar-documento-com-inconsistencia-retroativo')
->name('integracao:ria-enviar-documento-com-inconsistencia-retroativo')
->withoutOverlapping()
->weeklyOn(0, '01:00');
$schedule->command('integracao:ria-enviar-documento-alterado')
->name('integracao:ria-enviar-documento-alterado')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('04:00');
$schedule->command('integracao:ria-enviar-exclusao-documento')
->name('integracao:ria-enviar-exclusao-documento')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('05:00');
// RIRA
$schedule->command('integracao:rira-enviar')
->name('integracao:rira-enviar')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('02:00');
// ERPSAUDE
$schedule->command('integracao:erpsaude-buscar-atendimentos-hospital-santa-casa')
->name('integracao:erpsaude-buscar-atendimentos-hospital-santa-casa')
->withoutOverlapping()
->days([0,1,2,3,4,5,6])
->at('02:30');
// ESUS
$schedule->command('integracao:esus-enviar')
->name('integracao:esus-enviar')
->withoutOverlapping()
->days([1,2,3,4,5])
->at('03:00');
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Database;
use App\Database\Schema\Blueprint;
use Illuminate\Database\MySqlConnection as BaseMySqlConnection;
class MySqlConnection extends BaseMySqlConnection
{
/**
* Get a schema builder instance for the connection.
*
* @return \Illuminate\Database\Schema\MySqlBuilder
*/
public function getSchemaBuilder()
{
$builder = parent::getSchemaBuilder();
$builder->blueprintResolver(function ($table, $callback) {
return new Blueprint($table, $callback);
});
return $builder;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Database\Schema;
use Illuminate\Database\Schema\Blueprint as BaseBlueprint;
class Blueprint extends BaseBlueprint
{
private const MAX_INDEX_NAME_LENGTH = 64;
private const MAX_FRAGMENT_INDEX_LEGNTH = 4;
private const MIN_FRAGMENT_INDEX_LEGNTH = 3;
/**
* Create a default index name for the table.
*
* @param string $type
* @param array $columns
* @return string
*/
protected function createIndexName($type, array $columns)
{
$index = $this->getReducedIndexName($type, $columns, self::MAX_FRAGMENT_INDEX_LEGNTH);
return str_replace(['-', '.'], '_', $index);
}
private function getReducedIndexName($type, array $columns, int $fragmentLength)
{
$table = null;
foreach($columns as $key => $value) {
$column = null;
foreach(explode('_', $value) as $explode) {
$column .= substr($explode, 0, $fragmentLength) . '_';
}
$columns[$key] = rtrim($column, '_');
}
foreach(explode('_', $this->table) as $explode) {
$table .= substr($explode, 0, $fragmentLength) . '_';
}
$index = strtolower($this->prefix.rtrim($table, '_').'_'.implode('_', $columns).'_'.$type);
if (strlen($index) > self::MAX_INDEX_NAME_LENGTH) {
$newFragmentLength = $fragmentLength - 1;
if ($newFragmentLength < self::MIN_FRAGMENT_INDEX_LEGNTH) {
throw new \Exception("An error has occurred while reducing the length of the index. "
. "Please, name it manually.\nThe last try result in: {$index}.");
}
return $this->getReducedIndexName($type, $columns, $newFragmentLength);
}
return $index;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums\Administracao;
class CrudAcao
{
const CRIAR = 1;
const VISUALIZAR = 2;
const ATUALIZAR = 3;
const EXCLUIR = 4;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Administracao;
class HistoricoAcao
{
const CADASTRO = 1;
const EXCLUSAO = 2;
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Enums\Administracao;
class Modulo
{
const AMBULATORIO = 'ambulatorio';
const EXAME_LABORATORIAL = 'exame-laboratorial';
const EXAME_IMAGEM = 'exame-imagem';
const IMUNIZACAO = 'imunizacao';
const FARMACIA = 'farmacia';
const PAINEL_SENHA = 'painel-senha';
const PRONTO_ATENDIMENTO = 'pronto-atendimento';
const TRANSPORTE_PACIENTE = 'transporte-paciente';
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Administracao;
class NotificacaoTipo
{
const ATUALIZACAO = 1;
const AVISO = 2;
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums\Almoxarifado;
class DocumentoTipo
{
const NOTA_FISCAL = 1;
const OUTRAS = 2;
const DOACAO = 3;
const DEVOLUCAO = 4;
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Enums\Almoxarifado;
class EntradaTipo
{
const MANUAL = 1;
const REQUISICAO = 2;
const INVENTARIO = 3;
const ESTORNO = 4;
const INICIAL = 5;
const TRANSFERENCIA = 6;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Almoxarifado;
class MotivoBaixaTipo
{
const CONSUMO_INTERNO = 1;
const QUEBRADO = 2;
const VENCIDO = 3;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Enums\Almoxarifado;
class RequisicaoRespostaItemMotivo
{
const SEM_ESTOQUE = 1;
const PEDIDO_EM_EXCESSO = 2;
const ENTREGA_PARCIAL = 3;
const PENDENTE_DE_ENTREGA = 4;
const RECUSADO = 5;
const ADEQUACAO_DE_CAIXA = 6;
const OUTRO = 7;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Almoxarifado;
class RequisicaoRespostaStatus
{
const INICIADA = 1;
const ENVIADA = 2;
const RECEBIDA = 3;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Almoxarifado;
class RequisicaoStatus
{
const ABERTA = 1;
const RESPOSTA_INICIADA = 2;
const FINALIZADA = 3;
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Enums\Almoxarifado;
class ReservaMotivo
{
const REQUISICAO = 1;
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Enums\Almoxarifado;
class SaidaTipo
{
const MANUAL = 1;
const REQUISICAO = 2;
const INVENTARIO = 3;
const ESTORNO = 4;
const DISPENSACAO_FARMACIA = 5;
const BAIXA = 6;
const TRANSFERENCIA = 7;
const DISPENSACAO_ALMOXARIFADO = 8;
const DISPENSACAO_AMBULATORIO = 9;
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Enums\Ambulatorio;
class AcompanhamentoOciMotivoEncerramento
{
const ABANDONO = 1;
const OBITO = 2;
const CANCELAMENTO_PELO_AUDITOR = 3;
const VENCIDO = 4;
const CONCLUIDO = 5;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Ambulatorio;
class AcompanhamentoOciStatus
{
const EM_ANDAMENTO = 1;
const ENCERRADO_MANUALMENTE = 2;
const ENCERRADO_AUTOMATICAMENTE = 3;
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Enums\Ambulatorio;
class AgendamentoStatus
{
const AGENDADO = 1;
const REAGENDADO_PELO_PACIENTE = 2;
const REAGENDADO_PELO_MEDICO = 3;
const CANCELADO_PELO_PACIENTE = 4;
const CANCELADO_PELO_MEDICO = 5;
const FALTA_DO_PACIENTE = 6;
const FALTA_DO_MEDICO = 7;
const REALIZADO = 8;
const EVADIDO = 9;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class AgendamentoTipo
{
const CONSULTA = 1;
const RETORNO = 2;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class ArcadaDentaria
{
const SUPERIOR = 1;
const INFERIOR = 2;
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums\Ambulatorio;
class AtendimentoEspecificoTipo
{
const SAUDE_BUCAL = 1;
const PRE_NATAL = 2;
const PUERICULTURA = 3;
const IDOSO = 4;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enums\Ambulatorio;
class AtendimentoImpressaoTipo
{
const RECEITA_COMUM = 1;
const RECEITA_ESPECIAL = 2;
const RECEITA_ESPECIAL_AZUL = 3;
const RECEITA_ESPECIAL_AMARELA = 4;
const RECEITA_NAO_PADRONIZADA_COMUM = 5;
const RECEITA_NAO_PADRONIZADA_ESPECIAL = 6;
const SERVICO_DE_ANALISES_DIAGNOSTICOS_E_TERAPIA_SOLICITACAO_DE_EXAMES_SADAT = 7;
const FICHA_DE_REQUISICAO_DE_SERVICOS_AUXILIARES_DE_DIAGOSE_E_TERAPIA_SADT = 8;
const LAUDO_PARA_SOLICITACAO_AUTORIZACAO_DE_PROCEDIMENTO_AMBULATORIAL = 9;
const ENCAMINHAMENTO = 10;
const ATESTADO_MEDICO = 11;
const DECLARACAO_COMPARECIMENTO = 12;
const CARTA_MEDICA = 13;
const ORIENTACAO = 14;
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Enums\Ambulatorio;
class AtendimentoSoapSolicitacaoExameItemStatus
{
const SOLICITADO = 1;
const AGENDADO = 2;
const REALIZADO = 3;
const FALTA = 4;
const CANCELADO = 5;
const REAGENDADO = 6;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class AtendimentoTipo
{
const DEMANDA_ESPONTANEA = 1;
const PROGRAMADA = 2;
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Enums\Ambulatorio;
class AtestadoModelo
{
const PADRAO = 1;
const EM_BRANCO = 2;
const PERIODO_MANHA = 3;
const PERIODO_TARDE = 4;
const PERIODO_NOITE = 5;
const QUANTIDADE_HORAS = 6;
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Enums\Ambulatorio;
class CasoSuspeito
{
const ACIDENTE_DE_TRABALHO_COM_EXPOSICAO_A_MATERIAL_BIOLOGICO = 1;
const ACIDENTE_DE_TRABALHO_GRAVE = 2;
const ACIDENTES_POR_ANIMAIS_PECONHENTOS = 3;
const AIDS_PACIENTES_MAIORES_DE_13_ANOS = 4;
const ATENDIMENTO_ANTI_RABICO_HUMANO = 5;
const BOTULISMO = 6;
const CHIKUNGUNYA = 7;
const COQUELUCHE = 8;
const COLERA = 9;
const DENGUE = 10;
const DIFTERIA = 11;
const DOENCA_MENINGOCOCICA = 12;
const DOENCAS_DE_CHAGAS_AGUDA = 13;
const DRT_CANCER_RELACIONADO_AO_TRABALHO = 14;
const DRT_DERMATOSES_OCUPACIONAIS = 15;
const DRT_LER_DORT = 16;
const DRT_PAIR = 17;
const DRT_PNEUMOCONIOSES = 18;
const DRT_TRANSTORNOS_MENTAIS_RELACIONADOS_AO_TRABALHO = 19;
const ESQUISTOSSOMOSE = 20;
const FEBRE_AMARELA = 21;
const FEBRE_MACULOSA_RICKETTSIOSES = 22;
const FEBRE_POR_VIRUS_DO_NILO_OCIDENTAL = 23;
const FEBRE_TIFOIDE = 24;
const GESTANTE_HIV = 25;
const HANSENIASE = 26;
const HANTAVIROSE = 27;
const HEPATITES_VIRAIS = 28;
const INTOXICACAO_EXOGENA = 29;
const LEISHMANIOSE_TEGUMENTAR_AMERICANA = 30;
const LEISHMANIOSE_VISCERAL = 31;
const LEPTOSPIROSE = 32;
const MALARIA = 33;
const OUTRAS_MENINGITES = 34;
const PARALISIA_FLACIDA_AGUDA_POLIOMELITE = 35;
const PESTE = 36;
const RAIVA_HUMANA = 37;
const ROTAVIRUS = 38;
const RUBEOLA = 39;
const SARAMPO = 40;
const SIFILIS_CONGENITA = 41;
const SIFILIS_EM_GESTANTE = 42;
const SINDROME_DA_RUBEOLA_CONGENITA = 43;
const TUBERCULOSE = 44;
const TETANO_ACIDENTAL = 45;
const TETANO_NEONATAL = 46;
const AIDS_PACIENTES_MENORES_QUE_13_ANOS = 47;
const BOLETIM_DE_INQUERITO_DE_TRACOMA = 48;
const DOENCA_DE_CHAGAS_ADULTA = 49;
const ENCEFALITE = 50;
const INFLUENZA_HUMANA_POR_NOVO_SUBTIPO_PANDEMICO = 51;
const MENINGITE = 52;
const SINDROME_HEMOLITICO_UREMICA = 53;
const SINDROME_RESPIRATORIA_AGUDA_GRAVE_SRAG_INTERNADA_OU_OBITO_POR_SRAG = 54;
const SINDROME_DE_GUILLAIN_BARRE_E_OU_OUTRA_MANIFESTACAO_NEUROLOGICA_ASSOCIADA_COM_ARBOVIRUS = 55;
const VIOLENCIA_INTERPESSOAL_AUTOPROVOCADA = 56;
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums\Ambulatorio;
class ClassificacaoRisco
{
const NAO_AGUDA = 1;
const BAIXA = 2;
const INTERMEDIARIA = 3;
const ALTA = 4;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Ambulatorio;
class ColetaMaterial
{
const COLETA_DE_PAPANICOLAU = 1;
const COLETA_DE_EXAME_LABORATORIAL = 2;
const COLETA_DE_TRIAGEM_NEONATAL = 3;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class DemandaTipo
{
const AGENDADA = 'agendada';
const ESPONTANEA = 'espontanea';
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Ambulatorio;
class Desfecho
{
const PACIENTE_LIBERADO = 'Paciente liberado';
const PACIENTE_ENCAMINHADO = 'Paciente encaminhado';
const MANTER_NA_FILA = 'Paciente mantido na fila';
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Enums\Ambulatorio;
class ExameResultadoTipo
{
const NORMAL = 1;
const ALTERADO = 2;
const SUGESTIVO_DE_INFECCAO_CONGENITA = 3;
const OUTRAS_ALTERACOES = 4;
const INDETERMINADO = 5;
const FALHOU = 6;
const PASSOU = 7;
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Enums\Ambulatorio;
final class FeridaExsudato
{
public const ESCASSO = 1;
public const BAIXO = 2;
public const MEDIO = 3;
public const ALTO = 4;
public static function fromDescricao(?string $descricao): ?int
{
if ($descricao === null) {
return null;
}
$norm = self::normalize($descricao);
$map = [
'escasso' => self::ESCASSO,
'baixo' => self::BAIXO,
'medio' => self::MEDIO,
'médio' => self::MEDIO,
'alto' => self::ALTO,
];
return $map[$norm] ?? null;
}
private static function normalize(string $text): string
{
$text = trim(mb_strtolower($text));
$text = str_replace(
['á','à','ã','â','ä','é','è','ê','ë','í','ì','î','ï','ó','ò','õ','ô','ö','ú','ù','û','ü','ç'],
['a','a','a','a','a','e','e','e','e','i','i','i','i','o','o','o','o','o','u','u','u','u','c'],
$text
);
return preg_replace('/\s+/', ' ', $text);
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class FeridaStatus
{
const ATIVO = 0;
const CICATRIZADA = 1;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Enums\Ambulatorio;
class ImpressaoAssinaturaEletronica
{
const REGISTRO_DO_ATENDIMENTO_MEDICO = 1;
const REGISTRO_DO_ATENDIMENTO_DE_ENFERMAGEM = 2;
const RECEITUARIO_MEDICO = 3;
const ENCAMINHAMENTOS = 4;
const ORIENTACAO = 5;
const SOLICITACAO_DE_EXAMES_SADAT = 6;
const SOLICITACAO_DE_EXAMES_SADT = 7;
const SOLICITACAO_DE_EXAMES_APAC = 8;
const SOLICITACAO_DE_EXAMES_CITOPATOLOGICO_DE_COLO_DE_UTERO = 9;
const SOLICITACAO_DE_EXAMES_HISTOPATOLOGICO_DE_MAMA = 10;
const SOLICITACAO_DE_EXAMES_CITOPATOLOGICO_DE_MAMA = 11;
const ATESTADOS_MEDICOS = 12;
const DECLARACOES_DE_COMPARECIMENTO = 13;
const CARTAS_MEDICAS = 14;
const NOTIFICACAO_COMPULSORIA = 15;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class JornadaTipo
{
const DINAMICA = 1;
const FIXA = 2;
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Enums\Ambulatorio;
final class LocalAnatomico
{
public const CABECA = 1;
public const PESCOCO = 2;
public const TRONCO_FRENTE = 3;
public const TRONCO_COSTAS = 4;
public const BRACO = 5;
public const MAO_DETALHES = 6;
public const PERNA = 7;
public const PES_DEDOS = 8;
public const PES_REGIOES = 9;
public const PES_CALCANHARES = 10;
public const PES_VISTA_EXTERNA = 11;
public const PARTES_LATERAIS = 12;
public const LADO_DIREITO = 13;
public const LADO_ESQUERDO = 14;
public const PARTES_ESPECIFICAS = 15;
public const GLUTEOS_DETALHES = 16;
public const GENITALIA = 17;
public const NADEGAS = 18;
public const OUTROS = 19;
public static function fromDescricao(?string $descricao): ?int
{
if ($descricao === null) {
return null;
}
$norm = self::normalize($descricao);
$map = [
'cabeça' => self::CABECA,
'pescoço' => self::PESCOCO,
'tronco frente' => self::TRONCO_FRENTE,
'tronco costas' => self::TRONCO_COSTAS,
'braço' => self::BRACO,
'mão detalhes' => self::MAO_DETALHES,
'perna' => self::PERNA,
'pés dedos' => self::PES_DEDOS,
'pés regiões' => self::PES_REGIOES,
'pés calcanhares' => self::PES_CALCANHARES,
'pés vista externa' => self::PES_VISTA_EXTERNA,
'partes laterais' => self::PARTES_LATERAIS,
'lado direito' => self::LADO_DIREITO,
'lado esquerdo' => self::LADO_ESQUERDO,
'partes específicas' => self::PARTES_ESPECIFICAS,
'glúteos detalhes' => self::GLUTEOS_DETALHES,
'genitália' => self::GENITALIA,
'nádegas' => self::NADEGAS,
'outros' => self::OUTROS,
];
return $map[$norm] ?? null;
}
private static function normalize(string $text): string
{
$text = trim(mb_strtolower($text));
$text = str_replace(
['á','à','ã','â','ä','é','è','ê','ë','í','ì','î','ï','ó','ò','õ','ô','ö','ú','ù','û','ü','ç'],
['a','a','a','a','a','e','e','e','e','i','i','i','i','o','o','o','o','o','u','u','u','u','c'],
$text
);
return preg_replace('/\s+/', ' ', $text);
}
}

View File

@@ -0,0 +1,410 @@
<?php
namespace App\Enums\Ambulatorio;
use Illuminate\Support\Facades\DB;
final class LocalAnatomicoRegiao
{
private const chaveLocalSafecare = [
'head' => 1,
'forehead' => 1,
'nose' => 1,
'chin' => 1,
'right_cheek' => 1,
'left_cheek' => 1,
'neck' => 2,
'right_deltoid' => 3,
'left_deltoid' => 3,
'right_chest' => 3,
'left_chest' => 3,
'abdomen' => 3,
'right_abdomen' => 3,
'left_abdomen' => 3,
'sacral' => 3,
'back_body_right' => 4,
'back_body_left' => 4,
'trapezius_right' => 4,
'trapezius_left' => 4,
'glute_right' => 4,
'glute_left' => 4,
'popliteal_right' => 4,
'popliteal_left' => 4,
'calf_right' => 4,
'calf_left' => 4,
'right_arm' => 5,
'left_arm' => 5,
'right_forearm' => 5,
'left_forearm' => 5,
'right_hand' => 5,
'left_hand' => 5,
'pinky' => 6,
'ring' => 6,
'middle' => 6,
'index' => 6,
'thumb' => 6,
'palm' => 6,
'right_pinky' => 6,
'right_ring' => 6,
'right_middle' => 6,
'right_index' => 6,
'right_thumb' => 6,
'right_palm' => 6,
'right_thigh' => 7,
'left_thigh' => 7,
'right_knee' => 7,
'left_knee' => 7,
'right_tibial' => 7,
'left_tibial' => 7,
'right_shin' => 7,
'left_shin' => 7,
'right_foot' => 7,
'left_foot' => 7,
'big_toe' => 8,
'second_toe' => 8,
'middle_toe' => 8,
'fourth_toe' => 8,
'little_toe' => 8,
'sole' => 8,
'right_big_toe' => 8,
'right_second_toe' => 8,
'right_middle_toe' => 8,
'right_fourth_toe' => 8,
'right_little_toe' => 8,
'right_sole' => 8,
'medial_forefoot' => 9,
'central_forefoot' => 9,
'distal_forefoot' => 9,
'plantar_arch' => 9,
'central_midfoot' => 9,
'distal_midfoot' => 9,
'medial_hindfoot' => 9,
'central_hindfoot' => 9,
'distal_hindfoot' => 9,
'right_heel' => 10,
'left_heel' => 10,
'right_achilles_tendon' => 10,
'left_achilles_tendon' => 10,
'right_external_malleolus' => 11,
'left_external_malleolus' => 11,
'right_external_arch' => 11,
'left_external_arch' => 11,
'right_external_metatarsal' => 11,
'left_external_metatarsal' => 11,
'side_head' => 12,
'side_neck' => 12,
'side_abdomen' => 12,
'side_sacral' => 12,
'side_trochanter1' => 12,
'side_trochanter2' => 12,
'side_malleolus' => 12,
'side_right_deltoid' => 13,
'side_right_arm' => 13,
'side_right_forearm' => 13,
'side_right_hand' => 13,
'side_right_chest' => 13,
'side_right_thigh' => 13,
'side_right_shin' => 13,
'side_right_foot' => 13,
'side_left_deltoid' => 14,
'side_left_arm' => 14,
'side_left_forearm' => 14,
'side_left_hand' => 14,
'side_left_chest' => 14,
'side_left_thigh' => 14,
'side_left_shin' => 14,
'side_left_foot' => 14,
'trochanter1' => 15,
'trochanter2' => 15,
'malleolus' => 15,
'upper_left' => 16,
'upper_right' => 16,
'lower_left' => 16,
'lower_right' => 16,
'left_foot_subarea_1' => 17,
'left_foot_subarea_2' => 17,
'left_foot_subarea_3' => 17,
'left_foot_subarea_4' => 17,
'left_foot_subarea_5' => 17,
'left_foot_subarea_6' => 17,
'left_foot_subarea_7' => 17,
'left_foot_subarea_8' => 17,
'left_foot_subarea_9' => 17,
'left_foot_subarea_10' => 17,
'left_foot_subarea_11' => 17,
'left_foot_subarea_12' => 17,
'left_foot_subarea_13' => 17,
'left_foot_subarea_14' => 17,
'left_foot_subarea_15' => 17,
'right_foot_subarea_1' => 18,
'right_foot_subarea_2' => 18,
'right_foot_subarea_3' => 18,
'right_foot_subarea_4' => 18,
'right_foot_subarea_5' => 18,
'right_foot_subarea_6' => 18,
'right_foot_subarea_7' => 18,
'right_foot_subarea_8' => 18,
'right_foot_subarea_9' => 18,
'right_foot_subarea_10' => 18,
'right_foot_subarea_11' => 18,
'right_foot_subarea_12' => 18,
'right_foot_subarea_13' => 18,
'right_foot_subarea_14' => 18,
'right_foot_subarea_15' => 18,
];
private const chaveTraduzida = [
'cabeca' => 'head',
'testa' => 'forehead',
'nariz' => 'nose',
'queixo' => 'chin',
'bochecha direita' => 'right_cheek',
'bochecha esquerda' => 'left_cheek',
'pescoco' => 'neck',
'deltoide direito' => 'right_deltoid',
'deltoide esquerdo' => 'left_deltoid',
'peito direito' => 'right_chest',
'peito esquerdo' => 'left_chest',
'abdomen' => 'abdomen',
'abdomen direito' => 'right_abdomen',
'abdomen esquerdo' => 'left_abdomen',
'sacral' => 'sacral',
'dorso direito' => 'back_body_right',
'dorso esquerdo' => 'back_body_left',
'trapezio direito' => 'trapezius_right',
'trapezio esquerdo' => 'trapezius_left',
'gluteo direito' => 'glute_right',
'gluteo esquerdo' => 'glute_left',
'poplitea direita' => 'popliteal_right',
'poplitea esquerda' => 'popliteal_left',
'panturrilha direita' => 'calf_right',
'panturrilha esquerda' => 'calf_left',
'braco direito' => 'right_arm',
'braco esquerdo' => 'left_arm',
'antebraco direito' => 'right_forearm',
'antebraco esquerdo' => 'left_forearm',
'mao direita' => 'right_hand',
'mao esquerda' => 'left_hand',
'minimo esquerdo' => 'pinky',
'anelar esquerdo' => 'ring',
'medio esquerdo' => 'middle',
'indicador esquerdo' => 'index',
'polegar esquerdo' => 'thumb',
'palma esquerda' => 'palm',
'minimo direito' => 'right_pinky',
'anelar direito' => 'right_ring',
'medio direito' => 'right_middle',
'indicador direito' => 'right_index',
'polegar direito' => 'right_thumb',
'palma direita' => 'right_palm',
'coxa direita' => 'right_thigh',
'coxa esquerda' => 'left_thigh',
'joelho direito' => 'right_knee',
'joelho esquerdo' => 'left_knee',
'tibial direita' => 'right_tibial',
'tibial esquerda' => 'left_tibial',
'canela direita' => 'right_shin',
'canela esquerda' => 'left_shin',
'pe direito' => 'right_foot',
'pe esquerdo' => 'left_foot',
'dedo 1 esquerdo' => 'big_toe',
'dedo 2 esquerdo' => 'second_toe',
'terceiro dedo esquerdo' => 'middle_toe',
'dedo 4 esquerdo' => 'fourth_toe',
'dedo 5 esquerdo' => 'little_toe',
'planta do pe esquerda' => 'sole',
'dedo 1 direito' => 'right_big_toe',
'dedo 2 direito' => 'right_second_toe',
'dedo 3 direito' => 'right_middle_toe',
'dedo 4 direito' => 'right_fourth_toe',
'dedo 5 direito' => 'right_little_toe',
'planta do pe direita' => 'right_sole',
'antepe medial' => 'medial_forefoot',
'antepe central' => 'central_forefoot',
'antepe distal' => 'distal_forefoot',
'arco plantar' => 'plantar_arch',
'mediope central' => 'central_midfoot',
'mediope distal' => 'distal_midfoot',
'retrope medial' => 'medial_hindfoot',
'retrope central' => 'central_hindfoot',
'retrope distal' => 'distal_hindfoot',
'calcanhar direito' => 'right_heel',
'calcanhar esquerdo' => 'left_heel',
'tendao de aquiles direito' => 'right_achilles_tendon',
'tendao de aquiles esquerdo' => 'left_achilles_tendon',
'maleolo externo direito' => 'right_external_malleolus',
'maleolo externo esquerdo' => 'left_external_malleolus',
'arco externo direito' => 'right_external_arch',
'arco externo esquerdo' => 'left_external_arch',
'metatarso externo direito' => 'right_external_metatarsal',
'metatarso externo esquerdo' => 'left_external_metatarsal',
'cabeca lateral' => 'side_head',
'pescoco lateral' => 'side_neck',
'abdomen lateral' => 'side_abdomen',
'sacral lateral' => 'side_sacral',
'trocanter 1 lateral' => 'side_trochanter1',
'trocanter 2 lateral' => 'side_trochanter2',
'maleolo lateral' => 'side_malleolus',
'deltoide direito lateral' => 'side_right_deltoid',
'braco direito lateral' => 'side_right_arm',
'antebraco direito lateral' => 'side_right_forearm',
'mao direita lateral' => 'side_right_hand',
'peito direito lateral' => 'side_right_chest',
'coxa direita lateral' => 'side_right_thigh',
'canela direita lateral' => 'side_right_shin',
'pe direito lateral' => 'side_right_foot',
'deltoide esquerdo lateral' => 'side_left_deltoid',
'braco esquerdo lateral' => 'side_left_arm',
'antebraco esquerdo lateral' => 'side_left_forearm',
'mao esquerda lateral' => 'side_left_hand',
'peito esquerdo lateral' => 'side_left_chest',
'coxa esquerda lateral' => 'side_left_thigh',
'canela esquerda lateral' => 'side_left_shin',
'pe esquerdo lateral' => 'side_left_foot',
'trocanter 1' => 'trochanter1',
'trocanter 2' => 'trochanter2',
'maleolo' => 'malleolus',
'superior esquerdo' => 'upper_left',
'superior direito' => 'upper_right',
'inferior esquerdo' => 'lower_left',
'inferior direito' => 'lower_right',
'subarea 1 do pe esquerdo' => 'left_foot_subarea_1',
'subarea 2 do pe esquerdo' => 'left_foot_subarea_2',
'subarea 3 do pe esquerdo' => 'left_foot_subarea_3',
'subarea 4 do pe esquerdo' => 'left_foot_subarea_4',
'subarea 5 do pe esquerdo' => 'left_foot_subarea_5',
'subarea 6 do pe esquerdo' => 'left_foot_subarea_6',
'subarea 7 do pe esquerdo' => 'left_foot_subarea_7',
'subarea 8 do pe esquerdo' => 'left_foot_subarea_8',
'subarea 9 do pe esquerdo' => 'left_foot_subarea_9',
'subarea 10 do pe esquerdo' => 'left_foot_subarea_10',
'subarea 11 do pe esquerdo' => 'left_foot_subarea_11',
'subarea 12 do pe esquerdo' => 'left_foot_subarea_12',
'subarea 13 do pe esquerdo' => 'left_foot_subarea_13',
'subarea 14 do pe esquerdo' => 'left_foot_subarea_14',
'subarea 15 do pe esquerdo' => 'left_foot_subarea_15',
'subarea 1 do pe direito' => 'right_foot_subarea_1',
'subarea 2 do pe direito' => 'right_foot_subarea_2',
'subarea 3 do pe direito' => 'right_foot_subarea_3',
'subarea 4 do pe direito' => 'right_foot_subarea_4',
'subarea 5 do pe direito' => 'right_foot_subarea_5',
'subarea 6 do pe direito' => 'right_foot_subarea_6',
'subarea 7 do pe direito' => 'right_foot_subarea_7',
'subarea 8 do pe direito' => 'right_foot_subarea_8',
'subarea 9 do pe direito' => 'right_foot_subarea_9',
'subarea 10 do pe direito' => 'right_foot_subarea_10',
'subarea 11 do pe direito' => 'right_foot_subarea_11',
'subarea 12 do pe direito' => 'right_foot_subarea_12',
'subarea 13 do pe direito' => 'right_foot_subarea_13',
'subarea 14 do pe direito' => 'right_foot_subarea_14',
'subarea 15 do pe direito' => 'right_foot_subarea_15',
];
public static function fromKey(?string $key, ?int $localAnatomicoId = null): ?int
{
if ($key === null) {
return null;
}
$key = self::normalizeKey($key);
$query = DB::table('cadastro_local_anatomico_regiao')
->where('key', $key);
if ($localAnatomicoId !== null) {
$query->where('local_anatomico_id', $localAnatomicoId);
} else {
$hint = self::chaveLocalSafecare[$key] ?? null;
if ($hint !== null) {
$query->where('local_anatomico_id', $hint);
}
}
$row = $query->first(['id']);
return $row ? (int) $row->id : null;
}
public static function fromDescricao(?string $descricao, ?int $localAnatomicoId = null): ?int
{
if ($descricao === null) {
return null;
}
$norm = self::normalize($descricao);
$key = self::chaveTraduzida[$norm] ?? null;
if ($key === null) {
return null;
}
return self::fromKey($key, $localAnatomicoId);
}
public static function localAnatomicoIdForKey(string $key): ?int
{
$key = self::normalizeKey($key);
return self::chaveLocalSafecare[$key] ?? null;
}
public static function normalize(string $text): string
{
$text = trim(mb_strtolower($text));
$text = str_replace(
['á','à','ã','â','ä','é','è','ê','ë','í','ì','î','ï','ó','ò','õ','ô','ö','ú','ù','û','ü','ç','́','’',"'",'º','ª'],
['a','a','a','a','a','e','e','e','e','i','i','i','i','o','o','o','o','o','u','u','u','u','c','','','','',''],
$text
);
return preg_replace('/\s+/', ' ', $text);
}
public static function normalizeKey(string $key): string
{
$key = trim(mb_strtolower($key));
$key = preg_replace('/\s+/', '_', $key);
return $key;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class Origem
{
const AGENDADA = 1;
const ESPONTANEA = 2;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Enums\Ambulatorio;
class OutroProcedimento
{
const INALACAO = 1;
const RETIRADA_DE_PONTOS = 2;
const CURATIVO_SIMPLES = 3;
const CURATIVO_GRAU_II_COM_OU_SEM_DEBRIDAMENTO = 4;
const PROVA_DO_LACO = 5;
const CURATIVO_ESPECIAL = 6;
const PREPARACAO_PARA_ECG = 7;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Ambulatorio;
class PrescricaoFrequenciaDose
{
const INTERVALO = 'intervalo';
const FREQUENCIA = 'frequencia';
const TURNO = 'turno';
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Ambulatorio;
class ProblemaCondicaoSituacao
{
const ATIVO = 1;
const LATENTE = 2;
const RESOLVIDO = 3;
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Enums\Ambulatorio;
class SaudeBucalDenteProblemaCondicao
{
const AUSENTE_A = 1;
const EXTRAIDO_E = 2;
const HIGIDO_H = 3;
const HIGIDO_SELADO_HS = 4;
const PROTESE_PARCIAL_REMOVIVEL = 5;
const PROTESE_TEMPORARIA = 6;
const INCLUSO_I = 7;
const PROTESE_CORONARIA_UNITARIA = 8;
const COROA_CO = 9;
const IMPLANTE = 10;
const PILAR_P = 11;
const NUCLEO_PINO = 12;
const RESTO_RADICULAR_RR = 13;
const RETRACAO_GENGIVAL_RG = 14;
const CALCULO_DENTAL_CD = 15;
const EXTRACAO_INDICADA_EI = 16;
const SELANTE_INDICADO_SI = 17;
const FRATURA_FR = 18;
const MANCHA_BRANCA_ATIVA_M = 19;
const CARIADO_C = 20;
const RESTAURADO_R = 21;
const CARIE_DA_RAIZ = 22;
const RESTAURADO_COM_CARIE_RC = 23;
const RAIZ_RESTAURADA = 24;
const NECESSITA_DE_TRATAMENTO_ENDODONTICO = 25;
const POSSUI_LESAO_DE_FURCA = 26;
const TRATAMENTO_ENDODONTICO_REALIZADO = 27;
const LESAO_DE_FURCA_TRATADA = 28;
const MANTENEDOR_DE_ESPACO_ME = 29;
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums\Ambulatorio;
class ServicoTipo
{
const VACINACAO = 1;
const DEMANDA_ESPONTANEA = 2;
const DEMANDA_AGENDADA = 3;
const POS_CONSULTA = 4;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Ambulatorio;
class SigiloTipo
{
const SIGILO_ATENDIMENTO = 1;
const SIGILO_COMPLETO = 2;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Ambulatorio;
class SituacaoCodigoEsus
{
const ATIVO = 0;
const LATENTE = 1;
const RESOLVIDO = 2;
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Enums\Ambulatorio;
class TesteRapido
{
const GRAVIDEZ = 1;
const SIFILIS = 2;
const DETECCAO_DE_INFECCAO_PELO_HIV = 3;
const DETECCAO_DE_INFECCAO_PELO_HBV = 4;
const DETECCAO_DE_HEPATITE_C = 5;
const DENGUE_IGG_IGM = 6;
const PSA = 7;
const DETECCAO_DE_SARS_COVID_2 = 8;
const FEBRE_CHIKUNGUNYA_IGM = 9;
const VIRUS_ZIKA_IGG_IGM = 10;
const SIFILIS_NA_GESTANTE_OU_PAI_PARCEIRO = 11;
const DETECCAO_DE_HIV_NA_GESTANTE_OU_PAI_PARCEIRO = 12;
const DENGUE_NS1 = 13;
const PROTEINURIA = 14;
const TESTE_DO_OLHINHO_TRV = 15;
const TESTE_RAPIDO_SIFILIS_EM_GESTANTE = 16;
const TESTE_RAPIDO_SIFILIS_EM_PARCEIRO_OU_PARCERIA_DE_GESTANTE = 17;
const TESTE_RAPIDO_DETECCAO_DE_ANTICORPOS_ANTI_HIV_EM_GESTANTE = 18;
const TESTE_RAPIDO_DETECCAO_DE_ANTICORPOS_ANTI_HIV_EM_PARCEIRO_OU_PARCERIA_DE_GESTANTE = 19;
const TESTE_RAPIDO_PARA_DETECCAO_DE_HEPATITE_C_POPULACAO_GERAL_EXCETO_GESTANTE_PARCEIRO_OU_PARCERIA = 20;
const DETECCAO_DE_INFECCAO_PELO_HBV_POPULACAO_GERAL_EXCETO_GESTANTE_PARCEIRO_OU_PARCERIA = 21;
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Enums\Apac;
class CaraterAtendimento
{
const ELETIVO = 1;
const URGENCIA = 2;
const ACIDENTE_NO_LOCAL_DE_TRABALHO_OU_A_SERVICO_DA_EMPRESA = 3;
const ACIDENTE_NO_TRAJETO_PARA_O_TRABALHO = 4;
const OUTROS_TIPOS_DE_ACIDENTE_DE_TRANSITO = 5;
const OUTROS_TIPOS_DE_LESOES_E_ENVENENAMENTOS_POR_AGENTES_QUIMICOS_OU_FISICOS = 6;
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums\Apac;
class FonteOrcamentaria
{
const MOD_1_SERVICO_DE_SAUDE_AES = 1;
const MOD_2_EQUIPE_VOLANTES_AES = 2;
const CREDITO_FINANC_PARC_VENCIDAS_E_VICENDAS = 3;
const CREDITO_FINANCEIRO_TRANSACAO_TRIBUTARIA = 4;
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Enums\Apac;
class Inconsistencia
{
const COMPETENCIA_INVALIDA = 1;
const NUMERO_DA_APAC_E_OBRIGATORIO = 2;
const NUMERO_DA_APAC_INVALIDO = 3;
const IBGE_DO_ESTADO_DA_UNIDADE_EXECUTANTE_INVALIDO = 4;
const CNES_DA_UNIDADE_EXECUTANTE_E_OBRIGATORIO = 5;
const CNES_DA_UNIDADE_EXECUTANTE_INVALIDO = 6;
const TIPO_DE_ATENDIMENTO_E_OBRIGATORIO = 7;
const NOME_DA_MAE_DO_PACIENTE_E_OBRIGATORIO = 8;
const ENDERECO_DO_PACIENTE_E_OBRIGATORIO = 9;
const CEP_DO_PACIENTE_INVALIDO = 10;
const IBGE_DO_MUNICIPIO_DE_RESIDENCIA_DO_PACIENTE_INVALIDO = 11;
const DATA_DE_NASCIMENTO_DO_PACIENTE_E_OBRIGATORIA = 12;
const SEXO_DO_PACIENTE_E_OBRIGATORIO = 13;
const PROCEDIMENTO_PRINCIPAL_INVALIDO_PARA_A_COMPETENCIA = 14;
const PROCEDIMENTO_PRINCIPAL_INVALIDO_PARA_O_SEXO_DO_PACIENTE = 15;
const PROCEDIMENTO_PRINCIPAL_INVALIDO_PARA_A_IDADE_DO_PACIENTE = 16;
const NOME_DO_PROFISSIONAL_AUTORIZADOR_E_OBRIGATORIO = 17;
const CNS_DO_PACIENTE_E_OBRIGATORIO = 18;
const CNS_DO_PACIENTE_INVALIDO = 19;
const CNS_DO_PROFISSIONAL_RESPONSAVEL_E_OBRIGATORIO = 20;
const CNS_DO_PROFISSIONAL_RESPONSAVEL_INVALIDO = 21;
const CNS_DO_PROFISSIONAL_AUTORIZADOR_E_OBRIGATORIO = 22;
const CNS_DO_PROFISSIONAL_AUTORIZADOR_INVALIDO = 23;
const CID_CAUSAS_ASSOCIADAS_INVALIDO_PARA_O_PROCEDIMENTO_PRINCIPAL = 24;
const CNES_DA_UNIDADE_SOLICITANTE_INVALIDO = 25;
const DATA_DA_AUTORIZACAO_E_OBRIGATORIA = 26;
const CODIGO_DO_EMISSOR_E_OBRIGATORIO = 27;
const CODIGO_DO_EMISSOR_INVALIDO = 28;
const NUMERO_DA_APAC_ANTERIOR_INVALIDO = 29;
const RACA_COR_DO_PACIENTE_E_OBRIGATORIO = 30;
const ETNIA_INDIGENA_DO_PACIENTE_E_OBRIGATORIA = 31;
const PAIS_DE_NASCIMENTO_DO_PACIENTE_E_OBRIGATORIO = 32;
const TELEFONE_DO_PACIENTE_INVALIDO = 33;
const CNS_DO_PROFISSIONAL_EXECUTANTE_E_OBRIGATORIO = 34;
const CNS_DO_PROFISSIONAL_EXECUTANTE_INVALIDO = 35;
const CPF_DO_PACIENTE_INVALIDO = 36;
const CID_PRINCIPAL_E_OBRIGATORIO = 37;
const CID_PRINCIPAL_INVALIDO_PARA_O_PROCEDIMENTO_PRINCIPAL = 38;
const CID_SECUNDARIO_INVALIDO_PARA_O_PROCEDIMENTO_PRINCIPAL = 39;
const DATA_DE_IDENTIFICACAO_PATOLOGICA_E_OBRIGATORIA = 40;
const PROCEDIMENTO_INVALIDO_PARA_A_COMPETENCIA = 41;
const PROCEDIMENTO_INVALIDO_PARA_O_SEXO_DO_PACIENTE = 42;
const PROCEDIMENTO_INVALIDO_PARA_O_CBO = 43;
const PROCEDIMENTO_INVALIDO_PARA_A_IDADE_DO_PACIENTE = 44;
const QUANTIDADE_DO_PROCEDIMENTO_INVALIDA = 45;
const CNPJ_DE_CESSAO_DE_CREDITO_INVALIDO = 46;
const CID_PRINCIPAL_INVALIDO_PARA_O_PROCEDIMENTO = 47;
const CID_SECUNDARIO_INVALIDO_PARA_O_PROCEDIMENTO = 48;
const CLASSIFICACAO_INVALIDA_PARA_O_PROCEDIMENTO = 49;
const CLASSIFICACAO_INVALIDA_PARA_O_SERVICO = 50;
const CNES_DA_UNIDADE_PRESTADORA_DE_TERCEIRO_INVALIDO = 51;
const CID_CAUSA_ASSOCIADA_INVALIDO_PARA_O_SEXO_DO_PACIENTE = 52;
const CID_PRINCIPAL_INVALIDO_PARA_O_SEXO_DO_PACIENTE = 53;
const CID_SECUNDARIO_INVALIDO_PARA_O_SEXO_DO_PACIENTE = 54;
const CEP_DO_PACIENTE_E_OBRIGATORIO = 55;
const NUMERO_DO_ENDERECO_DO_PACIENTE_E_OBRIGATORIO = 56;
const PROCEDIMENTO_PRINCIPAL_INVALIDO_PARA_O_CBO = 57;
const QUANTIDADE_INVALIDA_PARA_O_PROCEDIMENTO_PRINCIPAL = 58;
const CLASSIFICACAO_INVALIDA_PARA_O_PROCEDIMENTO_PRINCIPAL = 59;
const CID_PRINCIPAL_DADOS_COMPLEMENTARES_E_OBRIGATORIO = 60;
const CID_PRINCIPAL_DADOS_COMPLEMENTARES_INVALIDO_PARA_O_PROCEDIMENTO_PRINCIPAL = 61;
const CID_PRINCIPAL_DADOS_COMPLEMENTARES_INVALIDO_PARA_O_SEXO_DO_PACIENTE = 62;
const CID_SECUNDARIO_DADOS_COMPLEMENTARES_INVALIDO_PARA_O_PROCEDIMENTO_PRINCIPAL = 63;
const CID_SECUNDARIO_DADOS_COMPLEMENTARES_INVALIDO_PARA_O_SEXO_DO_PACIENTE = 64;
const CID_PRINCIPAL_E_OBRIGATORIO_PARA_O_PROCEDIMENTO_PRINCIPAL = 65;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enums\Apac;
class Linha
{
const CABECALHO = '01';
const CORPO = '14';
const REGISTRO_PROCEDIMENTO = '13';
const PARTE_VARIAVEL_PROCEDIMENTO_QUIMIOTERAPIA = '07';
const PARTE_VARIAVEL_PROCEDIMENTO_RADIOTERAPIA = '08';
const PARTE_VARIAVEL_NEFROLOGIA = '09';
const PARTE_VARIAVEL_PROCEDIMENTO_MEDICAMENTO = '10';
const PARTE_VARIAVEL_PROCEDIMENTO_ACOMPANHAMENTO_POS_CIRURGIA_BARIATRICA = '11';
const PARTE_VARIAVEL_PROCEDIMENTO_ACOMPANHAMENTO_PRE_CIRURGIA_BARIATRICA = '17';
const PARTE_VARIAVEL_PROCEDIMENTO_LAUDO_GERAL = '06';
const PARTE_VARIAVEL_PROCEDIMENTO_LAUDO_PROTESE_MAMA = '12';
const PARTE_VARIAVEL_PROCEDIMENTO_LAUDO_TRATAMENTO_DIALITICO = '18';
const PARTE_VARIAVEL_PROCEDIMENTO_LAUDO_ACOMPANHAMENTO_MULTIPROFISSIONAL_DRC = '19';
const PARTE_VARIAVEL_PROCEDIMENTO_LAUDO_CONFECCAO_FISTULA_ARTERIOVENOSA = '20';
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Enums\Apac;
class MotivoSaidaPermanencia
{
const ALTA_CURADO = 1;
const ALTA_MELHORADO = 2;
const ALTA_DA_PUERPERA_E_PERMANENCIA_DO_RECEM_NASCIDO = 3;
const ALTA_A_PEDIDO = 4;
const ALTA_COM_PREVISAO_DE_RETORNO_PARA_ACOMPANHAMENTO_DO_PACIENTE = 5;
const ALTA_POR_EVASAO = 6;
const ALTA_DA_PUERPERA_E_RECEM_NASCIDO = 7;
const ALTA_POR_OUTROS_MOTIVOS = 8;
const POR_CARACTERISTICAS_PROPRIAS_DA_DOENCA = 9;
const POR_INTERCORRENCIA = 10;
const POR_IMPOSSIBILIDADE_SOCIO_FAMILIAR = 11;
const POR_PROCESSO_DE_DOACAO_DE_ORGAOS_TECIDOS_E_CELULAS_DOADOR_VIVO = 12;
const POR_PROCESSO_DE_DOACAO_DE_ORGAOS_TECIDOS_E_CELULAS_DOADOR_MORTO = 13;
const POR_MUDANCA_DE_PROCEDIMENTO = 14;
const POR_REOPERACAO = 15;
const OUTROS_MOTIVOS = 16;
const TRANSFERIDO_PARA_OUTRO_ESTABELECIMENTO = 17;
const COM_DECLARACAO_DE_OBITO_FORNECIDA_PELO_MEDICO_ASSISTENTE = 18;
const COM_DECLARACAO_DE_OBITO_FORNECIDA_PELO_INSTITUTO_MEDICO_LEGAL_IML = 19;
const COM_DECLARACAO_DE_OBITO_FORNECIDA_PELO_SERVICO_DE_VERIFICACAO_DE_OBITO_SVO = 20;
const ENCERRAMENTO_ADMINISTRATIVO = 21;
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Enums\Apac;
class RacaCor
{
const BRANCA = '01';
const PRETA = '02';
const PARDA = '03';
const AMARELA = '04';
const INDIGENA = '05';
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Apac;
class Sexo
{
const MASCULINO = 'M';
const FEMININO = 'F';
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Enums\Apac;
class Tipo
{
const INICIAL = 1;
const CONTINUIDADE = 2;
const UNICA = 3;
const ENCERRAMENTO = 4;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\Apac;
class TipoAtendimento
{
const TRS = 1;
const RADIOTERAPIA = 2;
const QUIMIOTERAPIA = 3;
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Enums\AtencaoBasica\AtendimentoDomiciliar;
class CondicaoAvaliada
{
const OXIGENOTERAPIA_DOMICILIAR = 17;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Enums\AtencaoBasica\AtendimentoDomiciliar;
class CondutaDesfecho
{
const PERMANENCIA = 1;
const ALTA_ADMINISTRATIVA = 2;
const ALTA_CLINICA = 3;
const OBITO = 4;
const ATENCAO_BASICA_AD1 = 5;
const SERVICO_DE_URGENCIA_E_EMERGENCIA = 6;
const SERVICO_DE_INTERNACAO_HOSPITALAR = 7;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Enums\AtencaoBasica\AtendimentoDomiciliar;
class Modalidade
{
const AD_1 = 1;
const AD_2 = 2;
const AD_3 = 3;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enums\AtencaoBasica\AtendimentoDomiciliar;
class PerfilEpidemiologico
{
const USO_DE_DIETAS_ESPECIAIS = 1;
const ENTREGA_DE_MATERIAIS = 2;
const ADQUIRIU_INFECCAO_DOMICILIAR = 3;
const SISTEMA_NEUROLOGICO = 4;
const SISTEMA_RESPIRATORIO = 5;
const SISTEMA_ENDOCRINO = 6;
const SISTEMA_GENITURINARIO = 7;
const USO_DE_ESPESSANTE = 8;
const AVALIACAO_DE_PROCESSO = 9;
const RESIDENTE_EM_AREA_DE_RISCO = 10;
const SISTEMA_CARDIOLOGICO = 11;
const SISTEMA_OSTEO_MIO_ARTICULAR = 12;
const SISTEMA_TEGUMENTAR = 13;
const OUTROS = 14;
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Enums\AtencaoBasica\AtendimentoDomiciliar;
class Procedimento
{
const ACOMPANHAMENTO_PACIENTE_REABILITACAO_COMUNICACAO = '0301070024';
const ANTIBIOTICOTERAPIA_PARENTERAL = '0301050082';
const ATENDIMENTO_ACOMPANHAMENTO_PACIENTE_REABILITACAO_DESENVOLVIMENTO_MOTOR = '0301070075';
const ATENDIMENTO_FISIOTERAPEUTICO_PACIENTE_TRANSTORNO_RESPIRATORIO_SEM_COMPLICACOES_SISTEMICAS = '0302040021';
const ATEMDIMENTO_MEDICO_FINALIDADE_ATESTAR_OBITO = '0301050090';
const ATENDIMENTO_ACOMPANHAMENTO_REABILITACAO_MULTIPLAS_DEFICIENCIAS = '0301070067';
const CATETERISMO_VESICAL_ALIVIO = '0301100047';
const CATETERISMO_VESICAL_DEMORA = '0301100055';
const COLETA_MATERIAL_EXAME_LABORATORIAL = '0201020041';
const CUIDADOS_ESTOMAS = '0301100063';
const CUIDADOS_TRAQUEOSTOMIA = '0301100071';
const ENEMA = '0301100098';
const OXIGENOTERAPIA = '0301100144';
const RETIRADAS_PONTOS_CIRURGIAS_BASICAS = '0301100152';
const SONDAGEM_GASTRICA = '0301100179';
const TERAPIA_REIDRATACAO_ORAL = '0301100187';
const TERAPIA_REIDRATACAO_PARENTERAL = '0301050120';
const TERAPIA_FONOAUDIOLOGICA_INDIVIDUAL = '0301070113';
const TRATAMENTO_TRAUMATISMO = '0308010019';
const TRATAMENTO_REABILITACAO = '0303190019';
const TRAQUEOSTOMIA = '0404010377';
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Enums\AtencaoBasica\AtendimentoIndividual;
class CondicaoAvaliada
{
const ASMA = 1;
const DESNUTRICAO = 2;
const DIABETES = 3;
const DPOC = 4;
const HIPERTENSAO_ARTERIAL = 5;
const OBESIDADE = 6;
const PRE_NATAL = 7;
const PUERICULTURA = 8;
const PUERPERIO_ATE_42_DIAS = 9;
const REABILITACAO = 10;
const SAUDE_MENTAL = 11;
const SAUDE_SEXUAL_E_REPRODUTIVA = 12;
const TABAGISMO = 13;
const USUARIO_DE_ALCOOL = 14;
const USUARIO_DE_OUTRAS_DROGAS = 15;
}

Some files were not shown because too many files have changed in this diff Show More