Atualização de Read.me
This commit is contained in:
333
README.md
333
README.md
@@ -3072,118 +3072,291 @@ caminho do servidor e o trace inteiro — para **qualquer pessoa** que provocass
|
|||||||
---
|
---
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>🕐 Relógio, restart automático e migrations no boot</strong></summary>
|
<summary><strong>🛠️ 27/08/2026 — Deploy: relógio, arquivos órfãos e tudo que caiu junto</strong></summary>
|
||||||
|
|
||||||
## O problema relatado
|
Um relato só, porque foi **uma corrente**: um problema descoberto expunha o
|
||||||
|
seguinte. Vale mais como mapa de diagnóstico do que como changelog.
|
||||||
|
|
||||||
> "toda vez que faço o deploy ou reinicio o Docker a data e hora ficam erradas."
|
| Sintoma na tela | Causa real | Onde se resolveu |
|
||||||
|
|
||||||
São **dois problemas diferentes** que se confundem porque aparecem no mesmo lugar:
|
|
||||||
|
|
||||||
| Sintoma | Causa | Onde se resolve |
|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Hora **sempre 3h adiantada**, todo restart | O container subia em **UTC** | `Dockerfile` (fuso) |
|
| Hora sempre 3h adiantada | Container subia em **UTC** | `Dockerfile` (TZ + `/etc/localtime`) |
|
||||||
| Hora **derivando / errada depois de reboot** | Relógio do **host** sem sincronia | `deploy/ntp-seguro.sh` (NTS) |
|
| Hora errada depois de reboot | Relógio do **host** sem sincronia | `deploy/ntp-seguro.sh` (chrony + NTS) |
|
||||||
|
| `env file .env not found` | `.env` não é versionado e a pasta foi recriada | recriar no servidor + backup fora da árvore |
|
||||||
|
| `Couldn't find Active Storage configuration` | `config/storage.yml` nunca esteve no git | arquivo versionado |
|
||||||
|
| `port is already allocated` | Porta 3001 vivia como alteração **local** no compose | `${PORTA_APP:-3001}` |
|
||||||
|
| `Blocked hosts: 100.75.222.23:3001` | `config.hosts` ligado só com o domínio | `HOSTS_PERMITIDOS` |
|
||||||
|
| `Translation missing … devise.failure` | `fallbacks = true` caía no próprio pt-BR | `fallbacks = [:en]` + traduções |
|
||||||
|
| 422 "recusada por verificação de segurança" | `assume_ssl` fixo x acesso por IP em HTTP | gate do `FORCE_SSL` |
|
||||||
|
| `URI::InvalidURIError: … SEU_IP` | `DATABASE_URL` com o valor de exemplo | falha rápida no boot |
|
||||||
|
|
||||||
### 1. Fuso do container (o "sempre 3h à frente")
|
## 1. Relógio: fuso é do container, hora é do host
|
||||||
|
|
||||||
A imagem `ruby:3.2.2-slim` sobe em UTC. O Rails até mostrava a hora certa
|
A imagem `ruby:3.2.2-slim` sobe em UTC. O Rails mostrava a hora certa
|
||||||
(`config.time_zone = "America/Sao_Paulo"`), mas **tudo que era do sistema
|
(`config.time_zone`), mas **tudo do sistema operacional** ficava adiantado — e o
|
||||||
operacional continuava adiantado** — e o pior caso não era visual:
|
pior caso não era visual:
|
||||||
|
|
||||||
> **O cron rodava no horário errado.** `every "*/30 8-18"` no `config/schedule.rb`
|
> **O cron rodava no horário errado.** `every "*/30 8-18"` executava das **05h às
|
||||||
> executava das **05h às 15h** de Brasília, não das 08h às 18h.
|
> 15h** de Brasília.
|
||||||
|
|
||||||
Agora o `Dockerfile` fixa `TZ=America/Sao_Paulo` **e** o symlink de
|
O `Dockerfile` agora fixa `TZ=America/Sao_Paulo` **e** o symlink de
|
||||||
`/etc/localtime` — os dois, porque o cron do Debian lê o arquivo, não a
|
`/etc/localtime` — os dois, porque o cron do Debian lê o arquivo, não a variável.
|
||||||
variável. O mesmo foi feito no `whatsapp/Dockerfile` (Alpine precisa do pacote
|
Mesmo tratamento no `whatsapp/Dockerfile` (Alpine precisa do pacote `tzdata`).
|
||||||
`tzdata`, que não vem na imagem). Dá para ajustar sem rebuild pelo `TZ` do `.env`.
|
|
||||||
|
|
||||||
### 2. Hora do host — NTP autenticado (NTS)
|
**A hora em si não dá para sincronizar de dentro do container**: ele lê o clock do
|
||||||
|
kernel do host, e rodar NTP lá dentro exigiria `CAP_SYS_TIME`. Por isso
|
||||||
**Não dá para sincronizar o relógio de dentro do container**: ele lê o clock do
|
`deploy/ntp-seguro.sh`, que roda **uma vez no servidor** e instala **chrony com
|
||||||
kernel do host. Rodar NTP lá dentro exigiria `CAP_SYS_TIME` e mudaria a hora do
|
NTS** (RFC 8915): troca de chaves por TLS (TCP 4460) e pacotes NTP assinados.
|
||||||
servidor inteiro a partir de um processo da aplicação.
|
NTP comum é UDP sem autenticação, e hora errada aqui muda **o dia da
|
||||||
|
consolidação, o recorte do período financeiro e a validade da sessão**. O script
|
||||||
No servidor, uma vez:
|
usa 3 fontes independentes, **desliga as não autenticadas** (deixar o `pool`
|
||||||
|
padrão anularia o ganho), desativa o `systemd-timesyncd` e liga o `rtcsync`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo bash deploy/ntp-seguro.sh
|
sudo bash deploy/ntp-seguro.sh
|
||||||
|
chronyc tracking # 'System time' na casa dos milissegundos
|
||||||
|
chronyc -N authdata # NTS ativo em cada fonte
|
||||||
```
|
```
|
||||||
|
|
||||||
O script instala e configura o **chrony com NTS** (RFC 8915): a troca de chaves
|
## 2. Restart automático e migrations
|
||||||
é por TLS (TCP 4460) e os pacotes NTP vêm assinados, então uma resposta forjada
|
|
||||||
no caminho é descartada. NTP comum é UDP sem autenticação — e hora errada aqui
|
|
||||||
não é detalhe: ela muda **o dia da consolidação, o recorte do período financeiro
|
|
||||||
e a validade da sessão**.
|
|
||||||
|
|
||||||
O script ainda: usa **3 fontes independentes** (Cloudflare, Netnod, PTB),
|
`restart: unless-stopped` no serviço `app`. Escolhido em vez de `always` porque
|
||||||
**desliga as fontes não autenticadas** (deixar o `pool` padrão anularia o ganho),
|
`always` reergue o container até depois de um `stop` deliberado, tirando do
|
||||||
desativa o `systemd-timesyncd` (não fala NTS e brigaria pelo relógio), liga o
|
operador a chance de deixar o sistema fora do ar de propósito.
|
||||||
`rtcsync` (é o relógio de hardware que dá a hora no boot) e mostra a
|
|
||||||
conferência no fim. É idempotente e guarda `.bak` da config.
|
|
||||||
|
|
||||||
Conferir depois, a qualquer momento:
|
O boot saiu do `command:` de uma linha e virou **`bin/docker-boot`**:
|
||||||
|
|
||||||
```bash
|
1. **relógio** — imprime hora e fuso (se aparecer UTC, o rebuild não pegou);
|
||||||
chronyc tracking # 'System time' deve ficar na casa dos milissegundos
|
2. **pid órfão**; 3. **gems** (o volume `bundle_cache` sombreia os da imagem);
|
||||||
chronyc -N authdata # NTS ativo em cada fonte (Cook > 0)
|
4. **migrations** — `db:prepare`, com **espera pelo banco**;
|
||||||
|
5. **cron** (best-effort); 6. **Puma**.
|
||||||
|
|
||||||
|
A migration é automática a todo restart. A espera do passo 4 existe porque o
|
||||||
|
PostgreSQL é externo ao compose: num reboot o Rails pode subir antes de o banco
|
||||||
|
aceitar conexão e, com `unless-stopped`, isso viraria **loop de reinício**
|
||||||
|
parecendo erro de migration.
|
||||||
|
|
||||||
|
**Falha rápida:** `DATABASE_URL` vazia ou com o valor de exemplo não é "banco fora
|
||||||
|
do ar" — nenhuma espera resolve. O boot para na primeira tentativa dizendo o que
|
||||||
|
preencher, **com a credencial mascarada no log**. Este projeto **não tem
|
||||||
|
`config/database.yml`**: a conexão inteira sai dessa variável, e ninguém lê
|
||||||
|
`DB_HOST`/`DB_NAME`/`DB_USER`/`DB_PASSWORD`.
|
||||||
|
|
||||||
|
## 3. Arquivos que o git não levava — e por isso sumiram juntos
|
||||||
|
|
||||||
|
A pasta do deploy foi recriada e levou embora todo arquivo **não versionado**. Dois
|
||||||
|
apareceram:
|
||||||
|
|
||||||
|
- **`.env`** — está no `.gitignore` por conter senha, e assim deve continuar.
|
||||||
|
Guarde uma cópia fora da árvore do projeto e prefira `git pull`/`reset --hard`
|
||||||
|
na própria pasta a recriá-la (arquivo ignorado sobrevive ao pull).
|
||||||
|
- **`config/storage.yml`** — não tinha motivo para estar de fora. O app faz
|
||||||
|
`require "rails/all"`, então o Active Storage sempre carrega e os
|
||||||
|
`config/environments/*.rb` definem `active_storage.service = :local`: com um
|
||||||
|
serviço definido, o Rails **exige** o arquivo no `initialize!` e o servidor não
|
||||||
|
sobe. **Agora é versionado.**
|
||||||
|
|
||||||
|
## 4. Configuração do ambiente saiu do `.env` e entrou no compose
|
||||||
|
|
||||||
|
A porta do teste (**3001**, porque a 3000 está ocupada por outro stack) vivia como
|
||||||
|
alteração **local** no `docker-compose.yml` do servidor — e um `git reset --hard`
|
||||||
|
a levou junto, derrubando o ambiente com o proxy apontando para uma porta sem
|
||||||
|
ninguém escutando. Agora os valores **não secretos** têm padrão no compose:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ports:
|
||||||
|
- "${PORTA_APP:-3001}:3000"
|
||||||
|
environment:
|
||||||
|
TZ: ${TZ:-America/Sao_Paulo}
|
||||||
|
APP_HOST: ${APP_HOST:-teste.reemtransportes.com.br}
|
||||||
|
APP_NAME: "${APP_NAME:-Reem Logística}"
|
||||||
|
WHATSAPP_URL: ${WHATSAPP_URL:-http://whatsapp:3001}
|
||||||
|
HOSTS_PERMITIDOS: ${HOSTS_PERMITIDOS:-100.75.222.23}
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Firewall:** precisa de saída em **UDP 123** e **TCP 4460**. Sem a 4460 o NTS
|
`${VAR:-padrão}`: **o `.env` continua mandando**; o padrão só entra quando a
|
||||||
> não fecha e o chrony fica sem fonte.
|
variável falta. Segredos (`DB_*`, `SECRET_KEY_BASE`, `SIMPLIROUTE_TOKEN`,
|
||||||
|
`WHATSAPP_TOKEN`, `SMTP_*`, `NOTIFICACAO_SECRET`) **nunca** vão para o yaml.
|
||||||
|
|
||||||
## Restart automático + migrations
|
> ⚠️ **`APP_HOST` é só o host, sem `https://`.** O código monta os links como
|
||||||
|
> `"https://#{APP_HOST}/motorista"` (`consolidacao_mailer.rb`,
|
||||||
|
> `notificacao_service.rb`, `gatilhos.rb`): com o esquema no valor sai
|
||||||
|
> `https://https://…` e todo link de e-mail e WhatsApp quebra.
|
||||||
|
|
||||||
`docker-compose.yml`, serviço `app`: **`restart: unless-stopped`**. Sobe sozinho
|
## 5. Produção de verdade: o que só aparece fora do modo development
|
||||||
depois de queda do processo e de reboot do servidor, e só fica parado se alguém
|
|
||||||
der `docker compose stop/down`. (`always` foi descartado: ele reergue o container
|
|
||||||
até depois de um `stop` deliberado, tirando do operador a chance de deixar o
|
|
||||||
sistema fora do ar de propósito.)
|
|
||||||
|
|
||||||
O boot saiu do `command:` de uma linha só e virou **`bin/docker-boot`**, com o
|
O servidor passou a rodar `RAILS_ENV=production`, e três coisas apareceram:
|
||||||
motivo de cada passo comentado:
|
|
||||||
|
|
||||||
1. **relógio** — imprime a hora e o fuso no log (se aparecer UTC, o rebuild não pegou);
|
- **`config.hosts`** — a linha que parecia só liberar o `APP_HOST` na verdade
|
||||||
2. **pid órfão** — sem limpar, o Puma se recusa a subir depois de uma queda;
|
**liga** a verificação e recusa todo o resto. Agora entram também
|
||||||
3. **gems** — `bundle check || bundle install` (o volume `bundle_cache` sombreia os gems da imagem);
|
`localhost`/`127.0.0.1` (para o `curl` de dentro do NAS servir de diagnóstico) e
|
||||||
4. **migrations** — `db:prepare`, **com espera pelo banco**;
|
a lista `HOSTS_PERMITIDOS`.
|
||||||
5. **cron** — `whenever --update-crontab` + daemon, *best-effort*;
|
- **`i18n.fallbacks = true`** significa "caia no `default_locale`" — que aqui é o
|
||||||
6. **Puma**.
|
próprio pt-BR. O fallback apontava para si mesmo e chave ausente virava
|
||||||
|
`Translation missing` **na tela do usuário** (tela de login). Agora `[:en]`,
|
||||||
|
igual ao `application.rb`, e o `pt-BR.yml` ganhou as traduções do Devise
|
||||||
|
(`failure`, `sessions`, `passwords`) — o projeto não usa `devise-i18n`.
|
||||||
|
- **`assume_ssl`** estava fixo, então `request.base_url` virava `https://` e o
|
||||||
|
`Origin: http://` de quem abre pelo IP não batia: **todo POST voltava 422**
|
||||||
|
("recusada por uma verificação de segurança") enquanto os GETs passavam e a tela
|
||||||
|
parecia normal. Foi o que quebrou a importação do plano. Um gate `FORCE_SSL=false`
|
||||||
|
devolve o acesso por HTTP para diagnóstico; sem a variável, HTTPS continua
|
||||||
|
obrigatório.
|
||||||
|
|
||||||
Ou seja: **a migration é automática a todo restart** — `db:prepare` cria o banco
|
> **Proxy reverso:** o destino tem que ser **HTTP** na porta do container. Com
|
||||||
se não existir, aplica as migrations pendentes e só faz seed em banco novo
|
> `https://` o Puma responde
|
||||||
(idempotente). Não há passo manual depois do deploy.
|
> `Are you trying to open an SSL connection to a non-SSL Puma?`.
|
||||||
|
|
||||||
> **Por que a espera no passo 4:** o PostgreSQL é externo ao compose. Num reboot
|
## 6. `db/schema.rb` entrou no `.gitignore`
|
||||||
> do servidor o Rails pode subir antes de o banco aceitar conexão — e agora, com
|
|
||||||
> `restart: unless-stopped`, isso viraria um **loop de reinício** parecendo erro
|
|
||||||
> de migration. São 10 tentativas × 6s. Se falhar depois disso (migration
|
|
||||||
> quebrada, credencial errada), o container sai com erro **de propósito**: melhor
|
|
||||||
> do que servir a aplicação contra um schema desatualizado.
|
|
||||||
|
|
||||||
## Deploy
|
Nunca foi versionado (o schema vive nas migrations). Como o compose monta
|
||||||
|
`.:/app`, o `db:prepare` do boot escreve o arquivo **no host** — e um `git add .`
|
||||||
|
no meio de um rebase o capturou e virou conflito de deploy. Em
|
||||||
|
`RAILS_ENV=production` ele nem seria gerado (`dump_schema_after_migration = false`).
|
||||||
|
|
||||||
|
> **Deploy é `git fetch` + `git reset --hard origin/<branch>`**, não `pull --rebase`:
|
||||||
|
> o container reescreve `Gemfile.lock` e `db/schema.rb` no host, então o checkout
|
||||||
|
> produz alteração local sozinho e todo rebase bate de frente com ela.
|
||||||
|
|
||||||
|
## ▶️ Deploy
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. No servidor, uma única vez — relógio com NTP autenticado:
|
sudo bash deploy/ntp-seguro.sh # 1x no servidor
|
||||||
sudo bash deploy/ntp-seguro.sh
|
git fetch origin && git reset --hard origin/teste
|
||||||
|
docker compose up -d --build # --build: o fuso entra na imagem
|
||||||
# 2. Rebuild obrigatório (o fuso entra na imagem):
|
docker compose logs app | grep '\[boot\]'
|
||||||
docker compose up -d --build
|
curl -I http://127.0.0.1:3001/ # separa "app caiu" de "proxy errado"
|
||||||
|
|
||||||
# 3. Conferir no log que o container está em -03 e não em UTC:
|
|
||||||
docker compose logs app | grep '\[boot\] relógio'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📂 Arquivos
|
## 📂 Arquivos
|
||||||
|
|
||||||
```
|
```
|
||||||
Dockerfile # TZ=America/Sao_Paulo + tzdata + /etc/localtime; CMD -> bin/docker-boot
|
Dockerfile / whatsapp/Dockerfile # TZ + tzdata + /etc/localtime
|
||||||
whatsapp/Dockerfile # tzdata (Alpine) + mesmo fuso
|
docker-compose.yml # restart, PORTA_APP, APP_HOST/NAME, TZ, HOSTS_PERMITIDOS
|
||||||
docker-compose.yml # restart: unless-stopped, TZ, command: bin/docker-boot
|
bin/docker-boot # boot documentado: espera do banco, cron, Puma — NOVO
|
||||||
bin/docker-boot # boot documentado: migrations com espera, cron, Puma — NOVO
|
deploy/ntp-seguro.sh # chrony + NTS no host (1x, root) — NOVO
|
||||||
deploy/ntp-seguro.sh # chrony + NTS no host (rodar 1x, como root) — NOVO
|
config/storage.yml # versionado — NOVO
|
||||||
.env.example # TZ=America/Sao_Paulo + aviso "fuso ≠ hora"
|
config/environments/production.rb # hosts, i18n fallbacks, gate do FORCE_SSL
|
||||||
|
config/locales/pt-BR.yml # traduções do Devise
|
||||||
|
.gitignore # db/schema.rb
|
||||||
|
.env.example # TZ, PORTA_APP, HOSTS_PERMITIDOS, APP_HOST sem esquema
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>🚚 Romaneio — a tela recuperou as funções do programa antigo</strong></summary>
|
||||||
|
|
||||||
|
Referência: o **Romaneiro PDF (GADE Hospitalar)**, o programa em Python que a
|
||||||
|
operação usava. O PDF já estava fechado; o que faltava era a **tela**.
|
||||||
|
|
||||||
|
## Veículos: lista visível, não um `<select>`
|
||||||
|
|
||||||
|
Era um `<select>` com ‹ ›, escolhido para não virar armadilha de scroll com 72
|
||||||
|
carros. Resolveu o scroll e criou outro problema: o operador via **um carro por
|
||||||
|
vez** e perdia a contagem de paradas de cada um — que é justamente a conferência
|
||||||
|
feita antes de imprimir.
|
||||||
|
|
||||||
|
Agora é a lista inteira, cada item com a contagem (`GADE_038 · 31`), o atual em
|
||||||
|
laranja, rolagem própria, e as setas ‹ › preservadas para percorrer carro a carro.
|
||||||
|
Cada veículo é um **link de verdade** (`?veiculo=GADE_038`): volta com o botão
|
||||||
|
"voltar" do navegador e sobrevive a um F5. No celular vira faixa horizontal —
|
||||||
|
72 itens empilhados empurrariam a tabela para fora da primeira tela.
|
||||||
|
|
||||||
|
Na mesma coluna, como no original: **motorista do veículo**, **PDF deste veículo**
|
||||||
|
e **PDF de TODOS os veículos**.
|
||||||
|
|
||||||
|
## Prévia sob demanda, em sobreposição
|
||||||
|
|
||||||
|
Antes o `<iframe>` do PDF era montado em **todo** carregamento da tela — e cada
|
||||||
|
montagem gerava um PDF no servidor, inclusive quando ninguém ia olhar. Agora é o
|
||||||
|
botão **"Ver prévia do PDF"**: abre sobreposta, fundo escurecido, `✕`/Esc/clique
|
||||||
|
fora fecham (clique **dentro** do PDF não fecha, senão rolar o documento fecharia
|
||||||
|
a prévia). Editar com a prévia fechada só a marca como desatualizada; o PDF novo
|
||||||
|
sai no próximo "Ver prévia".
|
||||||
|
|
||||||
|
## Busca com "Limpar" e contador vivo
|
||||||
|
|
||||||
|
`4 de 31 paradas — filtrando por "jardim"`. A contagem **saiu do cabeçalho da
|
||||||
|
tabela**: dois números para a mesma coisa, um atualizando e o outro não, é a
|
||||||
|
divergência que a diretriz 1 proíbe. Sem contador, uma busca que não casa com nada
|
||||||
|
deixa a tabela vazia e parece plano vazio.
|
||||||
|
|
||||||
|
## NOVOS × RECORRENTES no topo
|
||||||
|
|
||||||
|
> **184** com aparelho (NOVOS) · **1863** recorrentes
|
||||||
|
|
||||||
|
É o equivalente à caixa de diálogo que o programa antigo mostrava ao aplicar o
|
||||||
|
status. Verde quando há novos, **âmbar quando é zero** — e zero quase sempre
|
||||||
|
significa importação **sem operação vinculada**, em que a coluna APARELHO sai
|
||||||
|
vazia para todos. Um romaneio assim parece idêntico a um certo e só revela o erro
|
||||||
|
no papel, com aparelho não entregue.
|
||||||
|
|
||||||
|
## Logo escolhível
|
||||||
|
|
||||||
|
Era fixo em `public/logo-gade.png`. Agora vem de `Configuracao` (chave
|
||||||
|
`romaneio_logo`, separada do `empresa_logo`, que é a marca da Reem — o romaneio é
|
||||||
|
documento do **cliente**). PNG ou JPG até 2 MB, gravado em `storage/logos/` e
|
||||||
|
**não** em `public/`: o que o operador sobe não vira arquivo servido pela web sem
|
||||||
|
autorização, e o nome do arquivo é gerado pelo sistema, nunca o que vem do
|
||||||
|
navegador.
|
||||||
|
|
||||||
|
## Operação deduzida pelo NOME DO PLANO
|
||||||
|
|
||||||
|
A operação não trabalha por data — identifica o plano pelo nome
|
||||||
|
(`ENTREGAS EMAD 09.2026`). E é a operação do mês que diz quem é **NOVO**.
|
||||||
|
|
||||||
|
`Romaneios::CasadorDeOperacao` compara por **conjunto de palavras + (mês, ano)**,
|
||||||
|
não por texto, porque as diferenças são sistemáticas:
|
||||||
|
|
||||||
|
| Nome do plano | Tabela da operação |
|
||||||
|
|---|---|
|
||||||
|
| `ENTREGAS EMAD 09.2026` | `gade_entregas_emad_set_2026` |
|
||||||
|
| `UBS SUL E LESTE AGOSTO 2026` | `gade_entregas_ubs_sul_leste_ago_2026` |
|
||||||
|
| `AVULSAS 3 EMAD/SUDESTE AGOSTO 2026` | `gade_entregas_avulsas_3_emad_sudeste_ago_2026` |
|
||||||
|
|
||||||
|
Três armadilhas: o conectivo **E**, o mês **abreviado × por extenso**, e o mês
|
||||||
|
**numérico** (`09.2026`) — este só lido como mês quando vem colado ao ano, senão o
|
||||||
|
`3` de "AVULSAS 3" viraria março. Usa a tabela `MESES` do próprio `Operacao`
|
||||||
|
(via `Operacao.mes_numero`) em vez de duplicá-la.
|
||||||
|
|
||||||
|
**Empate devolve `nil` de propósito.** Vincular a operação errada é pior do que
|
||||||
|
não vincular: APARELHO e TELEFONE sairiam preenchidos com dados de **outro mês** e
|
||||||
|
ninguém desconfiaria. A tela avisa quando ficou sem operação, e a importação diz o
|
||||||
|
que deduziu ("Operação EMAD SET 2026 vinculada pelo nome do plano") — dedução
|
||||||
|
silenciosa que acerta é invisível, mas a que erra é indefensável.
|
||||||
|
|
||||||
|
## Sobre buscar o plano por nome na API
|
||||||
|
|
||||||
|
**Não é possível.** A documentação
|
||||||
|
(https://documentation.simpliroute.com) não expõe endpoint que **liste** planos —
|
||||||
|
só `POST /v1/plans/create-plan/` (onde o plano tem `name`),
|
||||||
|
`GET /v1/plans/{planned_date}/vehicles/` e `GET /v1/plans/routes/{PLAN_ID}/visits/`.
|
||||||
|
A lista de planos que aparece no site deles é da interface web. Por isso o nome é
|
||||||
|
**colado pelo operador** e casado localmente.
|
||||||
|
|
||||||
|
> `GET /v1/plans/{planned_date}/vehicles/` devolve veículos + UUIDs de rota por
|
||||||
|
> data — é o degrau (a) que o `Romaneios::PlanoDoDia` hoje tenta adivinhar. Vale
|
||||||
|
> checar com `bin/sondar_plano_do_dia`, que ganhou uma seção `[4]` procurando
|
||||||
|
> endpoint de planos com nome.
|
||||||
|
|
||||||
|
## 📂 Arquivos
|
||||||
|
|
||||||
|
```
|
||||||
|
app/views/admin/romaneios/_veiculos.html.erb # lista de veículos — NOVO
|
||||||
|
app/views/admin/romaneios/_controles.html.erb # busca + Limpar + contador vivo
|
||||||
|
app/views/admin/romaneios/show.html.erb # 2 zonas + prévia em sobreposição
|
||||||
|
app/views/admin/romaneios/index.html.erb # logo + "deduzir pelo nome do plano"
|
||||||
|
app/javascript/controllers/romaneio_controller.js # navegação, filtro, overlay
|
||||||
|
app/services/romaneios/casador_de_operacao.rb # nome do plano → operação — NOVO
|
||||||
|
spec/services/romaneios/casador_de_operacao_spec.rb # os nomes reais — NOVO
|
||||||
|
app/models/romaneio.rb # contagem_aparelho (NOVOS x recorrentes)
|
||||||
|
app/models/operacao.rb # mes_numero público
|
||||||
|
app/models/configuracao.rb # chave romaneio_logo
|
||||||
|
app/services/pdf/romaneio_pdf.rb # logo configurável
|
||||||
|
app/controllers/admin/romaneios_controller.rb # atualizar_logo + dedução da operação
|
||||||
|
config/routes.rb # POST atualizar_logo
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
Reference in New Issue
Block a user