99 lines
18 KiB
Markdown
99 lines
18 KiB
Markdown
# Changelog — Perfis de Acesso / Usuários
|
|
|
|
> Histórico específico desta aplicação, extraído de `plano.md`. Rodadas que mudaram o modelo de permissões transversal em si, ou o mecanismo de autenticação/menu de conta como um todo, 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 2 — Perfis de Acesso
|
|
|
|
Tela de administração (`perfis-acesso.html`) com lista + edição de perfis: Código, Nome, árvore de permissões por módulo, aba "Usuários do Escritório". Criado um perfil especial **"Integração e Inovação"**, com acesso total e uma capacidade exclusiva — `gerenciaPermissoes` — que também gate-ia o acesso à própria tela de Perfis de Acesso.
|
|
|
|
### Rodada 3 — Usuários
|
|
|
|
Tela de cadastro de contas (`usuarios.html`), movida para dentro do grupo "Administração" no menu (junto de Perfis de Acesso), com a mesma restrição: só quem tem `gerenciaPermissoes` acessa e pode cadastrar/editar/excluir usuários.
|
|
|
|
### Rodada 5 — Múltiplos perfis por usuário
|
|
|
|
Pedido explícito: "algumas pessoas podem ter acesso ao perfil financeiro e fisco/contábil". `profileCodigo` (número único) virou `profileCodigos` (array) em `pid_users`; a resolução de acesso em `access.js` passou a fazer **união** entre todos os perfis vinculados a um usuário — uma seção aparece se qualquer um dos perfis do usuário conceder acesso a ela.
|
|
|
|
### Rodada 7 — Permissão granular por aplicação
|
|
|
|
A árvore de permissões de cada módulo mostrava ações genéricas (Visualizar/Incluir/Editar/Excluir), sem relação com o conteúdo real do menu. Trocado por uma lista das aplicações reais de cada seção (ex.: em Portais, os checkboxes viraram "Portal do Cliente" e "Portal Fiscal") — e isso passou a **funcionar de verdade**: desmarcar uma aplicação esconde só aquele item do sidebar (`data-app` + `access.js`), não o módulo inteiro. Nesta mesma rodada, o campo **Ambiente** (Escritório/Empresa) foi removido do cadastro de perfil — "trabalharemos como portal interno, sem mais de um ambiente".
|
|
|
|
### Rodada 12 — Menu de conta
|
|
|
|
O chip do canto superior direito mostrava o **perfil/departamento** ("Departamento Pessoal" etc.) — trocado para mostrar o **usuário logado** (nome + ícone de pessoa). Clicar abre um menu com nome + perfis vinculados, "Alterar senha" (modal com senha atual/nova/confirmação, validada contra `pid_users`) e, só para quem tem `gerenciaPermissoes`, um atalho para Perfis de Acesso (antes era o clique direto no chip que levava pra lá). Esse menu de conta é a base do que, na rodada 33, viraria o modal "Gerenciar Usuário".
|
|
|
|
### Rodada 33 — Liderança (gerente/coordenador)
|
|
|
|
Pedido: gerentes/coordenadores precisam enxergar a agenda de compromissos privados de quem lideram, sem precisar que cada liderado marque o compromisso como "todos"/"departamento". Implementado com `Usuario.lideranca` (booleano) + `Usuario.liderados` (M2M auto-referenciado, `symmetrical=False` — "A lidera B" não implica o contrário). Dois pontos gravam a mesma relação: a tela de Usuários (seção "Liderança" no formulário, só quem tem `gerencia_permissoes`) e um modal novo "Gerenciar Usuário" (substituiu o antigo botão direto "Alterar senha" no dropdown da conta, ver rodada 12), que permite ao próprio gerente se autogerenciar via `PATCH /api/me/liderados/` sem precisar de acesso à tela administrativa. `CompromissoAgendaViewSet.get_queryset` passou a incluir compromissos `"somente_eu"` de quem está em `usuario.liderados`, mas sem dar direito de editar (`sou_dono` continua `False` pra esses — ver `docs/calendario-individual/calendario-individual.md`). O checklist de liderados (com busca por nome/departamento e "marcar todos os resultados da busca") virou um componente genérico (`components.css`) reaproveitado nos dois lugares. Migração `0015_usuario_liderados_usuario_lideranca`. Detalhe completo em `docs/perfis-usuarios/perfis-usuarios.md`, seção "Liderança (gerente/coordenador) e o modal 'Gerenciar Usuário'".
|
|
|
|
### Rodada 65 — Filtro Ativos/Inativos/Todos na lista de Usuários
|
|
|
|
Usuário pediu um botão pra filtrar a lista de `usuarios.html` entre Ativos/Inativos, abrindo por padrão só com os Ativos, mas com opção de tirar o filtro pra ver os inativos também.
|
|
|
|
Implementado como 3 chips ("Ativos"/"Inativos"/"Todos", `#ua-status-filtros`, ao lado do título "Usuários") — mesma linguagem visual de `.ind-departamento-chip` (Indicador de Desempenho). Filtro 100% client-side sobre o array `users` já carregado (`statusFiltro`, padrão `"ativos"`), aplicado em `renderList()` antes da busca por texto. Sem endpoint novo.
|
|
|
|
### Rodada 66 — Colunas de código cadastral (Folha/Questor/Tareffa/Contabit/Ramal) + ordenação na lista de Usuários
|
|
|
|
Usuário pediu pra ver os campos cadastrais opcionais (`codigo_folha`/`codigo_questor`/`codigo_tareffa`/`codigo_contabit`/`ramal`, hoje só visíveis dentro do formulário de edição) como colunas na lista, e poder ordenar por elas — mesmo sendo campos opcionais, o objetivo é achar quem está sem algum deles cadastrado antes de outras aplicações passarem a depender dessas informações.
|
|
|
|
`UsuarioListSerializer` já expunha os 5 campos (nenhuma mudança de backend necessária). Adicionadas 5 colunas em `#ua-table` (`usuarios.html`) entre Nome e Perfil de Acesso; célula vazia mostra `—` em itálico apagado (`.ua-campo-vazio`) em vez de ficar em branco, pra ficar visualmente óbvio ao ordenar. Cabeçalhos com `data-sort` (login, nome, os 4 códigos, ramal, status) ficam clicáveis e alternam asc/desc — mesmo padrão (`comparaValoresUsuario`, ícone `↕` que vira `--accent` no ativo) já usado em `#ips-list-table` (Importação de Plano de Saúde) e no modal de Ramais.
|
|
|
|
### Rodada 67 — Aba "Usuários do Escritório" (Perfis de Acesso) reestruturada em duas tabelas com seleção múltipla
|
|
|
|
Usuário anexou um print de outro sistema (duas grades lado a lado, cada uma com checkbox, busca por coluna, ordenação e um botão de ação em lote — "VINCULAR"/"DESVINCULAR") e pediu pra reestruturar a aba "Usuários do Escritório" (dentro da edição de um perfil, `perfis-acesso.html`) nesse mesmo estilo: ver todos os usuários ativos de um lado e vincular, ver os já vinculados do outro lado e desvincular.
|
|
|
|
Substituído o antigo `<select>` + botão "Vincular" + lista simples com X (`.pa-users-add`/`.pa-user-list`, removidos) por `.pa-users-dual` — duas tabelas (`#pa-users-available-*` à esquerda, só usuários ativos ainda não vinculados; `#pa-users-linked-*` à direita, todos os já vinculados, inclusive inativos, com selo `.status-pill--inativo`), cada uma com checkbox por linha + "selecionar todos" (sobre as linhas filtradas visíveis), busca por nome/e-mail, ordenação por nome (clique no cabeçalho, ícone `↕`) e um botão de atualizar. "Vincular"/"Desvincular" operam em lote sobre a seleção (`bulkAlterarVinculo()`, `Promise.all` de `PATCH /api/usuarios/{id}/` por usuário selecionado) — sem endpoint novo, só reaproveitando `GET/PATCH /api/usuarios/` que já existiam. Abrir a aba de um perfil diferente sempre reseta seleção/ordenação/busca dos dois painéis.
|
|
|
|
### Rodada 68 — Botão "Vincular" (só visual) nos campos Código da Folha/Questor/Tareffa
|
|
|
|
Usuário anexou um print mostrando 3 campos específicos (Código da Folha, Código do Questor, Código do Tareffa — não Contabit nem Ramal) e explicou que, no futuro, esses códigos serão buscados/vinculados automaticamente a partir de ferramentas externas (ex.: `codigo_tareffa` via a view que já existe no banco pra ler o Tareffa) em vez de digitados manualmente. Pediu explicitamente pra **não** construir essa vinculação agora — só deixar a estrutura visual pronta, com um botão "Vincular".
|
|
|
|
Passou por 3 versões visuais no mesmo dia até o formato final: (1) botão "Vincular" solto ao lado do input (`.btn-outline`, com texto); (2) só o ícone de elo no canto do próprio input (`position:absolute`, sem texto — pedido do usuário: "deixe o botão mais simples, só o símbolo no canto"); (3) formato final — input + botão "Vincular" (ícone + texto) encostados numa única caixa (`.ua-field-link`, borda/raio únicos, `border-left` separando os dois), depois do usuário mostrar um print de referência (campo de busca com botão "Buscar" atado à direita) e pedir esse mesmo estilo. Sempre `disabled` com `title` explicando que a vinculação ainda não foi implementada. Sem handler de JS nenhum — puramente estrutural, pra reaproveitar quando a integração de verdade for construída.
|
|
|
|
### Rodada 69 — Bug: `IntegrityError` ao criar um novo perfil de acesso (`codigo` duplicado)
|
|
|
|
Usuário reportou erro `500` ao criar um perfil pela tela: `django.db.utils.IntegrityError: duplicate key value violates unique constraint "portal_api_perfilacesso_pkey" — DETAIL: Key (codigo)=(6) already exists`.
|
|
|
|
Causa: `seed_portal.py` semeia os 8 perfis com `codigo` **explícito** (`update_or_create(codigo=dado["codigo"], ...)`, necessário porque vários lugares do código referenciam código fixo, ex. `codigo == 8`). No Postgres, `INSERT` com PK explícita não avança a sequence do `AutoField` — a sequence de `PerfilAcesso.codigo` ficou parada em 1 desde a criação do banco, então o primeiro `POST /api/perfis/` (sem PK explícita) pediu `nextval()` e recebeu um código baixo já usado pelo seed (6 = "Financeiro").
|
|
|
|
Corrigido com `_reset_sequence()` (nova função em `seed_portal.py`, `SELECT setval(pg_get_serial_sequence(...), MAX(codigo))` via SQL puro) chamada logo após semear `PERFIS_SEED` — toda execução do comando realinha a sequence de novo, então o bug não volta a acontecer mesmo que o seed seja reexecutado no futuro. Aplicado no banco em produção rodando `python manage.py seed_portal` (confirmado via `SELECT last_value FROM portal_api_perfilacesso_codigo_seq` = 8, igual ao maior código existente).
|
|
|
|
### Rodada 70 — "Usuários sob liderança" reestruturado em duas tabelas (não vinculados/vinculados)
|
|
|
|
Usuário anexou dois prints — um da própria seção "Liderança" (checklist único, com busca e "marcar todos") e outro de um sistema externo com duas grades lado a lado (checkbox, "Código"/"Nome", ordenação, paginação, VINCULAR/DESVINCULAR) — e pediu pra reestruturar "Usuários sob liderança" nesse mesmo estilo de duas tabelas: não vinculados de um lado, vinculados do outro (corrigido depois pra confirmar: não vinculados à **esquerda**, vinculados à **direita** — mesma convenção já usada no widget "Usuários do Escritório" de Perfis de Acesso, rodada 67).
|
|
|
|
Essa seção aparece em dois lugares (formulário de edição de usuário em `usuarios.html` **e** o modal "Gerenciar Usuário", presente em todo shell, pra autogestão do próprio gerente/coordenador) — perguntado ao usuário se a mudança deveria valer só pra tela de Usuários ou pros dois lugares; resposta: os dois.
|
|
|
|
Como o widget "Usuários do Escritório" (rodada 67) já tinha esse exato padrão visual mas vivia só em `perfis-acesso.css`/inline em `profiles.js` (só usado nessa página), foi extraída uma versão genérica reaproveitável: `static/js/dual-select.js` (`pidCriarSeletorDuplo()`, fábrica que recebe as referências de DOM de cada painel + uma função pro valor da coluna secundária, devolve `{setDados, getVinculadosIds}` — vinculação só em memória, sem chamada de API própria) + `.dual-select`/`.dual-select__*` em `components.css` (não em `perfis-acesso.css`, já que o modal "Gerenciar Usuário" existe em todo shell e a maioria não carrega esse CSS). `dual-select.js` foi adicionado ao prefixo de scripts compartilhado das 10 páginas-shell, logo depois de `api.js`.
|
|
|
|
Trocados: `usuarios.html` (seção "Liderança") e as 10 cópias do modal "Gerenciar Usuário" (uma por shell) — `.checklist-box`+busca+"marcar todos" virou o par de tabelas com coluna "Nome" (ordenável) + "Departamento" (busca própria). `#manage-account-modal-card` ganhou id novo pra `account.js` poder aplicar `.modal-card--wide` só quando `me.lideranca` é `true` (o modal padrão de 420px não cabe duas tabelas lado a lado). `users-admin.js`/`account.js` tiveram a lógica de checklist único substituída por chamadas a `pidCriarSeletorDuplo` — o restante do fluxo (salvar `liderados` junto do form em `usuarios.html`, `PATCH /api/me/liderados/` imediato no modal) não mudou.
|
|
|
|
**Ajuste de proporção no mesmo dia**: usuário reportou dois problemas visuais depois de testar — (1) "Perfis de Acesso" e "Departamento" (`usuarios.html`) não ficaram do mesmo tamanho; (2) o par de tabelas de "Usuários sob liderança" ficou comprimido num canto, sem usar a largura da seção. Causa de (1): `.pa-field-row.ua-permissions-row` usava `grid-template-columns: 1.6fr 1fr` (resquício de quando só existia um checklist de cada lado, sem se importar com simetria) — trocado para `1fr 1fr`. Causa de (2): `.ua-liderados-field` tinha `max-width: 480px`, herdado de quando o campo continha só um `.checklist-box` estreito — removido, já que agora o campo precisa da largura inteira da seção pras duas tabelas do `.dual-select`.
|
|
|
|
### Rodada 72 — Inativar usuário desvincula perfis de acesso e liderança automaticamente
|
|
|
|
Regra de negócio pedida pelo usuário: ao marcar um usuário como Inativo, ele deve ser desvinculado automaticamente de todos os perfis de acesso que tinha, e removido da lista de liderados de qualquer gerente que o tivesse — pra não deixar um vínculo "pendurado" em alguém que já não pode mais acessar o Portal.
|
|
|
|
Implementado em `_revogar_acesso_se_inativo()` (`serializers.py`), chamada ao final de `UsuarioSerializer.create()`/`update()` (depois dos `.set()` de `perfis`/`departamentos`/`liderados`, nunca antes — o formulário de edição sempre reenvia o checklist de perfis inteiro junto com qualquer alteração no checkbox "Usuário ativo", então limpar antes dos `.set()` seria desfeito na mesma requisição). Cobre os dois pontos reais de entrada (botão de alternar na lista e checkbox no formulário), ambos via `PATCH /api/usuarios/{id}/`.
|
|
|
|
Uma primeira versão tentou resolver isso em `Usuario.save()` (model), mas foi abandonada: tanto o `ModelSerializer.update()` do DRF quanto o admin do Django chamam `.set()`/`save_m2m()` nos M2M **depois** de `instance.save()`, então uma limpeza dentro de `save()` seria sempre desfeita pelo `.set(perfis)` que vem a seguir no mesmo request. Por isso a limpeza mora no serializer, como o último passo, não no model.
|
|
|
|
**Complemento no mesmo dia**: usuário reportou visualmente que usuários inativos ainda apareciam nos dois widgets de vinculação (dual-select de "Usuários sob liderança" e de "Usuários do Escritório" em Perfis de Acesso) — pediu que usuário inativo nunca seja exibido em nenhum dos dois painéis (nem disponível, nem já vinculado). Corrigido: `listaParaPainelUsuarios()` (`profiles.js`) agora filtra `usuariosCacheAtual` por `is_active` antes de separar entre disponível/vinculado (removido também o selo "Inativo" que antes marcava um usuário inativo ainda vinculado, já que esse caso não deve mais aparecer); `fillLideradosChecklist()` (`users-admin.js`) ganhou o mesmo filtro. No modal "Gerenciar Usuário" (`account.js`) já não era um problema — `GET /api/usuarios-resumo/` só devolve usuários ativos.
|
|
|
|
### Rodada 81 (parte) — Correção do `seed_portal.py` resetando nomes de perfil
|
|
|
|
Ver `plano.md` (rodada 81) para o pedido completo de "Mais informações" por aplicação — mecanismo genérico e transversal. A parte específica desta aplicação: o usuário tinha renomeado o perfil "Integração e Inovação" (código 8) pra "Inovação" e criado um perfil novo "Integração" (código 9) — mas `seed_portal.py` fazia `update_or_create(codigo=..., defaults={"nome": ...})`, que reescrevia `nome` de volta pro valor original a cada execução do seed. Corrigido pra `get_or_create(codigo=..., defaults={"nome": ...})` (nome só gravado na criação); `ativo`/`gerencia_permissoes`/`permissoes` continuam realinhados a cada execução, de propósito. Todas as 8 telas/textos do frontend que ainda diziam "Integração e Inovação" foram atualizadas pra "Inovação".
|
|
|
|
### Rodada 82 — Revisão de interface de 30/09/2026: Perfis de Acesso e Usuários
|
|
|
|
Correções vindas da revisão de interface de 30/09/2026 (ver `plano.md`), só no frontend:
|
|
|
|
- **Envio duplo**: "Salvar" de perfil e de usuário e "Adicionar" departamento ficam travados ("Salvando…"/"Adicionando…") até a resposta; dois cliques não criam mais dois registros. Erro de validação leva o cursor ao campo que falta (Nome do perfil; Login/Nome/Senha do usuário; novo departamento); erro do servidor no perfil volta o cursor ao Nome e, no usuário, rola até a mensagem.
|
|
- **Sair sem salvar**: "Voltar"/"Cancelar" das duas telas de edição comparam com um retrato tirado na abertura (nome + árvore de permissões no perfil; todos os campos, checklists e liderados no usuário) e só então pedem `pidConfirm("Sair sem salvar as alterações?")`. Os vínculos da aba "Usuários do Escritório" ficam fora da comparação porque já são gravados na hora.
|
|
- **Falha silenciosa**: carga inicial (catálogo/perfis, usuários), abertura da edição de usuário, atualizar/vincular/desvincular em lote, excluir perfil/usuário/departamento e ativar/desativar usuário passaram a tratar erro (`pidAlert` com título ou aviso "Não foi possível carregar..." na própria tabela, nunca a mensagem de lista vazia).
|
|
- **Perfil novo**: a aba "Usuários do Escritório" fica desabilitada com a dica "Salve o perfil para vincular usuários." até o primeiro salvamento (antes vincular mandava código `null`).
|
|
- **Desativar usuário**: a confirmação avisa que isso remove todos os perfis e o tira da liderança de quem o lidera (`_revogar_acesso_se_inativo`), com botão de perigo.
|
|
- Ordenação com `localeCompare(..., "pt-BR")`; `tabular-nums` (`.pa-table .pa-num`) nas colunas de código e ramal; `.pa-toolbar`/`.pa-tabs` com `flex-wrap`; hover em `.pa-tab`; a regra de 1 coluna abaixo de 900px de `.ua-permissions-row` ganhou a mesma especificidade da de 2 colunas (antes perdia); placeholders com "…", `spellcheck="false"` em login, e-mail e códigos, `autocomplete="off"` no Nome, `inputmode="numeric"` no ramal e `translate="no"` nos cabeçalhos Questor/Tareffa/Contabit.
|