16 KiB
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 emplano.md.Formato: uma entrada por rodada,
### Rodada N — Título; quando a rodada não tem número registrado,### Títulosó. 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 deplano.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".