portal_publico/docs/ramais/CHANGELOG.md

116 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog — Ramais
> Histórico específico desta aplicação, extraído de `plano.md`. Rodadas que mudaram mais de uma aplicação ao mesmo tempo (favicon, tema, texto do menu) continuam em `plano.md`.
>
> **Formato**: uma entrada por rodada, `### Rodada N — Título`; quando a rodada não tem número registrado, `### Título` só. **A numeração de rodada não é global** — cada aplicação conta as próprias, e o mesmo número designa trabalhos diferentes em arquivos diferentes. Ao citar uma rodada, sempre nomear o arquivo. Ver o topo de `plano.md`.
### Rodada 21 — Ramais — tela nova
Pedido: reconstruir de verdade a tela "Ramais" (até então só um item de menu com `href="#"` e um botão "Acessar Ramais" sem destino em todos os shells), com base em prints da ferramenta antiga: diretório de ramais alimentado pelo cadastro real de usuário (Nome/Departamento/Ramal), modal de "Adicionar um Novo Ramal" (com opção de vincular um usuário existente ou criar uma linha avulsa "Não Tem Usuário"), botão "Novo Chamado", botão "Criar Ausência" e cartões especiais para colaborador ausente/aniversariante. Três decisões confirmadas com o usuário antes de implementar:
- **Novo Chamado** abre embutido na própria tela (modal com `<iframe>` apontando pro token da ferramenta de chamados), não em nova aba — diferente do padrão de todo outro link externo do portal.
- **Editar um ramal vinculado a usuário**: só o número do Ramal é gravável nesta tela — grava direto em `Usuario.ramal`; Nome/Departamento continuam só leitura do cadastro.
- **Ausência**: "(AUSENTE)" é calculado automaticamente comparando a hora atual com o período criado, mais uma ação de "Encerrar Ausência" pra quem volta antes do previsto.
- **Férias**: fora de escopo — o próprio usuário disse que depende de uma integração futura com outro banco; nenhuma UI de férias foi construída.
O que foi construído (detalhe técnico completo em `docs/ramais/ramais.md`):
- Dois models novos: `Ramal` (linha da tela, opcionalmente vinculada a `Usuario` — vinculada, os dados vêm sempre do cadastro; avulsa, `nome`/`departamento`/`numero` moram na própria linha) e `RamalAusencia` (`esta_ativa()` calcula "ausente agora" comparando datas/horas, sem armazenar um flag; `encerrada_manualmente` permite encerrar antes do previsto sem mexer no período original).
- Mesmo padrão visualizar/editar da rodada 19 (Links & Ferramentas): `ramais` ganhou os dois apps na árvore de permissões, com o mesmo cuidado no `seed_portal.py` de forçar `editar=False` pra todo perfil que não seja Integração e Inovação (já que `ramais` está em `BASE_KEYS`, `permissions_from_keys()` habilitaria os dois de uma vez sem esse override).
- Endpoint dedicado `GET /api/ramais/usuarios/` pra alimentar o `<select>` "Lista de Usuários" dos modais, em vez de reaproveitar `/api/usuarios/` — aquele é restrito a `gerencia_permissoes`, e a permissão de Ramais foi mantida deliberadamente desacoplada disso (mesmo espírito da própria refatoração visualizar/editar da rodada 19).
- Página nova `ramais.html` + `ramais.js` + `ramais.css`, réplica do shell padrão, reaproveitando a tabela genérica `.pa-table*` de `perfis-acesso.css` (mesmo padrão que `usuarios.html` já usa sem CSS de tabela próprio). Nos outros 5 templates, o item de menu "Ramais" (antes `href="#"`) e o botão "Acessar Ramais" do topbar (antes um `<button>` sem handler nenhum) passaram a apontar de verdade pra `ramais.html`.
- Migração `0012_ramal_ramalausencia` gerada e aplicada no ambiente local; `seed_portal` reaplicado (confirmado via shell: 7 perfis com `ramais.apps.editar=False`, só "Integração e Inovação" com `True`).
### Rodada 22 — Ramais: colaboradores aparecem automaticamente, sem precisar "adicionar"
Depois de testar a rodada 21 no navegador (print anexado: só Gabriel aparecia na lista, porque ele tinha se "adicionado" manualmente pelo modal), o usuário pediu duas coisas:
1. "Pode trazer automaticamente o cadastro do usuário para esta tela de ramais, ficando apenas pendente o cadastro de ramal, se necessário" — ou seja, o modelo original (rodada 21), que exigia criar uma linha `Ramal` vinculada a um `Usuario` pra alguém aparecer no diretório, criava um passo manual desnecessário: todo colaborador com conta no Portal já deveria aparecer sozinho, com o ramal pendente até ser preenchido.
2. "Melhore a visualização da opção de adicionar ramal, o ícone de seleção está feio e destoa da página" — o `<select>` nativo do browser (sem nenhum CSS) destoava do tema escuro do resto do portal.
O que mudou:
- **`Ramal` perdeu o campo `usuario`** (migração `0013_remove_ramal_usuario_alter_ramal_nome`) — agora é só linha avulsa (sem conta de sistema por trás, ex.: telefone de sala), com `nome` obrigatório e `departamento`/`numero` opcionais (podem ficar pendentes, igual a um colaborador de verdade).
- **`RamalViewSet.list()` foi reescrito** pra mesclar duas fontes numa lista só, sem passar pelo `RamalSerializer`: todo `Usuario` ativo (linha montada direto do cadastro — `nome`, `departamentos`, `ramal`) + as linhas avulsas de `Ramal`. Cada item ganha um `id` sintético (`"usuario-<id>"`/`"avulso-<id>"`) e um `tipo`, que o frontend usa pra decidir a ação certa.
- **Editar o ramal de um colaborador de verdade** deixou de ser "criar uma linha vinculada" — agora é a nova action `PATCH /api/ramais/usuarios/{usuario_id}/` (`atualizar_ramal_usuario`), que grava direto em `Usuario.ramal`. "Adicionar Ramal" ficou só para linhas avulsas — o `<select>` "Lista de Usuários" que existia nesse modal foi **removido** (não faz mais sentido, já que o colaborador já aparece sozinho); só o modal de Criar Ausência ainda tem esse `<select>` (via `GET /api/ramais/usuarios/`, agora simplificado pra só `id`/`nome`).
- **Excluir** só existe pra linhas avulsas agora (não dá pra "excluir" um colaborador do diretório sem excluir a conta dele, o que é escopo da tela de Usuários) — o ícone de lixeira some das linhas de usuário de verdade.
- **Usuário inativo não aparece mais** no diretório (`list()` filtra `is_active=True`) — efeito colateral da reescrita que corrige uma limitação que a rodada 21 tinha deixado em aberto.
- **Select/textarea estilizados**: `components.css` ganhou `.modal-field select`/`.modal-field textarea` genéricos (seta customizada via `background-image`, sem o `appearance` nativo do browser) — não existia nenhum estilo pra esses dois elementos antes (só `input`), então qualquer modal futuro com `<select>`/`<textarea>` já sai consistente com o tema escuro sem precisar de CSS próprio.
Pegadinha resolvida durante a migração: como o campo `usuario` foi removido por completo (não só esvaziado), a única linha `Ramal` vinculada que já existia (a que o usuário tinha criado testando a rodada 21, ligada à própria conta dele) ficou órfã — virou uma linha avulsa com todos os campos em branco, porque a informação de qual usuário ela representava dependia só da FK removida. Identificada e apagada manualmente depois da migração (não tinha dado nenhum pra preservar, já que os três campos ficavam vazios pra linhas vinculadas no desenho antigo); o ramal do usuário de teste `bruno` também tinha sido alterado por um smoke test rodado antes desta correção e foi restaurado ao valor original. Sem consequência pra dado real, já que a tela nunca tinha sido usada em produção — mas fica registrado porque é o tipo de coisa que merece atenção se acontecer de novo com dado que importe.
### Rodada 23 — Ramais: ajustes visuais + visualizar/editar/excluir ausência
Três pedidos pequenos, feitos em sequência depois de testar a rodada 22 no navegador:
1. **Overflow no modal de Adicionar Ramal**: `.modal-field-row` usava `grid-template-columns: 1fr 1fr` sem `minmax(0, ...)`, e os inputs de `.modal-field` não tinham `width: 100%` explícito — o conteúdo (tamanho intrínseco do `<input>`) empurrava a grade além da borda do modal. Corrigido em `components.css` (`grid-template-columns: minmax(0, 1fr) minmax(0, 1fr)` + `width: 100%` em `input`/`select`/`textarea` de `.modal-field`) — bug genérico, vale pra qualquer modal do portal com campos lado a lado, não só Ramais.
2. **Modal "Novo Chamado" cortando conteúdo**: a ferramenta externa embutida no iframe (rodada 21) é mais alta que um modal padrão; o iframe tinha altura fixa (`min(70vh, 640px)`) menor que o conteúdo, cortando o formulário no meio de um campo em vez de rolar de forma previsível. Corrigido em `ramais.css`: `.ram-chamado-card` passou a ocupar quase a tela inteira (`min(960px, 95vw)` × `min(92vh, ...)`, o teto em px ajustado depois pelo próprio usuário direto no CSS) e o iframe virou `flex:1` dentro do `.modal-card` (que já é flex-column), preenchendo todo o espaço vertical restante.
3. **Visualizar/editar/excluir ausência**: o usuário passou um print de como devia ficar — clicar no badge "(AUSENTE)" de uma linha abre um modal "Visualizar Ausência" com os campos do período (desabilitados) e dois botões, "Editar Ausência" e "Deletar Ausência". Implementado reaproveitando o `RamalAusenciaViewSet` que já existia (nenhuma mudança de backend — `GET`/`PATCH`/`DELETE` em `/api/ramais-ausencias/{id}/` já funcionavam, só faltava a UI): o badge virou um `<button data-ram-ver-ausencia>` que busca o registro via `GET` e abre o modal; "Editar" habilita os mesmos campos in-place (sem modal novo) e troca os botões por "Cancelar"/"Salvar Ausência"; "Deletar" remove o registro de verdade. Isso **substituiu** o botão "Encerrar Ausência" da rodada 21 (que só marcava `encerrada_manualmente=True`, preservando o registro) — o campo continua no model, mas sem UI própria; editar/excluir cobre o caso de uso real de forma mais direta, do jeito que foi demonstrado.
Nenhuma migração nova (mudança 3 não tocou o backend). `manage.py check` limpo depois das mudanças 1 e 2 (não alteraram Python, só confirmado por precaução).
### Rodada 24 — Ramais: contraste dos indicadores de ausente/aniversariante
Prints em claro e escuro mostrando as linhas de Bruno (ausente) e Willian (aniversariante) — pedido pra melhorar a legibilidade. Perguntado antes de mexer, porque "observações" no pedido original não batia com nenhum print (nenhum mostrava o campo Observações do modal de Ausência); o usuário confirmou que o problema era **o tingimento de fundo da linha inteira** (`.ram-row--ausente td`/`.ram-row--aniversario td`, rodada 21) somado à cor do próprio selo — duas camadas de cor translúcida da mesma matiz empilhadas, contraste ruim nos dois temas.
Primeira tentativa: removido o tingimento da linha inteira, deixando só o selo comunicar o status (mesmo padrão do selo "Pendente"). O usuário pediu de volta o tingimento — "quero que deixe o tingimento, porém que fique num tom mais forte, para facilitar sua visualização". Desenho final: `.ram-row--ausente`/`.ram-row--aniversario` td voltaram (com opacidade maior que a versão original da rodada 21) **e** `.ram-badge--ausente` virou um selo (pill) de verdade, igual ao de aniversariante — as duas coisas juntas, cada uma com cor de texto/fundo ajustada por tema via `:root[data-theme="light"]` (mesmo padrão de override que `tokens.css` já usa) em vez de reaproveitar `--danger`/`--gold` crus, que não tinham contraste suficiente pensados pra texto pequeno sobre um selo ou fundo de linha.
### Rodada 30 — "Acessar Ramais" vira modal de consulta rápida, em vez de navegar
Pedido, com print de referência do portal antigo: o botão "Acessar Ramais" do topbar (presente em `portal.html`, `links-ferramentas.html` e `calendario-individual.html` — só essas 3 páginas têm o atalho) não deveria mais levar pra `ramais.html`; deveria abrir um modal com a lista de ramais cadastrados, com busca por nome/departamento/ramal. O print de referência mostrava um estilo DataTables (branco/azul, "Show N entries", colunas ordenáveis, paginação numerada) — decisão de modernizar a funcionalidade pro visual escuro/roxo do resto do portal, não clonar pixel a pixel (mesmo critério já usado desde a rodada 1).
O que foi construído:
- `#ramais-btn` voltou a ser um `<button>` (não `<a href="ramais.html">`, que era o estado desde a rodada 21) nas 3 páginas.
- Modal novo (`#ramais-lookup-modal`, duplicado nas 3 páginas — mesmo padrão de outros modais compartilhados como o de senha) + `static/js/ramais-lookup.js` + `static/css/ramais-lookup.css`: busca numa caixa só (filtra as três colunas ao mesmo tempo, mais simples que a busca em duas caixas da tela `ramais.html`), ordenação por coluna (clique no cabeçalho alterna asc/desc) e paginação (10/25/50/100 por página) — tudo client-side, sem endpoint novo, reaproveitando `GET /api/ramais/` (a mesma listagem mesclada usuário+avulso da tela completa), buscado uma vez por abertura de página.
- Gate por permissão: o botão some (`hidden`) se o perfil do usuário não tiver `apps.visualizar` em `ramais` — mesmo padrão do resto do app.
- Botão "Ir para Controle de Ramais" no rodapé do modal é o link de verdade pra `ramais.html` (tela com edição); o modal em si é só consulta.
- Pegadinha resolvida: a tabela do modal não podia reaproveitar `.pa-table` de `perfis-acesso.css` porque `portal.html`/`links-ferramentas.html`/`calendario-individual.html` não carregam esse CSS (só `ramais.html`/`usuarios.html`/`perfis-acesso.html` carregam) — `ramais-lookup.css` ficou com estilo de tabela autocontido (`.ram-lookup-table*`) em vez de depender de um CSS que a página não tem.
Nenhuma mudança de backend — `GET /api/ramais/` já existia e já retornava exatamente os dados necessários. `manage.py check` limpo (confirmado por precaução, já que a mudança foi só front-end).
### Rodada 31 — Ramais vira uma seção com 5 subtelas (abas)
Dois prints do sistema antigo mostraram que "Ramais" no fundo é uma seção com 5 subtelas navegáveis por abas: Ramais, Responsável no Tareffa, Telefones Externos, Férias, Funções de Telefonia. Pedido: adicionar essa navegação em `ramais.html`, construir Telefones Externos e Funções de Telefonia de verdade, e criar Responsável no Tareffa/Férias como abas vazias ("sem informações, pois serão trabalhadas posteriormente"). Isso **supera** a decisão da rodada 21 de que Férias estava fora de escopo — agora a aba existe (vazia); a limitação de fundo (integração futura com outro banco) continua valendo, só a navegação foi antecipada.
Perguntado antes de implementar se "Funções de Telefonia" (tabela de comandos tipo `*01 + Código de Agente` → LogOn) deveria ser conteúdo fixo no HTML ou uma lista administrável no banco — o usuário escolheu **lista administrável (CRUD completo)**, mesmo padrão de Telefones Externos.
O que foi construído:
- **Dois models novos**, ambos sem FK pra `Usuario` (dados avulsos, mesmo espírito de `Ramal` avulso): `TelefoneExterno` (nome/ramal/telefone/observações, só `nome` obrigatório) e `FuncaoTelefonia` (comando/função/resumo, só `comando` obrigatório). `FuncaoTelefonia.Meta.ordering = ["comando"]` reproduz sozinho a ordem do print (`*0, *01, *02, *03, *1, *2, *20, *21, *22, *23, *5, *503, *8`) porque essa é exatamente a ordem lexicográfica da string — dispensou um campo `ordem` manual e endpoint de reorder.
- **Permissão reaproveitada**: as duas subtelas usam o mesmo par `ramais.apps.visualizar`/`ramais.apps.editar` que já existia (`PermissaoApp("ramais", app_key)`) — são subtelas da mesma seção do menu, não aplicações novas; nenhuma mudança em `catalogo.py`.
- **Seed só para Funções de Telefonia**: `seed_portal.py` ganhou `FUNCOES_TELEFONIA_SEED` (as 13 linhas do print, via `update_or_create` por `comando`, idempotente) — decisão de que são comandos padrão de central telefônica (documentação genérica), diferente de Telefones Externos, que começa **vazio** de propósito (são contatos reais de fornecedores/terceiros, o usuário cadastra pela própria tela).
- **Abas**: reaproveitado o CSS genérico `.pa-tabs`/`.pa-tab`/`.pa-tab-panel` que já existia em `perfis-acesso.css` (mesmo padrão das abas Permissões/Usuários de `perfis-acesso.html`), com uma implementação independente em `ramais.js` (`data-ram-tab`/`data-ram-tab-panel`/`activeRamTab`) pra não colidir com `profiles.js`. O conteúdo que já existia (filtros/tabela/botões de Ramais) migrou pro painel `data-ram-tab-panel="ramais"`, sem mudar de comportamento.
- Endpoints novos: `/api/telefones-externos/` e `/api/funcoes-telefonia/`, CRUD padrão via `ModelViewSet`.
Migração `0014_funcaotelefonia_telefoneexterno` gerada e aplicada no ambiente local; `seed_portal` reexecutado (confirmado via shell: 13 linhas de Funções de Telefonia, na ordem certa). `manage.py check` limpo. Ainda não testado num navegador de verdade — cadastrar/editar/excluir um Telefone Externo e uma Função de Telefonia, alternar entre as 5 abas, e conferir que um perfil só-visualizar não vê nenhum botão de escrita fica para a próxima validação.
### Rodada 32 — Ramais: permissão granular por subtela (visualizar por aba + editar nas 3 administráveis)
No mesmo dia da rodada 31, o usuário pediu um ajuste no modelo de permissão recém-criado: em vez de um único par `visualizar`/`editar` cobrindo as 5 abas de Ramais, cada aba deveria ter sua própria permissão de visualização, e só as 3 com conteúdo administrável (Ramais, Telefones Externos, Funções de Telefonia) deveriam ter também uma permissão de edição própria — Responsável no Tareffa/Férias, sendo placeholders vazios, só precisam de visualizar. Pedido explícito de "estado inicial": só "Integração e Inovação" com acesso de editar nas 3, os demais 7 perfis com visualizar liberado em todas as 5.
Implementado reestruturando `catalogo.MODULE_APPS["ramais"]` de uma lista flat de 2 apps pra **5 subgrupos** (mesmo formato `{"key", "label", "tools": [...]}` já usado em Auditorias) — cada subgrupo é uma aba, com 1 tool (`Visualizar`) ou 2 (`Visualizar`/`Editar`). Isso reaproveitou 100% a árvore de permissões genérica de `profiles.js` (`renderTree`/`renderEntry`/`renderLeaf` já sabem renderizar subgrupos com `tools`, usado desde a rodada de Auditorias) — nenhum código de UI novo em Perfis de Acesso.
O que mudou:
- **Backend**: os 4 `ModelViewSet` relacionados (`RamalViewSet`, `RamalAusenciaViewSet`, `TelefoneExternoViewSet`, `FuncaoTelefoniaViewSet`) passaram a checar a chave específica da própria subtela (`"telefones-externos-visualizar"`/`"editar"`, etc.) em vez do genérico `"visualizar"`/`"editar"` — `RamalAusenciaViewSet` usa as mesmas chaves de `ramais-visualizar`/`ramais-editar` do diretório, por ser parte dessa mesma aba.
- **`seed_portal.py`**: o override que força `editar=False` pros 7 perfis "normais" cresceu de 1 chave (`ramais.apps.editar`) pra 3 (`ramais-editar`, `telefones-externos-editar`, `funcoes-telefonia-editar`) — mesmo princípio de sempre, só mais chaves.
- **`ramais.js`**: reescrito o bloco de permissão — de um único `canView`/`canManage` pra 5 pares (um por aba), um objeto `ramTabViewPerms` que esconde (`hidden`) o botão de cada aba sem `visualizar`, e a aba ativa por padrão passou a ser a primeira visível (não sempre "Ramais", que pode estar oculta pra um perfil específico no futuro, embora hoje todos os 7 perfis "normais" vejam as 5).
- **`ramais-lookup.js`** (modal de consulta rápida no topbar): trocado de `apps.visualizar` genérico pra `apps["ramais-visualizar"]` especificamente, já que esse modal só mostra o diretório de Ramais.
Como isso mudou as **chaves** armazenadas em `PerfilAcesso.permissoes["ramais"]["apps"]` (não só valores), foi necessário reexecutar `seed_portal` pra sincronizar os 8 perfis com o novo formato — sem isso, todo perfil ficaria sem nenhum acesso a Ramais (as chaves antigas `visualizar`/`editar` não existem mais no catálogo). Confirmado via shell depois do reseed: os 7 perfis "normais" saíram com as 5 chaves `*-visualizar=True` e as 3 `*-editar=False`; só "Integração e Inovação" saiu com as 3 `*-editar=True` também. `gabriel`/`bruno` não foram tocados (proteção da rodada anterior, `[[feedback_seed_nao_reseta_gabriel_bruno]]` segue valendo). `manage.py check` limpo.
**Nota pra quem for customizar um perfil manualmente na tela de Perfis de Acesso**: qualquer ajuste fino que já tivesse sido feito nas chaves antigas (`ramais.visualizar`/`ramais.editar`) precisa ser refeito nas novas chaves — é uma troca de namespace, não uma migração automática de valor (JSONField não tem esse mecanismo).
### Rodada 33 — Revisão de interface de 30/09/2026: Ramais e consulta rápida
Correções vindas da revisão de interface de 30/09/2026 (ver `plano.md`), só no frontend:
- **Carga por permissão**: `ramais.js` só busca a lista de cada aba que o usuário pode visualizar (antes buscava as três e gerava 403 nas abas sem permissão). Falha de carga mostra "Não foi possível carregar. Recarregue a página; se persistir, contate a Inovação." na própria tabela.
- **Lista vazia x filtro**: Ramais e Telefones Externos diferenciam "Nenhum ... cadastrado ainda." de "Nenhum resultado para o filtro.".
- **Envio duplo**: os cinco "Salvar"/"Adicionar" (ramal, telefone externo, função, criar e editar ausência) ficam travados até a resposta (helper `comTrava`). Excluir ramal/telefone/função/ausência trava o próprio botão e mostra `pidAlert` na falha.
- **Sair sem salvar**: Cancelar e clique fora dos cinco formulários (inclusive o modo de edição do popup de ausência e o "Cancelar edição") comparam com o retrato da abertura e pedem confirmação quando houve alteração.
- **Ausência**: o select de usuário ganhou a opção vazia "Selecione…" (antes o primeiro nome vinha marcado) e a validação aponta e foca o campo que falta.
- **Consulta rápida** (`ramais-lookup.js`): erro de carga não vira mais "Nenhum ramal encontrado." nem fica em cache (a próxima abertura tenta de novo); "Carregando…"; ordenação com `localeCompare("pt-BR", { numeric: true })`; `overscroll-behavior: contain` na tabela e `tabular-nums` na coluna Ramal. Botão "Close" virou "Fechar" e o placeholder da busca usa "…" (em `links-ferramentas.html` e `calendario-individual.html`).
- Template: placeholders que repetiam o rótulo viraram exemplos ("Ex.: Recepção", "Ex.: 201"...), telefone com `type="tel"`, ramal com `inputmode="numeric"`, comando de função com `spellcheck="false"`; `tabular-nums` nas colunas de ramal/telefone.