Criação da bateria de testes automatizados, correção da pagina de gerar romaneio e plano da criação do modulo cliente

This commit is contained in:
2026-08-28 09:40:59 -03:00
parent b20299dd6b
commit c454cb8cc7
4 changed files with 423 additions and 2 deletions

195
README.md
View File

@@ -3513,3 +3513,198 @@ Dockerfile # tira a linha de precompile mort
```
</details>
---
<details>
<summary><strong>🆕 Atualização 28/08/2026 — PLANO: Módulo do Cliente (aprovado, NÃO implementado)</strong></summary>
> ⚠️ **Isto é um plano de implementação, não uma feature entregue.** Foi escrito
> como ordem de serviço para ser executado em fases (inclusive por modelos
> menores), com o levantamento do código já feito. Implementar **uma fase por
> vez**, na ordem, validando cada uma antes da próxima.
## O que é o módulo
Hoje o cliente (Gade Hospitalar) manda uma **planilha** por mês/operação, que
alguém sobe no banco como tabela `gade_entregas_*` (fora do sistema). O módulo
do cliente substitui esse fluxo: uma área **`/cliente`** com login próprio onde
o próprio cliente:
1. **Cria operações** (ex.: "UBS NORTE — setembro/2026") e lança entregas nelas;
2. **Adiciona entregas em operações existentes**;
3. Lança **entregas avulsas** (fora de qualquer operação);
4. Cadastra **em massa via planilha .xlsx** (o módulo é um espelho da planilha
atual — mesmos campos), com conferência antes de gravar;
5. Recebe **detecção de duplicidade na hora**: "você tem X apontamentos
duplicados" e "a NF Y já está roteirizada / em curso".
## 🔒 Regras invioláveis (valem para TODAS as fases)
- `db_reem_simplerout_2026` e as `gade_entregas_*` são **somente leitura**:
nunca migration, nunca INSERT/UPDATE/DELETE/DROP nelas. O módulo grava apenas
em **tabelas próprias novas**.
- Todo nome de tabela externa que entra em SQL passa por `Operacao.sanitizar` +
`quote_table_name` (padrão já usado no projeto inteiro).
- **NÃO transformar `app/models/operacao.rb` em ActiveRecord.** Ele é um PORO
(só métodos de classe) que descobre as tabelas externas pelo catálogo do
Postgres e é usado por ~8 services. Os models novos têm outros nomes.
- Seguir o CLAUDE.md do projeto: número na tela é contrato; modo de visualização
em aba, não tela nova; mobile-first com alvos de 48px; comentário explica o
porquê; validar com Docker (`ruby -c` / actionview) antes de entregar.
- Ao final de cada fase: listar os `git add` por nome (commit/push são do
usuário) e lembrar que o deploy para o teste é manual.
## 📌 Fatos do código que o implementador precisa saber
| Assunto | Onde está | O que copiar |
|---|---|---|
| "Operação" hoje | `app/models/operacao.rb` (PORO) | catálogo/whitelist (`nomes_validos`, `sanitizar`, `label`, `MESES`) — só consumir |
| Colunas da planilha do cliente | `app/services/simpli_route/planilha_carga.rb` (`COLUNAS_OP`) | `nota_fiscal`, `nome_completo`, `endereco_sem_complemento`, `complemento`, `supervisao`, `telefones`; geo nas gade é `lat`/`long` |
| Esquema variável por mês | `app/services/romaneios/enriquecimento_gade.rb` | detecção de colunas via `conn.columns(tabela)`; só `nota_fiscal` é garantida |
| Dedup existente (melhor modelo) | `app/services/romaneios/importador.rb#deduplicar` + `RomaneioLinha.chave_para` + `app/services/romaneios/normalizador.rb` | chave normalizada (NF + título + endereço), primeira ocorrência vence, contagem reportada |
| Rastreio (roteirizado/em curso) | `app/models/entrega.rb` (`db_reem_simplerout_2026`, `readonly?=true`) | scopes `da_conta_gade`, `por_nf`; status da visita indica o andamento |
| Ler .xlsx | gem `roo` (~2.10, já no Gemfile; usada no romaneio) | |
| Gerar .xlsx | gem `caxlsx` (já no Gemfile; usada na planilha SimpliRoute) | |
| Papéis/permissões | `app/models/user.rb` (enum `role`), `app/models/permissao.rb`, `app/models/perfil_acesso.rb`, policies em `app/policies/` | `Permissao::TODAS` + `GRUPOS` + `PADRAO_POR_ROLE` (esquecer o PADRAO tira acesso silenciosamente — aviso no próprio model) |
| Migration de permissão (modelo) | `db/migrate/20260827000004_add_permissao_romaneio_aos_perfis.rb` | sincronizar perfis existentes |
| Rotas | `config/routes.rb` | seguir o padrão `namespace :admin`; atenção ao aviso do Inflector pt-BR no próprio arquivo (singular == plural gera `_index`) |
| Notificações (evento novo) | infra em `app/models/notificacao_*` / `app/services/notificacao/` | criar evento "cliente lançou entregas" na Fase 5 |
**Não existe hoje nenhum conceito de "cliente" no código** (nem model, nem
coluna, nem namespace) — a fundação é a Fase 0.
## Fase 0 — Fundação de acesso (`/cliente` existe e loga)
1. `User`: adicionar `cliente: 5` ao enum `role`.
2. `Permissao`: novas chaves no grupo novo `cliente`:
`cliente.ver`, `cliente.operacoes_criar`, `cliente.entregas_lancar`,
`cliente.importar_planilha`, `cliente.avulsas` — em `TODAS`, `GRUPOS` e
`PADRAO_POR_ROLE` (role `cliente` recebe todas as `cliente.*` e NADA de
dashboard/consolidação/admin).
3. `HOME_POR_PERMISSAO` / `home_rota`: usuário com `cliente.ver` cai em `/cliente`.
4. Rotas: `namespace :cliente` com `root`, `resources :operacoes` (conferir o
plural com o Inflector), `resources :entregas`, `resource :importacao`.
5. Layout `app/views/layouts/cliente.html.erb`: identidade visual do projeto
(dark, laranja `#f97316`), **sem** menus de admin; menu próprio: Operações ·
Avulsas · Importar planilha · Sair.
6. Policies Pundit novas baseadas em `user.pode?('cliente.…')`; os controllers
de `/cliente` herdam de um `Cliente::BaseController` que bloqueia quem não
tem `cliente.ver`.
7. Migration sincronizando perfis + seeds: perfil "Cliente" e um usuário de
teste `cliente@reem.com`.
**Pronto quando**: login com usuário cliente cai em `/cliente` (vazio, com estado
vazio instrutivo), e esse usuário NÃO acessa `/dashboard`, `/consolidacoes` nem
`/admin/*` (redireciona para `/sem-acesso`); admin continua acessando tudo.
## Fase 1 — Modelagem (tabelas próprias)
1. `ClienteOperacao` (`cliente_operacoes`): `nome`, `mes`, `ano`,
`status` (enum: `rascunho` → `enviada` → `roteirizada` → `fechada`),
`criado_por_id` (FK users), timestamps. Índice único em
`[nome, mes, ano]` (case-insensitive).
2. `ClienteEntrega` (`cliente_entregas`): `cliente_operacao_id` (**nulo =
avulsa**), campos espelhando a planilha atual — `nota_fiscal`,
`nome_completo`, `endereco_sem_complemento`, `complemento`, `supervisao`,
`telefones`, `lat`, `long`, `observacoes` — mais `chave_dedup` (string
normalizada), `origem` (enum: `manual`/`planilha`), `criado_por_id`,
timestamps.
3. `chave_dedup`: gerada em callback com a MESMA normalização de
`Romaneios::Normalizador` (extrair para um módulo compartilhável se preciso,
sem quebrar os chamadores atuais). Índice único parcial em
`[cliente_operacao_id, chave_dedup]` — e validação amigável antes do índice.
4. Validações: `nota_fiscal` obrigatória; `nome_completo` ou endereço presentes.
**Pronto quando**: migrations rodam no Docker, models com validações e specs de
unicidade; nada de tela ainda.
## Fase 2 — Telas `/cliente` (cadastro individual intuitivo)
1. **Lista de operações**: cards (nome, mês/ano, nº de entregas, status, botão
"Adicionar entrega"); estado vazio dizendo o que fazer ("Crie sua primeira
operação ou importe uma planilha").
2. **Criar operação**: form curto (nome + mês/ano), sem jargão técnico.
3. **Adicionar entrega**: form mobile-first (alvos 4856px), campos na ordem da
planilha, busca de endereço livre; ao salvar, o painel de duplicidade da
Fase 4 responde na mesma tela (inline, não outra página).
4. **Entregas avulsas**: mesma tela de lançamento com aba/rótulo "Avulsa
(sem operação)" — mesma unidade, rótulo explícito (regra 1 do CLAUDE.md).
5. Lista de entregas da operação com edição inline (padrão Stimulus do projeto,
ver `romaneio_controller.js`) e remoção com confirmação.
**Pronto quando**: fluxo completo criar operação → lançar 3 entregas → editar →
remover, no desktop e no celular (validar com agent-browser + `set device`).
## Fase 3 — Importação em massa (.xlsx)
1. **Modelo para download**: gerar com `caxlsx` uma planilha com o cabeçalho
exato dos campos da Fase 1 + 2 linhas de exemplo (o cliente já conhece esse
formato — é a planilha que ele manda hoje).
2. **Upload**: `roo` lendo .xlsx; mapear cabeçalhos com tolerância (acentos,
caixa, espaços — reusar normalização existente).
3. **Tela de conferência ANTES de gravar** (nada entra direto): resumo com
N válidas · N com erro (motivo por linha) · N duplicadas (painel da Fase 4);
o cliente escolhe "Importar só as válidas" ou cancelar.
4. Gravação transacional (`insert_all` ou transaction com validação); relatório
final na tela com os mesmos números da conferência.
5. Limites e mensagens: arquivo até ~5 MB / ~5.000 linhas; erro de formato tem
mensagem em português dizendo o que corrigir.
**Pronto quando**: importar a planilha-modelo preenchida com linhas repetidas de
propósito grava só as válidas e reporta as duplicadas com os números certos.
## Fase 4 — Duplicidade inteligente (`Cliente::DetectorDuplicidade`)
Serviço único usado pelo form individual (Fase 2) e pela importação (Fase 3).
Entrada: coleção de entregas candidatas (+ operação alvo). Saída: para cada
candidata, `status_duplicidade` e a evidência. **Quatro camadas, nesta ordem:**
1. **Dentro do lote**: chave normalizada repetida no próprio arquivo/form —
padrão de `Romaneios::Importador#deduplicar` (primeira vence, conta o resto).
2. **Contra o módulo**: `ClienteEntrega` já gravada com a mesma `chave_dedup`
(na mesma operação ou avulsa do mesmo mês).
3. **Contra as `gade_entregas_*` do mesmo mês/ano**: SELECT read-only (tabelas
via `Operacao.sanitizar` + `quote_table_name`; só `nota_fiscal` é garantida —
detectar colunas via `conn.columns` como no `EnriquecimentoGade`).
4. **Contra o rastreio** (`Entrega.da_conta_gade.por_nf`): se a NF já existe no
espelho, classificar pelo status da visita — **"já roteirizada"** (planejada)
ou **"em curso/entregue"** (checkout feito). Conferir os valores reais de
status no espelho antes de fixar o mapeamento.
Na tela (regra 1 do CLAUDE.md — explicar onde o número aparece):
resumo "⚠️ X apontamentos duplicados neste lançamento" + lista expandível
inline com a camada que acusou cada um ("já lançada nesta operação",
"consta na planilha de agosto", "NF 79093 já roteirizada em 27/08").
Duplicata **avisa e bloqueia por padrão**, com opção explícita "lançar mesmo
assim" (grava com flag `duplicada_confirmada` para auditoria).
**Pronto quando**: os 4 cenários têm teste (fixtures/factories) e a tela mostra
a contagem e o motivo camada por camada.
## Fase 5 — Lado admin + integração
1. **Visão admin**: aba/tela em `/admin` listando lançamentos do cliente por
operação/status, com os MESMOS números que o cliente vê (recorte único).
2. **Exportação**: baixar as entregas de uma `ClienteOperacao` no formato da
planilha de carga do SimpliRoute (referência: `SimpliRoute::PlanilhaCarga`) —
é o que fecha o ciclo: o que o cliente lança vira rota.
3. **Notificação**: evento novo na infra existente ("Cliente lançou N entregas
na operação X") para o grupo do WhatsApp da operação.
4. **Auditoria**: registrar criar/editar/excluir/importar do módulo no padrão de
auditoria existente (`/admin/auditoria_logs`).
5. Transição de status da operação (`enviada` → `roteirizada`) — manual pelo
admin nesta fase; automatizar via rastreio fica para depois.
**Pronto quando**: admin vê, exporta e é notificado; auditoria registra.
## Fase 6 — Validação e encerramento
1. Adicionar à `docs/BATERIA-DE-TESTES.md` os casos: login cliente; criar
operação; lançar entrega; importar .xlsx com duplicatas propositais e conferir
as contagens; bloqueio de acesso do cliente às áreas de admin.
2. Rodar a bateria completa (`/bateria-testes`) no ambiente de teste após o
deploy manual.
3. Atualizar este README (marcar fases entregues) e o perfil "Cliente" nos seeds.
</details>